summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
Diffstat (limited to 'zen')
-rw-r--r--zen/assert_static.h44
-rw-r--r--zen/base64.h159
-rw-r--r--zen/basic_math.h356
-rw-r--r--zen/build_info.h18
-rw-r--r--zen/com_error.h206
-rw-r--r--zen/com_ptr.h123
-rw-r--r--zen/com_util.h121
-rw-r--r--zen/debug_log.h82
-rw-r--r--zen/debug_new.cpp54
-rw-r--r--zen/debug_new.h96
-rw-r--r--zen/dir_watcher.cpp493
-rw-r--r--zen/dir_watcher.h50
-rw-r--r--zen/disable_standby.h27
-rw-r--r--zen/dll.h120
-rw-r--r--zen/dst_hack.cpp369
-rw-r--r--zen/dst_hack.h40
-rw-r--r--zen/file_error.h48
-rw-r--r--zen/file_handling.cpp2060
-rw-r--r--zen/file_handling.h120
-rw-r--r--zen/file_id.cpp67
-rw-r--r--zen/file_id.h27
-rw-r--r--zen/file_id_internal.h48
-rw-r--r--zen/file_io.cpp217
-rw-r--r--zen/file_io.h74
-rw-r--r--zen/file_traverser.cpp611
-rw-r--r--zen/file_traverser.h86
-rw-r--r--zen/file_update_handle.h67
-rw-r--r--zen/fixed_list.h142
-rw-r--r--zen/guid.h36
-rw-r--r--zen/i18n.h96
-rw-r--r--zen/int64.h258
-rw-r--r--zen/last_error.h110
-rw-r--r--zen/long_path_prefix.h98
-rw-r--r--zen/notify_removal.cpp236
-rw-r--r--zen/notify_removal.h36
-rw-r--r--zen/perf.h73
-rw-r--r--zen/privilege.cpp79
-rw-r--r--zen/privilege.h64
-rw-r--r--zen/read_txt.cpp91
-rw-r--r--zen/read_txt.h32
-rw-r--r--zen/scope_guard.h77
-rw-r--r--zen/stl_tools.h49
-rw-r--r--zen/string_base.h824
-rw-r--r--zen/string_tools.h569
-rw-r--r--zen/string_traits.h176
-rw-r--r--zen/symlink_target.h140
-rw-r--r--zen/thread.h92
-rw-r--r--zen/type_tools.h74
-rw-r--r--zen/type_traits.h88
-rw-r--r--zen/utf8.h336
-rw-r--r--zen/warn_static.h34
-rw-r--r--zen/win.h30
-rw-r--r--zen/win_ver.h72
-rw-r--r--zen/zstring.cpp234
-rw-r--r--zen/zstring.h216
55 files changed, 10145 insertions, 0 deletions
diff --git a/zen/assert_static.h b/zen/assert_static.h
new file mode 100644
index 00000000..00c4c5c8
--- /dev/null
+++ b/zen/assert_static.h
@@ -0,0 +1,44 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef ASSERTSTATIC_H_INCLUDED
+#define ASSERTSTATIC_H_INCLUDED
+
+/*
+//compile time assert based on Loki (http://loki-lib.sourceforge.net)
+
+#ifdef NDEBUG
+
+#define assert_static(x) //((void)0) -> leads to error when seen in namespace scope!
+
+#else // debugging enabled
+namespace static_check_impl
+{
+template<int>
+struct CompileTimeError;
+
+template<>
+struct CompileTimeError<true> {};
+}
+
+#define LOKI_CONCAT( X, Y ) LOKI_CONCAT_SUB( X, Y )
+#define LOKI_CONCAT_SUB( X, Y ) X##Y
+
+#define assert_static(expr) \
+ enum { LOKI_CONCAT(loki_enum_dummy_value, __LINE__) = sizeof(StaticCheckImpl::CompileTimeError<static_cast<bool>(expr) >) }
+
+// #define assert_static(expr) \
+// { Loki::CompileTimeError<((expr) != 0)> Static_Assert_Has_Failed; (void)Static_Assert_Has_Failed; }
+
+#endif
+*/
+
+//C++11:
+#define assert_static(X) \
+ static_assert(X, "Static assert has failed!");
+
+#endif //ASSERTSTATIC_H_INCLUDED
diff --git a/zen/base64.h b/zen/base64.h
new file mode 100644
index 00000000..4a81787c
--- /dev/null
+++ b/zen/base64.h
@@ -0,0 +1,159 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+#ifndef BASE64_HEADER_08473021856321840873021487213453214
+#define BASE64_HEADER_08473021856321840873021487213453214
+
+#include <iterator>
+#include <cassert>
+#include "assert_static.h"
+
+namespace zen
+{
+//http://en.wikipedia.org/wiki/Base64
+/*
+Usage:
+ const std::string input = "Sample text";
+ std::string output;
+ zen::encodeBase64(input.begin(), input.end(), std::back_inserter(output));
+ //output contains "U2FtcGxlIHRleHQ="
+*/
+template <class InputIterator, class OutputIterator>
+OutputIterator encodeBase64(InputIterator first, InputIterator last, OutputIterator result); //throw ()
+
+template <class InputIterator, class OutputIterator>
+OutputIterator decodeBase64(InputIterator first, InputIterator last, OutputIterator result); //throw ()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------------------------------- implementation ----------------------------------------
+namespace implementation
+{
+const char ENCODING_MIME[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; //64 chars for base64 encoding + padding char
+const signed char DECODING_MIME[] =
+{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 64, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
+};
+const size_t INDEX_PAD = 64;
+}
+
+
+//http://en.wikipedia.org/wiki/Base64
+template <class InputIterator, class OutputIterator> inline
+OutputIterator encodeBase64(InputIterator first, InputIterator last, OutputIterator result)
+{
+ using namespace implementation;
+ assert_static(sizeof(std::iterator_traits<InputIterator>::value_type) == 1);
+ assert_static(sizeof(ENCODING_MIME) == 65 + 1);
+
+ while (first != last)
+ {
+ const unsigned char a = *first++;
+ *result++ = ENCODING_MIME[a >> 2];
+
+ if (first == last)
+ {
+ *result++ = ENCODING_MIME[((a & 0x3) << 4)];
+ *result++ = ENCODING_MIME[INDEX_PAD];
+ *result++ = ENCODING_MIME[INDEX_PAD];
+ break;
+ }
+ const unsigned char b = *first++;
+ *result++ = ENCODING_MIME[((a & 0x3) << 4) | (b >> 4)];
+
+ if (first == last)
+ {
+ *result++ = ENCODING_MIME[((b & 0xf) << 2)];
+ *result++ = ENCODING_MIME[INDEX_PAD];
+ break;
+ }
+ const unsigned char c = *first++;
+ *result++ = ENCODING_MIME[((b & 0xf) << 2) | (c >> 6)];
+ *result++ = ENCODING_MIME[c & 0x3f];
+ }
+
+ return result;
+}
+
+
+template <class InputIterator, class OutputIterator> inline
+OutputIterator decodeBase64(InputIterator first, InputIterator last, OutputIterator result)
+{
+ using namespace implementation;
+ assert_static(sizeof(std::iterator_traits<InputIterator>::value_type) == 1);
+ assert_static(sizeof(DECODING_MIME) == 128);
+
+ const int INDEX_END = INDEX_PAD + 1;
+
+ auto readIndex = [&]() -> unsigned char //return index within [0, 64] or INDEX_END if end of input
+ {
+ while (true)
+ {
+ if (first == last)
+ return INDEX_END;
+
+ unsigned char ch = *first++;
+ if (ch < 128) //skip all unknown characters (including carriage return, line-break, tab)
+ {
+ const int index = implementation::DECODING_MIME[ch];
+ if (0 <= index && index <= INDEX_PAD) //respect padding
+ return index;
+ }
+ }
+ };
+
+ while (true)
+ {
+ const unsigned char index1 = readIndex();
+ const unsigned char index2 = readIndex();
+ if (index1 >= INDEX_PAD || index2 >= INDEX_PAD)
+ {
+ assert(index1 == INDEX_END && index2 == INDEX_END);
+ break;
+ }
+ *result++ = (index1 << 2) | (index2 >> 4);
+
+ const unsigned char index3 = readIndex();
+ if (index3 >= INDEX_PAD) //padding
+ {
+ assert(index3 == INDEX_PAD);
+ break;
+ }
+ *result++ = ((index2 & 0xf) << 4) | (index3 >> 2);
+
+ const unsigned char index4 = readIndex();
+ if (index4 >= INDEX_PAD) //padding
+ {
+ assert(index4 == INDEX_PAD);
+ break;
+ }
+ *result++ = ((index3 & 0x3) << 6) | index4;
+ }
+ return result;
+}
+}
+
+#endif //BASE64_HEADER_08473021856321840873021487213453214
diff --git a/zen/basic_math.h b/zen/basic_math.h
new file mode 100644
index 00000000..24bcf27a
--- /dev/null
+++ b/zen/basic_math.h
@@ -0,0 +1,356 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef BASIC_MATH_HEADER_34726398432
+#define BASIC_MATH_HEADER_34726398432
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <functional>
+
+
+namespace numeric
+{
+template <class T>
+T abs(T value);
+
+template <class T>
+T dist(T a, T b);
+
+template <class T>
+int sign(T value); //returns -1/0/1
+
+template <class T>
+const T& min(const T& a, const T& b, const T& c);
+
+template <class T>
+const T& max(const T& a, const T& b, const T& c);
+
+template <class T>
+void confine(T& val, const T& minVal, const T& maxVal); //make sure minVal <= val && val <= maxVal
+
+template <class InputIterator>
+std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last);
+template <class InputIterator, class Compare>
+std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare comp);
+
+template <class T>
+bool isNull(T value);
+
+int round(double d); //little rounding function
+
+template <size_t N, class T>
+T power(const T& value);
+
+double radToDeg(double rad); //convert unit [rad] into [°]
+double degToRad(double degree); //convert unit [°] into [rad]
+
+template <class InputIterator>
+double arithmeticMean(InputIterator first, InputIterator last);
+
+template <class RandomAccessIterator>
+double median(RandomAccessIterator first, RandomAccessIterator last); //note: invalidates input range!
+
+template <class InputIterator>
+double stdDeviation(InputIterator first, InputIterator last, double* mean = NULL); //estimate standard deviation (and thereby arithmetic mean)
+
+//median absolute deviation: "mad / 0.6745" is a robust measure for standard deviation of a normal distribution
+template <class RandomAccessIterator>
+double mad(RandomAccessIterator first, RandomAccessIterator last); //note: invalidates input range!
+
+
+template <class InputIterator>
+double norm2(InputIterator first, InputIterator last);
+
+//constants
+const double pi = 3.14159265358979323846;
+const double e = 2.71828182845904523536;
+const double sqrt2 = 1.41421356237309504880;
+const double ln2 = 0.693147180559945309417;
+//----------------------------------------------------------------------------------
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################# inline implementation #########################
+template <class T> inline
+T abs(T value)
+{
+ return value < 0 ? -1 * value : value;
+}
+
+template <class T> inline
+T dist(T a, T b)
+{
+ return a > b ? a - b : b - a;
+}
+
+
+template <class T> inline
+int sign(T value) //returns -1/0/1
+{
+ return value < 0 ? -1 : (value > 0 ? 1 : 0);
+}
+
+
+template <class T> inline
+const T& min(const T& a, const T& b, const T& c)
+{
+ return std::min(std::min(a, b), c);
+}
+
+
+template <class T> inline
+const T& max(const T& a, const T& b, const T& c)
+{
+ return std::max(std::max(a, b), c);
+}
+
+
+template <class T> inline
+void confine(T& val, const T& minVal, const T& maxVal)
+{
+ assert(minVal <= maxVal);
+ if (val < minVal)
+ val = minVal;
+ else if (val > maxVal)
+ val = maxVal;
+}
+
+
+template <class InputIterator, class Compare> inline
+std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare comp)
+{
+ InputIterator lowest = first;
+ InputIterator largest = first;
+
+ if (first != last)
+ while (++first != last)
+ {
+ if (comp(*largest, *first)) // or: if (comp(*largest,*lowest)) for the comp version
+ largest = first;
+ else if (comp(*first, *lowest))
+ lowest = first;
+ }
+ return std::make_pair(lowest, largest);
+}
+
+
+template <class InputIterator> inline
+std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last)
+{
+ return minMaxElement(first, last, std::less<typename std::iterator_traits<InputIterator>::value_type>());
+}
+
+
+template <class T> inline
+bool isNull(T value)
+{
+ return abs(value) <= std::numeric_limits<T>::epsilon(); //epsilon is 0 für integral types => less-equal
+}
+
+
+inline
+int round(double d)
+{
+ return static_cast<int>(d < 0 ? d - 0.5 : d + 0.5);
+}
+
+
+namespace
+{
+template <size_t N, class T>
+struct PowerImpl
+{
+ static T result(const T& value)
+ {
+ return PowerImpl<N - 1, T>::result(value) * value;
+ }
+};
+
+template <class T>
+struct PowerImpl<2, T>
+{
+ static T result(const T& value)
+ {
+ return value * value;
+ }
+};
+
+template <class T>
+struct PowerImpl<0, T>; //not defined: invalidates power<0> and power<1>
+
+template <class T>
+struct PowerImpl<10, T>; //not defined: invalidates power<N> for N >= 10
+}
+
+template <size_t N, class T> inline
+T power(const T& value)
+{
+ return PowerImpl<N, T>::result(value);
+}
+
+
+inline
+double radToDeg(double rad)
+{
+ return rad * 180.0 / numeric::pi;
+}
+
+
+inline
+double degToRad(double degree)
+{
+ return degree * numeric::pi / 180.0;
+}
+
+
+template <class InputIterator> inline
+double arithmeticMean(InputIterator first, InputIterator last)
+{
+ //low level implementation to avoid random-access requirement on iterator
+ size_t n = 0;
+ double sum_xi = 0;
+
+ for (; first != last; ++first, ++n)
+ sum_xi += *first;
+
+ return n == 0 ? 0 : sum_xi / n;
+}
+
+
+template <class RandomAccessIterator> inline
+double median(RandomAccessIterator first, RandomAccessIterator last) //note: invalidates input range!
+{
+ const size_t n = last - first;
+ if (n > 0)
+ {
+ std::nth_element(first, first + n / 2, last); //complexity: O(n)
+ const double midVal = *(first + n / 2);
+
+ if (n % 2 != 0)
+ return midVal;
+ else //n is even and >= 2 in this context: return mean of two middle values
+ return 0.5 * (*std::max_element(first, first + n / 2) + midVal); //this operation is the reason why median() CANNOT support a comparison predicate!!!
+ }
+ return 0;
+}
+
+
+class LessMinusMedAbs : public std::binary_function<double, double, bool>
+{
+public:
+ LessMinusMedAbs(double median) : median_(median) {}
+ bool operator()(double lhs, double rhs) const
+ {
+ return abs(lhs - median_) < abs(rhs - median_);
+ }
+private:
+ double median_;
+};
+
+
+template <class RandomAccessIterator> inline
+double mad(RandomAccessIterator first, RandomAccessIterator last) //note: invalidates input range!
+{
+ //http://en.wikipedia.org/wiki/Median_absolute_deviation
+
+ const size_t n = last - first;
+ if (n > 0)
+ {
+ const double m = median(first, last);
+
+ //the second median needs to operate on absolute residuals => avoid transforming input range as it may decrease precision!
+
+ std::nth_element(first, first + n / 2, last, LessMinusMedAbs(m)); //complexity: O(n)
+ const double midVal = abs(*(first + n / 2) - m);
+
+ if (n % 2 != 0)
+ return midVal;
+ else //n is even and >= 2 in this context: return mean of two middle values
+ {
+ const double midVal2 = abs(*std::max_element(first, first + n / 2, LessMinusMedAbs(m)) - m);
+ return 0.5 * (midVal2 + midVal);
+ }
+ }
+ return 0;
+}
+
+
+template <class InputIterator> inline
+double stdDeviation(InputIterator first, InputIterator last, double* arithMean)
+{
+ //implementation minimizing rounding errors, see: http://en.wikipedia.org/wiki/Standard_deviation
+ //combined with techinque avoiding overflow, see: http://www.netlib.org/blas/dnrm2.f -> only 10% performance degregation
+
+ size_t n = 0;
+ double mean = 0;
+ double q = 0;
+ double scale = 1;
+
+ for (; first != last; ++first)
+ {
+ ++n;
+ const double val = *first - mean;
+
+ if (abs(val) > scale)
+ {
+ q = (n - 1.0) / n + q * power<2>(scale / val);
+ scale = abs(val);
+ }
+ else
+ q += (n - 1.0) * power<2>(val / scale) / n;
+
+ mean += val / n;
+ }
+
+ if (arithMean)
+ *arithMean = mean;
+
+ return n <= 1 ? 0 : std::sqrt(q / (n - 1)) * scale;
+}
+
+
+template <class InputIterator> inline
+double norm2(InputIterator first, InputIterator last)
+{
+ double result = 0;
+ double scale = 1;
+ for (; first != last; ++first)
+ {
+ const double tmp = abs(*first);
+ if (tmp > scale)
+ {
+ result = 1 + result * power<2>(scale / tmp);
+ scale = tmp;
+ }
+ else
+ result += power<2>(tmp / scale);
+ }
+ return std::sqrt(result) * scale;
+}
+}
+
+#endif //BASIC_MATH_HEADER_34726398432
diff --git a/zen/build_info.h b/zen/build_info.h
new file mode 100644
index 00000000..e57d0c77
--- /dev/null
+++ b/zen/build_info.h
@@ -0,0 +1,18 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef BUILDINFO_H_INCLUDED
+#define BUILDINFO_H_INCLUDED
+
+namespace zen
+{
+//determine build info
+//safer than checking for _WIN64 (defined on windows for 64-bit compilations only) while _WIN32 is always defined (even for x64 compiler!)
+static const bool is32BitBuild = sizeof(void*) == 4;
+static const bool is64BitBuild = sizeof(void*) == 8;
+}
+
+#endif //BUILDINFO_H_INCLUDED
diff --git a/zen/com_error.h b/zen/com_error.h
new file mode 100644
index 00000000..2ba76c0f
--- /dev/null
+++ b/zen/com_error.h
@@ -0,0 +1,206 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef COM_ERROR_HEADER
+#define COM_ERROR_HEADER
+
+#include <string>
+#include <cstdio>
+#include "win.h" //includes "windows.h"
+
+namespace zen
+{
+std::wstring generateErrorMsg(const std::wstring& input, HRESULT hr);
+std::wstring formatWin32Msg(DWORD dwMessageId); //return empty string on error
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################# implementation #####################
+std::wstring formatWin32Msg(DWORD dwMessageId) //return empty string on error
+{
+ std::wstring output;
+ LPWSTR buffer = NULL;
+ if (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK |
+ FORMAT_MESSAGE_IGNORE_INSERTS | //important: without this flag ::FormatMessage() will fail if message contains placeholders
+ FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, dwMessageId, 0, reinterpret_cast<LPWSTR>(&buffer), 0, NULL) != 0)
+ {
+ if (buffer) //just to be sure
+ {
+ output = buffer;
+ ::LocalFree(buffer);
+ }
+ }
+ return output;
+}
+
+namespace
+{
+std::wstring formatFacility(HRESULT hr)
+{
+ switch (HRESULT_FACILITY(hr))
+ {
+ case FACILITY_XPS:
+ return L"XPS";
+ case FACILITY_WINRM:
+ return L"Windows Resource Manager";
+ case FACILITY_WINDOWSUPDATE:
+ return L"Windows Update";
+ case FACILITY_WINDOWS_DEFENDER:
+ return L"Windows Defender Component";
+ case FACILITY_WINDOWS_CE:
+ return L"Windows CE";
+ case FACILITY_WINDOWS:
+ return L"Windows Subsystem";
+ case FACILITY_USERMODE_VOLMGR:
+ return L"User Mode Volume Manager";
+ case FACILITY_USERMODE_VIRTUALIZATION:
+ return L"User Mode Virtualization Subsystem";
+ case FACILITY_USERMODE_VHD:
+ return L"User Mode Virtual Hard Disk Support";
+ case FACILITY_URT:
+ return L".NET CLR";
+ case FACILITY_UMI:
+ return L"Ubiquitous Memoryintrospection Service";
+ case FACILITY_UI:
+ return L"UI";
+ case FACILITY_TPM_SOFTWARE:
+ return L"Trusted Platform Module Applications";
+ case FACILITY_TPM_SERVICES:
+ return L"Trusted Platform Module Services";
+ case FACILITY_SXS:
+ return L"Side-by-side Servicing";
+ case FACILITY_STORAGE:
+ return L"OLE Storage";
+ case FACILITY_STATE_MANAGEMENT:
+ return L"State Management Services";
+ case FACILITY_SCARD:
+ return L"Smart-card Subsystem";
+ case FACILITY_SHELL:
+ return L"User Shell";
+ case FACILITY_SETUPAPI:
+ return L"Setup API";
+ case FACILITY_SECURITY:
+ return L"Security API Layer";
+ case FACILITY_SDIAG:
+ return L"System Diagnostics";
+ case FACILITY_RPC:
+ return L"RPC Subsystem";
+ case FACILITY_RAS:
+ return L"RAS";
+ case FACILITY_PLA:
+ return L"Performance Logs and Alerts";
+ case FACILITY_OPC:
+ return L"Open Connectivity Service";
+ case FACILITY_WIN32:
+ return L"Win32";
+ case FACILITY_CONTROL:
+ return L"Control Mechanism";
+ case FACILITY_WEBSERVICES:
+ return L"Web Services";
+ case FACILITY_NDIS:
+ return L"Network Driver Interface";
+ case FACILITY_METADIRECTORY:
+ return L"Microsoft Identity Server";
+ case FACILITY_MSMQ:
+ return L"Microsoft Message Queue";
+ case FACILITY_MEDIASERVER:
+ return L"Windows Media Server";
+ case FACILITY_MBN:
+ return L"MBN";
+ case FACILITY_INTERNET:
+ return L"Wininet";
+ case FACILITY_ITF:
+ return L"COM/OLE Interface Management";
+ case FACILITY_USERMODE_HYPERVISOR:
+ return L"Usermode Hypervisor Components";
+ case FACILITY_HTTP:
+ return L"HTTP Support";
+ case FACILITY_GRAPHICS:
+ return L"Graphics Drivers";
+ case FACILITY_FWP:
+ return L"Firewall Platform";
+ case FACILITY_FVE:
+ return L"Full volume encryption";
+ case FACILITY_USERMODE_FILTER_MANAGER:
+ return L"User Mode Filter Manager";
+ case FACILITY_DPLAY:
+ return L"Direct Play";
+ case FACILITY_DISPATCH:
+ return L"COM Dispatch";
+ case FACILITY_DIRECTORYSERVICE:
+ return L"Active Directory";
+ case FACILITY_CONFIGURATION:
+ return L"Configuration Services";
+ case FACILITY_COMPLUS:
+ return L"COM+";
+ case FACILITY_USERMODE_COMMONLOG:
+ return L"Common Logging Support";
+ case FACILITY_CMI:
+ return L"Configuration Management Infrastructure";
+ case FACILITY_CERT:
+ return L"Certificate";
+ case FACILITY_BCD:
+ return L"Boot Configuration Database";
+ case FACILITY_BACKGROUNDCOPY:
+ return L"Background Copy Control";
+ case FACILITY_ACS:
+ return L"Audit Collection Service";
+ case FACILITY_AAF:
+ return L"Microsoft Agent";
+ default:
+ return L"Unknown";
+ }
+}
+}
+
+inline
+std::wstring numberToHexString(long number)
+{
+ wchar_t result[100];
+ ::swprintf(result, 100, L"0x%08x", number);
+ return std::wstring(result);
+}
+
+
+inline
+std::wstring generateErrorMsg(const std::wstring& input, HRESULT hr)
+{
+ std::wstring output(input);
+ output += L"\n";
+ output += L"HRESULT: " + numberToHexString(hr) + L",\n";
+
+ //don't use _com_error(hr).ErrorMessage(): internally this is nothing more than a call to ::FormatMessage()
+ std::wstring win32Msg = formatWin32Msg(hr);
+ if (!win32Msg.empty()) //empty string on error
+ output += win32Msg;
+ else
+ {
+ output += L"Facility: " + formatFacility(hr) + L",\n";
+ output += L"Win32 Error: " + formatWin32Msg(HRESULT_CODE(hr)); //interpret hr as a Win32 code; this is often useful...
+ }
+ return output;
+}
+}
+#endif //COM_ERROR_HEADER
diff --git a/zen/com_ptr.h b/zen/com_ptr.h
new file mode 100644
index 00000000..380f4536
--- /dev/null
+++ b/zen/com_ptr.h
@@ -0,0 +1,123 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef SMART_COM_PTR_H
+#define SMART_COM_PTR_H
+
+#include <algorithm>
+#include <Objbase.h>
+
+namespace zen
+{
+/*
+ComPtr: RAII class handling COM objects
+
+Example:
+ --------
+ ComPtr<IUPnPDeviceFinder> devFinder;
+ if (FAILED(::CoCreateInstance(CLSID_UPnPDeviceFinder,
+ NULL,
+ CLSCTX_ALL,
+ IID_PPV_ARGS(devFinder.init()))))
+ return -1;
+
+ ComPtr<IEnumUnknown> devEnum = com_dynamic_cast<IEnumUnknown>(devColl);
+ if (!devEnum)
+ return -1;
+*/
+
+template <class T>
+class ComPtr
+{
+public:
+ ComPtr() : ptr(NULL) {}
+
+ ComPtr(const ComPtr& other) : ptr(other.ptr) { if (ptr) ptr->AddRef(); }
+ ComPtr( ComPtr&& other) : ptr(other.release()) {}
+
+ ComPtr& operator=(const ComPtr& other) { ComPtr(other).swap(*this); return *this; }
+ ComPtr& operator=( ComPtr&& other) { swap(other); return *this; }
+
+ ~ComPtr() { if (ptr) ptr->Release(); }
+
+ T** init() //get pointer for use with ::CoCreateInstance()
+ {
+ ComPtr<T>().swap(*this);
+ return &ptr;
+ }
+
+ T* get() const { return ptr; }
+
+ T* release() //throw()
+ {
+ T* tmp = ptr;
+ ptr = NULL;
+ return tmp;
+ }
+
+ void swap(ComPtr& rhs) { std::swap(ptr, rhs.ptr); } //throw()
+
+ T* operator->() const { return ptr; }
+
+private:
+ T* ptr;
+
+ struct ConversionToBool { int dummy; };
+public:
+ //use member pointer as implicit conversion to bool (C++ Templates - Vandevoorde/Josuttis; chapter 20)
+ operator int ConversionToBool::* () const { return ptr != NULL ? &ConversionToBool::dummy : NULL; }
+};
+
+
+template <class S, class T>
+ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //throw()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################# Inline Implementation #############################
+
+//we cannot 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)
+{
+ lhs.swap(rhs);
+}
+
+
+template <class S, class T> inline
+ComPtr<S> com_dynamic_cast(const ComPtr<T>& other) //throw()
+{
+ ComPtr<S> outPtr;
+ if (other)
+ other->QueryInterface(IID_PPV_ARGS(outPtr.init()));
+ return outPtr;
+}
+}
+
+
+#endif //SMART_COM_PTR_H \ No newline at end of file
diff --git a/zen/com_util.h b/zen/com_util.h
new file mode 100644
index 00000000..db51404b
--- /dev/null
+++ b/zen/com_util.h
@@ -0,0 +1,121 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef COM_UTILITY_HEADER
+#define COM_UTILITY_HEADER
+
+#include "com_ptr.h"
+#include <string>
+#include <cassert>
+
+namespace zen
+{
+//get an enumeration interface as a std::vector of bound(!) ComPtr(s)
+template <class T, class U>
+std::vector<ComPtr<T> > convertEnum(const ComPtr<U>& enumObj); //enumObj: must have the "_NewEnum" property that supports the IEnumUnknown interface
+
+/*
+extract text from com object member function returning a single BSTR: HRESULT ComInterface::MemFun([out] BSTR *pbstr);
+ Example: ComPtr<...> comObj =...;
+ std::wstring description = getText(comObj, &IUPnPDevice::get_Description);
+*/
+template <class T, class MemFun>
+std::wstring getText(ComPtr<T> comObj, MemFun memFun);
+
+
+//RAII class handling BSTR
+class Bstring
+{
+public:
+ Bstring(const std::wstring& str) { str_ = ::SysAllocStringLen(str.data(), str.length()); } //string::data() returns unmodified string potentially containing 0-values
+ ~Bstring() { if (str_) ::SysFreeString(str_); }
+
+ const BSTR get() const { return str_; }
+
+private:
+ Bstring(const Bstring&); //not implemented
+ Bstring& operator=(const Bstring&); //
+
+ BSTR str_;
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//############################ inline implemenatation ##################################
+template <class T, class U> inline
+std::vector<ComPtr<T> > convertEnum(const ComPtr<U>& enumObj)
+{
+ std::vector<ComPtr<T> > output;
+
+ if (enumObj)
+ {
+ ComPtr<IUnknown> unknown;
+ enumObj->get__NewEnum(unknown.init());
+ ComPtr<IEnumUnknown> enumUnknown = com_dynamic_cast<IEnumUnknown>(unknown);
+
+ assert(enumUnknown); //IEnumUnknown must be supported!
+ if (enumUnknown)
+ {
+ ComPtr<IUnknown> itemTmp;
+ while (enumUnknown->Next(1, itemTmp.init(), NULL) == S_OK) //returns S_FALSE == 1 when finished! Don't use SUCCEEDED()!!!
+ {
+ ComPtr<T> itemNew = com_dynamic_cast<T>(itemTmp);
+ if (itemNew)
+ output.push_back(itemNew);
+ }
+ }
+ }
+
+ return output;
+}
+
+
+template <class T, class MemFun> inline
+std::wstring getText(ComPtr<T> comObj, MemFun memFun)
+{
+ std::wstring text;
+ {
+ if (!comObj)
+ return std::wstring();
+
+ BSTR bstr = NULL;
+ if (FAILED((comObj.get()->*memFun)(&bstr)))
+ return std::wstring();
+
+ if (bstr) //NULL means "no text"
+ {
+ text = std::wstring(bstr, ::SysStringLen(bstr)); //correctly copy 0-characters
+ ::SysFreeString(bstr);
+ }
+ }
+ return text;
+}
+}
+
+
+#endif //COM_UTILITY_HEADER \ No newline at end of file
diff --git a/zen/debug_log.h b/zen/debug_log.h
new file mode 100644
index 00000000..d8871ef9
--- /dev/null
+++ b/zen/debug_log.h
@@ -0,0 +1,82 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DEBUG_LOG_HEADER_017324601673246392184621895740256342
+#define DEBUG_LOG_HEADER_017324601673246392184621895740256342
+
+#include "zstring.h"
+
+cleanup this mess + remove any wxWidgets dependency!
+
+//small macro for writing debug information into a logfile
+#define WRITE_DEBUG_LOG(x) globalLogFile().write(getCodeLocation(__TFILE__, __LINE__) + x);
+//speed alternative: wxLogDebug(wxT("text")) + DebugView
+
+
+class DebugLog
+{
+public:
+ wxDEPRECATED(DebugLog(const wxString& filePrefix = wxString()))
+ prefix(filePrefix),
+ lineCount(0)
+ {
+ logfileName = assembleFileName();
+ logFile.Open(logfileName, wxFile::write);
+ }
+
+ void write(const std::string& logText)
+ {
+ todo;
+ }
+
+ void write(const wxString& logText)
+ {
+ ++lineCount;
+ if (lineCount % 50000 == 0) //prevent logfile from becoming too big
+ {
+ logFile.Close();
+ wxRemoveFile(logfileName);
+
+ logfileName = assembleFileName();
+ logFile.Open(logfileName, wxFile::write);
+ }
+
+ersetze wxDateTime::Now() durch eigene lib:
+ z.b. iso_time.h
+
+ logFile.Write(wxString(wxT("[")) + wxDateTime::Now().FormatTime() + wxT("] "));
+ logFile.Write(logText + LINE_BREAK);
+ }
+
+private:
+ wxString assembleFileName()
+ {
+ wxString tmp = wxDateTime::Now().FormatISOTime();
+ tmp.Replace(wxT(":"), wxEmptyString);
+ return prefix + wxString(wxT("DEBUG_")) + wxDateTime::Now().FormatISODate() + wxChar('_') + tmp + wxT(".log");
+ }
+
+ wxString logfileName;
+ wxString prefix;
+ int lineCount;
+ wxFile logFile; //logFile.close(); <- not needed
+};
+
+inline
+DebugLog& globalLogFile()
+{
+ static DebugLog inst; //external linkage despite header definition!
+ return inst;
+}
+
+inline
+wxString getCodeLocation(const wxString& file, int line)
+{
+ return wxString(file).AfterLast(FILE_NAME_SEPARATOR) + wxT(", LINE ") + toString<wxString>(line) + wxT(" | ");
+}
+
+
+#endif //DEBUG_LOG_HEADER_017324601673246392184621895740256342
diff --git a/zen/debug_new.cpp b/zen/debug_new.cpp
new file mode 100644
index 00000000..c830a36b
--- /dev/null
+++ b/zen/debug_new.cpp
@@ -0,0 +1,54 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "debug_new.h"
+
+#include "win.h" //includes "windows.h"
+#include "DbgHelp.h" //available for MSC only
+#pragma comment(lib, "Dbghelp.lib")
+
+
+namespace
+{
+LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo)
+{
+ HANDLE hFile = ::CreateFile(L"exception.dmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ MINIDUMP_EXCEPTION_INFORMATION exInfo = {};
+ exInfo.ThreadId = ::GetCurrentThreadId();
+ exInfo.ExceptionPointers = pExceptionInfo;
+ exInfo.ClientPointers = NULL;
+
+ MINIDUMP_EXCEPTION_INFORMATION* exceptParam = pExceptionInfo ? &exInfo : NULL;
+
+ ::MiniDumpWriteDump(::GetCurrentProcess(), //__in HANDLE hProcess,
+ ::GetCurrentProcessId(), //__in DWORD ProcessId,
+ hFile, //__in HANDLE hFile,
+ MiniDumpWithDataSegs, //__in MINIDUMP_TYPE DumpType, ->Standard: MiniDumpNormal, Medium: MiniDumpWithDataSegs, Full: MiniDumpWithFullMemory
+ exceptParam, //__in PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
+ NULL, //__in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
+ NULL); //__in PMINIDUMP_CALLBACK_INFORMATION CallbackParam
+
+ ::CloseHandle(hFile);
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+
+struct WriteDumpOnUnhandledException
+{
+ WriteDumpOnUnhandledException()
+ {
+ ::SetUnhandledExceptionFilter(writeDumpOnException);
+ }
+} dummy; //ensure that a dump-file is written for uncaught exceptions
+}
+
+
+void mem_check::writeMinidump()
+{
+ writeDumpOnException(NULL);
+}
diff --git a/zen/debug_new.h b/zen/debug_new.h
new file mode 100644
index 00000000..2976d3d7
--- /dev/null
+++ b/zen/debug_new.h
@@ -0,0 +1,96 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef 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
+
+/*overwrite "operator new" to get more detailed error messages on bad_alloc, detect memory leaks and write memory dumps
+Usage:
+- Include everywhere before any other file: $(ProjectDir)\shared\debug_new.h
+For Minidumps:
+- Compile "debug_new.cpp"
+- Compile with debugging symbols and optimization deactivated
+*/
+
+namespace mem_check
+{
+class BadAllocDetailed : public std::bad_alloc
+{
+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
new file mode 100644
index 00000000..7b45b014
--- /dev/null
+++ b/zen/dir_watcher.cpp
@@ -0,0 +1,493 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "dir_watcher.h"
+#include <algorithm>
+#include <set>
+#include "thread.h" //includes <boost/thread.hpp>
+#include "scope_guard.h"
+
+#ifdef FFS_WIN
+#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>
+#include <fcntl.h>
+#include "file_traverser.h"
+#endif
+
+
+using namespace zen;
+
+#ifdef FFS_WIN
+namespace
+{
+inline
+bool errorCodeForNotExisting(const DWORD lastError)
+{
+ return lastError == ERROR_PATH_NOT_FOUND ||
+ lastError == ERROR_BAD_NETPATH ||
+ lastError == ERROR_NETNAME_DELETED;
+}
+
+
+class SharedData
+{
+public:
+ //context of worker thread
+ void addChanges(const char* buffer, DWORD bytesWritten, const Zstring& dirname) //throw ()
+ {
+ 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!");
+ else
+ {
+ const char* bufPos = &buffer[0];
+ for(;;)
+ {
+ const FILE_NOTIFY_INFORMATION& notifyInfo = reinterpret_cast<const FILE_NOTIFY_INFORMATION&>(*bufPos);
+
+ 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!
+ [&]()
+ {
+ if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice
+ return;
+
+ if (notifyInfo.Action == FILE_ACTION_MODIFIED)
+ {
+ //note: this check will not work if top watched directory has been renamed
+ const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(fullname).c_str());
+ if (ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY)) //returns true for (dir-)symlinks also
+ return;
+ }
+
+ output.insert(fullname);
+ }();
+
+ if (notifyInfo.NextEntryOffset == 0)
+ break;
+ bufPos += notifyInfo.NextEntryOffset;
+ }
+ }
+ }
+
+ //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
+ {
+ boost::lock_guard<boost::mutex> dummy(lockAccess);
+
+ //first check whether errors occured in thread
+ if (!errorMsg.first.empty())
+ {
+ const std::wstring msg = errorMsg.first.c_str();
+ const DWORD lastError = errorMsg.second;
+
+ if (errorCodeForNotExisting(lastError))
+ throw ErrorNotExisting(msg);
+ throw FileError(msg);
+ }
+
+ output.assign(changedFiles.begin(), changedFiles.end());
+ changedFiles.clear();
+ }
+
+
+ //context of worker thread
+ void reportError(const std::wstring& msg, DWORD errorCode) //throw()
+ {
+ boost::lock_guard<boost::mutex> dummy(lockAccess);
+ errorMsg = std::make_pair(cvrtString<BasicWString>(msg), errorCode);
+ }
+
+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::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occured in thread
+};
+
+
+class ReadChangesAsync
+{
+public:
+ //constructed in main thread!
+ ReadChangesAsync(const Zstring& directory, //make sure to not leak in thread-unsafe types!
+ const std::shared_ptr<SharedData>& shared) :
+ shared_(shared),
+ dirname(directory)
+ {
+ if (!endsWith(dirname, FILE_NAME_SEPARATOR))
+ dirname += FILE_NAME_SEPARATOR;
+
+ //these two privileges are required by ::CreateFile FILE_FLAG_BACKUP_SEMANTICS according to
+ //http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx
+ try
+ {
+ Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError
+ Privileges::getInstance().ensureActive(SE_RESTORE_NAME); //
+ }
+ catch (const FileError&) {}
+
+ hDir = ::CreateFile(applyLongPathPrefix(dirname.c_str()).c_str(),
+ FILE_LIST_DIRECTORY,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
+ NULL);
+ if (hDir == INVALID_HANDLE_VALUE )
+ {
+ const std::wstring errorMsg = _("Could not initialize directory monitoring:") + "\n\"" + utf8CvrtTo<std::wstring>(dirname) + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ if (errorCodeForNotExisting(::GetLastError()))
+ throw ErrorNotExisting(errorMsg);
+ throw FileError(errorMsg);
+ }
+
+ //end of constructor, no need to start managing "hDir"
+ }
+
+ ~ReadChangesAsync()
+ {
+ if (hDir != INVALID_HANDLE_VALUE)
+ ::CloseHandle(hDir);
+ }
+
+ void operator()() //thread entry
+ {
+ try
+ {
+ std::vector<char> buffer(64 * 1024); //needs to be aligned on a DWORD boundary; maximum buffer size restricted by some networks protocols (according to docu)
+
+ for(;;)
+ {
+ boost::this_thread::interruption_point();
+
+ //actual work
+ OVERLAPPED overlapped = {};
+ overlapped.hEvent = ::CreateEvent(NULL, //__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
+ true, //__in BOOL bManualReset,
+ false, //__in BOOL bInitialState,
+ NULL); //__in_opt LPCTSTR lpName
+ if (overlapped.hEvent == NULL)
+ return shared_->reportError(_("Error when monitoring directories.") + " (CreateEvent)" + "\n\n" + getLastErrorFormatted(), ::GetLastError());
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(overlapped.hEvent));
+
+ //asynchronous variant: runs on this thread's APC queue!
+ if (!::ReadDirectoryChangesW(hDir, // __in HANDLE hDirectory,
+ &buffer[0], // __out LPVOID lpBuffer,
+ static_cast<DWORD>(buffer.size()), // __in DWORD nBufferLength,
+ true, // __in BOOL bWatchSubtree,
+ FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_DIR_NAME |
+ FILE_NOTIFY_CHANGE_SIZE |
+ FILE_NOTIFY_CHANGE_LAST_WRITE, // __in DWORD dwNotifyFilter,
+ NULL, // __out_opt LPDWORD lpBytesReturned,
+ &overlapped, // __inout_opt LPOVERLAPPED lpOverlapped,
+ NULL)) // __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
+ return shared_->reportError(_("Error when monitoring directories.") + " (ReadDirectoryChangesW)" + "\n\n" + getLastErrorFormatted(), ::GetLastError());
+
+ //async I/O is a resource that needs to be guarded since it will write to local variable "buffer"!
+ zen::ScopeGuard lockAio = zen::makeGuard([&]()
+ {
+ //http://msdn.microsoft.com/en-us/library/aa363789(v=vs.85).aspx
+ if (::CancelIo(hDir) == TRUE) //cancel all async I/O related to this handle and thread
+ {
+ DWORD bytesWritten = 0;
+ ::GetOverlappedResult(hDir, &overlapped, &bytesWritten, true); //wait until cancellation is complete
+ }
+ });
+
+ DWORD bytesWritten = 0;
+
+ //wait for results
+ while (!::GetOverlappedResult(hDir, //__in HANDLE hFile,
+ &overlapped, //__in LPOVERLAPPED lpOverlapped,
+ &bytesWritten, //__out LPDWORD lpNumberOfBytesTransferred,
+ false)) //__in BOOL bWait
+ {
+ if (::GetLastError() != ERROR_IO_INCOMPLETE)
+ return shared_->reportError(_("Error when monitoring directories.") + " (GetOverlappedResult)" + "\n\n" + getLastErrorFormatted(), ::GetLastError());
+
+ //execute asynchronous procedure calls (APC) queued on this thread
+ ::SleepEx(50, // __in DWORD dwMilliseconds,
+ true); // __in BOOL bAlertable
+
+ boost::this_thread::interruption_point();
+ }
+ lockAio.dismiss();
+
+ shared_->addChanges(&buffer[0], bytesWritten, dirname); //throw ()
+ }
+ }
+ catch (boost::thread_interrupted&)
+ {
+ throw; //this is the only reasonable exception!
+ }
+ }
+
+ ReadChangesAsync(ReadChangesAsync && other) :
+ hDir(INVALID_HANDLE_VALUE)
+ {
+ shared_ = std::move(other.shared_);
+ dirname = std::move(other.dirname);
+ std::swap(hDir, other.hDir);
+ }
+
+ HANDLE getDirHandle() const { return hDir; } //for reading purposes only, don't abuse (e.g. close handle)!
+
+private:
+ //shared between main and worker:
+ std::shared_ptr<SharedData> shared_;
+ //worker thread only:
+ Zstring dirname; //thread safe!
+ HANDLE hDir;
+};
+
+
+class HandleVolumeRemoval : public NotifyRequestDeviceRemoval
+{
+public:
+ HandleVolumeRemoval(HANDLE hDir,
+ boost::thread& worker,
+ const std::shared_ptr<SharedData>& shared,
+ const Zstring& dirname) :
+ NotifyRequestDeviceRemoval(hDir), //throw FileError
+ worker_(worker),
+ shared_(shared),
+ dirname_(dirname),
+ removalRequested(false),
+ operationComplete(false) {}
+
+ //all functions are called by main thread!
+
+ bool requestReceived() const { return removalRequested; }
+ bool finished() const { return operationComplete; }
+
+private:
+ virtual void onRequestRemoval(HANDLE hnd)
+ {
+ //must release hDir immediately => stop monitoring!
+ worker_.interrupt();
+ worker_.join();
+ //now hDir should have been released
+
+ //report removal as change to main directory
+ shared_->addChange(dirname_);
+
+ removalRequested = true;
+ } //don't throw!
+ virtual void onRemovalFinished(HANDLE hnd, bool successful) { operationComplete = true; } //throw()!
+
+ boost::thread& worker_;
+ std::shared_ptr<SharedData> shared_;
+ Zstring dirname_;
+ bool removalRequested;
+ bool operationComplete;
+};
+}
+
+
+struct DirWatcher::Pimpl
+{
+ boost::thread worker;
+ std::shared_ptr<SharedData> shared;
+
+ std::unique_ptr<HandleVolumeRemoval> volRemoval;
+};
+
+
+DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError
+ pimpl_(new Pimpl)
+{
+ pimpl_->shared = std::make_shared<SharedData>();
+
+ ReadChangesAsync reader(directory, pimpl_->shared); //throw FileError
+ pimpl_->volRemoval.reset(new HandleVolumeRemoval(reader.getDirHandle(), pimpl_->worker, pimpl_->shared, directory)); //throw FileError
+ pimpl_->worker = boost::thread(std::move(reader));
+}
+
+
+DirWatcher::~DirWatcher()
+{
+ pimpl_->worker.interrupt();
+ //pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway
+ //caveat: exitting the app may simply kill this thread!
+
+ //wait until device removal is confirmed, to (hopefully!) prevent locking hDir again by new watch!
+ if (pimpl_->volRemoval->requestReceived())
+ {
+ const boost::system_time maxwait = boost::get_system_time() + boost::posix_time::seconds(3); //HandleVolumeRemoval::finished() not guaranteed!
+
+ while (!pimpl_->volRemoval->finished() && boost::get_system_time() < maxwait)
+ boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50));
+ }
+}
+
+
+std::vector<Zstring> DirWatcher::getChanges() //throw FileError
+{
+ std::vector<Zstring> output;
+ pimpl_->shared->getChanges(output); //throw FileError
+ return output;
+}
+
+
+#elif defined FFS_LINUX
+struct DirWatcher::Pimpl
+{
+ int notifDescr;
+ std::map<int, Zstring> watchDescrs; //watch descriptor and corresponding directory name (postfixed with separator!)
+};
+
+
+namespace
+{
+class DirsOnlyTraverser : public zen::TraverseCallback
+{
+public:
+ DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {}
+
+ virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {}
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) {}
+ virtual ReturnValDir onDir (const Zchar* shortName, const Zstring& fullName)
+ {
+ dirs_.push_back(fullName);
+ return ReturnValDir(zen::Int2Type<ReturnValDir::TRAVERSING_DIR_CONTINUE>(), *this);
+ }
+ virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); }
+
+private:
+ std::vector<Zstring>& dirs_;
+};
+}
+
+
+DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError
+ pimpl_(new Pimpl)
+{
+ //still in main thread
+ Zstring dirname = directory;
+ if (endsWith(dirname, FILE_NAME_SEPARATOR))
+ dirname.resize(dirname.size() - 1);
+
+ //get all subdirectories
+ std::vector<Zstring> fullDirList;
+ fullDirList.push_back(dirname);
+
+ DirsOnlyTraverser traverser(fullDirList); //throw FileError
+ zen::traverseFolder(dirname, false, traverser); //don't traverse into symlinks (analog to windows build)
+
+ //init
+ pimpl_->notifDescr = ::inotify_init();
+ if (pimpl_->notifDescr == -1)
+ throw FileError(_("Could not initialize directory monitoring:") + "\n\"" + dirname + "\"" + "\n\n" + getLastErrorFormatted());
+
+ zen::ScopeGuard guardDescr = zen::makeGuard([&]() { ::close(pimpl_->notifDescr); });
+
+ //set non-blocking mode
+ bool initSuccess = false;
+ int flags = ::fcntl(pimpl_->notifDescr, F_GETFL);
+ if (flags != -1)
+ initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1;
+
+ if (!initSuccess)
+ throw FileError(_("Could not initialize directory monitoring:") + "\n\"" + dirname + "\"" + "\n\n" + getLastErrorFormatted());
+
+ //add watches
+ std::for_each(fullDirList.begin(), fullDirList.end(),
+ [&](Zstring subdir)
+ {
+ int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(),
+ IN_ONLYDIR | //watch directories only
+ IN_DONT_FOLLOW | //don't follow symbolic links
+ IN_MODIFY |
+ IN_CLOSE_WRITE |
+ IN_MOVE |
+ IN_CREATE |
+ IN_DELETE |
+ IN_DELETE_SELF |
+ IN_MOVE_SELF);
+ if (wd == -1)
+ {
+ std::wstring errorMsg = _("Could not initialize directory monitoring:") + "\n\"" + subdir + "\"" + "\n\n" + getLastErrorFormatted();
+ if (errno == ENOENT)
+ throw ErrorNotExisting(errorMsg);
+ throw FileError(errorMsg);
+ }
+
+ if (!endsWith(subdir, FILE_NAME_SEPARATOR))
+ subdir += FILE_NAME_SEPARATOR;
+ pimpl_->watchDescrs.insert(std::make_pair(wd, subdir));
+ });
+
+ guardDescr.dismiss();
+}
+
+
+DirWatcher::~DirWatcher()
+{
+ ::close(pimpl_->notifDescr); //associated watches are removed automatically!
+}
+
+
+std::vector<Zstring> DirWatcher::getChanges() //throw FileError
+{
+ std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16));
+
+ //non-blocking call, see O_NONBLOCK
+ ssize_t bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size());
+
+ if (bytesRead == -1)
+ {
+ 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>();
+
+ throw FileError(_("Error when monitoring directories.") + "\n\n" + getLastErrorFormatted());
+ }
+
+ std::set<Zstring> tmp; //get rid of duplicate entries (actually occur!)
+
+ ssize_t bytePos = 0;
+ while (bytePos < bytesRead)
+ {
+ struct inotify_event& evt = reinterpret_cast<struct inotify_event&>(buffer[bytePos]);
+
+ if (evt.len != 0) //exclude case: deletion of "self", already reported by parent directory watch
+ {
+ auto iter = pimpl_->watchDescrs.find(evt.wd);
+ if (iter != pimpl_->watchDescrs.end())
+ {
+ //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);
+ }
+ }
+
+ bytePos += sizeof(struct inotify_event) + evt.len;
+ }
+
+ return std::vector<Zstring>(tmp.begin(), tmp.end());
+}
+
+#endif
diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h
new file mode 100644
index 00000000..df3c444c
--- /dev/null
+++ b/zen/dir_watcher.h
@@ -0,0 +1,50 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DIR_WATCHER_348577025748023458
+#define DIR_WATCHER_348577025748023458
+
+#include <vector>
+#include <memory>
+#include "file_error.h"
+
+namespace zen
+{
+//Windows: ReadDirectoryChangesW http://msdn.microsoft.com/en-us/library/aa365465(v=vs.85).aspx
+//Linux: inotify http://linux.die.net/man/7/inotify
+
+//watch directory including subdirectories
+/*
+!Note handling of directories!:
+ Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher!
+ 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!)
+
+ Overcome all issues portably: check existence of watched directory externally + reinstall watch after changes in directory structure (added directories) are possible
+*/
+class DirWatcher
+{
+public:
+ DirWatcher(const Zstring& directory); //throw FileError, ErrorNotExisting
+ ~DirWatcher();
+
+ //extract accumulated changes since last call
+ std::vector<Zstring> getChanges(); //throw FileError
+
+private:
+ DirWatcher(const DirWatcher&);
+ DirWatcher& operator=(const DirWatcher&);
+
+ struct Pimpl;
+ std::unique_ptr<Pimpl> pimpl_;
+};
+
+}
+
+#endif
diff --git a/zen/disable_standby.h b/zen/disable_standby.h
new file mode 100644
index 00000000..ec112427
--- /dev/null
+++ b/zen/disable_standby.h
@@ -0,0 +1,27 @@
+#ifndef PREVENTSTANDBY_H_INCLUDED
+#define PREVENTSTANDBY_H_INCLUDED
+
+#ifdef FFS_WIN
+#include "win.h" //includes "windows.h"
+#endif
+
+namespace zen
+{
+class DisableStandby
+{
+public:
+#ifdef FFS_WIN
+ DisableStandby()
+ {
+ ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED /* | ES_AWAYMODE_REQUIRED*/ );
+ }
+
+ ~DisableStandby()
+ {
+ ::SetThreadExecutionState(ES_CONTINUOUS);
+ }
+#endif
+};
+}
+
+#endif // PREVENTSTANDBY_H_INCLUDED
diff --git a/zen/dll.h b/zen/dll.h
new file mode 100644
index 00000000..302a3ac8
--- /dev/null
+++ b/zen/dll.h
@@ -0,0 +1,120 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DLLLOADER_H_INCLUDED
+#define DLLLOADER_H_INCLUDED
+
+#include <memory>
+#include <string>
+#include "scope_guard.h"
+#include "win.h" //includes "windows.h"
+
+namespace zen
+{
+/*
+Manage DLL function and library ownership
+ - thread safety: like built-in type
+ - full value semantics
+
+ Usage:
+ typedef BOOL (WINAPI *IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process);
+ const zen::DllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process");
+ if (isWow64Process) ... use function ptr ...
+*/
+
+template <class Func>
+class DllFun
+{
+public:
+ DllFun() : fun(NULL) {}
+
+ DllFun(const wchar_t* libraryName, const char* functionName) :
+ hLibRef(new HMODULE(::LoadLibrary(libraryName)), deleter),
+ fun(*hLibRef ? reinterpret_cast<Func>(::GetProcAddress(*hLibRef, functionName)) : NULL) {}
+
+ operator Func() const { return fun; }
+
+private:
+ static void deleter(HMODULE* ptr) { if (*ptr) ::FreeLibrary(*ptr); delete ptr; }
+
+ std::shared_ptr<const HMODULE> hLibRef;
+ Func fun;
+};
+
+//if the dll is already part of the process space, e.g. "kernel32.dll" or "shell32.dll", we can use a faster variant:
+//NOTE: since the lifetime of the referenced library is *not* controlled, this is safe to use only for permanently loaded libraries like these!
+template <class Func>
+class SysDllFun
+{
+public:
+ SysDllFun() : fun(NULL) {}
+
+ SysDllFun(const wchar_t* systemLibrary, const char* functionName) :
+ fun(reinterpret_cast<Func>(::GetProcAddress(::GetModuleHandle(systemLibrary), functionName))) {}
+
+ operator Func() const { return fun; }
+
+private:
+ Func fun;
+};
+
+
+/*
+extract binary resources from .exe/.dll:
+
+-- resource.h --
+#define MY_BINARY_RESOURCE 1337
+
+-- resource.rc --
+MY_BINARY_RESOURCE RCDATA "filename.dat"
+*/
+std::string getResourceStream(const std::wstring& libraryName, size_t resourceId);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------Inline Implementation---------------------------------------------------
+inline
+std::string getResourceStream(const wchar_t* libraryName, size_t resourceId)
+{
+ std::string output;
+ HMODULE module = ::LoadLibrary(libraryName);
+ if (module)
+ {
+ ZEN_ON_BLOCK_EXIT(::FreeLibrary(module));
+
+ const HRSRC res = ::FindResource(module, MAKEINTRESOURCE(resourceId), RT_RCDATA);
+ if (res != NULL)
+ {
+ const HGLOBAL resHandle = ::LoadResource(module, res);
+ if (resHandle != NULL)
+ {
+ const char* stream = static_cast<const char*>(::LockResource(resHandle));
+ if (stream)
+ {
+ const DWORD streamSize = ::SizeofResource(module, res);
+ output.assign(stream, streamSize);
+ }
+ }
+ }
+ }
+ return output;
+}
+}
+
+#endif // DLLLOADER_H_INCLUDED
diff --git a/zen/dst_hack.cpp b/zen/dst_hack.cpp
new file mode 100644
index 00000000..f6579441
--- /dev/null
+++ b/zen/dst_hack.cpp
@@ -0,0 +1,369 @@
+#include "dst_hack.h"
+#include <bitset>
+#include "basic_math.h"
+#include "long_path_prefix.h"
+#include "utf8.h"
+#include "assert_static.h"
+#include "int64.h"
+#include "file_error.h"
+#include "dll.h"
+
+using namespace zen;
+
+
+namespace
+{
+//fast ::GetVolumePathName() clone: let's hope it's not too simple (doesn't honor mount points)
+Zstring getVolumeName(const Zstring& filename)
+{
+ //this call is expensive: ~1.5 ms!
+ // if (!::GetVolumePathName(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpszFileName,
+ // fsName, //__out LPTSTR lpszVolumePathName,
+ // BUFFER_SIZE)) //__in DWORD cchBufferLength
+ // ...
+ // Zstring volumePath = fsName;
+ // if (!volumePath.EndsWith(FILE_NAME_SEPARATOR)) //a trailing backslash is required
+ // volumePath += FILE_NAME_SEPARATOR;
+
+ Zstring nameFmt = removeLongPathPrefix(filename); //throw()
+ if (!endsWith(nameFmt, FILE_NAME_SEPARATOR))
+ nameFmt += FILE_NAME_SEPARATOR; //GetVolumeInformation expects trailing backslash
+
+ if (startsWith(nameFmt, Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\"
+ {
+ size_t nameSize = nameFmt.size();
+ const size_t posFirstSlash = nameFmt.find(Zstr("\\"), 2);
+ if (posFirstSlash != Zstring::npos)
+ {
+ nameSize = posFirstSlash + 1;
+ const size_t posSecondSlash = nameFmt.find(Zstr("\\"), posFirstSlash + 1);
+ if (posSecondSlash != Zstring::npos)
+ nameSize = posSecondSlash + 1;
+ }
+ return Zstring(nameFmt.c_str(), nameSize); //include trailing backslash!
+ }
+ else //local path: "C:\Folder\"
+ {
+ const size_t pos = nameFmt.find(Zstr(":\\"));
+ if (pos == 1) //expect single letter volume
+ return Zstring(nameFmt.c_str(), 3);
+ }
+
+ return Zstring();
+}
+}
+
+
+bool dst::isFatDrive(const Zstring& fileName) //throw()
+{
+ const size_t BUFFER_SIZE = MAX_PATH + 1;
+ wchar_t fsName[BUFFER_SIZE];
+
+ const Zstring volumePath = getVolumeName(fileName);
+ if (volumePath.empty())
+ return false;
+
+ //suprisingly fast: ca. 0.03 ms per call!
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
+ NULL, //__out LPTSTR lpVolumeNameBuffer,
+ 0, //__in DWORD nVolumeNameSize,
+ NULL, //__out_opt LPDWORD lpVolumeSerialNumber,
+ NULL, //__out_opt LPDWORD lpMaximumComponentLength,
+ NULL, //__out_opt LPDWORD lpFileSystemFlags,
+ fsName, //__out LPTSTR lpFileSystemNameBuffer,
+ BUFFER_SIZE)) //__in DWORD nFileSystemNameSize
+ {
+ assert(false); //shouldn't happen
+ return false;
+ }
+ //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised)
+ return fsName == Zstring(L"FAT") ||
+ fsName == Zstring(L"FAT32");
+}
+
+
+bool dst::isFatDrive(HANDLE hFile) //throw()
+{
+ //dynamically load windows API function
+ typedef BOOL (WINAPI *GetVolumeInformationByHandleWFunc)(HANDLE hFile,
+ LPWSTR lpVolumeNameBuffer,
+ DWORD nVolumeNameSize,
+ LPDWORD lpVolumeSerialNumber,
+ LPDWORD lpMaximumComponentLength,
+ LPDWORD lpFileSystemFlags,
+ LPWSTR lpFileSystemNameBuffer,
+ DWORD nFileSystemNameSize);
+
+ const DllFun<GetVolumeInformationByHandleWFunc> getVolumeInformationByHandle(L"kernel32.dll", "GetVolumeInformationByHandleW");
+ if (!getVolumeInformationByHandle)
+ {
+ assert(false);
+ return false;
+ }
+
+ const size_t BUFFER_SIZE = MAX_PATH + 1;
+ wchar_t fsName[BUFFER_SIZE];
+
+ if (!getVolumeInformationByHandle(hFile, //__in HANDLE hFile,
+ NULL, //__out LPTSTR lpVolumeNameBuffer,
+ 0, //__in DWORD nVolumeNameSize,
+ NULL, //__out_opt LPDWORD lpVolumeSerialNumber,
+ NULL, //__out_opt LPDWORD lpMaximumComponentLength,
+ NULL, //__out_opt LPDWORD lpFileSystemFlags,
+ fsName, //__out LPTSTR lpFileSystemNameBuffer,
+ BUFFER_SIZE)) //__in DWORD nFileSystemNameSize
+ {
+ assert(false); //shouldn't happen
+ return false;
+ }
+ //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised)
+ return fsName == Zstring(L"FAT") ||
+ fsName == Zstring(L"FAT32");
+}
+
+
+namespace
+{
+//convert UInt64 and Int64 to FILETIME
+inline
+FILETIME toFiletime(Int64 number)
+{
+ const UInt64 unsig = to<UInt64>(number);
+
+ FILETIME output = {};
+ output.dwLowDateTime = unsig.getLo();
+ output.dwHighDateTime = unsig.getHi();
+ return output;
+}
+
+FILETIME toFiletime(UInt64 number)
+{
+ FILETIME output = {};
+ output.dwLowDateTime = number.getLo();
+ output.dwHighDateTime = number.getHi();
+ return output;
+}
+
+inline
+UInt64 toUInt64(const FILETIME& fileTime)
+{
+ return UInt64(fileTime.dwLowDateTime, fileTime.dwHighDateTime);
+}
+
+
+inline
+Int64 toInt64(const FILETIME& fileTime)
+{
+ return to<Int64>(UInt64(fileTime.dwLowDateTime, fileTime.dwHighDateTime));
+}
+
+
+FILETIME utcToLocal(const FILETIME& utcTime) //throw (std::runtime_error)
+{
+ //treat binary local time representation (which is invariant under DST time zone shift) as logical UTC:
+ FILETIME localTime = {};
+ if (!::FileTimeToLocalFileTime(
+ &utcTime, //__in const FILETIME *lpFileTime,
+ &localTime)) //__out LPFILETIME lpLocalFileTime
+ {
+ const std::wstring errorMessage = _("Conversion error:") + " FILETIME -> local FILETIME: " + "(" +
+ "High: " + toString<std::wstring>(utcTime.dwHighDateTime) + " " +
+ "Low: " + toString<std::wstring>(utcTime.dwLowDateTime) + ") " + "\n\n" + getLastErrorFormatted();
+ throw std::runtime_error(wideToUtf8<std::string>(errorMessage));
+ }
+ return localTime;
+}
+
+
+FILETIME localToUtc(const FILETIME& localTime) //throw (std::runtime_error)
+{
+ //treat binary local time representation (which is invariant under DST time zone shift) as logical UTC:
+ FILETIME utcTime = {};
+ if (!::LocalFileTimeToFileTime(
+ &localTime, //__in const FILETIME *lpLocalFileTime,
+ &utcTime)) //__out LPFILETIME lpFileTime
+ {
+ const std::wstring errorMessage = _("Conversion error:") + " local FILETIME -> FILETIME: " + "(" +
+ "High: " + toString<std::wstring>(localTime.dwHighDateTime) + " " +
+ "Low: " + toString<std::wstring>(localTime.dwLowDateTime) + ") " + "\n\n" + getLastErrorFormatted();
+ throw std::runtime_error(wideToUtf8<std::string>(errorMessage));
+ }
+ return utcTime;
+}
+
+
+//struct FILETIME {DWORD dwLowDateTime; DWORD dwHighDateTime;};
+const FILETIME FAT_MIN_TIME = { 13374976, 27846544 }; //1980 \ both are valid max/min FAT dates for 2 second precision
+const FILETIME FAT_MAX_TIME = { 14487552, 37251238 }; //2107 /
+
+//http://en.wikipedia.org/wiki/File_Allocation_Table
+const size_t PRECISION_WRITE_TIME = 20000000; //number of 100 ns per step -> 2 s
+const size_t PRECISION_CREATE_TIME = 100000; // -> 1/100 s
+
+/*
+Number of bits of information in create time: ln_2((FAT_MAX_TIME - FAT_MIN_TIME) / PRECISION_CREATE_TIME) = 38.55534023
+Number of bits of information in write time: 30.91148404
+*/
+//total size available to store data:
+const size_t CREATE_TIME_INFO_BITS = 38;
+// I. indicator that offset in II) is present
+const size_t INDICATOR_EXISTING_BITS = 1;
+// II. local<->UTC time offset
+const size_t UTC_LOCAL_OFFSET_BITS = 7;
+// III. indicator that offset in II) corresponds to current local write time (this could be a hash of the write time)
+const size_t WRITE_TIME_HASH_BITS = CREATE_TIME_INFO_BITS - INDICATOR_EXISTING_BITS - UTC_LOCAL_OFFSET_BITS;
+
+
+template <size_t precision>
+FILETIME encodeRawInformation(UInt64 rawInfo)
+{
+ rawInfo *= precision;
+ rawInfo += toUInt64(FAT_MIN_TIME);
+
+ assert(rawInfo <= toUInt64(FAT_MAX_TIME));
+ return toFiletime(rawInfo);
+}
+
+
+template <size_t precision>
+UInt64 extractRawInformation(const FILETIME& createTime)
+{
+ assert(toUInt64(FAT_MIN_TIME) <= toUInt64(createTime));
+ assert(toUInt64(createTime) <= toUInt64(FAT_MAX_TIME));
+
+ //FAT create time ranges from 1980 - 2107 (2^7 years) with 1/100 seconds precision
+ UInt64 rawInfo = toUInt64(createTime);
+
+ rawInfo -= toUInt64(FAT_MIN_TIME);
+ rawInfo /= precision; //reduce precision (FILETIME has unit 10^-7 s)
+
+ assert(toUInt64(encodeRawInformation<precision>(rawInfo)) == toUInt64(createTime)); //must be reversible
+ return rawInfo;
+}
+
+
+//convert write time to it's minimal representation (no restriction to FAT range "1980 - 2107")
+UInt64 extractRawWriteTime(const FILETIME& writeTime)
+{
+ UInt64 rawInfo = toUInt64(writeTime);
+ assert(rawInfo % PRECISION_WRITE_TIME == 0U);
+ rawInfo /= PRECISION_WRITE_TIME; //reduce precision (FILETIME has unit 10^-7 s)
+ return rawInfo;
+}
+
+
+//files with different resolution than 2 seconds are rounded up when written to FAT
+FILETIME roundToFatWriteTime(const FILETIME& writeTime)
+{
+ UInt64 rawData = toUInt64(writeTime);
+
+ if (rawData % PRECISION_WRITE_TIME != 0U)
+ rawData += PRECISION_WRITE_TIME;
+
+ rawData /= PRECISION_WRITE_TIME;
+ rawData *= PRECISION_WRITE_TIME;
+ return toFiletime(rawData);
+}
+
+
+//get 7-bit value representing time shift in number of quarter-hours
+std::bitset<UTC_LOCAL_OFFSET_BITS> getUtcLocalShift()
+{
+ FILETIME utcTime = FAT_MIN_TIME;
+ utcTime.dwHighDateTime += 5000000; //some arbitrary valid time
+
+ const FILETIME localTime = utcToLocal(utcTime);
+
+ const int timeShiftSec = to<int>((toInt64(localTime) - toInt64(utcTime)) / 10000000); //time shift in seconds
+
+ const int timeShiftQuarter = timeShiftSec / (60 * 15); //time shift in quarter-hours
+
+ const int absValue = numeric::abs(timeShiftQuarter); //MSVC C++0x bug: std::bitset<>(unsigned long) is ambiguous
+
+ if (std::bitset < UTC_LOCAL_OFFSET_BITS - 1 > (absValue).to_ulong() != static_cast<unsigned long>(absValue) || //time shifts that big shouldn't be possible!
+ timeShiftSec % (60 * 15) != 0) //all known time shift have at least 15 minute granularity!
+ {
+ const std::wstring errorMessage = _("Conversion error:") + " Unexpected UTC <-> local time shift: " +
+ "(" + toString<std::wstring>(timeShiftSec) + ") " + "\n\n" + getLastErrorFormatted();
+ throw std::runtime_error(wideToUtf8<std::string>(errorMessage));
+ }
+
+ std::bitset<UTC_LOCAL_OFFSET_BITS> output(absValue);
+ output[UTC_LOCAL_OFFSET_BITS - 1] = timeShiftQuarter < 0; //sign bit
+ return output;
+}
+
+
+//get time-zone shift in seconds
+inline
+int convertUtcLocalShift(std::bitset<UTC_LOCAL_OFFSET_BITS> rawShift)
+{
+ const bool hasSign = rawShift[UTC_LOCAL_OFFSET_BITS - 1];
+ rawShift[UTC_LOCAL_OFFSET_BITS - 1] = false;
+
+ assert_static(UTC_LOCAL_OFFSET_BITS <= sizeof(unsigned long) * 8);
+ return hasSign ?
+ static_cast<int>(rawShift.to_ulong()) * 15 * 60 * -1 :
+ static_cast<int>(rawShift.to_ulong()) * 15 * 60;
+}
+}
+
+
+bool dst::fatHasUtcEncoded(const RawTime& rawTime) //"createTimeRaw" as retrieved by ::FindFirstFile() and ::GetFileAttributesEx(); throw (std::runtime_error)
+{
+ if (toUInt64(rawTime.createTimeRaw) < toUInt64(FAT_MIN_TIME) ||
+ toUInt64(FAT_MAX_TIME) < toUInt64(rawTime.createTimeRaw))
+ {
+ assert(false); //shouldn't be possible according to FAT specification
+ return false;
+ }
+
+ const UInt64 rawInfo = extractRawInformation<PRECISION_CREATE_TIME>(utcToLocal(rawTime.createTimeRaw));
+
+ assert_static(WRITE_TIME_HASH_BITS == 30);
+ return (extractRawWriteTime(utcToLocal(rawTime.writeTimeRaw)) & 0x3FFFFFFFU) == (rawInfo & 0x3FFFFFFFU) && //ensure write time wasn't changed externally
+ rawInfo >> (CREATE_TIME_INFO_BITS - INDICATOR_EXISTING_BITS) == 1U; //extended data available
+}
+
+
+dst::RawTime dst::fatEncodeUtcTime(const FILETIME& writeTimeRealUtc) //throw (std::runtime_error)
+{
+ const FILETIME fatWriteTimeUtc = roundToFatWriteTime(writeTimeRealUtc); //writeTimeRealUtc may have incompatible precision (NTFS)
+
+ //create time lets us store 40 bit of information
+
+ //indicator that utc time is encoded -> hopefully results in a date long way in the future; but even if this bit is accidentally set, we still have the hash!
+ UInt64 data = 1U;
+
+ const std::bitset<UTC_LOCAL_OFFSET_BITS> utcShift = getUtcLocalShift();
+ data <<= UTC_LOCAL_OFFSET_BITS;
+ data |= utcShift.to_ulong();
+
+ data <<= WRITE_TIME_HASH_BITS;
+ data |= extractRawWriteTime(utcToLocal(fatWriteTimeUtc)) & 0x3FFFFFFFU; //trim to last 30 bit of information
+ assert_static(WRITE_TIME_HASH_BITS == 30);
+
+ const FILETIME encodedData = localToUtc(encodeRawInformation<PRECISION_CREATE_TIME>(data)); //localToUtc: make sure data is physically saved as FAT local time
+ assert(toUInt64(FAT_MIN_TIME) <= toUInt64(encodedData));
+ assert(toUInt64(encodedData) <= toUInt64(FAT_MAX_TIME));
+
+ return RawTime(encodedData, fatWriteTimeUtc); //keep compatible with other applications, at least until DST shift actually occurs
+}
+
+
+FILETIME dst::fatDecodeUtcTime(const RawTime& rawTime) //return real UTC time; throw (std::runtime_error)
+{
+ if (!fatHasUtcEncoded(rawTime))
+ return rawTime.writeTimeRaw;
+
+ const UInt64 rawInfo = extractRawInformation<PRECISION_CREATE_TIME>(utcToLocal(rawTime.createTimeRaw));
+
+ const std::bitset<UTC_LOCAL_OFFSET_BITS> rawShift(to<int>((rawInfo >> WRITE_TIME_HASH_BITS) & 0x7FU)); //static_cast<int>: a shame MSC... "unsigned long" should be supported instead!
+ assert_static(UTC_LOCAL_OFFSET_BITS == 7);
+
+ const int timeShiftSec = convertUtcLocalShift(rawShift);
+ const FILETIME writeTimeLocal = utcToLocal(rawTime.writeTimeRaw);
+
+ const Int64 realUTC = toInt64(writeTimeLocal) - Int64(timeShiftSec) * 10000000;
+ return toFiletime(realUTC);
+}
diff --git a/zen/dst_hack.h b/zen/dst_hack.h
new file mode 100644
index 00000000..07d08dc5
--- /dev/null
+++ b/zen/dst_hack.h
@@ -0,0 +1,40 @@
+#ifndef DST_HACK_H_INCLUDED
+#define DST_HACK_H_INCLUDED
+
+#include "win.h" //includes "windows.h"
+#include "zstring.h"
+#include <stdexcept>
+
+
+namespace dst
+{
+/*
+Solve DST +-1h and time zone shift issues on FAT drives
+-------------------------------------------------------
+- (local) last write time is not touched!
+- all additional metadata is encoded in local create time:
+ I. indicator that offset in II) is present
+ II. local<->UTC time offset
+ III. indicator that offset in II) corresponds to current local write time (a hash of local last write time)
+*/
+
+bool isFatDrive(const Zstring& fileName); //throw ()
+bool isFatDrive(HANDLE hFile); //throw() -> call ONLY if vistaOrLater() == true!
+bool vistaOrLater();
+
+//all subsequent functions may throw the std::runtime_error exception!
+
+struct RawTime //time as retrieved by ::FindFirstFile() and ::GetFileAttributesEx()
+{
+ RawTime(const FILETIME& create, const FILETIME& lastWrite) : createTimeRaw(create), writeTimeRaw(lastWrite) {}
+ FILETIME createTimeRaw;
+ FILETIME writeTimeRaw;
+};
+//save UTC time resistant against DST/time zone shifts
+bool fatHasUtcEncoded(const RawTime& rawTime); //as retrieved by ::FindFirstFile() and ::GetFileAttributesEx(); throw (std::runtime_error)
+
+RawTime fatEncodeUtcTime(const FILETIME& writeTimeRealUtc); //throw (std::runtime_error)
+FILETIME fatDecodeUtcTime(const RawTime& rawTime); //return last write time in real UTC; throw (std::runtime_error)
+}
+
+#endif // DST_HACK_H_INCLUDED
diff --git a/zen/file_error.h b/zen/file_error.h
new file mode 100644
index 00000000..8c49937c
--- /dev/null
+++ b/zen/file_error.h
@@ -0,0 +1,48 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FILEERROR_H_INCLUDED
+#define FILEERROR_H_INCLUDED
+
+#include <string>
+#include "zstring.h"
+#include "utf8.h"
+#include "last_error.h" //we'll need this later anyway!
+
+namespace zen
+{
+class FileError //Exception base class used to notify file/directory copy/delete errors
+{
+public:
+ FileError(const std::wstring& message) : errorMessage(message) {}
+ virtual ~FileError() {}
+
+ const std::wstring& msg() const { return errorMessage; }
+
+private:
+ std::wstring errorMessage;
+};
+
+#define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& message) : FileError(message) {} };
+
+DEFINE_NEW_FILE_ERROR(ErrorNotExisting);
+DEFINE_NEW_FILE_ERROR(ErrorTargetExisting);
+DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing);
+DEFINE_NEW_FILE_ERROR(ErrorFileLocked);
+
+
+
+//----------- facilitate usage of std::wstring for error messages --------------------
+
+//allow implicit UTF8 conversion: since std::wstring models a GUI string, convenience is more important than performance
+inline std::wstring operator+(const std::wstring& lhs, const Zstring& rhs) { return std::wstring(lhs) += zen::utf8CvrtTo<std::wstring>(rhs); }
+
+//we musn't put our overloads in namespace std, but namespace zen (+ using directive) is sufficient
+inline std::wstring operator+(const std::wstring& lhs, const char* rhs) { return std::wstring(lhs) += utf8CvrtTo<std::wstring>(rhs); }
+inline std::wstring operator+(const std::wstring& lhs, const std::string& rhs) { return std::wstring(lhs) += utf8CvrtTo<std::wstring>(rhs); }
+}
+
+#endif // FILEERROR_H_INCLUDED
diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp
new file mode 100644
index 00000000..7b46181b
--- /dev/null
+++ b/zen/file_handling.cpp
@@ -0,0 +1,2060 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "file_handling.h"
+#include <map>
+#include <algorithm>
+#include <stdexcept>
+#include "file_traverser.h"
+#include "scope_guard.h"
+#include "symlink_target.h"
+#include "file_io.h"
+#include "assert_static.h"
+#include <boost/thread/tss.hpp>
+#include <boost/thread/once.hpp>
+#include "file_id_internal.h"
+
+#ifdef FFS_WIN
+#include "privilege.h"
+#include "dll.h"
+#include "win.h" //includes "windows.h"
+#include "long_path_prefix.h"
+#include <Aclapi.h>
+#include "dst_hack.h"
+#include "file_update_handle.h"
+#include "win_ver.h"
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#include <time.h>
+#include <utime.h>
+#include <cerrno>
+#include <sys/time.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+#endif
+
+using namespace zen;
+
+
+bool zen::fileExists(const Zstring& filename)
+{
+ //symbolic links (broken or not) are also treated as existing files!
+#ifdef FFS_WIN
+ const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(filename).c_str());
+ return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also
+
+#elif defined FFS_LINUX
+ struct stat fileInfo = {};
+ return ::lstat(filename.c_str(), &fileInfo) == 0 &&
+ (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)); //in Linux a symbolic link is neither file nor directory
+#endif
+}
+
+
+bool zen::dirExists(const Zstring& dirname)
+{
+ //symbolic links (broken or not) are also treated as existing directories!
+#ifdef FFS_WIN
+ const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str());
+ return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also
+
+#elif defined FFS_LINUX
+ struct stat dirInfo = {};
+ return ::lstat(dirname.c_str(), &dirInfo) == 0 &&
+ (S_ISLNK(dirInfo.st_mode) || S_ISDIR(dirInfo.st_mode)); //in Linux a symbolic link is neither file nor directory
+#endif
+}
+
+
+bool zen::symlinkExists(const Zstring& objname)
+{
+#ifdef FFS_WIN
+ const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(objname).c_str());
+ return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+
+#elif defined FFS_LINUX
+ struct stat fileInfo = {};
+ return ::lstat(objname.c_str(), &fileInfo) == 0 &&
+ S_ISLNK(fileInfo.st_mode); //symbolic link
+#endif
+}
+
+
+bool zen::somethingExists(const Zstring& objname) //throw() check whether any object with this name exists
+{
+#ifdef FFS_WIN
+ return ::GetFileAttributes(applyLongPathPrefix(objname).c_str()) != INVALID_FILE_ATTRIBUTES;
+
+#elif defined FFS_LINUX
+ struct stat fileInfo = {};
+ return ::lstat(objname.c_str(), &fileInfo) == 0;
+#endif
+}
+
+
+namespace
+{
+void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl) //throw FileError
+{
+#ifdef FFS_WIN
+ WIN32_FIND_DATA fileInfo = {};
+ {
+ const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filename).c_str(), &fileInfo);
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+ ::FindClose(searchHandle);
+ }
+ // WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {};
+ // if (!::GetFileAttributesEx(applyLongPathPrefix(sourceObj).c_str(), //__in LPCTSTR lpFileName,
+ // GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ // &sourceAttr)) //__out LPVOID lpFileInformation
+
+ const bool isSymbolicLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+ if (!isSymbolicLink || procSl == SYMLINK_DIRECT)
+ {
+ //####################################### DST hack ###########################################
+ const bool isDirectory = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
+ if (!isDirectory && dst::isFatDrive(filename)) //throw()
+ {
+ const dst::RawTime rawTime(fileInfo.ftCreationTime, fileInfo.ftLastWriteTime);
+ if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
+ {
+ fileInfo.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error)
+ ::GetSystemTimeAsFileTime(&fileInfo.ftCreationTime); //real creation time information is not available...
+ }
+ }
+ //####################################### DST hack ###########################################
+
+ attr.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
+ attr.modificationTime = toTimeT(fileInfo.ftLastWriteTime);
+ }
+ else
+ {
+ const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filename).c_str(), //open handle to target of symbolic link
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hFile));
+
+ BY_HANDLE_FILE_INFORMATION fileInfoHnd = {};
+ if (!::GetFileInformationByHandle(hFile, &fileInfoHnd))
+ throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+
+ attr.fileSize = UInt64(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh);
+ attr.modificationTime = toTimeT(fileInfoHnd.ftLastWriteTime);
+ }
+
+#elif defined FFS_LINUX
+ struct stat fileInfo = {};
+
+ const int rv = procSl == SYMLINK_FOLLOW ?
+ :: stat(filename.c_str(), &fileInfo) :
+ ::lstat(filename.c_str(), &fileInfo);
+ if (rv != 0) //follow symbolic links
+ throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+
+ attr.fileSize = UInt64(fileInfo.st_size);
+ attr.modificationTime = fileInfo.st_mtime;
+#endif
+}
+}
+
+
+UInt64 zen::getFilesize(const Zstring& filename) //throw FileError
+{
+ FileAttrib attr;
+ getFileAttrib(filename, attr, SYMLINK_FOLLOW); //throw FileError
+ return attr.fileSize;
+}
+
+
+Int64 zen::getFileTime(const Zstring& filename, ProcSymlink procSl) //throw FileError
+{
+ FileAttrib attr;
+ getFileAttrib(filename, attr, procSl); //throw FileError
+ return attr.modificationTime;
+}
+
+
+namespace
+{
+
+
+#ifdef FFS_WIN
+DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error!
+{
+ std::vector<wchar_t> buffer(10000);
+
+ //full pathName need not yet exist!
+ if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName,
+ &buffer[0], //__out LPTSTR lpszVolumePathName,
+ static_cast<DWORD>(buffer.size()))) //__in DWORD cchBufferLength
+ return 0;
+
+ Zstring volumePath = &buffer[0];
+ if (!endsWith(volumePath, FILE_NAME_SEPARATOR))
+ volumePath += FILE_NAME_SEPARATOR;
+
+ DWORD volumeSerial = 0;
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
+ NULL, //__out LPTSTR lpVolumeNameBuffer,
+ 0, //__in DWORD nVolumeNameSize,
+ &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber,
+ NULL, //__out_opt LPDWORD lpMaximumComponentLength,
+ NULL, //__out_opt LPDWORD lpFileSystemFlags,
+ NULL, //__out LPTSTR lpFileSystemNameBuffer,
+ 0)) //__in DWORD nFileSystemNameSize
+ return 0;
+
+ return volumeSerial;
+}
+#elif defined FFS_LINUX
+
+dev_t retrieveVolumeSerial(const Zstring& pathName) //return 0 on error!
+{
+ Zstring volumePathName = pathName;
+
+ //remove trailing slash
+ if (volumePathName.size() > 1 && endsWith(volumePathName, FILE_NAME_SEPARATOR)) //exception: allow '/'
+ volumePathName = beforeLast(volumePathName, FILE_NAME_SEPARATOR);
+
+ struct stat fileInfo = {};
+ while (::lstat(volumePathName.c_str(), &fileInfo) != 0) //go up in folder hierarchy until existing folder is found
+ {
+ volumePathName = beforeLast(volumePathName, FILE_NAME_SEPARATOR); //returns empty string if ch not found
+ if (volumePathName.empty())
+ return 0; //this includes path "/" also!
+ }
+
+ return fileInfo.st_dev;
+}
+#endif
+}
+
+
+zen::ResponseSame zen::onSameVolume(const Zstring& folderLeft, const Zstring& folderRight) //throw()
+{
+ const auto serialLeft = retrieveVolumeSerial(folderLeft); //returns 0 on error!
+ const auto serialRight = retrieveVolumeSerial(folderRight); //returns 0 on error!
+ if (serialLeft == 0 || serialRight == 0)
+ return IS_SAME_CANT_SAY;
+
+ return serialLeft == serialRight ? IS_SAME_YES : IS_SAME_NO;
+}
+
+
+bool zen::removeFile(const Zstring& filename) //throw FileError;
+{
+#ifdef FFS_WIN
+ //remove file, support for \\?\-prefix
+ const Zstring filenameFmt = applyLongPathPrefix(filename);
+ if (!::DeleteFile(filenameFmt.c_str()))
+#elif defined FFS_LINUX
+ if (::unlink(filename.c_str()) != 0)
+#endif
+ {
+#ifdef FFS_WIN
+ //perf: apply ONLY when necessary!
+ if (::GetLastError() == ERROR_ACCESS_DENIED) //function fails if file is read-only
+ {
+ //(try to) normalize file attributes
+ ::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL);
+
+ //now try again...
+ if (::DeleteFile(filenameFmt.c_str()))
+ return true;
+ }
+ //eval error code before next call
+ DWORD lastError = ::GetLastError();
+#elif defined FFS_LINUX
+ int lastError = errno;
+#endif
+
+ //no error situation if file is not existing! manual deletion relies on it!
+ //perf: check is placed in error handling block
+ //warning: this call changes error code!!
+ if (!somethingExists(filename))
+ return false; //neither file nor any other object (e.g. broken symlink) with that name existing
+
+ throw FileError(_("Error deleting file:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted(lastError));
+ }
+ return true;
+}
+
+
+namespace
+{
+DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume);
+
+/* Usage overview: (avoid circular pattern!)
+
+ renameFile() --> renameFile_sub()
+ | /|\
+ \|/ |
+ Fix8Dot3NameClash()
+*/
+//wrapper for file system rename function:
+void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+{
+#ifdef FFS_WIN
+ const Zstring oldNameFmt = applyLongPathPrefix(oldName);
+ const Zstring newNameFmt = applyLongPathPrefix(newName);
+
+ if (!::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName,
+ newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName,
+ 0)) //__in DWORD dwFlags
+ {
+ DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this
+ {
+ const DWORD oldAttr = ::GetFileAttributes(oldNameFmt.c_str());
+ if (oldAttr != INVALID_FILE_ATTRIBUTES && (oldAttr & FILE_ATTRIBUTE_READONLY))
+ {
+ if (::SetFileAttributes(oldNameFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute
+ {
+ //try again...
+ if (::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName,
+ newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName,
+ 0)) //__in DWORD dwFlags
+ {
+ //(try to) restore file attributes
+ ::SetFileAttributes(newNameFmt.c_str(), oldAttr); //don't handle error
+ return;
+ }
+ else
+ {
+ lastError = ::GetLastError(); //use error code from second call to ::MoveFileEx()
+
+ //cleanup: (try to) restore file attributes: assume oldName is still existing
+ ::SetFileAttributes(oldNameFmt.c_str(), oldAttr);
+ }
+ }
+ }
+ }
+
+ std::wstring errorMessage = _("Error moving file:") + "\n\"" + oldName + "\" ->\n\"" + newName + "\"" + "\n\n" + getLastErrorFormatted(lastError);
+
+ if (lastError == ERROR_NOT_SAME_DEVICE)
+ throw ErrorDifferentVolume(errorMessage);
+ else if (lastError == ERROR_FILE_EXISTS)
+ throw ErrorTargetExisting(errorMessage);
+ else
+ throw FileError(errorMessage);
+ }
+
+#elif defined FFS_LINUX
+ if (::rename(oldName.c_str(), newName.c_str()) != 0)
+ {
+ const int lastError = errno;
+
+ std::wstring errorMessage = _("Error moving file:") + "\n\"" + oldName + "\" ->\n\"" + newName + "\"" + "\n\n" + getLastErrorFormatted(lastError);
+
+ if (lastError == EXDEV)
+ throw ErrorDifferentVolume(errorMessage);
+ else if (lastError == EEXIST)
+ throw ErrorTargetExisting(errorMessage);
+ else
+ throw FileError(errorMessage);
+ }
+#endif
+}
+
+
+#ifdef FFS_WIN
+/*small wrapper around
+::GetShortPathName()
+::GetLongPathName() */
+template <typename Function>
+Zstring getFilenameFmt(const Zstring& filename, Function fun) //throw(); returns empty string on error
+{
+ const Zstring filenameFmt = applyLongPathPrefix(filename);
+
+ const DWORD bufferSize = fun(filenameFmt.c_str(), NULL, 0);
+ if (bufferSize == 0)
+ return Zstring();
+
+ std::vector<wchar_t> buffer(bufferSize);
+
+ const DWORD rv = fun(filenameFmt.c_str(), //__in LPCTSTR lpszShortPath,
+ &buffer[0], //__out LPTSTR lpszLongPath,
+ static_cast<DWORD>(buffer.size())); //__in DWORD cchBuffer
+ if (rv == 0 || rv >= buffer.size())
+ return Zstring();
+
+ return &buffer[0];
+}
+
+
+Zstring findUnused8Dot3Name(const Zstring& filename) //find a unique 8.3 short name
+{
+ const Zstring pathPrefix = filename.find(FILE_NAME_SEPARATOR) != Zstring::npos ?
+ (beforeLast(filename, FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR) : Zstring();
+
+ Zstring extension = afterLast(afterLast(filename, FILE_NAME_SEPARATOR), Zchar('.')); //extension needn't contain reasonable data
+ if (extension.empty())
+ extension = Zstr("FFS");
+ truncate(extension, 3);
+
+ for (int index = 0; index < 100000000; ++index) //filename must be representable by <= 8 characters
+ {
+ const Zstring output = pathPrefix + toString<Zstring>(index) + Zchar('.') + extension;
+ if (!somethingExists(output)) //ensure uniqueness
+ return output;
+ }
+
+ throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...\n") + utf8CvrtTo<std::string>(pathPrefix));
+}
+
+
+bool have8dot3NameClash(const Zstring& filename)
+{
+ if (filename.find(FILE_NAME_SEPARATOR) == Zstring::npos)
+ return false;
+
+ if (somethingExists(filename)) //name OR directory!
+ {
+ const Zstring origName = afterLast(filename, FILE_NAME_SEPARATOR); //returns the whole string if ch not found
+ const Zstring shortName = afterLast(getFilenameFmt(filename, ::GetShortPathName), FILE_NAME_SEPARATOR); //throw() returns empty string on error
+ const Zstring longName = afterLast(getFilenameFmt(filename, ::GetLongPathName) , FILE_NAME_SEPARATOR); //
+
+ if (!shortName.empty() &&
+ !longName.empty() &&
+ EqualFilename()(origName, shortName) &&
+ !EqualFilename()(shortName, longName))
+ {
+ //for filename short and long file name are equal and another unrelated file happens to have the same short name
+ //e.g. filename == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
+ return true;
+ }
+ }
+ return false;
+}
+
+class Fix8Dot3NameClash
+{
+public:
+ Fix8Dot3NameClash(const Zstring& filename)
+ {
+ const Zstring longName = afterLast(getFilenameFmt(filename, ::GetLongPathName), FILE_NAME_SEPARATOR); //throw() returns empty string on error
+
+ unrelatedFile = beforeLast(filename, FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + longName;
+
+ //find another name in short format: this ensures the actual short name WILL be renamed as well!
+ unrelatedFileParked = findUnused8Dot3Name(filename);
+
+ //move already existing short name out of the way for now
+ renameFile_sub(unrelatedFile, unrelatedFileParked); //throw FileError, ErrorDifferentVolume
+ //DON'T call renameFile() to avoid reentrance!
+ }
+
+ ~Fix8Dot3NameClash()
+ {
+ //the file system should assign this unrelated file a new (unique) short name
+ try
+ {
+ renameFile_sub(unrelatedFileParked, unrelatedFile); //throw FileError, ErrorDifferentVolume
+ }
+ catch (...) {}
+ }
+private:
+ Zstring unrelatedFile;
+ Zstring unrelatedFileParked;
+};
+#endif
+}
+
+
+//rename file: no copying!!!
+void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+{
+ try
+ {
+ renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+ }
+ catch (const FileError&)
+ {
+#ifdef FFS_WIN
+ //try to handle issues with already existing short 8.3 file names on Windows
+ if (have8dot3NameClash(newName))
+ {
+ Fix8Dot3NameClash dummy(newName); //move clashing filename to the side
+ //now try again...
+ renameFile_sub(oldName, newName); //throw FileError
+ return;
+ }
+#endif
+ throw;
+ }
+}
+
+
+class CopyCallbackImpl : public zen::CallbackCopyFile //callback functionality
+{
+public:
+ CopyCallbackImpl(const Zstring& sourceFile, CallbackMoveFile& callback) : sourceFile_(sourceFile), moveCallback(callback) {}
+
+ virtual void deleteTargetFile(const Zstring& targetFile) { assert(!fileExists(targetFile)); }
+
+ virtual void updateCopyStatus(UInt64 totalBytesTransferred)
+ {
+ moveCallback.requestUiRefresh(sourceFile_);
+ }
+
+private:
+ CopyCallbackImpl(const CopyCallbackImpl&);
+ CopyCallbackImpl& operator=(const CopyCallbackImpl&);
+
+ const Zstring sourceFile_;
+ CallbackMoveFile& moveCallback;
+};
+
+
+void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError;
+{
+ //call back once per file (moveFile() is called by moveDirectory())
+ if (callback)
+ callback->requestUiRefresh(sourceFile);
+
+ const bool targetExisting = fileExists(targetFile);
+
+ if (targetExisting && !ignoreExisting) //test file existence: e.g. Linux might silently overwrite existing symlinks
+ throw FileError(_("Error moving file:") + "\n\"" + sourceFile + "\" ->\n\"" + targetFile + "\"" +
+ "\n\n" + _("Target file already existing!"));
+
+ if (!targetExisting)
+ {
+ //try to move the file directly without copying
+ try
+ {
+ renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume
+ return; //great, we get away cheaply!
+ }
+ //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file)
+ catch (const ErrorDifferentVolume&) {}
+
+ //file is on a different volume: let's copy it
+ std::unique_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL);
+
+ if (symlinkExists(sourceFile))
+ copySymlink(sourceFile, targetFile, false); //throw FileError; don't copy filesystem permissions
+ else
+ copyFile(sourceFile, targetFile, false, true, copyCallback.get()); //throw FileError;
+
+ //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish!
+ }
+
+ removeFile(sourceFile); //throw FileError
+ //note: copying file is NOT undone in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine
+}
+
+namespace
+{
+class TraverseOneLevel : public zen::TraverseCallback
+{
+public:
+ typedef std::pair<Zstring, Zstring> NamePair;
+ typedef std::vector<NamePair> NameList;
+
+ TraverseOneLevel(NameList& files, NameList& dirs) :
+ files_(files),
+ dirs_(dirs) {}
+
+ virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details)
+ {
+ files_.push_back(NamePair(shortName, fullName));
+ }
+
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
+ {
+ if (details.dirLink)
+ dirs_.push_back(NamePair(shortName, fullName));
+ else
+ files_.push_back(NamePair(shortName, fullName));
+ }
+
+ virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName)
+ {
+ dirs_.push_back(NamePair(shortName, fullName));
+ return Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; moveDirectory works recursively!
+ }
+
+ virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); }
+
+private:
+ TraverseOneLevel(const TraverseOneLevel&);
+ TraverseOneLevel& operator=(const TraverseOneLevel&);
+
+ NameList& files_;
+ NameList& dirs_;
+};
+
+
+struct RemoveCallbackImpl : public CallbackRemoveDir
+{
+ RemoveCallbackImpl(const Zstring& sourceDir,
+ CallbackMoveFile& moveCallback) :
+ sourceDir_(sourceDir),
+ moveCallback_(moveCallback) {}
+
+ virtual void notifyFileDeletion(const Zstring& filename) { moveCallback_.requestUiRefresh(sourceDir_); }
+ virtual void notifyDirDeletion(const Zstring& dirname) { moveCallback_.requestUiRefresh(sourceDir_); }
+
+private:
+ RemoveCallbackImpl(const RemoveCallbackImpl&);
+ RemoveCallbackImpl& operator=(const RemoveCallbackImpl&);
+
+ const Zstring sourceDir_;
+ CallbackMoveFile& moveCallback_;
+};
+}
+
+
+void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError;
+{
+ //call back once per folder
+ if (callback)
+ callback->requestUiRefresh(sourceDir);
+
+ const bool targetExisting = dirExists(targetDir);
+
+ if (targetExisting && !ignoreExisting) //directory or symlink exists (or even a file... this error will be caught later)
+ throw FileError(_("Error moving directory:") + "\n\"" + sourceDir + "\" ->\n\"" + targetDir + "\"" +
+ "\n\n" + _("Target directory already existing!"));
+
+ const bool isSymlink = symlinkExists(sourceDir);
+
+ if (!targetExisting)
+ {
+ //first try to move the directory directly without copying
+ try
+ {
+ renameFile(sourceDir, targetDir); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+ return; //great, we get away cheaply!
+ }
+ //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory)
+ catch (const ErrorDifferentVolume&) {}
+
+ //create target
+ if (isSymlink)
+ copySymlink(sourceDir, targetDir, false); //throw FileError -> don't copy permissions
+ else
+ createDirectory(targetDir, sourceDir, false); //throw FileError
+ }
+
+ if (!isSymlink) //handle symbolic links
+ {
+ //move files/folders recursively
+ TraverseOneLevel::NameList fileList; //list of names: 1. short 2.long
+ TraverseOneLevel::NameList dirList; //
+
+ //traverse source directory one level
+ TraverseOneLevel traverseCallback(fileList, dirList);
+ traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks
+
+ const Zstring targetDirFormatted = endsWith(targetDir, FILE_NAME_SEPARATOR) ? //ends with path separator
+ targetDir :
+ targetDir + FILE_NAME_SEPARATOR;
+
+ //move files
+ for (TraverseOneLevel::NameList::const_iterator i = fileList.begin(); i != fileList.end(); ++i)
+ moveFile(i->second, targetDirFormatted + i->first, ignoreExisting, callback); //throw FileError, ErrorTargetExisting
+
+ //move directories
+ for (TraverseOneLevel::NameList::const_iterator i = dirList.begin(); i != dirList.end(); ++i)
+ ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, ignoreExisting, callback);
+
+ //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish!
+ }
+
+ //delete source
+ std::unique_ptr<RemoveCallbackImpl> removeCallback(callback != NULL ? new RemoveCallbackImpl(sourceDir, *callback) : NULL);
+ removeDirectory(sourceDir, removeCallback.get()); //throw FileError;
+}
+
+
+void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError;
+{
+#ifdef FFS_WIN
+ const Zstring& sourceDirFormatted = sourceDir;
+ const Zstring& targetDirFormatted = targetDir;
+
+#elif defined FFS_LINUX
+ const Zstring sourceDirFormatted = //remove trailing slash
+ sourceDir.size() > 1 && endsWith(sourceDir, FILE_NAME_SEPARATOR) ? //exception: allow '/'
+ beforeLast(sourceDir, FILE_NAME_SEPARATOR) :
+ sourceDir;
+ const Zstring targetDirFormatted = //remove trailing slash
+ targetDir.size() > 1 && endsWith(targetDir, FILE_NAME_SEPARATOR) ? //exception: allow '/'
+ beforeLast(targetDir, FILE_NAME_SEPARATOR) :
+ targetDir;
+#endif
+
+ ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExisting, callback);
+}
+
+
+class FilesDirsOnlyTraverser : public zen::TraverseCallback
+{
+public:
+ FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) :
+ m_files(files),
+ m_dirs(dirs) {}
+
+ virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details)
+ {
+ m_files.push_back(fullName);
+ }
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
+ {
+ if (details.dirLink)
+ m_dirs.push_back(fullName);
+ else
+ m_files.push_back(fullName);
+ }
+ virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName)
+ {
+ m_dirs.push_back(fullName);
+ return Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; removeDirectory works recursively!
+ }
+ virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); }
+
+private:
+ FilesDirsOnlyTraverser(const FilesDirsOnlyTraverser&);
+ FilesDirsOnlyTraverser& operator=(const FilesDirsOnlyTraverser&);
+
+ std::vector<Zstring>& m_files;
+ std::vector<Zstring>& m_dirs;
+};
+
+
+void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback)
+{
+ //no error situation if directory is not existing! manual deletion relies on it!
+ if (!somethingExists(directory))
+ return; //neither directory nor any other object (e.g. broken symlink) with that name existing
+
+#ifdef FFS_WIN
+ const Zstring directoryFmt = applyLongPathPrefix(directory); //support for \\?\-prefix
+
+ //(try to) normalize file attributes: actually NEEDED for symbolic links also!
+ ::SetFileAttributes(directoryFmt.c_str(), FILE_ATTRIBUTE_NORMAL);
+#endif
+
+ //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!!
+ if (symlinkExists(directory)) //remove symlink directly
+ {
+#ifdef FFS_WIN
+ if (!::RemoveDirectory(directoryFmt.c_str()))
+#elif defined FFS_LINUX
+ if (::unlink(directory.c_str()) != 0)
+#endif
+ throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
+
+ if (callback)
+ callback->notifyDirDeletion(directory); //once per symlink
+ return;
+ }
+
+ std::vector<Zstring> fileList;
+ std::vector<Zstring> dirList;
+
+ //get all files and directories from current directory (WITHOUT subdirectories!)
+ FilesDirsOnlyTraverser traverser(fileList, dirList);
+ traverseFolder(directory, false, traverser); //don't follow symlinks
+
+ //delete files
+ for (std::vector<Zstring>::const_iterator i = fileList.begin(); i != fileList.end(); ++i)
+ {
+ const bool workDone = removeFile(*i);
+ if (callback && workDone)
+ callback->notifyFileDeletion(*i); //call once per file
+ }
+
+ //delete directories recursively
+ for (std::vector<Zstring>::const_iterator i = dirList.begin(); i != dirList.end(); ++i)
+ removeDirectory(*i, callback); //call recursively to correctly handle symbolic links
+
+ //parent directory is deleted last
+#ifdef FFS_WIN
+ if (!::RemoveDirectory(directoryFmt.c_str())) //remove directory, support for \\?\-prefix
+#else
+ if (::rmdir(directory.c_str()) != 0)
+#endif
+ {
+ throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+ if (callback)
+ callback->notifyDirDeletion(directory); //and once per folder
+}
+
+
+void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl) //throw FileError
+{
+#ifdef FFS_WIN
+ FILETIME creationTime = {};
+ FILETIME lastWriteTime = tofiletime(modificationTime);
+
+ //####################################### DST hack ###########################################
+ if (dst::isFatDrive(filename)) //throw()
+ {
+ const dst::RawTime encodedTime = dst::fatEncodeUtcTime(lastWriteTime); //throw (std::runtime_error)
+ creationTime = encodedTime.createTimeRaw;
+ lastWriteTime = encodedTime.writeTimeRaw;
+ }
+ //####################################### 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)
+ {
+ */
+
+ //may need to remove the readonly-attribute (e.g. FAT usb drives)
+ FileUpdateHandle targetHandle(filename, [ = ]()
+ {
+ return ::CreateFile(applyLongPathPrefix(filename).c_str(),
+ GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory
+ (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks
+ NULL);
+ });
+
+ if (targetHandle.get() == INVALID_HANDLE_VALUE)
+ throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+
+ /*
+ if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION)
+ ::Sleep(retryInterval); //wait then retry
+ else //success or unknown failure
+ break;
+ }
+ */
+
+ auto isNullTime = [](const FILETIME & ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; };
+
+ if (!::SetFileTime(targetHandle.get(),
+ isNullTime(creationTime) ? NULL : &creationTime,
+ NULL,
+ &lastWriteTime))
+ throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+
+#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
+
+ assert(::CompareFileTime(&debugeAttr.ftCreationTime, &creationTime) == 0);
+ assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &lastWriteTime) == 0);
+ }
+#endif
+
+#elif defined FFS_LINUX
+ if (procSl == SYMLINK_FOLLOW)
+ {
+ struct utimbuf newTimes = {};
+ newTimes.actime = ::time(NULL);
+ newTimes.modtime = to<time_t>(modificationTime);
+
+ // set new "last write time"
+ if (::utime(filename.c_str(), &newTimes) != 0)
+ throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+ else
+ {
+ struct timeval newTimes[2] = {};
+ newTimes[0].tv_sec = ::time(NULL); /* seconds */
+ newTimes[0].tv_usec = 0; /* microseconds */
+
+ newTimes[1].tv_sec = to<time_t>(modificationTime);
+ newTimes[1].tv_usec = 0;
+
+ if (::lutimes(filename.c_str(), newTimes) != 0)
+ throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+#endif
+}
+
+
+namespace
+{
+#ifdef FFS_WIN
+Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory; throw (FileError)
+{
+ //open handle to target of symbolic link
+ const HANDLE hDir = ::CreateFile(applyLongPathPrefix(dirLinkName).c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory
+ NULL);
+ if (hDir == INVALID_HANDLE_VALUE)
+ throw FileError(_("Error resolving symbolic link:") + "\n\"" + dirLinkName + "\"" + "\n\n" + getLastErrorFormatted());
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hDir));
+
+ const DWORD BUFFER_SIZE = 10000;
+ std::vector<wchar_t> targetPath(BUFFER_SIZE);
+
+ //dynamically load windows API function
+ typedef DWORD (WINAPI *GetFinalPathNameByHandleWFunc)(HANDLE hFile,
+ LPTSTR lpszFilePath,
+ DWORD cchFilePath,
+ DWORD dwFlags);
+ const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW");
+ if (!getFinalPathNameByHandle)
+ throw FileError(_("Error loading library function:") + "\n\"" + "GetFinalPathNameByHandleW" + "\"");
+
+ const DWORD charsWritten = getFinalPathNameByHandle(hDir, //__in HANDLE hFile,
+ &targetPath[0], //__out LPTSTR lpszFilePath,
+ BUFFER_SIZE, //__in DWORD cchFilePath,
+ FILE_NAME_NORMALIZED); //__in DWORD dwFlags
+ if (charsWritten >= BUFFER_SIZE || charsWritten == 0)
+ {
+ std::wstring errorMessage = _("Error resolving symbolic link:") + "\n\"" + dirLinkName + "\"";
+ if (charsWritten == 0)
+ errorMessage += L"\n\n" + getLastErrorFormatted();
+ throw FileError(errorMessage);
+ }
+
+ return Zstring(&targetPath[0], charsWritten);
+}
+#endif
+
+
+#ifdef HAVE_SELINUX
+//copy SELinux security context
+void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
+{
+ security_context_t contextSource = NULL;
+ const int rv = procSl == SYMLINK_FOLLOW ?
+ ::getfilecon(source.c_str(), &contextSource) :
+ ::lgetfilecon(source.c_str(), &contextSource);
+ if (rv < 0)
+ {
+ if (errno == ENODATA || //no security context (allegedly) is not an error condition on SELinux
+ errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem
+ return;
+
+ throw FileError(_("Error reading security context:") + "\n\"" + source + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+ ZEN_ON_BLOCK_EXIT(::freecon(contextSource));
+
+ {
+ security_context_t contextTarget = NULL;
+ const int rv2 = procSl == SYMLINK_FOLLOW ?
+ ::getfilecon(target.c_str(), &contextTarget) :
+ ::lgetfilecon(target.c_str(), &contextTarget);
+ if (rv2 < 0)
+ {
+ if (errno == EOPNOTSUPP)
+ return;
+ //else: still try to set security context
+ }
+ else
+ {
+ ZEN_ON_BLOCK_EXIT(::freecon(contextTarget));
+
+ if (::strcmp(contextSource, contextTarget) == 0) //nothing to do
+ return;
+ }
+ }
+
+ const int rv3 = procSl == SYMLINK_FOLLOW ?
+ ::setfilecon(target.c_str(), contextSource) :
+ ::lsetfilecon(target.c_str(), contextSource);
+ if (rv3 < 0)
+ throw FileError(_("Error writing security context:") + "\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted());
+}
+#endif //HAVE_SELINUX
+
+
+//copy permissions for files, directories or symbolic links: requires admin rights
+void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError;
+{
+#ifdef FFS_WIN
+ //setting privileges requires admin rights!
+ try
+ {
+ //enable privilege: required to read/write SACL information
+ Privileges::getInstance().ensureActive(SE_SECURITY_NAME); //polling allowed...
+
+ //enable privilege: required to copy owner information
+ Privileges::getInstance().ensureActive(SE_RESTORE_NAME);
+
+ //the following privilege may be required according to http://msdn.microsoft.com/en-us/library/aa364399(VS.85).aspx (although not needed nor active in my tests)
+ Privileges::getInstance().ensureActive(SE_BACKUP_NAME);
+ }
+ catch (const FileError& e)
+ {
+ throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + e.msg());
+ }
+
+ PSECURITY_DESCRIPTOR buffer = NULL;
+ PSID owner = NULL;
+ PSID group = NULL;
+ PACL dacl = NULL;
+ PACL sacl = NULL;
+
+ //http://msdn.microsoft.com/en-us/library/aa364399(v=VS.85).aspx
+ const HANDLE hSource = ::CreateFile(applyLongPathPrefix(source).c_str(),
+ READ_CONTROL | ACCESS_SYSTEM_SECURITY, //ACCESS_SYSTEM_SECURITY required for SACL access
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory
+ NULL);
+ if (hSource == INVALID_HANDLE_VALUE)
+ throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (OR)");
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hSource));
+
+ // DWORD rc = ::GetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(source).c_str()), -> does NOT dereference symlinks!
+ DWORD rc = ::GetSecurityInfo(hSource, //__in LPTSTR pObjectName,
+ SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType,
+ OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInfo,
+ &owner, //__out_opt PSID *ppsidOwner,
+ &group, //__out_opt PSID *ppsidGroup,
+ &dacl, //__out_opt PACL *ppDacl,
+ &sacl, //__out_opt PACL *ppSacl,
+ &buffer); //__out_opt PSECURITY_DESCRIPTOR *ppSecurityDescriptor
+ if (rc != ERROR_SUCCESS)
+ throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted(rc) + " (R)");
+ ZEN_ON_BLOCK_EXIT(::LocalFree(buffer));
+
+ //may need to remove the readonly-attribute (e.g. FAT usb drives)
+ FileUpdateHandle targetHandle(target, [ = ]()
+ {
+ return ::CreateFile(applyLongPathPrefix(target).c_str(), // lpFileName
+ GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY, // dwDesiredAccess: all four seem to be required!!!
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // dwShareMode
+ 0, // lpSecurityAttributes
+ OPEN_EXISTING, // dwCreationDisposition
+ FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), // dwFlagsAndAttributes
+ NULL); // hTemplateFile
+ });
+
+ if (targetHandle.get() == INVALID_HANDLE_VALUE)
+ throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (OW)");
+
+ // rc = ::SetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(target).c_str()), //__in LPTSTR pObjectName, -> does NOT dereference symlinks!
+ rc = ::SetSecurityInfo(targetHandle.get(), //__in LPTSTR pObjectName,
+ SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType,
+ OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInfo,
+ owner, //__in_opt PSID psidOwner,
+ group, //__in_opt PSID psidGroup,
+ dacl, //__in_opt PACL pDacl,
+ sacl); //__in_opt PACL pSacl
+
+ if (rc != ERROR_SUCCESS)
+ throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted(rc) + " (W)");
+
+#elif defined FFS_LINUX
+
+#ifdef HAVE_SELINUX //copy SELinux security context
+ copySecurityContext(source, target, procSl); //throw FileError
+#endif
+
+ struct stat fileInfo = {};
+ if (procSl == SYMLINK_FOLLOW)
+ {
+ if (::stat(source.c_str(), &fileInfo) != 0 ||
+ ::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights!
+ ::chmod(target.c_str(), fileInfo.st_mode) != 0)
+ throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (R)");
+ }
+ else
+ {
+ if (::lstat(source.c_str(), &fileInfo) != 0 ||
+ ::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights!
+ (!symlinkExists(target) && ::chmod(target.c_str(), fileInfo.st_mode) != 0)) //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (W)");
+ }
+#endif
+}
+
+
+void createDirectory_straight(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
+{
+ //default directory creation
+#ifdef FFS_WIN
+ //don't use ::CreateDirectoryEx:
+ //- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage
+ //- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)!
+ //- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!)
+ if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), NULL))
+#elif defined FFS_LINUX
+ if (::mkdir(directory.c_str(), 0755) != 0)
+#endif
+ {
+ if (level != 0) return;
+ throw FileError(_("Error creating directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+
+ if (!templateDir.empty())
+ {
+#ifdef FFS_WIN
+ //try to copy file attributes
+ Zstring sourcePath;
+
+ if (symlinkExists(templateDir)) //dereference symlink!
+ {
+ try
+ {
+ //get target directory of symbolic link
+ sourcePath = resolveDirectorySymlink(templateDir); //throw FileError
+ }
+ catch (FileError&) {} //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error...
+ }
+ else //no symbolic link
+ sourcePath = templateDir;
+
+ //try to copy file attributes
+ if (!sourcePath.empty())
+ {
+ const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str());
+ if (sourceAttr != INVALID_FILE_ATTRIBUTES)
+ {
+ const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0;
+ const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0;
+
+ ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), sourceAttr);
+
+ if (isEncrypted)
+ ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!)
+
+ if (isCompressed)
+ {
+ HANDLE hDir = ::CreateFile(applyLongPathPrefix(directory).c_str(),
+ GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hDir != INVALID_HANDLE_VALUE)
+ {
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hDir));
+
+ USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
+
+ DWORD bytesReturned = 0;
+ ::DeviceIoControl(hDir, //handle to file or directory
+ FSCTL_SET_COMPRESSION, //dwIoControlCode
+ &cmpState, //input buffer
+ sizeof(cmpState), //size of input buffer
+ NULL, //lpOutBuffer
+ 0, //OutBufferSize
+ &bytesReturned, //number of bytes returned
+ NULL); //OVERLAPPED structure
+ }
+ }
+ }
+ }
+#endif
+ zen::ScopeGuard guardNewDir = zen::makeGuard([&]() { removeDirectory(directory); }); //ensure cleanup:
+
+ //enforce copying file permissions: it's advertized on GUI...
+ if (copyFilePermissions)
+ copyObjectPermissions(templateDir, directory, SYMLINK_FOLLOW); //throw FileError
+
+ guardNewDir.dismiss(); //target has been created successfully!
+ }
+}
+
+
+void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
+{
+ if (level == 100) //catch endless recursion
+ return;
+
+#ifdef FFS_WIN
+ std::unique_ptr<Fix8Dot3NameClash> fnc;
+ if (somethingExists(directory))
+ {
+ //handle issues with already existing short 8.3 file names on Windows
+ if (have8dot3NameClash(directory))
+ fnc.reset(new Fix8Dot3NameClash(directory)); //move clashing object to the side
+ else if (dirExists(directory))
+ return;
+ }
+#elif defined FFS_LINUX
+ if (dirExists(directory))
+ return;
+#endif
+ else //if "somethingExists" we needn't create the parent directory
+ {
+ //try to create parent folders first
+ const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR);
+ if (!dirParent.empty() && !dirExists(dirParent))
+ {
+ //call function recursively
+ const Zstring templateParent = beforeLast(templateDir, FILE_NAME_SEPARATOR); //returns empty string if ch not found
+ createDirectoryRecursively(dirParent, templateParent, copyFilePermissions, level + 1);
+ }
+ }
+
+ //now creation should be possible
+ createDirectory_straight(directory, templateDir, copyFilePermissions, level); //throw FileError
+}
+}
+
+
+void zen::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions)
+{
+ //remove trailing separator
+ const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ?
+ beforeLast(directory, FILE_NAME_SEPARATOR) :
+ directory;
+
+ const Zstring templateFormatted = endsWith(templateDir, FILE_NAME_SEPARATOR) ?
+ beforeLast(templateDir, FILE_NAME_SEPARATOR) :
+ templateDir;
+
+ createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions, 0);
+}
+
+
+void zen::createDirectory(const Zstring& directory)
+{
+ zen::createDirectory(directory, Zstring(), false);
+}
+
+
+void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError
+{
+ const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw FileError
+
+#ifdef FFS_WIN
+ const bool isDirLink = [&]() -> bool
+ {
+ const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(sourceLink).c_str());
+ return ret != INVALID_FILE_ATTRIBUTES && (ret& FILE_ATTRIBUTE_DIRECTORY);
+ }();
+
+ //dynamically load windows API function
+ typedef BOOLEAN (WINAPI *CreateSymbolicLinkFunc)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags);
+
+ const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW");
+ if (!createSymbolicLink)
+ throw FileError(_("Error loading library function:") + "\n\"" + "CreateSymbolicLinkW" + "\"");
+
+ if (!createSymbolicLink(targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - seems no long path prefix is required...
+ linkPath.c_str(), //__in LPTSTR lpTargetFileName,
+ (isDirLink ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags
+#elif defined FFS_LINUX
+ if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0)
+#endif
+ throw FileError(_("Error copying symbolic link:") + "\n\"" + sourceLink + "\" ->\n\"" + targetLink + "\"" + "\n\n" + getLastErrorFormatted());
+
+ //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist
+ zen::ScopeGuard guardNewDir = zen::makeGuard([&]()
+ {
+#ifdef FFS_WIN
+ if (isDirLink)
+ removeDirectory(targetLink);
+ else
+#endif
+ removeFile(targetLink);
+ });
+
+ //file times: essential for a symlink: enforce this! (don't just try!)
+ {
+ const Int64 modTime = getFileTime(sourceLink, SYMLINK_DIRECT); //throw FileError
+ setFileTime(targetLink, modTime, SYMLINK_DIRECT); //throw FileError
+ }
+
+ if (copyFilePermissions)
+ copyObjectPermissions(sourceLink, targetLink, SYMLINK_DIRECT); //throw FileError
+
+ guardNewDir.dismiss(); //target has been created successfully!
+}
+
+
+namespace
+{
+Zstring createTempName(const Zstring& filename)
+{
+ Zstring output = filename + zen::TEMP_FILE_ENDING;
+
+ //ensure uniqueness
+ for (int i = 1; somethingExists(output); ++i)
+ output = filename + Zchar('_') + toString<Zstring>(i) + zen::TEMP_FILE_ENDING;
+
+ return output;
+}
+
+#ifdef FFS_WIN
+class CallbackData
+{
+public:
+ CallbackData(CallbackCopyFile* cb, //may be NULL
+ const Zstring& sourceFile,
+ const Zstring& targetFile,
+ bool osIsvistaOrLater) :
+ userCallback(cb),
+ sourceFile_(sourceFile),
+ targetFile_(targetFile),
+ osIsvistaOrLater_(osIsvistaOrLater),
+ exceptionInUserCallback(false) {}
+
+ CallbackCopyFile* userCallback; //optional!
+ const Zstring& sourceFile_;
+ const Zstring& targetFile_;
+ const bool osIsvistaOrLater_;
+
+ //there is some mixed responsibility in this class, pure read-only data and abstraction for error reporting
+ //however we need to keep it at one place as ::CopyFileEx() requires!
+
+ void reportUserException(const UInt64& bytesTransferred)
+ {
+ exceptionInUserCallback = true;
+ bytesTransferredOnException = bytesTransferred;
+ }
+
+ void reportError(const std::wstring& message) { errorMsg = message; }
+
+ void evaluateErrors() //throw
+ {
+ if (exceptionInUserCallback)
+ {
+ assert(userCallback);
+ if (userCallback)
+ userCallback->updateCopyStatus(bytesTransferredOnException); //rethrow (hopefully!)
+ }
+ if (!errorMsg.empty())
+ throw FileError(errorMsg);
+ }
+
+ void setSrcAttr(const FileAttrib& attr) { sourceAttr = attr; }
+ FileAttrib getSrcAttr() const { assert(sourceAttr.modificationTime != 0); return sourceAttr; }
+
+private:
+ CallbackData(const CallbackData&);
+ CallbackData& operator=(const CallbackData&);
+
+ FileAttrib sourceAttr;
+ std::wstring errorMsg; //
+ bool exceptionInUserCallback; //these two are exclusive!
+ UInt64 bytesTransferredOnException;
+};
+
+
+DWORD CALLBACK copyCallbackInternal(
+ LARGE_INTEGER totalFileSize,
+ LARGE_INTEGER totalBytesTransferred,
+ LARGE_INTEGER streamSize,
+ LARGE_INTEGER streamBytesTransferred,
+ DWORD dwStreamNumber,
+ DWORD dwCallbackReason,
+ HANDLE hSourceFile,
+ HANDLE hDestinationFile,
+ LPVOID lpData)
+{
+ //this callback is invoked for block sizes managed by Windows, these may vary from e.g. 64 kB up to 1MB. It seems this is dependent from file size amongst others.
+
+ //symlink handling:
+ //if source is a symlink and COPY_FILE_COPY_SYMLINK is specified, this callback is NOT invoked!
+ //if source is a symlink and COPY_FILE_COPY_SYMLINK is NOT specified, this callback is called and hSourceFile is a handle to the *target* of the link!
+
+ //file time handling:
+ //::CopyFileEx() will copy file modification time (only) over from source file AFTER the last inocation of this callback
+ //=> it is possible to adapt file creation time of target in here, but NOT file modification time!
+
+ CallbackData& cbd = *static_cast<CallbackData*>(lpData);
+
+ //#################### return source file attributes ################################
+ if (dwCallbackReason == CALLBACK_STREAM_SWITCH) //called up front for every file (even if 0-sized)
+ {
+ BY_HANDLE_FILE_INFORMATION fileInfo = {};
+ if (!::GetFileInformationByHandle(hSourceFile, &fileInfo))
+ {
+ cbd.reportError(_("Error reading file attributes:") + "\n\"" + cbd.sourceFile_ + "\"" + "\n\n" + getLastErrorFormatted());
+ return PROGRESS_CANCEL;
+ }
+
+ const FileAttrib attr = { UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh),
+ toTimeT(fileInfo.ftLastWriteTime)
+ };
+ //extractFileID(fileInfo));
+
+ cbd.setSrcAttr(attr);
+
+ //#################### copy file creation time ################################
+ FILETIME creationTime = {};
+
+ if (!::GetFileTime(hSourceFile, //__in HANDLE hFile,
+ &creationTime, //__out_opt LPFILETIME lpCreationTime,
+ NULL, //__out_opt LPFILETIME lpLastAccessTime,
+ NULL)) //__out_opt LPFILETIME lpLastWriteTime
+ {
+ cbd.reportError(_("Error reading file attributes:") + "\n\"" + cbd.sourceFile_ + "\"" + "\n\n" + getLastErrorFormatted());
+ return PROGRESS_CANCEL;
+ }
+
+ if (!::SetFileTime(hDestinationFile,
+ &creationTime,
+ NULL,
+ NULL))
+ {
+ cbd.reportError(_("Error changing modification time:") + "\n\"" + cbd.targetFile_ + "\"" + "\n\n" + getLastErrorFormatted());
+ return PROGRESS_CANCEL;
+ }
+ //##############################################################################
+ }
+
+ //if (totalFileSize.QuadPart == totalBytesTransferred.QuadPart) {} //called after copy operation is finished - note: for 0-sized files this callback is invoked just ONCE!
+
+ if (cbd.userCallback)
+ {
+ //some odd check for some possible(?) error condition
+ if (totalBytesTransferred.QuadPart < 0) //let's see if someone answers the call...
+ ::MessageBox(NULL, L"You've just discovered a bug in WIN32 API function \"CopyFileEx\"! \n\n\
+ Please write a mail to the author of FreeFileSync at zhnmju123@gmx.de and simply state that\n\
+ \"totalBytesTransferred.HighPart can be below zero\"!\n\n\
+ This will then be handled in future versions of FreeFileSync.\n\nThanks -ZenJu",
+ NULL, 0);
+ try
+ {
+ cbd.userCallback->updateCopyStatus(UInt64(totalBytesTransferred.QuadPart));
+ }
+ catch (...)
+ {
+ //#warning migrate to std::exception_ptr when available
+
+ cbd.reportUserException(UInt64(totalBytesTransferred.QuadPart));
+ return PROGRESS_CANCEL;
+ }
+ }
+ return PROGRESS_CONTINUE;
+}
+
+
+#ifndef COPY_FILE_ALLOW_DECRYPTED_DESTINATION
+#define COPY_FILE_ALLOW_DECRYPTED_DESTINATION 0x00000008
+#endif
+
+void rawCopyWinApi_sub(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ CallbackCopyFile* callback,
+ FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+{
+ zen::ScopeGuard guardTarget = zen::makeGuard([&]() { removeFile(targetFile); }); //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we ;)
+
+ DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS;
+
+ //allow copying from encrypted to non-encrytped location
+ static bool nonEncSupported = false;
+ {
+ static boost::once_flag initNonEncOnce = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010!
+ boost::call_once(initNonEncOnce, []() { nonEncSupported = winXpOrLater(); }); //encrypted destination is not supported with Windows 2000
+ }
+ if (nonEncSupported)
+ copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
+
+ static bool osIsvistaOrLater = false;
+ {
+ static boost::once_flag initVistaLaterOnce = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010!
+ boost::call_once(initVistaLaterOnce, []() { osIsvistaOrLater = vistaOrLater(); });
+ }
+
+ CallbackData cbd(callback, sourceFile, targetFile, osIsvistaOrLater);
+
+ const bool success = ::CopyFileEx( //same performance like CopyFile()
+ applyLongPathPrefix(sourceFile).c_str(),
+ applyLongPathPrefix(targetFile).c_str(),
+ copyCallbackInternal,
+ &cbd,
+ NULL,
+ copyFlags) == TRUE; //silence x64 perf warning
+
+ cbd.evaluateErrors(); //throw ?, process errors in callback first!
+ if (!success)
+ {
+ const DWORD lastError = ::GetLastError();
+
+ //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition!
+
+ //assemble error message...
+ std::wstring errorMessage = _("Error copying file:") + "\n\"" + sourceFile + "\" ->\n\"" + targetFile + "\"" +
+ "\n\n" + getLastErrorFormatted(lastError);
+
+ //if file is locked (try to) use Windows Volume Shadow Copy Service
+ if (lastError == ERROR_SHARING_VIOLATION ||
+ lastError == ERROR_LOCK_VIOLATION)
+ throw ErrorFileLocked(errorMessage);
+
+ if (lastError == ERROR_FILE_EXISTS) //if target is existing this functions is expected to throw ErrorTargetExisting!!!
+ {
+ guardTarget.dismiss(); //don't delete file that existed previously!
+ throw ErrorTargetExisting(errorMessage);
+ }
+
+ if (lastError == ERROR_PATH_NOT_FOUND)
+ {
+ guardTarget.dismiss(); //not relevant
+ throw ErrorTargetPathMissing(errorMessage);
+ }
+
+ try //add more meaningful message
+ {
+ //trying to copy > 4GB file to FAT/FAT32 volume gives obscure ERROR_INVALID_PARAMETER (FAT can indeed handle files up to 4 Gig, tested!)
+ if (lastError == ERROR_INVALID_PARAMETER &&
+ dst::isFatDrive(targetFile) &&
+ getFilesize(sourceFile) >= 4U * UInt64(1024U * 1024 * 1024)) //throw FileError
+ errorMessage += L"\nFAT volume cannot store files larger than 4 gigabyte!";
+
+ //note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target filename is of a restricted type.
+ }
+ catch (...) {}
+
+ throw FileError(errorMessage);
+ }
+
+ if (sourceAttr)
+ *sourceAttr = cbd.getSrcAttr();
+
+ {
+ const Int64 modTime = getFileTime(sourceFile, SYMLINK_FOLLOW); //throw FileError
+ setFileTime(targetFile, modTime, SYMLINK_FOLLOW); //throw FileError
+ //note: this sequence leads to a loss of precision of up to 1 sec!
+ //perf-loss on USB sticks with many small files of about 30%! damn!
+ }
+
+ guardTarget.dismiss(); //target has been created successfully!
+}
+
+
+inline
+void rawCopyWinApi(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ CallbackCopyFile* callback,
+ FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+{
+ try
+ {
+ rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); // throw ...
+ }
+ catch (ErrorTargetExisting&)
+ {
+ //try to handle issues with already existing short 8.3 file names on Windows
+ if (have8dot3NameClash(targetFile))
+ {
+ Fix8Dot3NameClash dummy(targetFile); //move clashing filename to the side
+ rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); //throw FileError; the short filename name clash is solved, this should work now
+ return;
+ }
+ throw;
+ }
+}
+
+//void rawCopyWinOptimized(const Zstring& sourceFile,
+// const Zstring& targetFile,
+// CallbackCopyFile* callback) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+//{
+// /*
+// BackupRead() FileRead() CopyFileEx()
+// --------------------------------------------
+// Attributes NO NO YES
+// create time NO NO NO
+// ADS YES NO YES
+// Encrypted NO(silent fail) NO YES
+// Compressed NO NO NO
+// Sparse YES NO NO
+// PERF 6% faster -
+//
+// Mark stream as compressed: FSCTL_SET_COMPRESSION
+// compatible with: BackupRead() FileRead()
+// */
+//
+// //open sourceFile for reading
+// HANDLE hFileIn = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(),
+// GENERIC_READ,
+// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications
+// 0,
+// OPEN_EXISTING,
+// FILE_FLAG_SEQUENTIAL_SCAN,
+// NULL);
+// if (hFileIn == INVALID_HANDLE_VALUE)
+// {
+// const DWORD lastError = ::GetLastError();
+// const std::wstring& errorMessage = _("Error opening file:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted(lastError);
+//
+// //if file is locked (try to) use Windows Volume Shadow Copy Service
+// if (lastError == ERROR_SHARING_VIOLATION ||
+// lastError == ERROR_LOCK_VIOLATION)
+// throw ErrorFileLocked(errorMessage);
+//
+// throw FileError(errorMessage);
+// }
+// ZEN_ON_BLOCK_EXIT(::CloseHandle, hFileIn);
+//
+//
+// BY_HANDLE_FILE_INFORMATION infoFileIn = {};
+// if (!::GetFileInformationByHandle(hFileIn, &infoFileIn))
+// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
+//
+// //####################################### DST hack ###########################################
+// if (dst::isFatDrive(sourceFile)) //throw()
+// {
+// const dst::RawTime rawTime(infoFileIn.ftCreationTime, infoFileIn.ftLastWriteTime);
+// if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
+// {
+// infoFileIn.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error)
+// ::GetSystemTimeAsFileTime(&infoFileIn.ftCreationTime); //real creation time information is not available...
+// }
+// }
+//
+// if (dst::isFatDrive(targetFile)) //throw()
+// {
+// const dst::RawTime encodedTime = dst::fatEncodeUtcTime(infoFileIn.ftLastWriteTime); //throw (std::runtime_error)
+// infoFileIn.ftCreationTime = encodedTime.createTimeRaw;
+// infoFileIn.ftLastWriteTime = encodedTime.writeTimeRaw;
+// }
+// //####################################### DST hack ###########################################
+//
+// const DWORD validAttribs = FILE_ATTRIBUTE_READONLY |
+// FILE_ATTRIBUTE_HIDDEN |
+// FILE_ATTRIBUTE_SYSTEM |
+// FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx())
+// FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | //
+// FILE_ATTRIBUTE_ENCRYPTED;
+//
+// //create targetFile and open it for writing
+// HANDLE hFileOut = ::CreateFile(applyLongPathPrefix(targetFile).c_str(),
+// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
+// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+// 0,
+// CREATE_NEW,
+// (infoFileIn.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN,
+// NULL);
+// if (hFileOut == INVALID_HANDLE_VALUE)
+// {
+// const DWORD lastError = ::GetLastError();
+// const std::wstring& errorMessage = _("Error writing file:") + "\n\"" + targetFile + "\"" +
+// "\n\n" + getLastErrorFormatted(lastError);
+//
+// if (lastError == ERROR_FILE_EXISTS)
+// throw ErrorTargetExisting(errorMessage);
+//
+// if (lastError == ERROR_PATH_NOT_FOUND)
+// throw ErrorTargetPathMissing(errorMessage);
+//
+// throw FileError(errorMessage);
+// }
+// Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just after opening target and before managing hFileOut
+//
+// ZEN_ON_BLOCK_EXIT(::CloseHandle, hFileOut);
+//
+//
+//#ifndef _MSC_VER
+//#warning teste perf von GetVolumeInformationByHandleW
+//#endif
+// DWORD fsFlags = 0;
+// if (!GetVolumeInformationByHandleW(hFileOut, //__in HANDLE hFile,
+// NULL, //__out_opt LPTSTR lpVolumeNameBuffer,
+// 0, //__in DWORD nVolumeNameSize,
+// NULL, //__out_opt LPDWORD lpVolumeSerialNumber,
+// NULL, //__out_opt LPDWORD lpMaximumComponentLength,
+// &fsFlags, //__out_opt LPDWORD lpFileSystemFlags,
+// NULL, //__out LPTSTR lpFileSystemNameBuffer,
+// 0)) //__in DWORD nFileSystemNameSize
+// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
+//
+// const bool sourceIsEncrypted = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0;
+// const bool sourceIsCompressed = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
+// const bool sourceIsSparse = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
+//
+// bool targetSupportSparse = (fsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0;
+// bool targetSupportCompressed = (fsFlags & FILE_FILE_COMPRESSION ) != 0;
+// bool targetSupportStreams = (fsFlags & FILE_NAMED_STREAMS ) != 0;
+//
+//
+// const bool useBackupFun = !sourceIsEncrypted; //http://msdn.microsoft.com/en-us/library/aa362509(v=VS.85).aspx
+//
+// if (sourceIsCompressed && targetSupportCompressed)
+// {
+// USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
+//
+// DWORD bytesReturned = 0;
+// if (!DeviceIoControl(hFileOut, //handle to file or directory
+// FSCTL_SET_COMPRESSION, //dwIoControlCode
+// &cmpState, //input buffer
+// sizeof(cmpState), //size of input buffer
+// NULL, //lpOutBuffer
+// 0, //OutBufferSize
+// &bytesReturned, //number of bytes returned
+// NULL)) //OVERLAPPED structure
+// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
+// "\n\n" + getLastErrorFormatted() +
+// "\nFailed to write NTFS compressed attribute!");
+// }
+//
+// //although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us
+// if (sourceIsSparse && targetSupportSparse)
+// {
+// if (useBackupFun)
+// {
+// DWORD bytesReturned = 0;
+// if (!DeviceIoControl(hFileOut, //handle to file
+// FSCTL_SET_SPARSE, //dwIoControlCode
+// NULL, //input buffer
+// 0, //size of input buffer
+// NULL, //lpOutBuffer
+// 0, //OutBufferSize
+// &bytesReturned, //number of bytes returned
+// NULL)) //OVERLAPPED structure
+// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
+// "\n\n" + getLastErrorFormatted() +
+// "\nFailed to write NTFS sparse attribute!");
+// }
+// }
+//
+//
+// const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size
+// static boost::thread_specific_ptr<std::vector<char>> cpyBuf;
+// if (!cpyBuf.get())
+// cpyBuf.reset(new std::vector<char>(BUFFER_SIZE)); //512 kb seems to be a reasonable buffer size
+// std::vector<char>& buffer = *cpyBuf;
+//
+// struct ManageCtxt //manage context for BackupRead()/BackupWrite()
+// {
+// ManageCtxt() : read(NULL), write(NULL) {}
+// ~ManageCtxt()
+// {
+// if (read != NULL)
+// ::BackupRead (0, NULL, 0, NULL, true, false, &read);
+// if (write != NULL)
+// ::BackupWrite(0, NULL, 0, NULL, true, false, &write);
+// }
+//
+// LPVOID read;
+// LPVOID write;
+// } context;
+//
+// //copy contents of sourceFile to targetFile
+// UInt64 totalBytesTransferred;
+//
+// bool eof = false;
+// do
+// {
+// DWORD bytesRead = 0;
+//
+// if (useBackupFun)
+// {
+// if (!::BackupRead(hFileIn, //__in HANDLE hFile,
+// &buffer[0], //__out LPBYTE lpBuffer,
+// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead,
+// &bytesRead, //__out LPDWORD lpNumberOfBytesRead,
+// false, //__in BOOL bAbort,
+// false, //__in BOOL bProcessSecurity,
+// &context.read)) //__out LPVOID *lpContext
+// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" +
+// "\n\n" + getLastErrorFormatted());
+// }
+// else if (!::ReadFile(hFileIn, //__in HANDLE hFile,
+// &buffer[0], //__out LPVOID lpBuffer,
+// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead,
+// &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead,
+// NULL)) //__inout_opt LPOVERLAPPED lpOverlapped
+// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" +
+// "\n\n" + getLastErrorFormatted());
+//
+// if (bytesRead > BUFFER_SIZE)
+// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" +
+// "\n\n" + "buffer overflow");
+//
+// if (bytesRead < BUFFER_SIZE)
+// eof = true;
+//
+// DWORD bytesWritten = 0;
+//
+// if (useBackupFun)
+// {
+// if (!::BackupWrite(hFileOut, //__in HANDLE hFile,
+// &buffer[0], //__in LPBYTE lpBuffer,
+// bytesRead, //__in DWORD nNumberOfBytesToWrite,
+// &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten,
+// false, //__in BOOL bAbort,
+// false, //__in BOOL bProcessSecurity,
+// &context.write)) //__out LPVOID *lpContext
+// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
+// "\n\n" + getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message!
+// }
+// else if (!::WriteFile(hFileOut, //__in HANDLE hFile,
+// &buffer[0], //__out LPVOID lpBuffer,
+// bytesRead, //__in DWORD nNumberOfBytesToWrite,
+// &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten,
+// NULL)) //__inout_opt LPOVERLAPPED lpOverlapped
+// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
+// "\n\n" + getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message!
+//
+// if (bytesWritten != bytesRead)
+// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" + "\n\n" + "incomplete write");
+//
+// totalBytesTransferred += bytesRead;
+//
+//#ifndef _MSC_VER
+//#warning totalBytesTransferred kann größer als filesize sein!!
+//#endif
+//
+// //invoke callback method to update progress indicators
+// if (callback != NULL)
+// switch (callback->updateCopyStatus(totalBytesTransferred))
+// {
+// case CallbackCopyFile::CONTINUE:
+// break;
+//
+// case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition!
+// throw FileError(_("Error copying file:") + "\n\"" + sourceFile + "\" ->\n\"" +
+// targetFile + "\"\n\n" + _("Operation aborted!"));
+// }
+// }
+// while (!eof);
+//
+//
+// if (totalBytesTransferred == 0) //BackupRead silently fails reading encrypted files -> double check!
+// {
+// LARGE_INTEGER inputSize = {};
+// if (!::GetFileSizeEx(hFileIn, &inputSize))
+// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
+//
+// if (inputSize.QuadPart != 0)
+// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" + "\n\n" + "unknown error");
+// }
+//
+// //time needs to be set at the end: BackupWrite() changes file time
+// if (!::SetFileTime(hFileOut,
+// &infoFileIn.ftCreationTime,
+// NULL,
+// &infoFileIn.ftLastWriteTime))
+// throw FileError(_("Error changing modification time:") + "\n\"" + targetFile + "\"" + "\n\n" + getLastErrorFormatted());
+//
+//
+//#ifndef NDEBUG //dst hack: verify data written
+// if (dst::isFatDrive(targetFile)) //throw()
+// {
+// WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {};
+// assert(::GetFileAttributesEx(applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpFileName,
+// GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
+// &debugeAttr)); //__out LPVOID lpFileInformation
+//
+// assert(::CompareFileTime(&debugeAttr.ftCreationTime, &infoFileIn.ftCreationTime) == 0);
+// assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &infoFileIn.ftLastWriteTime) == 0);
+// }
+//#endif
+//
+// guardTarget.Dismiss();
+//
+// /*
+// //create test sparse file
+// HANDLE hSparse = ::CreateFile(L"C:\\sparse.file",
+// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
+// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+// 0,
+// CREATE_NEW,
+// FILE_FLAG_SEQUENTIAL_SCAN,
+// NULL);
+// DWORD br = 0;
+// if (!::DeviceIoControl(hSparse, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &br,NULL))
+// throw 1;
+//
+// LARGE_INTEGER liDistanceToMove = {};
+// liDistanceToMove.QuadPart = 1024 * 1024 * 1024; //create 5 TB sparse file
+// liDistanceToMove.QuadPart *= 5 * 1024; //
+// if (!::SetFilePointerEx(hSparse, liDistanceToMove, NULL, FILE_BEGIN))
+// throw 1;
+//
+// if (!SetEndOfFile(hSparse))
+// throw 1;
+//
+// FILE_ZERO_DATA_INFORMATION zeroInfo = {};
+// zeroInfo.BeyondFinalZero.QuadPart = liDistanceToMove.QuadPart;
+// if (!::DeviceIoControl(hSparse, FSCTL_SET_ZERO_DATA, &zeroInfo, sizeof(zeroInfo), NULL, 0, &br, NULL))
+// throw 1;
+//
+// ::CloseHandle(hSparse);
+// */
+//}
+#endif
+
+#ifdef FFS_LINUX
+void rawCopyStream(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ CallbackCopyFile* callback,
+ FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
+{
+ zen::ScopeGuard guardTarget = zen::makeGuard([&]() { removeFile(targetFile); }); //transactional behavior: place guard before lifetime of FileOutput
+ try
+ {
+ //open sourceFile for reading
+ FileInput fileIn(sourceFile); //throw FileError
+
+ //create targetFile and open it for writing
+ FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
+
+ static boost::thread_specific_ptr<std::vector<char>> cpyBuf;
+ if (!cpyBuf.get())
+ cpyBuf.reset(new std::vector<char>(512 * 1024)); //512 kb seems to be a reasonable buffer size
+ std::vector<char>& buffer = *cpyBuf;
+
+ //copy contents of sourceFile to targetFile
+ UInt64 totalBytesTransferred;
+ do
+ {
+ const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
+
+ fileOut.write(&buffer[0], bytesRead); //throw FileError
+
+ totalBytesTransferred += bytesRead;
+
+ //invoke callback method to update progress indicators
+ if (callback)
+ callback->updateCopyStatus(totalBytesTransferred);
+ }
+ while (!fileIn.eof());
+ }
+ catch (ErrorTargetExisting&)
+ {
+ guardTarget.dismiss(); //don't delete file that existed previously!
+ throw;
+ }
+
+ //adapt file modification time:
+ {
+ struct stat srcInfo = {};
+ if (::stat(sourceFile.c_str(), &srcInfo) != 0) //read file attributes from source directory
+ throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
+
+ if (sourceAttr)
+ {
+ sourceAttr->fileSize = UInt64(srcInfo.st_size);
+ sourceAttr->modificationTime = srcInfo.st_mtime;
+ }
+
+ struct utimbuf newTimes = {};
+ newTimes.actime = srcInfo.st_atime;
+ newTimes.modtime = srcInfo.st_mtime;
+
+ //set new "last write time"
+ if (::utime(targetFile.c_str(), &newTimes) != 0)
+ throw FileError(_("Error changing modification time:") + "\n\"" + targetFile + "\"" + "\n\n" + getLastErrorFormatted());
+ }
+
+ guardTarget.dismiss(); //target has been created successfully!
+}
+#endif
+
+
+inline
+void copyFileImpl(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ CallbackCopyFile* callback,
+ FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+{
+#ifdef FFS_WIN
+ /*
+ rawCopyWinApi() rawCopyWinOptimized()
+ -------------------------------------
+ Attributes YES YES
+ Filetimes YES YES
+ ADS YES YES
+ Encrypted YES YES
+ Compressed NO YES
+ Sparse NO YES
+ PERF - 6% faster
+ SAMBA, ect. YES UNKNOWN! -> issues writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. damn!
+ */
+
+ rawCopyWinApi(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+ //rawCopyWinOptimized(sourceFile, targetFile, callback); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked ->about 8% faster
+
+#elif defined FFS_LINUX
+ rawCopyStream(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
+#endif
+}
+}
+
+
+void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissing, ErrorFileLocked
+ const Zstring& targetFile,
+ bool copyFilePermissions,
+ bool transactionalCopy,
+ CallbackCopyFile* callback,
+ FileAttrib* sourceAttr)
+{
+ if (transactionalCopy)
+ {
+ Zstring temporary = targetFile + zen::TEMP_FILE_ENDING; //use temporary file until a correct date has been set
+ zen::ScopeGuard guardTempFile = zen::makeGuard([&]() { removeFile(temporary); }); //transactional behavior: ensure cleanup (e.g. network drop) -> ref to temporary[!]
+
+ //raw file copy
+ try
+ {
+ copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+ }
+ catch (ErrorTargetExisting&)
+ {
+ //determine non-used temp file name "first":
+ //using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses
+ temporary = createTempName(targetFile);
+
+ //retry
+ copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError
+ }
+
+ //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite
+ if (callback)
+ callback->deleteTargetFile(targetFile);
+
+ //rename temporary file:
+ //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick!
+ renameFile(temporary, targetFile); //throw FileError
+
+ guardTempFile.dismiss();
+ }
+ else
+ {
+ //have target file deleted
+ if (callback) callback->deleteTargetFile(targetFile);
+
+ copyFileImpl(sourceFile, targetFile, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+ }
+ /*
+ Note: non-transactional file copy solves at least four problems:
+ -> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER
+ -> network renaming issues
+ -> allow for true delete before copy to handle low disk space problems
+ -> higher performance on non-buffered drives (e.g. usb sticks)
+ */
+
+ //set file permissions
+ if (copyFilePermissions)
+ {
+ zen::ScopeGuard guardTargetFile = zen::makeGuard([&]() { removeFile(targetFile);});
+ copyObjectPermissions(sourceFile, targetFile, SYMLINK_FOLLOW); //throw FileError
+ guardTargetFile.dismiss(); //target has been created successfully!
+ }
+}
diff --git a/zen/file_handling.h b/zen/file_handling.h
new file mode 100644
index 00000000..baa51516
--- /dev/null
+++ b/zen/file_handling.h
@@ -0,0 +1,120 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FILE_HANDLING_H_INCLUDED
+#define FILE_HANDLING_H_INCLUDED
+
+#include "string.h"
+#include "file_error.h"
+#include "int64.h"
+
+namespace zen
+{
+struct CallbackRemoveDir;
+struct CallbackMoveFile;
+struct CallbackCopyFile;
+
+
+bool fileExists (const Zstring& filename); //throw() check whether file or symlink exists
+bool dirExists (const Zstring& dirname); //throw() check whether directory or symlink exists
+bool symlinkExists (const Zstring& objname); //throw() check whether a symbolic link exists
+bool somethingExists(const Zstring& objname); //throw() check whether any object with this name exists
+
+//check whether two folders are located on the same (logical) volume
+//left and right directories NEED NOT yet exist! volume prefix is sufficient! path may end with PATH_SEPARATOR
+enum ResponseSame
+{
+ IS_SAME_YES,
+ IS_SAME_NO,
+ IS_SAME_CANT_SAY
+};
+ResponseSame onSameVolume(const Zstring& folderLeft, const Zstring& folderRight); //throw()
+
+enum ProcSymlink
+{
+ SYMLINK_DIRECT,
+ SYMLINK_FOLLOW
+};
+
+Int64 getFileTime(const Zstring& filename, ProcSymlink procSl); //throw FileError
+void setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl); //throw FileError
+
+//symlink handling: always evaluate target
+UInt64 getFilesize(const Zstring& filename); //throw FileError
+
+//file handling
+bool removeFile(const Zstring& filename); //return "true" if file was actually deleted; throw (FileError)
+void removeDirectory(const Zstring& directory, CallbackRemoveDir* callback = NULL); //throw FileError
+
+
+//rename file or directory: no copying!!!
+void renameFile(const Zstring& oldName, const Zstring& newName); //throw FileError;
+
+//move source to target; expectations: all super-directories of target exist
+//"ignoreExisting": if target already exists, source is deleted
+void moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback); //throw FileError;
+
+//move source to target including subdirectories
+//"ignoreExisting": existing directories and files will be enriched
+void moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback); //throw FileError;
+
+//creates superdirectories automatically:
+void createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions); //throw FileError;
+void createDirectory(const Zstring& directory); //throw FileError; -> function overload avoids default parameter ambiguity issues!
+
+struct FileAttrib
+{
+ UInt64 fileSize;
+ Int64 modificationTime; //size_t UTC compatible
+};
+
+void copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissing, ErrorFileLocked (Windows-only)
+ const Zstring& targetFile, //symlink handling: dereference source
+ bool copyFilePermissions,
+ bool transactionalCopy,
+ CallbackCopyFile* callback, //may be NULL
+ FileAttrib* sourceAttr = NULL); //return current attributes at the time of copy
+//Note: it MAY happen that copyFile() leaves temp files behind, e.g. temporary network drop.
+// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending:
+const Zstring TEMP_FILE_ENDING = Zstr(".ffs_tmp");
+
+void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError
+
+
+
+
+//----------- callbacks ---------------
+struct CallbackRemoveDir
+{
+ virtual ~CallbackRemoveDir() {}
+ virtual void notifyFileDeletion(const Zstring& filename) = 0;
+ virtual void notifyDirDeletion(const Zstring& dirname) = 0;
+};
+
+
+struct CallbackCopyFile //callback functionality
+{
+ virtual ~CallbackCopyFile() {}
+
+ //if target is existing user needs to implement deletion: copyFile() NEVER deletes target if already existing!
+ //if transactionalCopy == true, full read access on source had been proven at this point, so it's safe to delete it.
+ virtual void deleteTargetFile(const Zstring& targetFile) = 0; //may throw exceptions
+
+ //may throw:
+ //Linux: unconditionally
+ //Windows: first exception is swallowed, requestUiRefresh() is then called again where it should throw again and exception will propagate as expected
+ virtual void updateCopyStatus(UInt64 totalBytesTransferred) = 0;
+};
+
+
+struct CallbackMoveFile //callback functionality
+{
+ virtual ~CallbackMoveFile() {}
+ virtual void requestUiRefresh(const Zstring& currentObject) = 0; //see CallbackCopyFile!
+};
+}
+
+#endif //FILE_HANDLING_H_INCLUDED
diff --git a/zen/file_id.cpp b/zen/file_id.cpp
new file mode 100644
index 00000000..c9f422ac
--- /dev/null
+++ b/zen/file_id.cpp
@@ -0,0 +1,67 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "file_id.h"
+#include "file_id_internal.h"
+
+#ifdef FFS_WIN
+#include "win.h" //includes "windows.h"
+#include "long_path_prefix.h"
+#include "scope_guard.h"
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#endif
+
+
+std::string zen::getFileID(const Zstring& filename)
+{
+#ifdef FFS_WIN
+ //WARNING: CreateFile() is SLOW, while GetFileInformationByHandle() is cheap!
+ //http://msdn.microsoft.com/en-us/library/aa363788(VS.85).aspx
+
+ //privilege SE_BACKUP_NAME doesn't seem to be required here at all
+
+ const HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory
+ NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hFile));
+
+ BY_HANDLE_FILE_INFORMATION fileInfo = {};
+ if (::GetFileInformationByHandle(hFile, &fileInfo))
+ return extractFileID(fileInfo);
+ }
+
+#elif defined FFS_LINUX
+ struct ::stat fileInfo = {};
+ if (::lstat(filename.c_str(), &fileInfo) == 0) //lstat() does not follow symlinks
+ return extractFileID(fileInfo);
+#endif
+
+ assert(false);
+ return std::string();
+}
+
+
+bool zen::samePhysicalFile(const Zstring& file1, const Zstring& file2)
+{
+ if (EqualFilename()(file1, file2)) //quick check
+ return true;
+
+ const std::string id1 = getFileID(file1);
+ const std::string id2 = getFileID(file2);
+
+ if (id1.empty() || id2.empty())
+ return false;
+
+ return id1 == id2;
+}
diff --git a/zen/file_id.h b/zen/file_id.h
new file mode 100644
index 00000000..f015569d
--- /dev/null
+++ b/zen/file_id.h
@@ -0,0 +1,27 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FILEID_H_INCLUDED
+#define FILEID_H_INCLUDED
+
+#include "zstring.h"
+#include <string>
+
+//unique file identifier
+
+namespace zen
+{
+//get unique file id (symbolic link handling: opens the link!!!)
+//returns empty string on error!
+std::string getFileID(const Zstring& filename);
+
+//test whether two distinct paths point to the same file or directory:
+// true: paths point to same files/dirs
+// false: error occurred OR point to different files/dirs
+bool samePhysicalFile(const Zstring& file1, const Zstring& file2);
+}
+
+#endif // FILEID_H_INCLUDED
diff --git a/zen/file_id_internal.h b/zen/file_id_internal.h
new file mode 100644
index 00000000..492d8432
--- /dev/null
+++ b/zen/file_id_internal.h
@@ -0,0 +1,48 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FILE_ID_INTERNAL_HEADER_013287632486321493
+#define FILE_ID_INTERNAL_HEADER_013287632486321493
+
+#include <string>
+
+#ifdef FFS_WIN
+#include "win.h" //includes "windows.h"
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#endif
+
+namespace
+{
+template <class T> inline
+std::string numberToBytes(T number)
+{
+ const char* rawBegin = reinterpret_cast<const char*>(&number);
+ return std::string(rawBegin, rawBegin + sizeof(number));
+}
+
+#ifdef FFS_WIN
+inline
+std::string extractFileID(const BY_HANDLE_FILE_INFORMATION& fileInfo)
+{
+ return numberToBytes(fileInfo.dwVolumeSerialNumber) +
+ numberToBytes(fileInfo.nFileIndexHigh) +
+ numberToBytes(fileInfo.nFileIndexLow);
+}
+#elif defined FFS_LINUX
+inline
+std::string extractFileID(const struct stat& fileInfo)
+{
+ return numberToBytes(fileInfo.st_dev) +
+ numberToBytes(fileInfo.st_ino);
+}
+#endif
+
+}
+
+
+#endif //FILE_ID_INTERNAL_HEADER_013287632486321493
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
new file mode 100644
index 00000000..6b3a5214
--- /dev/null
+++ b/zen/file_io.cpp
@@ -0,0 +1,217 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "file_io.h"
+
+#ifdef FFS_WIN
+#include "long_path_prefix.h"
+#elif defined FFS_LINUX
+#include <cerrno>
+#endif
+
+using namespace zen;
+
+
+FileInput::FileInput(FileHandle handle, const Zstring& filename) :
+ eofReached(false),
+ fileHandle(handle),
+ filename_(filename) {}
+
+
+FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExisting
+ eofReached(false),
+ filename_(filename)
+{
+#ifdef FFS_WIN
+ fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read open files that are shared by other applications
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_SEQUENTIAL_SCAN,
+ /* possible values: (Reference http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx#caching_behavior)
+ FILE_FLAG_NO_BUFFERING
+ FILE_FLAG_RANDOM_ACCESS
+ FILE_FLAG_SEQUENTIAL_SCAN
+
+ tests on Win7 x64 show that FILE_FLAG_SEQUENTIAL_SCAN provides best performance for binary comparison in all cases:
+ - comparing different physical disks (DVD <-> HDD and HDD <-> HDD)
+ - even on same physical disk! (HDD <-> HDD)
+ - independent from client buffer size!
+
+ tests on XP show that FILE_FLAG_SEQUENTIAL_SCAN provides best performance for binary comparison when
+ - comparing different physical disks (DVD <-> HDD)
+
+ while FILE_FLAG_RANDOM_ACCESS offers best performance for
+ - same physical disk (HDD <-> HDD)
+
+ Problem: bad XP implementation of prefetch makes flag FILE_FLAG_SEQUENTIAL_SCAN effectively load two files at once from one drive
+ swapping every 64 kB (or similar). File access times explode!
+ => For XP it is critical to use FILE_FLAG_RANDOM_ACCESS (to disable prefetch) if reading two files on same disk and
+ FILE_FLAG_SEQUENTIAL_SCAN when reading from different disk (e.g. massive performance improvement compared to random access for DVD <-> HDD!)
+ => there is no compromise that satisfies all cases! (on XP)
+
+ for FFS most comparisons are probably between different disks => let's use FILE_FLAG_SEQUENTIAL_SCAN
+ */
+ NULL);
+ if (fileHandle == INVALID_HANDLE_VALUE)
+ {
+ const DWORD lastError = ::GetLastError();
+
+ std::wstring errorMessage = _("Error opening file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError);
+
+ if (lastError == ERROR_FILE_NOT_FOUND ||
+ lastError == ERROR_PATH_NOT_FOUND ||
+ lastError == ERROR_BAD_NETPATH ||
+ lastError == ERROR_NETNAME_DELETED)
+ throw ErrorNotExisting(errorMessage);
+
+ throw FileError(errorMessage);
+ }
+
+#elif defined FFS_LINUX
+ fileHandle = ::fopen(filename.c_str(), "r,type=record,noseek"); //utilize UTF-8 filename
+ if (fileHandle == NULL)
+ {
+ const int lastError = errno;
+
+ std::wstring errorMessage = _("Error opening file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError);
+
+ if (lastError == ENOENT)
+ throw ErrorNotExisting(errorMessage);
+
+ throw FileError(errorMessage);
+ }
+#endif
+}
+
+
+FileInput::~FileInput()
+{
+#ifdef FFS_WIN
+ ::CloseHandle(fileHandle);
+#elif defined FFS_LINUX
+ ::fclose(fileHandle); //NEVER allow passing NULL to fclose! -> crash!; fileHandle != NULL in this context!
+#endif
+}
+
+
+size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw (FileError)
+{
+#ifdef FFS_WIN
+ DWORD bytesRead = 0;
+ if (!::ReadFile(fileHandle, //__in HANDLE hFile,
+ buffer, //__out LPVOID lpBuffer,
+ static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead,
+ &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead,
+ NULL)) //__inout_opt LPOVERLAPPED lpOverlapped
+#elif defined FFS_LINUX
+ const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle);
+ if (::ferror(fileHandle) != 0)
+#endif
+ throw FileError(_("Error reading file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted() + " (r)");
+
+#ifdef FFS_WIN
+ if (bytesRead < bytesToRead) //falsify only!
+#elif defined FFS_LINUX
+ if (::feof(fileHandle) != 0)
+#endif
+ eofReached = true;
+
+ if (bytesRead > bytesToRead)
+ throw FileError(_("Error reading file:") + "\n\"" + filename_ + "\"" + "\n\n" + "buffer overflow");
+
+ return bytesRead;
+}
+
+
+bool FileInput::eof() //end of file reached
+{
+ return eofReached;
+}
+
+
+FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : fileHandle(handle), filename_(filename) {}
+
+
+FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
+ filename_(filename)
+{
+#ifdef FFS_WIN
+ fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(),
+ GENERIC_READ | GENERIC_WRITE,
+ /* http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx
+ quote: When an application creates a file across a network, it is better
+ to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone.
+ The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data.
+ This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */
+ FILE_SHARE_READ | FILE_SHARE_DELETE, //note: FILE_SHARE_DELETE is required to rename file while handle is open!
+ 0,
+ access == ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
+ NULL);
+ if (fileHandle == INVALID_HANDLE_VALUE)
+ {
+ const DWORD lastError = ::GetLastError();
+ std::wstring errorMessage = _("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError);
+
+ if (lastError == ERROR_FILE_EXISTS)
+ throw ErrorTargetExisting(errorMessage);
+
+ if (lastError == ERROR_PATH_NOT_FOUND)
+ throw ErrorTargetPathMissing(errorMessage);
+
+ throw FileError(errorMessage);
+ }
+
+#elif defined FFS_LINUX
+ fileHandle = ::fopen(filename.c_str(),
+ //GNU extension: https://www.securecoding.cert.org/confluence/display/cplusplus/FIO03-CPP.+Do+not+make+assumptions+about+fopen()+and+file+creation
+ access == ACC_OVERWRITE ? "w,type=record,noseek" : "wx,type=record,noseek");
+ if (fileHandle == NULL)
+ {
+ const int lastError = errno;
+ std::wstring errorMessage = _("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError);
+ if (lastError == EEXIST)
+ throw ErrorTargetExisting(errorMessage);
+
+ if (lastError == ENOENT)
+ throw ErrorTargetPathMissing(errorMessage);
+
+ throw FileError(errorMessage);
+ }
+#endif
+}
+
+
+FileOutput::~FileOutput()
+{
+#ifdef FFS_WIN
+ ::CloseHandle(fileHandle);
+#elif defined FFS_LINUX
+ ::fclose(fileHandle); //NEVER allow passing NULL to fclose! -> crash!
+#endif
+}
+
+
+void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError
+{
+#ifdef FFS_WIN
+ DWORD bytesWritten = 0;
+ if (!::WriteFile(fileHandle, //__in HANDLE hFile,
+ buffer, //__out LPVOID lpBuffer,
+ static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite,
+ &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten,
+ NULL)) //__inout_opt LPOVERLAPPED lpOverlapped
+#elif defined FFS_LINUX
+ const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle);
+ if (::ferror(fileHandle) != 0)
+#endif
+ throw FileError(_("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message!
+
+ if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes!
+ throw FileError(_("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + "incomplete write");
+}
diff --git a/zen/file_io.h b/zen/file_io.h
new file mode 100644
index 00000000..26964ae8
--- /dev/null
+++ b/zen/file_io.h
@@ -0,0 +1,74 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FILEIO_H_INCLUDED
+#define FILEIO_H_INCLUDED
+
+#ifdef FFS_WIN
+#include "win.h" //includes "windows.h"
+
+#elif defined FFS_LINUX
+#include <cstdio>
+#endif
+
+#include "string.h"
+#include "file_error.h"
+
+namespace zen
+{
+//file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks)
+
+#ifdef FFS_WIN
+typedef HANDLE FileHandle;
+#elif defined FFS_LINUX
+typedef FILE* FileHandle;
+#endif
+
+class FileInput
+{
+public:
+ FileInput(const Zstring& filename); //throw FileError, ErrorNotExisting
+ FileInput(FileHandle handle, const Zstring& filename); //takes ownership!
+ ~FileInput();
+
+ size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read
+ bool eof(); //end of file reached
+
+private:
+ FileInput(const FileInput&);
+ FileInput& operator=(const FileInput&);
+
+ bool eofReached;
+ FileHandle fileHandle;
+ const Zstring filename_;
+};
+
+
+class FileOutput
+{
+public:
+ enum AccessFlag
+ {
+ ACC_OVERWRITE,
+ ACC_CREATE_NEW
+ };
+ FileOutput(const Zstring& filename, AccessFlag access); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
+ FileOutput(FileHandle handle, const Zstring& filename); //takes ownership!
+ ~FileOutput();
+
+ void write(const void* buffer, size_t bytesToWrite); //throw FileError
+
+private:
+ FileOutput(const FileOutput&);
+ FileOutput& operator=(const FileOutput&);
+
+ FileHandle fileHandle;
+ const Zstring filename_;
+};
+
+}
+
+#endif // FILEIO_H_INCLUDED
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
new file mode 100644
index 00000000..44b9d184
--- /dev/null
+++ b/zen/file_traverser.cpp
@@ -0,0 +1,611 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "file_traverser.h"
+#include "last_error.h"
+#include "assert_static.h"
+#include "symlink_target.h"
+
+#ifdef FFS_WIN
+#include "win.h" //includes "windows.h"
+#include "long_path_prefix.h"
+#include "dst_hack.h"
+#include "file_update_handle.h"
+//#include "dll_loader.h"
+//#include "FindFilePlus/find_file_plus.h"
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#include <dirent.h>
+#include <cerrno>
+#endif
+
+using namespace zen;
+
+
+namespace
+{
+//implement "retry" in a generic way:
+
+//returns "true" if "cmd" was invoked successfully, "false" if error occured and was ignored
+template <class Command> inline //function object with bool operator()(std::wstring& errorMsg), returns "true" on success, "false" on error and writes "errorMsg" in this case
+bool tryReportingError(Command cmd, zen::TraverseCallback& callback)
+{
+ for (;;)
+ {
+ std::wstring errorMsg;
+ if (cmd(errorMsg))
+ return true;
+
+ switch (callback.onError(errorMsg))
+ {
+ case TraverseCallback::TRAV_ERROR_RETRY:
+ break;
+ case TraverseCallback::TRAV_ERROR_IGNORE:
+ return false;
+ default:
+ assert(false);
+ break;
+ }
+ }
+}
+
+
+#ifdef FFS_WIN
+inline
+bool setWin32FileInformationFromSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output)
+{
+ //open handle to target of symbolic link
+ HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return false;
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hFile));
+
+ BY_HANDLE_FILE_INFORMATION fileInfoByHandle = {};
+ if (!::GetFileInformationByHandle(hFile, &fileInfoByHandle))
+ return false;
+
+ //write output
+ output.lastWriteTimeRaw = toTimeT(fileInfoByHandle.ftLastWriteTime);
+ output.fileSize = zen::UInt64(fileInfoByHandle.nFileSizeLow, fileInfoByHandle.nFileSizeHigh);
+ return true;
+}
+
+/*
+warn_static("finish")
+ DllFun<findplus::OpenDirFunc> openDir (findplus::getDllName(), findplus::openDirFuncName);
+ DllFun<findplus::ReadDirFunc> readDir (findplus::getDllName(), findplus::readDirFuncName);
+ DllFun<findplus::CloseDirFunc> closeDir(findplus::getDllName(), findplus::closeDirFuncName);
+ -> thread safety!
+*/
+#endif
+}
+
+
+class DirTraverser
+{
+public:
+ DirTraverser(const Zstring& baseDirectory, bool followSymlinks, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) :
+#ifdef FFS_WIN
+ isFatFileSystem(dst::isFatDrive(baseDirectory)),
+#endif
+ followSymlinks_(followSymlinks)
+ {
+#ifdef FFS_WIN
+ //format base directory name
+ const Zstring& directoryFormatted = baseDirectory;
+
+#elif defined FFS_LINUX
+ const Zstring directoryFormatted = //remove trailing slash
+ baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/'
+ beforeLast(baseDirectory, FILE_NAME_SEPARATOR) :
+ baseDirectory;
+#endif
+
+ //traverse directories
+ traverse(directoryFormatted, sink, 0);
+
+ //apply daylight saving time hack AFTER file traversing, to give separate feedback to user
+#ifdef FFS_WIN
+ if (isFatFileSystem && dstCallback)
+ applyDstHack(*dstCallback);
+#endif
+ }
+
+private:
+ DirTraverser(const DirTraverser&);
+ DirTraverser& operator=(const DirTraverser&);
+
+ void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level)
+ {
+ tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ if (level == 100) //notify endless recursion
+ {
+ errorMsg = _("Endless loop when traversing directory:") + "\n\"" + directory + "\"";
+ return false;
+ }
+ return true;
+ }, sink);
+
+#ifdef FFS_WIN
+ /*
+ //ensure directoryPf ends with backslash
+ const Zstring& directoryPf = directory.EndsWith(FILE_NAME_SEPARATOR) ?
+ directory :
+ directory + FILE_NAME_SEPARATOR;
+
+ FindHandle searchHandle = NULL;
+ tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ searchHandle = this->openDir(applyLongPathPrefix(directoryPf).c_str());
+ //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH
+
+ if (searchHandle == NULL)
+ {
+ //const DWORD lastError = ::GetLastError();
+ //if (lastError == ERROR_FILE_NOT_FOUND) -> actually NOT okay, even for an empty directory this should not occur (., ..)
+ //return true; //fine: empty directory
+
+ //else: we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink);
+
+ if (searchHandle == NULL)
+ return; //empty dir or ignore error
+ ZEN_ON_BLOCK_EXIT(this->closeDir(searchHandle));
+
+ FileInformation fileInfo = {};
+ for (;;)
+ {
+ bool moreData = false;
+ tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ if (!this->readDir(searchHandle, fileInfo))
+ {
+ if (::GetLastError() == ERROR_NO_MORE_FILES) //this is fine
+ return true;
+
+ //else we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+
+ moreData = true;
+ return true;
+ }, sink);
+ if (!moreData) //no more items or ignore error
+ return;
+
+
+ //don't return "." and ".."
+ const Zchar* const shortName = fileInfo.shortName;
+ if (shortName[0] == L'.' &&
+ (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0')))
+ continue;
+
+ const Zstring& fullName = directoryPf + shortName;
+
+ const bool isSymbolicLink = (fileInfo.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+
+ if (isSymbolicLink && !followSymlinks_) //evaluate symlink directly
+ {
+ TraverseCallback::SymlinkInfo details;
+ try
+ {
+ details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError
+ }
+ catch (FileError& e)
+ {
+ (void)e;
+ #ifndef NDEBUG //show broken symlink / access errors in debug build!
+ sink.onError(e.msg());
+ #endif
+ }
+
+ details.lastWriteTimeRaw = toTimeT(fileInfo.lastWriteTime);
+ details.dirLink = (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows
+ sink.onSymlink(shortName, fullName, details);
+ }
+ else if (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!)
+ {
+ const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName);
+ switch (rv.returnCode)
+ {
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE:
+ break;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE:
+ traverse<followSymlinks_>(fullName, *rv.subDirCb, level + 1);
+ break;
+ }
+ }
+ else //a file or symlink that is followed...
+ {
+ TraverseCallback::FileInfo details;
+
+ if (isSymbolicLink) //dereference symlinks!
+ {
+ if (!setWin32FileInformationFromSymlink(fullName, details))
+ {
+ //broken symlink...
+ details.lastWriteTimeRaw = 0; //we are not interested in the modification time of the link
+ details.fileSize = 0U;
+ }
+ }
+ else
+ {
+ //####################################### DST hack ###########################################
+ if (isFatFileSystem)
+ {
+ const dst::RawTime rawTime(fileInfo.creationTime, fileInfo.lastWriteTime);
+
+ if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
+ fileInfo.lastWriteTime = dst::fatDecodeUtcTime(rawTime); //return real UTC time; throw (std::runtime_error)
+ else
+ markForDstHack.push_back(std::make_pair(fullName, fileInfo.lastWriteTime));
+ }
+ //####################################### DST hack ###########################################
+
+ details.lastWriteTimeRaw = toTimeT(fileInfo.lastWriteTime);
+ details.fileSize = fileInfo.fileSize.QuadPart;
+ }
+
+ sink.onFile(shortName, fullName, details);
+ }
+ }
+ */
+
+
+
+ //ensure directoryPf ends with backslash
+ const Zstring& directoryPf = endsWith(directory, FILE_NAME_SEPARATOR) ?
+ directory :
+ directory + FILE_NAME_SEPARATOR;
+ WIN32_FIND_DATA fileInfo = {};
+
+ HANDLE searchHandle = INVALID_HANDLE_VALUE;
+ tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &fileInfo);
+ //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH
+
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ {
+ //const DWORD lastError = ::GetLastError();
+ //if (lastError == ERROR_FILE_NOT_FOUND) -> actually NOT okay, even for an empty directory this should not occur (., ..)
+ //return true; //fine: empty directory
+
+ //else: we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink);
+
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ return; //empty dir or ignore error
+ ZEN_ON_BLOCK_EXIT(::FindClose(searchHandle));
+
+ do
+ {
+ //don't return "." and ".."
+ const Zchar* const shortName = fileInfo.cFileName;
+ if (shortName[0] == L'.' &&
+ (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0')))
+ continue;
+
+ const Zstring& fullName = directoryPf + shortName;
+
+ const bool isSymbolicLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+
+ if (isSymbolicLink && !followSymlinks_) //evaluate symlink directly
+ {
+ TraverseCallback::SymlinkInfo details;
+ try
+ {
+ details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError
+ }
+ catch (FileError& e)
+ {
+ (void)e;
+#ifndef NDEBUG //show broken symlink / access errors in debug build!
+ sink.onError(e.msg());
+#endif
+ }
+
+ details.lastWriteTimeRaw = toTimeT(fileInfo.ftLastWriteTime);
+ details.dirLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows
+ sink.onSymlink(shortName, fullName, details);
+ }
+ else if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!)
+ {
+ const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName);
+ switch (rv.returnCode)
+ {
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE:
+ break;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE:
+ traverse(fullName, *rv.subDirCb, level + 1);
+ break;
+ }
+ }
+ else //a file or symlink that is followed...
+ {
+ TraverseCallback::FileInfo details;
+
+ if (isSymbolicLink) //dereference symlinks!
+ {
+ if (!setWin32FileInformationFromSymlink(fullName, details))
+ {
+ //broken symlink...
+ details.lastWriteTimeRaw = 0; //we are not interested in the modification time of the link
+ details.fileSize = 0U;
+ }
+ }
+ else
+ {
+ //####################################### DST hack ###########################################
+ if (isFatFileSystem)
+ {
+ const dst::RawTime rawTime(fileInfo.ftCreationTime, fileInfo.ftLastWriteTime);
+
+ if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
+ fileInfo.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return real UTC time; throw (std::runtime_error)
+ else
+ markForDstHack.push_back(std::make_pair(fullName, fileInfo.ftLastWriteTime));
+ }
+ //####################################### DST hack ###########################################
+ details.lastWriteTimeRaw = toTimeT(fileInfo.ftLastWriteTime);
+ details.fileSize = zen::UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
+ }
+
+ sink.onFile(shortName, fullName, details);
+ }
+ }
+ while ([&]() -> bool
+ {
+ bool moreData = false;
+
+ tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ if (!::FindNextFile(searchHandle, // handle to search
+ &fileInfo)) // pointer to structure for data on found file
+ {
+ if (::GetLastError() == ERROR_NO_MORE_FILES) //this is fine
+ return true;
+
+ //else we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+
+ moreData = true;
+ return true;
+ }, sink);
+
+ return moreData;
+ }());
+
+#elif defined FFS_LINUX
+ DIR* dirObj = NULL;
+ if (!tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/"
+ if (dirObj == NULL)
+ {
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink))
+ return;
+ ZEN_ON_BLOCK_EXIT(::closedir(dirObj)); //never close NULL handles! -> crash
+
+ while (true)
+ {
+ struct dirent* dirEntry = NULL;
+ tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ errno = 0; //set errno to 0 as unfortunately this isn't done when readdir() returns NULL because it can't find any files
+ dirEntry = ::readdir(dirObj);
+ if (dirEntry == NULL)
+ {
+ if (errno == 0)
+ return true; //everything okay, not more items
+
+ //else: we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink);
+ if (dirEntry == NULL) //no more items or ignore error
+ return;
+
+
+ //don't return "." and ".."
+ const char* const shortName = dirEntry->d_name;
+ if (shortName[0] == '.' &&
+ (shortName[1] == '\0' || (shortName[1] == '.' && shortName[2] == '\0')))
+ continue;
+
+ const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? //e.g. "/"
+ directory + shortName :
+ directory + FILE_NAME_SEPARATOR + shortName;
+
+ struct stat fileInfo = {};
+
+ if (!tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ if (::lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks
+ {
+ errorMsg = _("Error reading file attributes:") + "\n\"" + fullName + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink))
+ continue; //ignore error: skip file
+
+ const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode);
+
+ if (isSymbolicLink)
+ {
+ if (followSymlinks_) //on Linux Symlinks need to be followed to evaluate whether they point to a file or directory
+ {
+ if (::stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks
+ {
+ sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); //report broken symlink as file!
+ continue;
+ }
+ }
+ else //evaluate symlink directly
+ {
+ TraverseCallback::SymlinkInfo details;
+ try
+ {
+ details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError
+ }
+ catch (FileError& e)
+ {
+#ifndef NDEBUG //show broken symlink / access errors in debug build!
+ sink.onError(e.msg());
+#endif
+ }
+
+ details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
+ details.dirLink = ::stat(fullName.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode); //S_ISDIR and S_ISLNK are mutually exclusive on Linux => need to follow link
+ sink.onSymlink(shortName, fullName, details);
+ continue;
+ }
+ }
+
+ //fileInfo contains dereferenced data in any case from here on
+
+ if (S_ISDIR(fileInfo.st_mode)) //a directory... cannot be a symlink on Linux in this case
+ {
+ const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName);
+ switch (rv.returnCode)
+ {
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE:
+ break;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE:
+ traverse(fullName, *rv.subDirCb, level + 1);
+ break;
+ }
+ }
+ else //a file... (or symlink; pathological!)
+ {
+ TraverseCallback::FileInfo details;
+ details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
+ details.fileSize = zen::UInt64(fileInfo.st_size);
+
+ sink.onFile(shortName, fullName, details);
+ }
+ }
+#endif
+ }
+
+
+#ifdef FFS_WIN
+ //####################################### DST hack ###########################################
+ void applyDstHack(zen::DstHackCallback& dstCallback)
+ {
+ int failedAttempts = 0;
+ int filesToValidate = 50; //don't let data verification become a performance issue
+
+ for (FilenameTimeList::const_iterator i = markForDstHack.begin(); i != markForDstHack.end(); ++i)
+ {
+ if (failedAttempts >= 10) //some cloud storages don't support changing creation/modification times => don't waste (a lot of) time trying to
+ return;
+
+ dstCallback.requestUiRefresh(i->first);
+
+ const dst::RawTime encodedTime = dst::fatEncodeUtcTime(i->second); //throw (std::runtime_error)
+ {
+ //may need to remove the readonly-attribute (e.g. FAT usb drives)
+ FileUpdateHandle updateHandle(i->first, [ = ]()
+ {
+ return ::CreateFile(zen::applyLongPathPrefix(i->first).c_str(),
+ GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ });
+ if (updateHandle.get() == INVALID_HANDLE_VALUE)
+ {
+ ++failedAttempts;
+ assert(false); //don't throw exceptions due to dst hack here
+ continue;
+ }
+
+ if (!::SetFileTime(updateHandle.get(),
+ &encodedTime.createTimeRaw,
+ NULL,
+ &encodedTime.writeTimeRaw))
+ {
+ ++failedAttempts;
+ assert(false); //don't throw exceptions due to dst hack here
+ continue;
+ }
+ }
+
+ //even at this point it's not sure whether data was written correctly, again cloud storages tend to lie about success status
+ if (filesToValidate > 0)
+ {
+ --filesToValidate; //don't change during check!
+
+ //dst hack: verify data written; attention: this check may fail for "sync.ffs_lock"
+ WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {};
+ ::GetFileAttributesEx(zen::applyLongPathPrefix(i->first).c_str(), //__in LPCTSTR lpFileName,
+ GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ &debugeAttr); //__out LPVOID lpFileInformation
+
+ if (::CompareFileTime(&debugeAttr.ftCreationTime, &encodedTime.createTimeRaw) != 0 ||
+ ::CompareFileTime(&debugeAttr.ftLastWriteTime, &encodedTime.writeTimeRaw) != 0)
+ {
+ ++failedAttempts;
+ assert(false); //don't throw exceptions due to dst hack here
+ continue;
+ }
+ }
+ }
+ }
+
+ const bool isFatFileSystem;
+ typedef std::vector<std::pair<Zstring, FILETIME> > FilenameTimeList;
+ FilenameTimeList markForDstHack;
+ //####################################### DST hack ###########################################
+#endif
+ const bool followSymlinks_;
+};
+
+
+void zen::traverseFolder(const Zstring& directory, bool followSymlinks, TraverseCallback& sink, DstHackCallback* dstCallback)
+{
+#ifdef FFS_WIN
+ try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail)
+ {
+ zen::Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError
+ }
+ catch (...) {} //don't cause issues in user mode
+#endif
+
+ DirTraverser(directory, followSymlinks, sink, dstCallback);
+}
diff --git a/zen/file_traverser.h b/zen/file_traverser.h
new file mode 100644
index 00000000..4dbed9f9
--- /dev/null
+++ b/zen/file_traverser.h
@@ -0,0 +1,86 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FILETRAVERSER_H_INCLUDED
+#define FILETRAVERSER_H_INCLUDED
+
+#include "zstring.h"
+#include "type_tools.h"
+#include "int64.h"
+
+//advanced file traverser returning metadata and hierarchical information on files and directories
+
+namespace zen
+{
+class TraverseCallback
+{
+public:
+ virtual ~TraverseCallback() {}
+
+ struct FileInfo
+ {
+ UInt64 fileSize; //unit: bytes!
+ Int64 lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC
+ };
+
+ struct SymlinkInfo
+ {
+ Int64 lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC
+ Zstring targetPath; //may be empty if something goes wrong
+ bool dirLink; //"true": point to dir; "false": point to file (or broken Link on Linux)
+ };
+
+ struct ReturnValDir
+ {
+ enum ReturnValueEnh
+ {
+ TRAVERSING_DIR_IGNORE,
+ TRAVERSING_DIR_CONTINUE
+ };
+
+ ReturnValDir(Int2Type<TRAVERSING_DIR_IGNORE>) : returnCode(TRAVERSING_DIR_IGNORE), subDirCb(NULL) {}
+ ReturnValDir(Int2Type<TRAVERSING_DIR_CONTINUE>, TraverseCallback& subDirCallback) : returnCode(TRAVERSING_DIR_CONTINUE), subDirCb(&subDirCallback) {}
+
+ ReturnValueEnh returnCode;
+ TraverseCallback* subDirCb;
+ };
+
+ enum HandleError
+ {
+ TRAV_ERROR_RETRY,
+ TRAV_ERROR_IGNORE
+ };
+
+ //overwrite these virtual methods
+ virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details) = 0;
+ virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) = 0;
+ virtual ReturnValDir onDir (const Zchar* shortName, const Zstring& fullName) = 0;
+ virtual HandleError onError (const std::wstring& errorText) = 0;
+};
+
+
+#ifdef FFS_WIN
+struct DstHackCallback
+{
+ virtual ~DstHackCallback() {}
+ virtual void requestUiRefresh(const Zstring& filename) = 0; //applying DST hack imposes significant one-time performance drawback => callback to inform user
+};
+#else
+struct DstHackCallback; //DST hack not required on Linux
+#endif
+
+//custom traverser with detail information about files
+//directory may end with PATH_SEPARATOR
+void traverseFolder(const Zstring& directory, //throw();
+ bool followSymlinks,
+ TraverseCallback& sink,
+ DstHackCallback* dstCallback = NULL); //apply DST hack if callback is supplied
+//followSymlinks:
+//"true": Symlinks dereferenced and reported via onFile() and onDir() => onSymlink not used!
+//"false": Symlinks directly reported via onSymlink(), directory symlinks are not followed
+}
+
+#endif // FILETRAVERSER_H_INCLUDED
diff --git a/zen/file_update_handle.h b/zen/file_update_handle.h
new file mode 100644
index 00000000..aa9edebd
--- /dev/null
+++ b/zen/file_update_handle.h
@@ -0,0 +1,67 @@
+#ifndef FILE_UPDATE_HANDLE_H_INCLUDED
+#define FILE_UPDATE_HANDLE_H_INCLUDED
+
+#include "win.h" //includes "windows.h"
+#include "long_path_prefix.h"
+
+namespace
+{
+//manage file handle to update existing files (temporarily resetting read-only if necessary)
+//CreateFileCmd: lambda directly returning non-owned file handle from ::CreateFile()
+class FileUpdateHandle
+{
+public:
+ template <class CreateFileCmd>
+ FileUpdateHandle(const Zstring& filename, CreateFileCmd cmd) :
+ filenameFmt(zen::applyLongPathPrefix(filename)),
+ hFile(INVALID_HANDLE_VALUE),
+ attr(INVALID_FILE_ATTRIBUTES)
+ {
+ hFile = cmd();
+ if (hFile != INVALID_HANDLE_VALUE)
+ return;
+
+ const DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only
+ {
+ zen::ScopeGuard guardErrorCode = zen::makeGuard([&]() { ::SetLastError(lastError); }); //transactional behavior: ensure cleanup (e.g. network drop) -> cref [!]
+
+ //read-only file attribute may cause trouble: temporarily reset it
+ const DWORD tmpAttr = ::GetFileAttributes(filenameFmt.c_str());
+ if (tmpAttr != INVALID_FILE_ATTRIBUTES && (tmpAttr & FILE_ATTRIBUTE_READONLY))
+ {
+ if (::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL))
+ {
+ guardErrorCode.dismiss();
+ attr = tmpAttr; //"create" guard on read-only attribute
+
+ //now try again
+ hFile = cmd();
+ }
+ }
+ }
+ }
+
+ ~FileUpdateHandle()
+ {
+ if (hFile != INVALID_HANDLE_VALUE)
+ ::CloseHandle(hFile);
+
+ if (attr != INVALID_FILE_ATTRIBUTES)
+ ::SetFileAttributes(filenameFmt.c_str(), attr);
+ }
+
+ //may return INVALID_FILE_ATTRIBUTES, in which case ::GetLastError() may be called directly after FileUpdateHandle()
+ HANDLE get() const { return hFile; }
+
+private:
+ FileUpdateHandle(const FileUpdateHandle&);
+ FileUpdateHandle& operator=(const FileUpdateHandle&);
+
+ Zstring filenameFmt;
+ HANDLE hFile;
+ DWORD attr;
+};
+}
+
+#endif // FILE_UPDATE_HANDLE_H_INCLUDED
diff --git a/zen/fixed_list.h b/zen/fixed_list.h
new file mode 100644
index 00000000..c17ce0da
--- /dev/null
+++ b/zen/fixed_list.h
@@ -0,0 +1,142 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef PTR_WRAP_012384670856841394535
+#define PTR_WRAP_012384670856841394535
+
+#include <iterator>
+
+namespace zen
+{
+//std::list(C++11) compatible class supporting inplace element construction for non-copyable/movable types
+//may be replaced by C++11 std::list when available
+template <class T>
+class FixedList
+{
+ struct Node
+ {
+ Node() : next(NULL), val() {}
+ template <class A> Node(A&& a) : next(NULL), val(a) {}
+ template <class A, class B> Node(A&& a, B&& b) : next(NULL), val(a, b) {}
+ template <class A, class B, class C> Node(A&& a, B&& b, C&& c) : next(NULL), val(a, b, c) {}
+ template <class A, class B, class C, class D> Node(A&& a, B&& b, C&& c, D&& d) : next(NULL), val(a, b, c, d) {}
+ template <class A, class B, class C, class D, class E> Node(A&& a, B&& b, C&& c, D&& d, E&& e) : next(NULL), val(a, b, c, d, e) {}
+ template <class A, class B, class C, class D, class E, class F> Node(A&& a, B&& b, C&& c, D&& d, E&& e, F&& f) : next(NULL), val(a, b, c, d, e, f) {}
+
+ Node* next; //singly linked list is sufficient
+ T val;
+ };
+
+public:
+ FixedList() :
+ first(NULL),
+ lastInsert(NULL),
+ sz(0) {}
+
+ ~FixedList() { clear(); }
+
+ template <class NodeT, class U>
+ class ListIterator : public std::iterator<std::forward_iterator_tag, U>
+ {
+ public:
+ ListIterator(NodeT* it = NULL) : iter(it) {}
+ ListIterator& operator++() { iter = iter->next; return *this; }
+ inline friend bool operator==(const ListIterator& lhs, const ListIterator& rhs) { return lhs.iter == rhs.iter; }
+ inline friend bool operator!=(const ListIterator& lhs, const ListIterator& rhs) { return !(lhs == rhs); }
+ U& operator* () { return iter->val; }
+ U* operator->() { return &iter->val; }
+ private:
+ NodeT* iter;
+ };
+
+ typedef T value_type;
+ typedef ListIterator<Node, T> iterator;
+ typedef ListIterator<const Node, const T> const_iterator;
+ typedef T& reference;
+ typedef const T& const_reference;
+
+ iterator begin() { return first; }
+ iterator end() { return iterator(); }
+
+ const_iterator begin() const { return first; }
+ const_iterator end () const { return const_iterator(); }
+
+ reference front() { return first->val; }
+ const_reference front() const { return first->val; }
+
+ reference& back() { return lastInsert->val; }
+ const_reference& back() const { return lastInsert->val; }
+
+ void emplace_back() { pushNode(new Node); }
+ template <class A> void emplace_back(A&& a) { pushNode(new Node(std::forward<A>(a))); }
+ template <class A, class B> void emplace_back(A&& a, B&& b) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b))); }
+ template <class A, class B, class C> void emplace_back(A&& a, B&& b, C&& c) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c))); }
+ template <class A, class B, class C, class D> void emplace_back(A&& a, B&& b, C&& c, D&& d) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c), std::forward<D>(d))); }
+ template <class A, class B, class C, class D, class E> void emplace_back(A&& a, B&& b, C&& c, D&& d, E&& e) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c), std::forward<D>(d), std::forward<E>(e))); }
+ template <class A, class B, class C, class D, class E, class F> void emplace_back(A&& a, B&& b, C&& c, D&& d, E&& e, F&& f) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c), std::forward<D>(d), std::forward<E>(e), std::forward<F>(f))); }
+
+ template <class Predicate>
+ void remove_if(Predicate pred)
+ {
+ Node* prev = NULL;
+ for (auto ptr = first; ptr;)
+ if (pred(ptr->val))
+ {
+ Node* tmp = ptr->next;
+ deleteNode(ptr);
+ if (prev)
+ prev->next = ptr = tmp;
+ else
+ first = ptr = tmp;
+ if (tmp == NULL)
+ lastInsert = prev;
+ }
+ else
+ {
+ prev = ptr;
+ ptr = ptr->next;
+ }
+ }
+
+ void clear() { remove_if([](T&) { return true; }); }
+ bool empty() const { return first == NULL; }
+ size_t size() const { return sz; }
+
+private:
+ FixedList(const FixedList&);
+ FixedList& operator=(const FixedList&);
+
+ void pushNode(Node* newNode)
+ {
+ ++sz;
+ if (lastInsert == NULL)
+ {
+ assert(first == NULL);
+ first = lastInsert = newNode;
+ }
+ else
+ {
+ assert(lastInsert->next == NULL);
+ lastInsert->next = newNode;
+ lastInsert = newNode;
+ }
+ }
+
+ void deleteNode(Node* oldNode)
+ {
+ assert(sz > 0);
+ --sz;
+ delete oldNode;
+ }
+
+ Node* first;
+ Node* lastInsert; //point to last insertion; required by emplace_back()
+ size_t sz;
+};
+}
+
+
+#endif //PTR_WRAP_012384670856841394535
diff --git a/zen/guid.h b/zen/guid.h
new file mode 100644
index 00000000..218c80a5
--- /dev/null
+++ b/zen/guid.h
@@ -0,0 +1,36 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef GUID_H_INCLUDED
+#define GUID_H_INCLUDED
+
+#include <string>
+#include <boost/uuid/uuid.hpp>
+
+#ifdef __MINGW32__ //boost should start and clean up!
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#pragma GCC diagnostic ignored "-Wuninitialized"
+#endif
+
+#include <boost/uuid/uuid_generators.hpp>
+
+#ifdef __MINGW32__
+#pragma GCC diagnostic pop
+#endif
+
+
+namespace zen
+{
+inline
+std::string generateGUID() //creates a 16 byte GUID
+{
+ boost::uuids::uuid nativeRep = boost::uuids::random_generator()();
+ return std::string(nativeRep.begin(), nativeRep.end());
+}
+}
+
+#endif // GUID_H_INCLUDED
diff --git a/zen/i18n.h b/zen/i18n.h
new file mode 100644
index 00000000..de615cdb
--- /dev/null
+++ b/zen/i18n.h
@@ -0,0 +1,96 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef I18_N_HEADER_3843489325045
+#define I18_N_HEADER_3843489325045
+
+#include <string>
+#include <memory>
+
+//thin layer to enable localization - without platform/library dependencies!
+#ifndef WXINTL_NO_GETTEXT_MACRO
+#error WXINTL_NO_GETTEXT_MACRO must be defined to deactivate wxWidgets underscore macro
+#endif
+
+#define ZEN_CONCAT_SUB(X, Y) X ## Y
+#define _(s) zen::implementation::translate(ZEN_CONCAT_SUB(L, s))
+#define _P(s, p, n) zen::implementation::translate(ZEN_CONCAT_SUB(L, s), ZEN_CONCAT_SUB(L, p), n)
+
+
+namespace zen
+{
+//implement handler to enable program wide localizations
+struct TranslationHandler
+{
+ virtual ~TranslationHandler() {}
+
+ virtual std::wstring thousandsSeparator() = 0;
+ virtual std::wstring translate(const std::wstring& text) = 0; //simple translation
+ virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n) = 0;
+};
+
+void setTranslator(TranslationHandler* newHandler = NULL); //takes ownership
+TranslationHandler* getTranslator();
+
+inline
+std::wstring getThousandsSeparator() { return getTranslator() ? getTranslator()->thousandsSeparator() : L","; };
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//######################## implementation ##############################
+namespace implementation
+{
+inline
+std::wstring translate(const std::wstring& text)
+{
+ return getTranslator() ? getTranslator()->translate(text) : text;
+}
+
+//translate plural forms: "%x day" "%x days"
+//returns "%x day" if n == 1; "%x days" else for english language
+inline
+std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n)
+{
+ return getTranslator() ? getTranslator()->translate(singular, plural, n) : n == 1 ? singular : plural;
+}
+
+template <class T> inline
+std::wstring translate(const std::wstring& singular, const std::wstring& plural, T n)
+{
+ return translate(singular, plural, static_cast<int>(n % 1000000));
+}
+
+inline
+std::unique_ptr<TranslationHandler>& globalHandler()
+{
+ static std::unique_ptr<TranslationHandler> inst; //external linkage even in header!
+ return inst;
+}
+}
+
+inline
+void setTranslator(TranslationHandler* newHandler) { implementation::globalHandler().reset(newHandler); } //takes ownership
+
+inline
+TranslationHandler* getTranslator() { return implementation::globalHandler().get(); }
+}
+
+#endif //I18_N_HEADER_3843489325045
diff --git a/zen/int64.h b/zen/int64.h
new file mode 100644
index 00000000..f7a7e800
--- /dev/null
+++ b/zen/int64.h
@@ -0,0 +1,258 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FFS_LARGE_64_BIT_INTEGER_H_INCLUDED
+#define FFS_LARGE_64_BIT_INTEGER_H_INCLUDED
+
+#include <cassert>
+#include <limits>
+#include <cstdint>
+#include <cstdint>
+#include <ostream>
+#include "assert_static.h"
+#include "type_tools.h"
+
+#ifdef FFS_WIN
+#include "win.h"
+#endif
+
+
+/*
+zen::Int64/zen::UInt64: wrapper classes around std::int64_t/std::uint64_t
+
+ - default initialization with 0
+ - debug runtime overflow/underflow checks
+ - safe and explicit semantics: no unsafe type conversions
+ - safe conversion to and from Windows 64-bit integers
+ - specializes std::numeric_limits
+ - support stream operator<< and operator>>
+*/
+
+namespace zen
+{
+template <class T, class U> inline void checkRange(U value)
+{
+ //caveat: std::numeric_limits<T>::min returns minimum positive(!) number for T = double, while behaving correctly for integer types... sigh
+ assert(double(std::numeric_limits<T>::lowest()) <= double(value) && //new with C++11!
+ double(std::numeric_limits<T>::max() ) >= double(value));
+
+ // assert(double(boost::numeric::bounds<T>::lowest ()) <= double(value) &&
+ // double(boost::numeric::bounds<T>::highest()) >= double(value));
+}
+
+class Int64
+{
+ struct DummyClass { operator int() { return 0; } };
+public:
+ //safe implicit conversions
+ Int64() : value(0) {}
+ Int64(const Int64& rhs) : value(rhs.value) {}
+ Int64(int rhs) : value(rhs) {} //ambiguity intentional for types other than these
+ Int64(long rhs) : value(rhs) {}
+ Int64(Select<IsSameType<std::int64_t, long>::result, DummyClass, std::int64_t>::Result rhs) :
+ value(rhs) {} //-> std::int64_t equals long int on x64 Linux! Still we want implicit behavior for all other systems!
+
+ //unsafe explicit but checked conversion from arbitrary integer type
+ template <class T> explicit Int64(T rhs) : value(rhs) { checkRange<std::int64_t>(rhs); }
+
+ Int64& operator=(const Int64& rhs) { value = rhs.value; return *this; }
+
+#ifdef FFS_WIN
+ Int64(DWORD low, LONG high)
+ {
+ assert_static(sizeof(low) + sizeof(high) == sizeof(value));
+
+ LARGE_INTEGER cvt = {};
+ cvt.LowPart = low;
+ cvt.HighPart = high;
+ value = cvt.QuadPart;
+ }
+ LONG getHi() const
+ {
+ LARGE_INTEGER cvt = {};
+ cvt.QuadPart = value;
+ return cvt.HighPart;
+ }
+ DWORD getLo() const
+ {
+ LARGE_INTEGER cvt = {};
+ cvt.QuadPart = value;
+ return cvt.LowPart;
+ }
+#endif
+
+ Int64& operator+=(const Int64& rhs) { checkRange<std::int64_t>(double(value) + rhs.value); value += rhs.value; return *this; }
+ Int64& operator-=(const Int64& rhs) { checkRange<std::int64_t>(double(value) - rhs.value); value -= rhs.value; return *this; }
+ Int64& operator*=(const Int64& rhs) { checkRange<std::int64_t>(double(value) * rhs.value); value *= rhs.value; return *this; }
+ Int64& operator/=(const Int64& rhs) { assert(rhs.value != 0); value /= rhs.value; return *this; }
+ Int64& operator%=(const Int64& rhs) { assert(rhs.value != 0); value %= rhs.value; return *this; }
+ Int64& operator&=(const Int64& rhs) { value &= rhs.value; return *this;}
+ Int64& operator|=(const Int64& rhs) { value |= rhs.value; return *this;}
+ Int64& operator<<=(int rhs) { assert(rhs < 0 || (value << rhs) >> rhs == value); value <<= rhs; return *this; }
+ Int64& operator>>=(int rhs) { assert(rhs > 0 || (value >> rhs) << rhs == value); value >>= rhs; return *this; }
+
+ inline friend bool operator==(const Int64& lhs, const Int64& rhs) { return lhs.value == rhs.value; }
+ inline friend bool operator!=(const Int64& lhs, const Int64& rhs) { return lhs.value != rhs.value; }
+ inline friend bool operator< (const Int64& lhs, const Int64& rhs) { return lhs.value < rhs.value; }
+ inline friend bool operator> (const Int64& lhs, const Int64& rhs) { return lhs.value > rhs.value; }
+ inline friend bool operator<=(const Int64& lhs, const Int64& rhs) { return lhs.value <= rhs.value; }
+ inline friend bool operator>=(const Int64& lhs, const Int64& rhs) { return lhs.value >= rhs.value; }
+
+ //checked conversion to arbitrary target integer type
+ template <class T> inline friend T to(Int64 number) { checkRange<T>(number.value); return static_cast<T>(number.value); }
+
+ template <class T> inline friend std::basic_istream<T>& operator>>(std::basic_istream<T>& lhs, Int64& rhs) { lhs >> rhs.value; return lhs; }
+ template <class T> inline friend std::basic_ostream<T>& operator<<(std::basic_ostream<T>& lhs, const Int64& rhs) { lhs << rhs.value; return lhs; }
+
+private:
+ std::int64_t value;
+};
+
+inline Int64 operator+(const Int64& lhs, const Int64& rhs) { return Int64(lhs) += rhs; }
+inline Int64 operator-(const Int64& lhs, const Int64& rhs) { return Int64(lhs) -= rhs; }
+inline Int64 operator*(const Int64& lhs, const Int64& rhs) { return Int64(lhs) *= rhs; }
+inline Int64 operator/(const Int64& lhs, const Int64& rhs) { return Int64(lhs) /= rhs; }
+inline Int64 operator%(const Int64& lhs, const Int64& rhs) { return Int64(lhs) %= rhs; }
+inline Int64 operator&(const Int64& lhs, const Int64& rhs) { return Int64(lhs) &= rhs; }
+inline Int64 operator|(const Int64& lhs, const Int64& rhs) { return Int64(lhs) |= rhs; }
+inline Int64 operator<<(const Int64& lhs, int rhs) { return Int64(lhs) <<= rhs; }
+inline Int64 operator>>(const Int64& lhs, int rhs) { return Int64(lhs) >>= rhs; }
+
+
+class UInt64
+{
+ struct DummyClass { operator size_t() { return 0U; } };
+public:
+ //safe implicit conversions
+ UInt64() : value(0) {}
+ UInt64(const UInt64& rhs) : value(rhs.value) {}
+ UInt64(unsigned int rhs) : value(rhs) {} //ambiguity intentional for types other than these
+ UInt64(unsigned long rhs) : value(rhs) {}
+ UInt64(Select<IsSameType<std::uint64_t, unsigned long>::result, DummyClass, std::uint64_t>::Result rhs) :
+ value(rhs) {} //-> std::uint64_t equals unsigned long int on x64 Linux! Still we want implicit behavior for all other systems!
+
+ //unsafe explicit but checked conversion from arbitrary integer type
+ template <class T> explicit UInt64(T rhs) : value(rhs) { checkRange<std::uint64_t>(rhs); }
+
+ UInt64& operator=(const UInt64& rhs) { value = rhs.value; return *this; }
+
+#ifdef FFS_WIN
+ UInt64(DWORD low, DWORD high)
+ {
+ assert_static(sizeof(low) + sizeof(high) == sizeof(value));
+
+ ULARGE_INTEGER cvt = {};
+ cvt.LowPart = low;
+ cvt.HighPart = high;
+ value = cvt.QuadPart;
+ }
+ DWORD getHi() const
+ {
+ ULARGE_INTEGER cvt = {};
+ cvt.QuadPart = value;
+ return cvt.HighPart;
+ }
+ DWORD getLo() const
+ {
+ ULARGE_INTEGER cvt = {};
+ cvt.QuadPart = value;
+ return cvt.LowPart;
+ }
+#endif
+
+ UInt64& operator+=(const UInt64& rhs) { checkRange<std::uint64_t>(double(value) + rhs.value); value += rhs.value; return *this; }
+ UInt64& operator-=(const UInt64& rhs) { checkRange<std::uint64_t>(double(value) - rhs.value); value -= rhs.value; return *this; }
+ UInt64& operator*=(const UInt64& rhs) { checkRange<std::uint64_t>(double(value) * rhs.value); value *= rhs.value; return *this; }
+ UInt64& operator/=(const UInt64& rhs) { assert(rhs.value != 0); value /= rhs.value; return *this; }
+ UInt64& operator%=(const UInt64& rhs) { assert(rhs.value != 0); value %= rhs.value; return *this; }
+ UInt64& operator&=(const UInt64& rhs) { value &= rhs.value; return *this;}
+ UInt64& operator|=(const UInt64& rhs) { value |= rhs.value; return *this;}
+ UInt64& operator<<=(int rhs) { assert(rhs < 0 || (value << rhs) >> rhs == value); value <<= rhs; return *this; }
+ UInt64& operator>>=(int rhs) { assert(rhs > 0 || (value >> rhs) << rhs == value); value >>= rhs; return *this; }
+
+ inline friend bool operator==(const UInt64& lhs, const UInt64& rhs) { return lhs.value == rhs.value; }
+ inline friend bool operator!=(const UInt64& lhs, const UInt64& rhs) { return lhs.value != rhs.value; }
+ inline friend bool operator< (const UInt64& lhs, const UInt64& rhs) { return lhs.value < rhs.value; }
+ inline friend bool operator> (const UInt64& lhs, const UInt64& rhs) { return lhs.value > rhs.value; }
+ inline friend bool operator<=(const UInt64& lhs, const UInt64& rhs) { return lhs.value <= rhs.value; }
+ inline friend bool operator>=(const UInt64& lhs, const UInt64& rhs) { return lhs.value >= rhs.value; }
+
+ //checked conversion to arbitrary target integer type
+ template <class T> inline friend T to(UInt64 number) { checkRange<T>(number.value); return static_cast<T>(number.value); }
+
+ template <class T> inline friend std::basic_istream<T>& operator>>(std::basic_istream<T>& lhs, UInt64& rhs) { lhs >> rhs.value; return lhs; }
+ template <class T> inline friend std::basic_ostream<T>& operator<<(std::basic_ostream<T>& lhs, const UInt64& rhs) { lhs << rhs.value; return lhs; }
+
+private:
+ std::uint64_t value;
+};
+
+inline UInt64 operator+(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) += rhs; }
+inline UInt64 operator-(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) -= rhs; }
+inline UInt64 operator*(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) *= rhs; }
+inline UInt64 operator/(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) /= rhs; }
+inline UInt64 operator%(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) %= rhs; }
+inline UInt64 operator&(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) &= rhs; }
+inline UInt64 operator|(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) |= rhs; }
+inline UInt64 operator<<(const UInt64& lhs, int rhs) { return UInt64(lhs) <<= rhs; }
+inline UInt64 operator>>(const UInt64& lhs, int rhs) { return UInt64(lhs) >>= rhs; }
+
+template <> inline UInt64 to(Int64 number) { checkRange<std::uint64_t>(number.value); return UInt64(number.value); }
+template <> inline Int64 to(UInt64 number) { checkRange<std:: int64_t>(number.value); return Int64(number.value); }
+
+
+#ifdef FFS_WIN
+//convert FILETIME (number of 100-nanosecond intervals since January 1, 1601 UTC)
+// to time_t (number of seconds since Jan. 1st 1970 UTC)
+//
+//FAT32 time is preserved exactly: FAT32 -> toTimeT -> tofiletime -> FAT32
+inline
+Int64 toTimeT(const FILETIME& ft)
+{
+ return to<Int64>(UInt64(ft.dwLowDateTime, ft.dwHighDateTime) / 10000000U) - Int64(3054539008UL, 2);
+ //timeshift between ansi C time and FILETIME in seconds == 11644473600s
+}
+
+inline
+FILETIME tofiletime(const Int64& utcTime)
+{
+ const UInt64 fileTimeLong = to<UInt64>(utcTime + Int64(3054539008UL, 2)) * 10000000U;
+ const FILETIME output = { fileTimeLong.getLo(), fileTimeLong.getHi() };
+ return output;
+}
+#endif
+}
+
+//specialize numeric limits
+namespace std
+{
+assert_static(std::numeric_limits<std:: int64_t>::is_specialized);
+assert_static(std::numeric_limits<std::uint64_t>::is_specialized);
+
+template <> class numeric_limits<zen::Int64> : public numeric_limits<std::int64_t>
+{
+public:
+ static zen::Int64 min() throw() { return numeric_limits<std::int64_t>::min(); }
+ static zen::Int64 max() throw() { return numeric_limits<std::int64_t>::max(); }
+};
+
+template <> class numeric_limits<zen::UInt64> : public numeric_limits<std::uint64_t>
+{
+public:
+ static zen::UInt64 min() throw() { return numeric_limits<std::uint64_t>::min(); }
+ static zen::UInt64 max() throw() { return numeric_limits<std::uint64_t>::max(); }
+};
+}
+
+/*
+//specialize zen type trait
+namespace zen -> we cannot mix signed/unsigned in general arithmetic operations -> we'll use the ostream-approach
+{
+template <> struct IsUnsignedInt<UInt64> { enum { result = true }; };
+template <> struct IsSignedInt <Int64> { enum { result = true }; };
+}
+*/
+#endif //FFS_LARGE_64_BIT_INTEGER_H_INCLUDED
diff --git a/zen/last_error.h b/zen/last_error.h
new file mode 100644
index 00000000..d2eaebfc
--- /dev/null
+++ b/zen/last_error.h
@@ -0,0 +1,110 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef SYSTEMFUNCTIONS_H_INCLUDED
+#define SYSTEMFUNCTIONS_H_INCLUDED
+
+#include <string>
+#include "utf8.h"
+#include "i18n.h"
+
+#ifdef FFS_WIN
+#include "win.h" //includes "windows.h"
+
+#elif defined FFS_LINUX
+#include <cstring>
+#include <cerrno>
+#endif
+
+
+
+namespace zen
+{
+//evaluate GetLastError()/errno and assemble specific error message
+#ifdef FFS_WIN
+std::wstring getLastErrorFormatted(DWORD lastError = 0);
+#elif defined FFS_LINUX
+std::wstring getLastErrorFormatted(int lastError = 0);
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//######################## Implementation ########################
+
+#ifdef FFS_WIN
+inline
+std::wstring getLastErrorFormatted(DWORD lastError) //try to get additional Windows error information
+{
+ //determine error code if none was specified
+ if (lastError == 0)
+ lastError = ::GetLastError();
+
+ std::wstring output = _("Windows Error Code %x:");
+ replace(output, L"%x", toString<std::wstring>(lastError));
+
+ LPWSTR buffer = NULL;
+ if (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK |
+ FORMAT_MESSAGE_IGNORE_INSERTS | //important: without this flag ::FormatMessage() will fail if message contains placeholders
+ FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, lastError, 0, reinterpret_cast<LPWSTR>(&buffer), 0, NULL) != 0)
+ {
+ if (buffer) //just to be sure
+ {
+ output += L" ";
+ output += buffer;
+ ::LocalFree(buffer);
+ }
+ }
+ ::SetLastError(lastError); //restore last error
+ return output;
+}
+
+#elif defined FFS_LINUX
+inline
+std::wstring getLastErrorFormatted(int lastError) //try to get additional Linux error information
+{
+ //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", toString<std::wstring>(lastError));
+
+ output += L" ";
+ output += utf8CvrtTo<std::wstring>(::strerror(lastError));
+
+ errno = lastError; //restore errno
+ return output;
+}
+#endif
+}
+
+
+#endif // SYSTEMFUNCTIONS_H_INCLUDED
diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h
new file mode 100644
index 00000000..1d9213d1
--- /dev/null
+++ b/zen/long_path_prefix.h
@@ -0,0 +1,98 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef LONGPATHPREFIX_H_INCLUDED
+#define LONGPATHPREFIX_H_INCLUDED
+
+#include "zstring.h"
+
+namespace zen
+{
+//handle filenames longer-equal 260 (== MAX_PATH) characters by applying \\?\-prefix (Reference: http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath)
+/*
+1. path must be absolute
+2. if path is smaller than MAX_PATH nothing is changed!
+3. path may already contain \\?\-prefix
+*/
+Zstring applyLongPathPrefix(const Zstring& path); //throw()
+Zstring applyLongPathPrefixCreateDir(const Zstring& path); //throw() -> special rule for ::CreateDirectoryEx(): MAX_PATH - 12(=^ 8.3 filename) is threshold
+
+Zstring removeLongPathPrefix(const Zstring& path); //throw()
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################## implementation ##################
+
+//there are two flavors of long path prefix: one for UNC paths, one for regular paths
+const Zstring LONG_PATH_PREFIX = L"\\\\?\\";
+const Zstring LONG_PATH_PREFIX_UNC = L"\\\\?\\UNC";
+
+template <size_t max_path> inline
+Zstring applyLongPathPrefixImpl(const Zstring& path)
+{
+ assert(!path.empty()); //nicely check almost all WinAPI accesses!
+
+ if (path.length() >= max_path && //maximum allowed path length without prefix is (MAX_PATH - 1)
+ !zen::startsWith(path, LONG_PATH_PREFIX))
+ {
+ if (zen::startsWith(path, L"\\\\")) //UNC-name, e.g. \\zenju-pc\Users
+ return LONG_PATH_PREFIX_UNC + zen::afterFirst(path, L'\\'); //convert to \\?\UNC\zenju-pc\Users
+ else
+ return LONG_PATH_PREFIX + path; //prepend \\?\ prefix
+ }
+ return path; //fallback
+}
+
+
+inline
+Zstring zen::applyLongPathPrefix(const Zstring& path)
+{
+ return applyLongPathPrefixImpl<MAX_PATH>(path);
+}
+
+
+inline
+Zstring zen::applyLongPathPrefixCreateDir(const Zstring& path) //throw()
+{
+ //special rule for ::CreateDirectoryEx(): MAX_PATH - 12(=^ 8.3 filename) is threshold
+ return applyLongPathPrefixImpl<MAX_PATH - 12> (path);
+}
+
+
+inline
+Zstring zen::removeLongPathPrefix(const Zstring& path) //throw()
+{
+ if (zen::startsWith(path, LONG_PATH_PREFIX))
+ {
+ if (zen::startsWith(path, LONG_PATH_PREFIX_UNC)) //UNC-name
+ return replaceCpy(path, LONG_PATH_PREFIX_UNC, Zstr("\\"), false);
+ else
+ return replaceCpy(path, LONG_PATH_PREFIX, Zstr(""), false);
+ }
+ return path; //fallback
+}
+
+#endif //LONGPATHPREFIX_H_INCLUDED
diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp
new file mode 100644
index 00000000..805c1c09
--- /dev/null
+++ b/zen/notify_removal.cpp
@@ -0,0 +1,236 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "notify_removal.h"
+#include <set>
+#include <algorithm>
+#include <dbt.h>
+#include "scope_guard.h"
+
+using namespace zen;
+
+/*
+//convert bitmask into "real" drive-letter
+Zstring getDriveFromMask(ULONG unitmask)
+{
+ for (int i = 0; i < 26; ++i)
+ {
+ if (unitmask & 0x1)
+ return Zstring() + static_cast<DefaultChar>(DefaultChar('A') + i) + DefaultStr(":\\");
+ unitmask >>= 1;
+ }
+ return Zstring();
+}
+*/
+
+namespace
+{
+bool messageProviderConstructed = false;
+}
+
+
+class MessageProvider //administrates a single dummy window to receive messages
+{
+public:
+ static MessageProvider& instance() //throw (FileError)
+ {
+ static MessageProvider inst;
+ messageProviderConstructed = true;
+ return inst;
+ }
+
+ class Listener
+ {
+ public:
+ virtual ~Listener() {}
+ virtual void onMessage(UINT message, WPARAM wParam, LPARAM lParam) = 0; //throw()!
+ };
+ void registerListener(Listener& l) { listener.insert(&l); }
+ void unregisterListener(Listener& l) { listener.erase(&l); } //don't unregister objects with static lifetime
+
+ HWND getWnd() const { return windowHandle; } //get handle in order to register additional notifications
+
+private:
+ MessageProvider();
+ ~MessageProvider();
+ MessageProvider(const MessageProvider&);
+ MessageProvider& operator=(const MessageProvider&);
+
+ static const wchar_t WINDOW_NAME[];
+
+ friend LRESULT CALLBACK topWndProc(HWND, UINT, WPARAM, LPARAM);
+ void processMessage(UINT message, WPARAM wParam, LPARAM lParam);
+
+ const HINSTANCE process;
+ HWND windowHandle;
+
+ std::set<Listener*> listener;
+};
+
+
+const wchar_t MessageProvider::WINDOW_NAME[] = L"E6AD5EB1-527B-4EEF-AC75-27883B233380"; //random name
+
+
+LRESULT CALLBACK topWndProc(
+ HWND hwnd, //handle to window
+ UINT uMsg, //message identifier
+ WPARAM wParam, //first message parameter
+ LPARAM lParam) //second message parameter
+{
+ if (messageProviderConstructed) //attention: this callback is triggered in the middle of singleton construction! It is a bad idea to to call back at this time!
+ try
+ {
+ MessageProvider::instance().processMessage(uMsg, wParam, lParam); //not supposed to throw
+ }
+ catch (...) {}
+
+ return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+
+MessageProvider::MessageProvider() :
+ process(::GetModuleHandle(NULL)), //get program's module handle
+ windowHandle(NULL)
+{
+ if (process == NULL)
+ throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + "\n\n" + getLastErrorFormatted() + " (GetModuleHandle)");
+
+ //register the main window class
+ WNDCLASS wc = {};
+ wc.lpfnWndProc = topWndProc;
+ wc.hInstance = process;
+ wc.lpszClassName = WINDOW_NAME;
+
+ if (::RegisterClass(&wc) == 0)
+ throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + "\n\n" + getLastErrorFormatted() + " (RegisterClass)");
+
+ zen::ScopeGuard guardClass = zen::makeGuard([&]() { ::UnregisterClass(WINDOW_NAME, process); });
+
+ //create dummy-window
+ windowHandle = ::CreateWindow(
+ WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word!
+ NULL, //LPCTSTR lpWindowName,
+ 0, //DWORD dwStyle,
+ 0, //int x,
+ 0, //int y,
+ 0, //int nWidth,
+ 0, //int nHeight,
+ 0, //note: we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)!
+ NULL, //HMENU hMenu,
+ process, //HINSTANCE hInstance,
+ NULL); //LPVOID lpParam
+ if (windowHandle == NULL)
+ throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + "\n\n" + getLastErrorFormatted() + " (CreateWindow)");
+
+ guardClass.dismiss();
+}
+
+
+MessageProvider::~MessageProvider()
+{
+ //clean-up in reverse order
+ ::DestroyWindow(windowHandle);
+ ::UnregisterClass(WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word!
+ process); //HINSTANCE hInstance
+}
+
+
+void MessageProvider::processMessage(UINT message, WPARAM wParam, LPARAM lParam)
+{
+ std::for_each(listener.begin(), listener.end(),
+ [&](Listener* ls) { ls->onMessage(message, wParam, lParam); });
+}
+//####################################################################################################
+
+
+class NotifyRequestDeviceRemoval::Pimpl : private MessageProvider::Listener
+{
+public:
+ Pimpl(NotifyRequestDeviceRemoval& parent, HANDLE hDir) : //throw (FileError)
+ parent_(parent)
+ {
+ MessageProvider::instance().registerListener(*this); //throw (FileError)
+
+ //register handles to receive notifications
+ DEV_BROADCAST_HANDLE filter = {};
+ filter.dbch_size = sizeof(filter);
+ filter.dbch_devicetype = DBT_DEVTYP_HANDLE;
+ filter.dbch_handle = hDir;
+
+ hNotification = ::RegisterDeviceNotification(
+ MessageProvider::instance().getWnd(), //__in HANDLE hRecipient,
+ &filter, //__in LPVOID NotificationFilter,
+ DEVICE_NOTIFY_WINDOW_HANDLE); //__in DWORD Flags
+ if (hNotification == NULL)
+ {
+ const DWORD lastError = ::GetLastError();
+ if (lastError != ERROR_CALL_NOT_IMPLEMENTED && //fail on SAMBA share: this shouldn't be a showstopper!
+ lastError != ERROR_SERVICE_SPECIFIC_ERROR && //neither should be fail for "Pogoplug" mapped network drives
+ lastError != ERROR_INVALID_DATA) //this seems to happen for a NetDrive-mapped FTP server
+ throw zen::FileError(std::wstring(L"Could not register device removal notifications:") + "\n\n" + getLastErrorFormatted(lastError));
+ }
+ }
+
+ ~Pimpl()
+ {
+ ::UnregisterDeviceNotification(hNotification);
+ MessageProvider::instance().unregisterListener(*this);
+ }
+
+private:
+ Pimpl(Pimpl&);
+ Pimpl& operator=(Pimpl&);
+
+ virtual void onMessage(UINT message, WPARAM wParam, LPARAM lParam) //throw()!
+ {
+ //DBT_DEVICEQUERYREMOVE example: http://msdn.microsoft.com/en-us/library/aa363427(v=VS.85).aspx
+ if (message == WM_DEVICECHANGE)
+ {
+ if ( wParam == DBT_DEVICEQUERYREMOVE ||
+ wParam == DBT_DEVICEQUERYREMOVEFAILED ||
+ wParam == DBT_DEVICEREMOVECOMPLETE)
+ {
+ PDEV_BROADCAST_HDR header = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam);
+ if (header->dbch_devicetype == DBT_DEVTYP_HANDLE)
+ {
+ PDEV_BROADCAST_HANDLE body = reinterpret_cast<PDEV_BROADCAST_HANDLE>(lParam);
+
+#ifdef __MINGW32__
+ const HDEVNOTIFY requestNotification = reinterpret_cast<HDEVNOTIFY>(body->dbch_hdevnotify);
+#else
+ const HDEVNOTIFY requestNotification = body->dbch_hdevnotify;
+#endif
+ if (requestNotification == hNotification) //is it for the notification we registered?
+ switch (wParam)
+ {
+ case DBT_DEVICEQUERYREMOVE:
+ parent_.onRequestRemoval(body->dbch_handle);
+ break;
+ case DBT_DEVICEQUERYREMOVEFAILED:
+ parent_.onRemovalFinished(body->dbch_handle, false);
+ break;
+ case DBT_DEVICEREMOVECOMPLETE:
+ parent_.onRemovalFinished(body->dbch_handle, true);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ NotifyRequestDeviceRemoval& parent_;
+ HDEVNOTIFY hNotification;
+};
+//####################################################################################################
+
+
+NotifyRequestDeviceRemoval::NotifyRequestDeviceRemoval(HANDLE hDir)
+{
+ pimpl.reset(new Pimpl(*this, hDir));
+}
+
+
+NotifyRequestDeviceRemoval::~NotifyRequestDeviceRemoval() {} //make sure ~auto_ptr() works with complete type
diff --git a/zen/notify_removal.h b/zen/notify_removal.h
new file mode 100644
index 00000000..bd47684e
--- /dev/null
+++ b/zen/notify_removal.h
@@ -0,0 +1,36 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef NOTIFY_H_INCLUDED
+#define NOTIFY_H_INCLUDED
+
+#include <vector>
+#include <memory>
+#include "win.h" //includes "windows.h"
+#include "file_error.h"
+
+//handle (user-) request for device removal via template method pattern
+//evaluate directly after processing window messages
+class NotifyRequestDeviceRemoval
+{
+public:
+ NotifyRequestDeviceRemoval(HANDLE hDir); //throw FileError
+ virtual ~NotifyRequestDeviceRemoval();
+
+private:
+ virtual void onRequestRemoval(HANDLE hnd) = 0; //throw()!
+ //NOTE: onRemovalFinished is NOT guaranteed to execute after onRequestRemoval()! but most likely will
+ virtual void onRemovalFinished(HANDLE hnd, bool successful) = 0; //throw()!
+
+ NotifyRequestDeviceRemoval(NotifyRequestDeviceRemoval&); //no copying
+ void operator=(NotifyRequestDeviceRemoval&); //
+
+ class Pimpl;
+ std::unique_ptr<Pimpl> pimpl;
+};
+
+
+#endif // NOTIFY_H_INCLUDED
diff --git a/zen/perf.h b/zen/perf.h
new file mode 100644
index 00000000..f9970d0a
--- /dev/null
+++ b/zen/perf.h
@@ -0,0 +1,73 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef DEBUG_PERF_HEADER
+#define DEBUG_PERF_HEADER
+
+#include <sstream>
+#include "win.h" //includes "windows.h"
+
+#ifdef __MINGW32__
+#define DEPRECATED(x) x __attribute__ ((deprecated))
+#elif defined _MSC_VER
+#define DEPRECATED(x) __declspec(deprecated) x
+#endif
+
+
+//two macros for quick performance measurements
+#define PERF_START CpuTimer perfTest;
+#define PERF_STOP perfTest.showResult();
+
+class CpuTimer
+{
+public:
+ class TimerError {};
+
+ DEPRECATED(CpuTimer()) : frequency(), startTime(), resultShown(false)
+ {
+ SetThreadAffinity dummy;
+ if (!::QueryPerformanceFrequency(&frequency)) throw TimerError();
+ if (!::QueryPerformanceCounter (&startTime)) throw TimerError();
+ }
+
+ ~CpuTimer()
+ {
+ if (!resultShown)
+ showResult();
+ }
+
+ void showResult()
+ {
+ SetThreadAffinity dummy;
+ LARGE_INTEGER currentTime = {};
+ if (!::QueryPerformanceCounter(&currentTime)) throw TimerError();
+
+ const long delta = static_cast<long>(1000.0 * (currentTime.QuadPart - startTime.QuadPart) / frequency.QuadPart);
+ std::ostringstream ss;
+ ss << delta << " ms";
+
+ ::MessageBoxA(NULL, ss.str().c_str(), "Timer", 0);
+ resultShown = true;
+
+ if (!::QueryPerformanceCounter(&startTime)) throw TimerError(); //don't include call to MessageBox()!
+ }
+
+private:
+ class SetThreadAffinity
+ {
+ public:
+ SetThreadAffinity() : oldmask(::SetThreadAffinityMask(::GetCurrentThread(), 1)) { if (oldmask == 0) throw TimerError(); }
+ ~SetThreadAffinity() { ::SetThreadAffinityMask(::GetCurrentThread(), oldmask); }
+ private:
+ const DWORD_PTR oldmask;
+ };
+
+ LARGE_INTEGER frequency;
+ LARGE_INTEGER startTime;
+ bool resultShown;
+};
+
+#endif //DEBUG_PERF_HEADER
diff --git a/zen/privilege.cpp b/zen/privilege.cpp
new file mode 100644
index 00000000..495b1254
--- /dev/null
+++ b/zen/privilege.cpp
@@ -0,0 +1,79 @@
+#include "privilege.h"
+#include "scope_guard.h"
+
+using namespace zen;
+
+
+Privileges& Privileges::getInstance()
+{
+ static Privileges instance;
+ return instance;
+}
+
+
+bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw FileError
+{
+ HANDLE hToken = NULL;
+ if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle,
+ TOKEN_QUERY, //__in DWORD DesiredAccess,
+ &hToken)) //__out PHANDLE TokenHandle
+ throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted());
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hToken));
+
+ LUID luid = {};
+ if (!::LookupPrivilegeValue(
+ NULL, //__in_opt LPCTSTR lpSystemName,
+ privilege, //__in LPCTSTR lpName,
+ &luid )) //__out PLUID lpLuid
+ throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted());
+
+ PRIVILEGE_SET priv = {};
+ priv.PrivilegeCount = 1;
+ priv.Control = PRIVILEGE_SET_ALL_NECESSARY;
+ priv.Privilege[0].Luid = luid;
+ priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ BOOL alreadyGranted = FALSE;
+ if (!::PrivilegeCheck(
+ hToken, //__in HANDLE ClientToken,
+ &priv, //__inout PPRIVILEGE_SET RequiredPrivileges,
+ &alreadyGranted)) //__out LPBOOL pfResult
+ throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted());
+
+ return alreadyGranted == TRUE;
+}
+
+
+void Privileges::setPrivilege(LPCTSTR privilege, bool enable) //throw FileError
+{
+ HANDLE hToken = NULL;
+ if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle,
+ TOKEN_ADJUST_PRIVILEGES, //__in DWORD DesiredAccess,
+ &hToken)) //__out PHANDLE TokenHandle
+ throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted());
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hToken));
+
+ LUID luid = {};
+ if (!::LookupPrivilegeValue(
+ NULL, //__in_opt LPCTSTR lpSystemName,
+ privilege, //__in LPCTSTR lpName,
+ &luid )) //__out PLUID lpLuid
+ throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted());
+
+ TOKEN_PRIVILEGES tp = {};
+ tp.PrivilegeCount = 1;
+ tp.Privileges[0].Luid = luid;
+ tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
+
+ if (!::AdjustTokenPrivileges(
+ hToken, //__in HANDLE TokenHandle,
+ false, //__in BOOL DisableAllPrivileges,
+ &tp, //__in_opt PTOKEN_PRIVILEGES NewState,
+ 0, //__in DWORD BufferLength,
+ NULL, //__out_opt PTOKEN_PRIVILEGES PreviousState,
+ NULL)) //__out_opt PDWORD ReturnLength
+ throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted());
+
+ if (::GetLastError() == ERROR_NOT_ALL_ASSIGNED) //check although previous function returned with success!
+ throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted());
+}
diff --git a/zen/privilege.h b/zen/privilege.h
new file mode 100644
index 00000000..4545aac7
--- /dev/null
+++ b/zen/privilege.h
@@ -0,0 +1,64 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef PRIVILEGE_H_INCLUDED
+#define PRIVILEGE_H_INCLUDED
+
+#include <map>
+#include "zstring.h"
+#include "file_error.h"
+#include "win.h" //includes "windows.h"
+
+namespace zen
+{
+#ifdef FFS_WIN
+class Privileges
+{
+public:
+ static Privileges& getInstance();
+
+ void ensureActive(LPCTSTR privilege) //throw FileError
+ {
+ if (activePrivileges.find(privilege) != activePrivileges.end())
+ return; //privilege already active
+
+ if (privilegeIsActive(privilege)) //privilege was already active before starting this tool
+ activePrivileges.insert(std::make_pair(privilege, false));
+ else
+ {
+ setPrivilege(privilege, true);
+ activePrivileges.insert(std::make_pair(privilege, true));
+ }
+ }
+
+private:
+ Privileges() {}
+ Privileges(Privileges&);
+ void operator=(Privileges&);
+
+ ~Privileges() //clean up: deactivate all privileges that have been activated by this application
+ {
+ for (PrivBuffType::const_iterator i = activePrivileges.begin(); i != activePrivileges.end(); ++i)
+ try
+ {
+ if (i->second)
+ Privileges::setPrivilege(i->first.c_str(), false);
+ }
+ catch (...) {}
+ }
+
+ static bool privilegeIsActive(LPCTSTR privilege); //throw FileError
+ static void setPrivilege(LPCTSTR privilege, bool enable); //throw FileError
+
+ typedef std::map<Zstring, bool> PrivBuffType; //bool: enabled by this application
+
+ PrivBuffType activePrivileges;
+};
+#endif
+}
+
+
+#endif // PRIVILEGE_H_INCLUDED
diff --git a/zen/read_txt.cpp b/zen/read_txt.cpp
new file mode 100644
index 00000000..fd92a10c
--- /dev/null
+++ b/zen/read_txt.cpp
@@ -0,0 +1,91 @@
+#include "read_txt.h"
+
+using namespace zen;
+
+
+namespace
+{
+std::string detectLineBreak(const Zstring& filename) //throw (FileError)
+{
+ //read a (hopefully) significant portion of data
+ zen::FileInput input(filename);
+
+ std::vector<char> buffer(64 * 1024);
+ size_t bytesRead = input.read(&buffer[0], buffer.size()); //throw (FileError);
+ buffer.resize(bytesRead);
+
+ //detect line break
+ std::string linebreakChars = "\r\n";
+ std::vector<char>::iterator iter = std::find_first_of(buffer.begin(), buffer.end(),
+ linebreakChars.begin(), linebreakChars.end());
+ if (iter != buffer.end())
+ {
+ if (*iter == '\r')
+ {
+ ++iter;
+ if (iter != buffer.end())
+ {
+
+ if (*iter == '\n')
+ return "\r\n"; //Windows
+ else
+ return "\r"; //Mac
+ }
+ }
+ else if (*iter == '\n')
+ return "\n"; //Linux
+ }
+ //fallback
+ return "\n";
+}
+}
+
+
+ExtractLines::ExtractLines(const Zstring& filename, const std::string& lineBreak) : //throw (FileError)
+ inputStream(filename), bufferLogBegin(buffer.begin()), lineBreak_(lineBreak)
+{
+ if (lineBreak.empty())
+ lineBreak_ = detectLineBreak(filename); //throw (FileError)
+}
+
+
+bool ExtractLines::getLine(std::string& output) //throw (FileError)
+{
+ for (;;)
+ {
+ //check if full line is in buffer
+ std::vector<char>::iterator iter = std::search(bufferLogBegin, buffer.end(), lineBreak_.begin(), lineBreak_.end());
+ if (iter != buffer.end())
+ {
+ output.assign(bufferLogBegin, iter);
+ bufferLogBegin = iter + lineBreak_.size();
+ return true;
+ }
+
+ buffer.erase(buffer.begin(), bufferLogBegin);
+ bufferLogBegin = buffer.begin();
+
+ //if done: cleanup
+ if (inputStream.eof())
+ {
+ if (buffer.empty())
+ return false;
+
+ output.assign(buffer.begin(), buffer.end());
+ buffer.clear();
+ return true;
+ }
+
+ //read next block
+ const size_t BLOCK_SIZE = 512 * 1024;
+ buffer.resize(buffer.size() + BLOCK_SIZE);
+
+ size_t bytesRead = inputStream.read(&buffer[0] + buffer.size() - BLOCK_SIZE, BLOCK_SIZE); //throw (FileError);
+ assert(bytesRead <= BLOCK_SIZE); //promised by FileInput()
+
+ if (bytesRead < BLOCK_SIZE)
+ buffer.resize(buffer.size() - (BLOCK_SIZE - bytesRead));
+
+ bufferLogBegin = buffer.begin();
+ }
+}
diff --git a/zen/read_txt.h b/zen/read_txt.h
new file mode 100644
index 00000000..479f950e
--- /dev/null
+++ b/zen/read_txt.h
@@ -0,0 +1,32 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef PARSE_TXT_H_INCLUDED
+#define PARSE_TXT_H_INCLUDED
+
+#include "file_io.h"
+#include <vector>
+#include <string>
+
+namespace zen
+{
+class ExtractLines
+{
+public:
+ ExtractLines(const Zstring& filename, const std::string& lineBreak = std::string()); //throw FileError
+ bool getLine(std::string& output); //throw FileError
+
+private:
+ zen::FileInput inputStream;
+ std::vector<char> buffer;
+ std::vector<char>::iterator bufferLogBegin;
+ std::string lineBreak_;
+};
+
+}
+
+
+#endif // PARSE_TXT_H_INCLUDED
diff --git a/zen/scope_guard.h b/zen/scope_guard.h
new file mode 100644
index 00000000..d3633284
--- /dev/null
+++ b/zen/scope_guard.h
@@ -0,0 +1,77 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef ZEN_SCOPEGUARD_8971632487321434
+#define ZEN_SCOPEGUARD_8971632487321434
+
+//best of Zen, Loki and C++11
+
+namespace zen
+{
+//Scope Guard
+/*
+ zen::ScopeGuard lockAio = zen::makeGuard([&]() { ::CancelIo(hDir); });
+ ...
+ lockAio.dismiss();
+*/
+
+//Scope Exit
+/*
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hDir));
+*/
+
+class ScopeGuardBase
+{
+public:
+ void dismiss() const { dismissed_ = true; }
+
+protected:
+ ScopeGuardBase() : dismissed_(false) {}
+ ScopeGuardBase(const ScopeGuardBase& other) : dismissed_(other.dismissed_) { other.dismissed_ = true; } //take over responsibility
+ ~ScopeGuardBase() {}
+
+ bool isDismissed() const { return dismissed_; }
+
+private:
+ ScopeGuardBase& operator=(const ScopeGuardBase&); // = delete;
+
+ mutable bool dismissed_;
+};
+
+
+template <typename F>
+class ScopeGuardImpl : public ScopeGuardBase
+{
+public:
+ ScopeGuardImpl(F fun) : fun_(fun) {}
+
+ ~ScopeGuardImpl()
+ {
+ if (!this->isDismissed())
+ try
+ {
+ fun_();
+ }
+ catch (...) {}
+ }
+
+private:
+ F fun_;
+};
+
+typedef const ScopeGuardBase& ScopeGuard;
+
+template <class F> inline
+ScopeGuardImpl<F> makeGuard(F fun) { return ScopeGuardImpl<F>(fun); }
+}
+
+#define ZEN_CONCAT_SUB(X, Y) X ## Y
+#define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y)
+
+#define ZEN_ON_BLOCK_EXIT(X) zen::ScopeGuard ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard([&](){X;}); (void)ZEN_CONCAT(dummy, __LINE__);
+
+#endif //ZEN_SCOPEGUARD_8971632487321434
diff --git a/zen/stl_tools.h b/zen/stl_tools.h
new file mode 100644
index 00000000..6cfe35f8
--- /dev/null
+++ b/zen/stl_tools.h
@@ -0,0 +1,49 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STL_TOOLS_HEADER_84567184321434
+#define STL_TOOLS_HEADER_84567184321434
+
+//no need to drag in any STL includes :)
+
+namespace zen
+{
+template <class V, class Predicate> inline
+void vector_remove_if(V& vec, Predicate p)
+{
+ vec.erase(std::remove_if(vec.begin(), vec.end(), p), vec.end());
+}
+
+
+template <class S, class Predicate> inline
+void set_remove_if(S& set, Predicate p)
+{
+ for (auto iter = set.begin(); iter != set.end();)
+ if (p(*iter))
+ set.erase(iter++);
+ else
+ ++iter;
+}
+
+
+template <class M, class Predicate> inline
+void map_remove_if(M& map, Predicate p) { set_remove_if(map, p); }
+
+
+// binary search returning an iterator
+template <class ForwardIterator, class T, typename Compare> inline
+ForwardIterator custom_binary_search(ForwardIterator first, ForwardIterator last, const T& value, Compare comp)
+{
+ first = std::lower_bound(first, last, value, comp);
+ if (first != last && !comp(value, *first))
+ return first;
+ else
+ return last;
+}
+}
+
+
+#endif //STL_TOOLS_HEADER_84567184321434 \ No newline at end of file
diff --git a/zen/string_base.h b/zen/string_base.h
new file mode 100644
index 00000000..914e0434
--- /dev/null
+++ b/zen/string_base.h
@@ -0,0 +1,824 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef Z_BASE_H_INCLUDED
+#define Z_BASE_H_INCLUDED
+
+#include <algorithm>
+#include <cassert>
+#include "string_tools.h"
+#include <boost/detail/atomic_count.hpp>
+
+//Zbase - a policy based string class
+
+
+/*
+Allocator Policy:
+-----------------
+ void* allocate(size_t size) //throw (std::bad_alloc)
+ void deallocate(void* ptr)
+ size_t calcCapacity(size_t length)
+*/
+class AllocatorOptimalSpeed //exponential growth + min size
+{
+public:
+ //::operator new/ ::operator delete show same performance characterisics like malloc()/free()!
+ static void* allocate(size_t size) { return ::operator new(size); } //throw (std::bad_alloc)
+ static void deallocate(void* ptr) { ::operator delete(ptr); }
+ static size_t calcCapacity(size_t length) { return std::max<size_t>(16, length + length / 2); }
+};
+
+
+class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string
+{
+public:
+ static void* allocate(size_t size) { return ::operator new(size); } //throw (std::bad_alloc)
+ static void deallocate(void* ptr) { ::operator delete(ptr); }
+ static size_t calcCapacity(size_t length) { return length; }
+};
+
+/*
+Storage Policy:
+---------------
+template <typename T, //Character Type
+ class AP> //Allocator Policy
+
+ T* create(size_t size)
+ T* create(size_t size, size_t minCapacity)
+ T* clone(T* ptr)
+ void destroy(T* ptr)
+ bool canWrite(const T* ptr, size_t minCapacity) //needs to be checked before writing to "ptr"
+ size_t length(const T* ptr)
+ void setLength(T* ptr, size_t newLength)
+*/
+
+template <typename T, //Character Type
+ class AP> //Allocator Policy
+class StorageDeepCopy : public AP
+{
+protected:
+ ~StorageDeepCopy() {}
+
+ static T* create(size_t size) { return create(size, size); }
+ static T* create(size_t size, size_t minCapacity)
+ {
+ const size_t newCapacity = AP::calcCapacity(minCapacity);
+ assert(newCapacity >= minCapacity);
+ assert(minCapacity >= size);
+
+ Descriptor* const newDescr = static_cast<Descriptor*>(AP::allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(T)));
+
+ newDescr->length = size;
+ newDescr->capacity = newCapacity;
+
+ return reinterpret_cast<T*>(newDescr + 1);
+ }
+
+ static T* clone(T* ptr)
+ {
+ T* newData = create(length(ptr));
+ std::copy(ptr, ptr + length(ptr) + 1, newData);
+ return newData;
+ }
+
+ static void destroy(T* ptr) { AP::deallocate(descr(ptr)); }
+
+ //this needs to be checked before writing to "ptr"
+ static bool canWrite(const T* ptr, size_t minCapacity) { return minCapacity <= descr(ptr)->capacity; }
+ static size_t length(const T* ptr) { return descr(ptr)->length; }
+
+ static void setLength(T* ptr, size_t newLength)
+ {
+ assert(canWrite(ptr, newLength));
+ descr(ptr)->length = newLength;
+ }
+
+private:
+ struct Descriptor
+ {
+ size_t length;
+ size_t capacity; //allocated size without null-termination
+ };
+
+ static Descriptor* descr( T* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; }
+ static const Descriptor* descr(const T* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; }
+};
+
+
+template <typename T, //Character Type
+ class AP> //Allocator Policy
+class StorageRefCountThreadSafe : public AP
+{
+protected:
+ ~StorageRefCountThreadSafe() {}
+
+ static T* create(size_t size)
+ {
+ return create(size, size);
+ }
+
+ static T* create(size_t size, size_t minCapacity)
+ {
+ const size_t newCapacity = AP::calcCapacity(minCapacity);
+ assert(newCapacity >= minCapacity);
+ assert(minCapacity >= size);
+
+ Descriptor* const newDescr = static_cast<Descriptor*>(AP::allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(T)));
+ new (newDescr) Descriptor(1, size, newCapacity);
+
+ return reinterpret_cast<T*>(newDescr + 1);
+ }
+
+ static T* clone(T* ptr)
+ {
+ assert(descr(ptr)->refCount > 0);
+ ++descr(ptr)->refCount;
+ return ptr;
+ }
+
+ static void destroy(T* ptr)
+ {
+ if (--descr(ptr)->refCount == 0)
+ {
+ descr(ptr)->~Descriptor();
+ AP::deallocate(descr(ptr));
+ }
+ }
+
+ static bool canWrite(const T* ptr, size_t minCapacity) //needs to be checked before writing to "ptr"
+ {
+ assert(descr(ptr)->refCount > 0);
+ return descr(ptr)->refCount == 1 && minCapacity <= descr(ptr)->capacity;
+ }
+
+ static size_t length(const T* ptr)
+ {
+ return descr(ptr)->length;
+ }
+
+ static void setLength(T* ptr, size_t newLength)
+ {
+ assert(canWrite(ptr, newLength));
+ descr(ptr)->length = newLength;
+ }
+
+private:
+ struct Descriptor
+ {
+ Descriptor(long rc, size_t len, size_t cap) : refCount(rc), length(len), capacity(cap) {}
+
+ boost::detail::atomic_count refCount; //practically no perf loss: ~0.2%! (FFS comparison)
+ size_t length;
+ size_t capacity; //allocated size without null-termination
+ };
+
+ static Descriptor* descr( T* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; }
+ static const Descriptor* descr(const T* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; }
+};
+//################################################################################################################################################################
+
+
+//perf note: interstingly StorageDeepCopy and StorageRefCountThreadSafe show same performance in FFS comparison
+
+template <class T, //Character Type
+ template <class, class> class SP = StorageRefCountThreadSafe, //Storage Policy
+ class AP = AllocatorOptimalSpeed> //Allocator Policy
+class Zbase : public SP<T, AP>
+{
+public:
+ Zbase();
+ Zbase(const T* source); //implicit conversion from a C-string
+ Zbase(const T* source, size_t length);
+ Zbase(const Zbase& source);
+ Zbase(Zbase&& tmp);
+ explicit Zbase(T source); //dangerous if implicit: T buffer[]; Zbase name = buffer; ups...
+ //allow explicit construction from different string type, prevent ambiguity via SFINAE
+ template <class S> explicit Zbase(const S& other, typename S::value_type = 0);
+ ~Zbase();
+
+ //operator const T* () const; //NO implicit conversion to a C-string!! Many problems... one of them: if we forget to provide operator overloads, it'll just work with a T*...
+
+ //STL accessors
+ typedef T* iterator;
+ typedef const T* const_iterator;
+ typedef T& reference;
+ typedef const T& const_reference;
+ typedef T value_type;
+ const T* begin() const;
+ const T* end() const;
+ T* begin();
+ T* end();
+
+ //std::string functions
+ size_t length() const;
+ size_t size() const;
+ const T* c_str() const; //C-string format with NULL-termination
+ const T* data() const; //internal representation, NULL-termination not guaranteed
+ const T operator[](size_t pos) const;
+ bool empty() const;
+ void clear();
+ size_t find(const Zbase& str, size_t pos = 0) const; //
+ size_t find(const T* str, size_t pos = 0) const; //returns "npos" if not found
+ size_t find(T ch, size_t pos = 0) const; //
+ size_t rfind(T ch, size_t pos = npos) const; //
+ size_t rfind(const T* str, size_t pos = npos) const; //
+ Zbase& replace(size_t pos1, size_t n1, const Zbase& str);
+ void reserve(size_t minCapacity);
+ Zbase& assign(const T* source, size_t len);
+ void resize(size_t newSize, T fillChar = 0);
+ void swap(Zbase& other);
+ void push_back(T val); //STL access
+
+ Zbase& operator=(const Zbase& source);
+ Zbase& operator=(Zbase&& tmp);
+ Zbase& operator=(const T* source);
+ Zbase& operator=(T source);
+ Zbase& operator+=(const Zbase& other);
+ Zbase& operator+=(const T* other);
+ Zbase& operator+=(T ch);
+
+ static const size_t npos = static_cast<size_t>(-1);
+
+private:
+ Zbase(int); //detect usage errors
+ Zbase& operator=(int); //
+
+ T* rawStr;
+};
+
+template <class T, template <class, class> class SP, class AP> bool operator==(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs);
+template <class T, template <class, class> class SP, class AP> bool operator==(const Zbase<T, SP, AP>& lhs, const T* rhs);
+template <class T, template <class, class> class SP, class AP> bool operator==(const T* lhs, const Zbase<T, SP, AP>& rhs);
+
+template <class T, template <class, class> class SP, class AP> bool operator!=(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs);
+template <class T, template <class, class> class SP, class AP> bool operator!=(const Zbase<T, SP, AP>& lhs, const T* rhs);
+template <class T, template <class, class> class SP, class AP> bool operator!=(const T* lhs, const Zbase<T, SP, AP>& rhs);
+
+template <class T, template <class, class> class SP, class AP> bool operator< (const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs);
+template <class T, template <class, class> class SP, class AP> bool operator< (const Zbase<T, SP, AP>& lhs, const T* rhs);
+template <class T, template <class, class> class SP, class AP> bool operator< (const T* lhs, const Zbase<T, SP, AP>& rhs);
+
+template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs);
+template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const T* rhs);
+template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const T* lhs, const Zbase<T, SP, AP>& rhs);
+template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+( T lhs, const Zbase<T, SP, AP>& rhs);
+template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, T rhs);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################################# inline implementation ########################################
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>::Zbase()
+{
+ //resist the temptation to avoid this allocation by referening a static global: NO performance advantage, MT issues!
+ rawStr = this->create(0);
+ rawStr[0] = 0;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>::Zbase(T source)
+{
+ rawStr = this->create(1);
+ rawStr[0] = source;
+ rawStr[1] = 0;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>::Zbase(const T* source)
+{
+ const size_t sourceLen = zen::strLength(source);
+ rawStr = this->create(sourceLen);
+ std::copy(source, source + sourceLen + 1, rawStr); //include null-termination
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>::Zbase(const T* source, size_t sourceLen)
+{
+ rawStr = this->create(sourceLen);
+ std::copy(source, source + sourceLen, rawStr);
+ rawStr[sourceLen] = 0;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>::Zbase(const Zbase<T, SP, AP>& source)
+{
+ rawStr = this->clone(source.rawStr);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>::Zbase(Zbase<T, SP, AP>&& tmp)
+{
+ rawStr = this->clone(tmp.rawStr); //for a ref-counting string there probably isn't a faster way, even with r-value references
+}
+
+
+template <class T, template <class, class> class SP, class AP>
+template <class S> inline
+Zbase<T, SP, AP>::Zbase(const S& other, typename S::value_type)
+{
+ const size_t sourceLen = other.size();
+ rawStr = this->create(sourceLen);
+ std::copy(other.c_str(), other.c_str() + sourceLen, rawStr);
+ rawStr[sourceLen] = 0;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>::~Zbase()
+{
+ this->destroy(rawStr);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+size_t Zbase<T, SP, AP>::find(const Zbase& str, size_t pos) const
+{
+ assert(pos <= length());
+ const T* thisEnd = end(); //respect embedded 0
+ const T* iter = std::search(begin() + pos, thisEnd,
+ str.begin(), str.end());
+ return iter == thisEnd ? npos : iter - begin();
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+size_t Zbase<T, SP, AP>::find(const T* str, size_t pos) const
+{
+ assert(pos <= length());
+ const T* thisEnd = end(); //respect embedded 0
+ const T* iter = std::search(begin() + pos, thisEnd,
+ str, str + zen::strLength(str));
+ return iter == thisEnd ? npos : iter - begin();
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+size_t Zbase<T, SP, AP>::find(T ch, size_t pos) const
+{
+ assert(pos <= length());
+ const T* thisEnd = end();
+ const T* iter = std::find(begin() + pos, thisEnd, ch); //respect embedded 0
+ return iter == thisEnd ? npos : iter - begin();
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+size_t Zbase<T, SP, AP>::rfind(T ch, size_t pos) const
+{
+ assert(pos == npos || pos <= length());
+
+ const size_t thisLen = length();
+ if (thisLen == 0) return npos;
+ pos = std::min(thisLen - 1, pos); //handle "npos" and "pos == length()" implicitly
+
+ while (rawStr[pos] != ch) //pos points to last char of the string
+ {
+ if (pos == 0)
+ return npos;
+ --pos;
+ }
+ return pos;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+size_t Zbase<T, SP, AP>::rfind(const T* str, size_t pos) const
+{
+ assert(pos == npos || pos <= length());
+
+ const size_t strLen = zen::strLength(str);
+ const T* currEnd = pos == npos ? end() : begin() + std::min(pos + strLen, length());
+
+ const T* iter = std::find_end(begin(), currEnd,
+ str, str + strLen);
+ return iter == currEnd ? npos : iter - begin();
+}
+
+
+template <class T, template <class, class> class SP, class AP>
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::replace(size_t pos1, size_t n1, const Zbase& str)
+{
+ assert(str.data() < rawStr || rawStr + length() < str.data()); //str mustn't point to data in this string
+ assert(pos1 + n1 <= length());
+
+ const size_t n2 = str.length();
+
+ const size_t oldLen = length();
+ if (oldLen == 0)
+ return *this = str;
+
+ const size_t newLen = oldLen - n1 + n2;
+
+ if (canWrite(rawStr, newLen))
+ {
+ if (n1 < n2) //move remainder right -> std::copy_backward
+ {
+ std::copy_backward(rawStr + pos1 + n1, rawStr + oldLen + 1, rawStr + newLen + 1); //include null-termination
+ setLength(rawStr, newLen);
+ }
+ else if (n1 > n2) //shift left -> std::copy
+ {
+ std::copy(rawStr + pos1 + n1, rawStr + oldLen + 1, rawStr + pos1 + n2); //include null-termination
+ setLength(rawStr, newLen);
+ }
+
+ std::copy(str.data(), str.data() + n2, rawStr + pos1);
+ }
+ else
+ {
+ //copy directly into new string
+ T* const newStr = this->create(newLen);
+
+ std::copy(rawStr, rawStr + pos1, newStr);
+ std::copy(str.data(), str.data() + n2, newStr + pos1);
+ std::copy(rawStr + pos1 + n1, rawStr + oldLen + 1, newStr + pos1 + n2); //include null-termination
+
+ destroy(rawStr);
+ rawStr = newStr;
+ }
+ return *this;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+void Zbase<T, SP, AP>::resize(size_t newSize, T fillChar)
+{
+ if (canWrite(rawStr, newSize))
+ {
+ if (length() < newSize)
+ std::fill(rawStr + length(), rawStr + newSize, fillChar);
+ rawStr[newSize] = 0;
+ setLength(rawStr, newSize); //keep after call to length()
+ }
+ else
+ {
+ T* newStr = this->create(newSize);
+ newStr[newSize] = 0;
+
+ if (length() < newSize)
+ {
+ std::copy(rawStr, rawStr + length(), newStr);
+ std::fill(newStr + length(), newStr + newSize, fillChar);
+ }
+ else
+ std::copy(rawStr, rawStr + newSize, newStr);
+
+ destroy(rawStr);
+ rawStr = newStr;
+ }
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator==(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return lhs.length() == rhs.length() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); //respect embedded 0
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator==(const Zbase<T, SP, AP>& lhs, const T* rhs)
+{
+ return lhs.length() == zen::strLength(rhs) && std::equal(lhs.begin(), lhs.end(), rhs); //respect embedded 0
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator==(const T* lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return operator==(rhs, lhs);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator!=(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return !operator==(lhs, rhs);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator!=(const Zbase<T, SP, AP>& lhs, const T* rhs)
+{
+ return !operator==(lhs, rhs);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator!=(const T* lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return !operator==(lhs, rhs);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator<(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0
+ rhs.begin(), rhs.end());
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator<(const Zbase<T, SP, AP>& lhs, const T* rhs)
+{
+ return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0
+ rhs, rhs + zen::strLength(rhs));
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool operator<(const T* lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return std::lexicographical_compare(lhs, lhs + zen::strLength(lhs), //respect embedded 0
+ rhs.begin(), rhs.end());
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+size_t Zbase<T, SP, AP>::length() const
+{
+ return SP<T, AP>::length(rawStr);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+size_t Zbase<T, SP, AP>::size() const
+{
+ return length();
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const T* Zbase<T, SP, AP>::c_str() const
+{
+ return rawStr;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const T* Zbase<T, SP, AP>::data() const
+{
+ return rawStr;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const T Zbase<T, SP, AP>::operator[](size_t pos) const
+{
+ assert(pos < length());
+ return rawStr[pos];
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const T* Zbase<T, SP, AP>::begin() const
+{
+ return rawStr;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const T* Zbase<T, SP, AP>::end() const
+{
+ return rawStr + length();
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+T* Zbase<T, SP, AP>::begin()
+{
+ reserve(length());
+ return rawStr;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+T* Zbase<T, SP, AP>::end()
+{
+ return begin() + length();
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+void Zbase<T, SP, AP>::push_back(T val)
+{
+ operator+=(val);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+bool Zbase<T, SP, AP>::empty() const
+{
+ return length() == 0;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+void Zbase<T, SP, AP>::clear()
+{
+ if (!empty())
+ {
+ if (canWrite(rawStr, 0))
+ {
+ rawStr[0] = 0; //keep allocated memory
+ setLength(rawStr, 0); //
+ }
+ else
+ *this = Zbase();
+ }
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return Zbase<T, SP, AP>(lhs) += rhs;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const T* rhs)
+{
+ return Zbase<T, SP, AP>(lhs) += rhs;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const Zbase<T, SP, AP> operator+(const T* lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return Zbase<T, SP, AP>(lhs) += rhs;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const Zbase<T, SP, AP> operator+(T lhs, const Zbase<T, SP, AP>& rhs)
+{
+ return (Zbase<T, SP, AP>() += lhs) += rhs;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, T rhs)
+{
+ return Zbase<T, SP, AP>(lhs) += rhs;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+void Zbase<T, SP, AP>::swap(Zbase<T, SP, AP>& other)
+{
+ std::swap(rawStr, other.rawStr);
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+void Zbase<T, SP, AP>::reserve(size_t minCapacity) //make unshared and check capacity
+{
+ if (!canWrite(rawStr, minCapacity))
+ {
+ //allocate a new string
+ T* newStr = create(length(), std::max(minCapacity, length())); //reserve() must NEVER shrink the string: logical const!
+ std::copy(rawStr, rawStr + length() + 1, newStr); //include NULL-termination
+
+ destroy(rawStr);
+ rawStr = newStr;
+ }
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::assign(const T* source, size_t len)
+{
+ if (canWrite(rawStr, len))
+ {
+ std::copy(source, source + len, rawStr);
+ rawStr[len] = 0; //include null-termination
+ setLength(rawStr, len);
+ }
+ else
+ *this = Zbase(source, len);
+
+ return *this;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(const Zbase<T, SP, AP>& source)
+{
+ Zbase(source).swap(*this);
+ return *this;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(Zbase<T, SP, AP>&& tmp)
+{
+ swap(tmp);
+ return *this;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(const T* source)
+{
+ return assign(source, zen::strLength(source));
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(T source)
+{
+ if (canWrite(rawStr, 1))
+ {
+ rawStr[0] = source;
+ rawStr[1] = 0; //include null-termination
+ setLength(rawStr, 1);
+ }
+ else
+ *this = Zbase(source);
+
+ return *this;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator+=(const Zbase<T, SP, AP>& other)
+{
+ const size_t thisLen = length();
+ const size_t otherLen = other.length();
+ reserve(thisLen + otherLen); //make unshared and check capacity
+
+ std::copy(other.rawStr, other.rawStr + otherLen + 1, rawStr + thisLen); //include null-termination
+ setLength(rawStr, thisLen + otherLen);
+ return *this;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator+=(const T* other)
+{
+ const size_t thisLen = length();
+ const size_t otherLen = zen::strLength(other);
+ reserve(thisLen + otherLen); //make unshared and check capacity
+
+ std::copy(other, other + otherLen + 1, rawStr + thisLen); //include null-termination
+ setLength(rawStr, thisLen + otherLen);
+ return *this;
+}
+
+
+template <class T, template <class, class> class SP, class AP> inline
+Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator+=(T ch)
+{
+ const size_t thisLen = length();
+ reserve(thisLen + 1); //make unshared and check capacity
+ rawStr[thisLen] = ch;
+ rawStr[thisLen + 1] = 0;
+ setLength(rawStr, thisLen + 1);
+ return *this;
+}
+
+#endif //Z_BASE_H_INCLUDED
diff --git a/zen/string_tools.h b/zen/string_tools.h
new file mode 100644
index 00000000..8cafad07
--- /dev/null
+++ b/zen/string_tools.h
@@ -0,0 +1,569 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STRING_TOOLS_HEADER_213458973046
+#define STRING_TOOLS_HEADER_213458973046
+
+#include <cctype> //isspace
+#include <cwctype> //iswspace
+#include <cstdio> //sprintf
+#include <cwchar> //swprintf
+#include <algorithm>
+#include <cassert>
+#include <sstream>
+#include <vector>
+#include "string_traits.h"
+#include "type_traits.h"
+
+
+//enhance arbitray string class with useful non-member functions:
+namespace zen
+{
+template <class C> bool cStringIsWhiteSpace(C ch);
+template <class C> bool cStringIsDigit(C ch);
+
+template <class S, class T> bool startsWith(const S& str, const T& prefix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t
+template <class S, class T> bool endsWith (const S& str, const T& postfix); //
+
+template <class S, class T> S afterLast (const S& str, const T& ch); //returns the whole string if ch not found
+template <class S, class T> S beforeLast (const S& str, const T& ch); //returns empty string if ch not found
+template <class S, class T> S afterFirst (const S& str, const T& ch); //returns empty string if ch not found
+template <class S, class T> S beforeFirst(const S& str, const T& ch); //returns the whole string if ch not found
+
+template <class S, class T> std::vector<S> split(const S& str, const T& delimiter);
+template <class S> void truncate(S& str, size_t newLen);
+template <class S> void trim(S& str, bool fromLeft = true, bool fromRight = true);
+template <class S, class T, class U> void replace ( S& str, const T& oldOne, const U& newOne, bool replaceAll = true);
+template <class S, class T, class U> S replaceCpy(const S& str, const T& oldOne, const U& newOne, bool replaceAll = true);
+
+//high-performance conversion from numbers to strings
+template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using ::sprintf
+
+template <class S, class Num> S toString(const Num& number);
+template <class Num, class S > Num toNumber(const S& str);
+
+//string to string conversion: converst string-like type into char-compatible target string class
+template <class T, class S> T cvrtString(const S& str);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------------- implementation ----------------------
+template <> inline
+bool cStringIsWhiteSpace(char ch)
+{
+ //caveat 1: std::isspace() takes an int, but expects an unsigned char
+ //caveat 2: some parts of UTF-8 chars are erroneously seen as whitespace, e.g. the a0 from "\xec\x8b\xa0" (MSVC)
+ return static_cast<unsigned char>(ch) < 128 &&
+ std::isspace(static_cast<unsigned char>(ch)) != 0;
+}
+
+//template <> inline bool cStringIsWhiteSpace(unsigned char ch) { return cStringIsWhiteSpace<char>(ch); } -> not character types!
+//template <> inline bool cStringIsWhiteSpace(signed char ch) { return cStringIsWhiteSpace<char>(ch); }
+template <> inline bool cStringIsWhiteSpace(wchar_t ch) { return std::iswspace(ch) != 0; }
+
+template <> inline
+bool cStringIsDigit(char ch)
+{
+ return std::isdigit(static_cast<unsigned char>(ch)) != 0; //caveat: takes an int, but expects an unsigned char
+}
+
+
+template <> inline
+bool cStringIsDigit(wchar_t ch)
+{
+ return std::iswdigit(ch) != 0;
+}
+
+
+template <class S, class T> inline
+bool startsWith(const S& str, const T& prefix)
+{
+ assert_static(StringTraits<S>::isStringLike);
+ assert_static(StringTraits<T>::isStringLike);
+
+ const size_t pfLength = strLength(prefix);
+ if (strLength(str) < pfLength)
+ return false;
+
+ return std::equal(strBegin(str), strBegin(str) + pfLength,
+ strBegin(prefix));
+}
+
+
+template <class S, class T> inline
+bool endsWith(const S& str, const T& postfix)
+{
+ assert_static(StringTraits<S>::isStringLike);
+ assert_static(StringTraits<T>::isStringLike);
+
+ size_t strLen = strLength(str);
+ size_t pfLen = strLength(postfix);
+ if (strLen < pfLen)
+ return false;
+
+ typedef typename StringTraits<S>::CharType CharType;
+
+ const CharType* cmpBegin = strBegin(str) + strLen - pfLen;
+ return std::equal(cmpBegin, cmpBegin + pfLen,
+ strBegin(postfix));
+}
+
+
+//returns the whole string if ch not found
+template <class S, class T> inline
+S afterLast(const S& str, const T& ch)
+{
+ assert_static(StringTraits<T>::isStringLike);
+
+ const size_t pos = str.rfind(ch);
+ if (pos != S::npos)
+ {
+ size_t chLen = strLength(ch);
+ return S(str.c_str() + pos + chLen, str.length() - pos - chLen);
+ }
+ else
+ return str;
+}
+
+
+//returns empty string if ch not found
+template <class S, class T> inline
+S beforeLast(const S& str, const T& ch)
+{
+ assert_static(StringTraits<T>::isStringLike);
+
+ const size_t pos = str.rfind(ch);
+ if (pos != S::npos)
+ return S(str.c_str(), pos); //data is non-empty string in this context: else ch would not have been found!
+ else
+ return S();
+}
+
+
+//returns empty string if ch not found
+template <class S, class T> inline
+S afterFirst(const S& str, const T& ch)
+{
+ assert_static(StringTraits<T>::isStringLike);
+
+ const size_t pos = str.find(ch);
+ if (pos != S::npos)
+ {
+ size_t chLen = strLength(ch);
+ return S(str.c_str() + pos + chLen, str.length() - pos - chLen);
+ }
+ else
+ return S();
+
+}
+
+
+//returns the whole string if ch not found
+template <class S, class T> inline
+S beforeFirst(const S& str, const T& ch)
+{
+ assert_static(StringTraits<T>::isStringLike);
+
+ const size_t pos = str.find(ch);
+ if (pos != S::npos)
+ return S(str.c_str(), pos); //data is non-empty string in this context: else ch would not have been found!
+ else
+ return str;
+}
+
+
+template <class S, class T> inline
+std::vector<S> split(const S& str, const T& delimiter)
+{
+ assert_static(StringTraits<T>::isStringLike);
+
+ std::vector<S> output;
+ size_t bockStart = 0;
+ size_t delimLen = strLength(delimiter);
+ if (delimLen != 0)
+ {
+ for (size_t blockEnd = str.find(delimiter, bockStart);
+ blockEnd != S::npos;
+ bockStart = blockEnd + delimLen, blockEnd = str.find(delimiter, bockStart))
+ {
+ output.push_back(S(str.c_str() + bockStart, blockEnd - bockStart));
+ }
+ }
+ output.push_back(S(str.c_str() + bockStart, str.length() - bockStart));
+ return output;
+}
+
+
+template <class S> inline
+void truncate(S& str, size_t newLen)
+{
+ if (newLen < str.length())
+ str.resize(newLen);
+}
+
+
+template <class S, class T, class U> inline
+S replaceCpy(const S& str, const T& oldOne, const U& newOne, bool replaceAll)
+{
+ assert_static(StringTraits<T>::isStringLike);
+ assert_static(StringTraits<U>::isStringLike);
+
+ typedef typename StringTraits<S>::CharType CharType;
+
+ const size_t oldLen = strLength(oldOne);
+ const size_t newLen = strLength(newOne);
+
+ S output;
+
+ const CharType* strPos = strBegin(str);
+ const CharType* strEnd = strPos + strLength(str);
+
+ for (;;)
+ {
+ const CharType* ptr = std::search(strPos, strEnd,
+ strBegin(oldOne), strBegin(oldOne) + oldLen);
+ if (ptr == strEnd)
+ break;
+
+ output += S(strPos, ptr - strPos);
+ output += S(strBegin(newOne), newLen);
+
+ strPos = ptr + oldLen;
+
+ if (!replaceAll)
+ break;
+ }
+ output += S(strPos, strEnd - strPos);
+
+ return output;
+}
+
+
+template <class S, class T, class U> inline
+void replace(S& str, const T& oldOne, const U& newOne, bool replaceAll)
+{
+ str = replaceCpy(str, oldOne, newOne, replaceAll);
+}
+
+
+template <class S> inline
+void trim(S& str, bool fromLeft, bool fromRight)
+{
+ assert(fromLeft || fromRight);
+
+ typedef typename S::value_type CharType;
+
+ const CharType* newBegin = str.c_str();
+ const CharType* newEnd = str.c_str() + str.length();
+
+ if (fromRight)
+ while (newBegin != newEnd && cStringIsWhiteSpace(newEnd[-1]))
+ --newEnd;
+
+ if (fromLeft)
+ while (newBegin != newEnd && cStringIsWhiteSpace(*newBegin))
+ ++newBegin;
+
+ const size_t newLength = newEnd - newBegin;
+ if (newLength != str.length())
+ {
+ if (newBegin != str.c_str())
+ str = S(newBegin, newLength); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only
+ else
+ str.resize(newLength);
+ }
+}
+
+
+namespace implementation
+{
+template <class S, class T>
+struct CnvtStringToString
+{
+ T convert(const S& src) const { return T(strBegin(src), strLength(src)); }
+};
+
+template <class S>
+struct CnvtStringToString<S, S> //perf: we don't need a deep copy if string types match
+{
+ const S& convert(const S& src) const { return src; }
+};
+}
+
+template <class T, class S> inline
+T cvrtString(const S& str) { return implementation::CnvtStringToString<S, T>().convert(str); }
+
+
+namespace implementation
+{
+template <class Num> inline
+int saferPrintf(char* buffer, size_t bufferSize, const char* format, const Num& number) //there is no such thing as a "safe" printf ;)
+{
+#ifdef _MSC_VER
+ return ::_snprintf(buffer, bufferSize, format, number); //VS2010 doesn't respect ISO C
+#else
+ return std::snprintf(buffer, bufferSize, format, number); //C99
+#endif
+}
+
+template <class Num> inline
+int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const Num& number)
+{
+#ifdef __MINGW32__ //MinGW doesn't respect ISO C
+ return ::snwprintf(buffer, bufferSize, format, number);
+#else
+ return std::swprintf(buffer, bufferSize, format, number); //C99
+#endif
+}
+}
+
+template <class S, class T, class Num> inline
+S printNumber(const T& format, const Num& number) //format a single number using ::sprintf
+{
+ assert_static(StringTraits<T>::isStringLike);
+ assert_static((IsSameType<
+ typename StringTraits<S>::CharType,
+ typename StringTraits<T>::CharType>::result));
+
+ typedef typename StringTraits<S>::CharType CharType;
+
+ const int BUFFER_SIZE = 128;
+ CharType buffer[BUFFER_SIZE];
+ const int charsWritten = implementation::saferPrintf(buffer, BUFFER_SIZE, format, number);
+
+ return charsWritten > 0 ? S(buffer, charsWritten) : S();
+}
+
+
+namespace implementation
+{
+enum NumberType
+{
+ NUM_TYPE_SIGNED_INT,
+ NUM_TYPE_UNSIGNED_INT,
+ NUM_TYPE_FLOATING_POINT,
+ NUM_TYPE_OTHER,
+};
+
+
+template <class S, class Num, NumberType>
+struct CvrtNumberToString
+{
+ S convert(const Num& number) const //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20)
+ {
+ typedef typename StringTraits<S>::CharType CharType;
+
+ std::basic_ostringstream<CharType> ss;
+ ss << number;
+ return cvrtString<S>(ss.str());
+ }
+};
+
+
+template <class S, class Num>
+struct CvrtNumberToString<S, Num, NUM_TYPE_FLOATING_POINT>
+{
+ S convert(const Num& number) const { return convertFloat(number, typename StringTraits<S>::CharType()); }
+
+private:
+ S convertFloat(const Num& number, char ) const { return printNumber<S>( "%g", static_cast<double>(number)); }
+ S convertFloat(const Num& number, wchar_t) const { return printNumber<S>(L"%g", static_cast<double>(number)); }
+};
+
+/*
+perf: integer to string: (executed 10 mio. times)
+ std::stringstream - 14796 ms
+ std::sprintf - 3086 ms
+ formatInteger - 778 ms
+*/
+
+template <class S, class Num> inline
+S formatInteger(Num n, bool hasMinus)
+{
+ assert(n >= 0);
+ S output;
+ do
+ {
+ output += '0' + n % 10;
+ n /= 10;
+ }
+ while (n != 0);
+ if (hasMinus)
+ output += '-';
+
+ std::reverse(output.begin(), output.end());
+ return output;
+}
+
+template <class S, class Num>
+struct CvrtNumberToString<S, Num, NUM_TYPE_SIGNED_INT>
+{
+ S convert(const Num& number) const { return formatInteger<S>(number < 0 ? -number : number, number < 0); }
+};
+
+template <class S, class Num>
+struct CvrtNumberToString<S, Num, NUM_TYPE_UNSIGNED_INT>
+{
+ S convert(const Num& number) const { return formatInteger<S>(number, false); }
+};
+
+//--------------------------------------------------------------------------------
+
+template <class S, class Num, NumberType>
+struct CvrtStringToNumber
+{
+ Num convert(const S& str) const //default string to number conversion using streams: convenient, but SLOW
+ {
+ typedef typename StringTraits<S>::CharType CharType;
+ Num number = 0;
+ std::basic_istringstream<CharType>(cvrtString<std::basic_string<CharType> >(str)) >> number;
+ return number;
+ }
+};
+
+
+template <class S, class Num>
+struct CvrtStringToNumber<S, Num, NUM_TYPE_FLOATING_POINT>
+{
+ Num convert(const S& str) const { return convertFloat(strBegin(str)); }
+
+private:
+ Num convertFloat(const char* str) const { return std::strtod(str, NULL); }
+ Num convertFloat(const wchar_t* str) const { return std::wcstod(str, NULL); }
+};
+
+template <class Num, class S>
+Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic
+{
+ typedef typename StringTraits<S>::CharType CharType;
+
+ const CharType* first = strBegin(str);
+ const CharType* last = first + strLength(str);
+
+ while (first != last && cStringIsWhiteSpace(*first)) //skip leading whitespace
+ ++first;
+
+ hasMinusSign = false; //handle minus sign
+ if (first != last)
+ {
+ if (*first == '-')
+ {
+ hasMinusSign = true;
+ ++first;
+ }
+ else if (*first == '+')
+ ++first;
+ }
+
+ Num number = 0;
+ for (const CharType* iter = first; iter != last; ++iter)
+ {
+ const CharType c = *iter;
+ if ('0' <= c && c <= '9')
+ {
+ number *= 10;
+ number += c - '0';
+ }
+ else
+ {
+ assert(std::find_if(iter, last, std::not1(std::ptr_fun(&cStringIsWhiteSpace<CharType>))) == last); //rest of string should contain whitespace only
+ break;
+ }
+ }
+ return number;
+}
+
+
+template <class S, class Num>
+struct CvrtStringToNumber<S, Num, NUM_TYPE_SIGNED_INT>
+{
+ Num convert(const S& str) const
+ {
+ bool hasMinusSign = false; //handle minus sign
+ const Num number = extractInteger<Num>(str, hasMinusSign);
+ return hasMinusSign ? -number : number;
+ }
+};
+
+
+template <class S, class Num>
+struct CvrtStringToNumber<S, Num, NUM_TYPE_UNSIGNED_INT>
+{
+ Num convert(const S& str) const //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic
+ {
+ bool hasMinusSign = false; //handle minus sign
+ const Num number = extractInteger<Num>(str, hasMinusSign);
+ if (hasMinusSign)
+ {
+ assert(false);
+ return 0U;
+ }
+ return number;
+ }
+};
+}
+
+
+template <class S, class Num>
+inline
+S toString(const Num& number) //convert number to string the C++ way
+{
+ using namespace implementation;
+ return CvrtNumberToString<S, Num,
+ IsSignedInt <Num>::result ? NUM_TYPE_SIGNED_INT :
+ IsUnsignedInt<Num>::result ? NUM_TYPE_UNSIGNED_INT :
+ IsFloat <Num>::result ? NUM_TYPE_FLOATING_POINT :
+ NUM_TYPE_OTHER
+ >().convert(number);
+}
+
+
+template <class Num, class S>
+inline
+Num toNumber(const S& str) //convert string to number the C++ way
+{
+ using namespace implementation;
+ return CvrtStringToNumber<S, Num,
+ IsSignedInt <Num>::result ? NUM_TYPE_SIGNED_INT :
+ IsUnsignedInt<Num>::result ? NUM_TYPE_UNSIGNED_INT :
+ IsFloat <Num>::result ? NUM_TYPE_FLOATING_POINT :
+ NUM_TYPE_OTHER
+ >().convert(str);
+}
+
+}
+
+#endif //STRING_TOOLS_HEADER_213458973046
diff --git a/zen/string_traits.h b/zen/string_traits.h
new file mode 100644
index 00000000..59da2f79
--- /dev/null
+++ b/zen/string_traits.h
@@ -0,0 +1,176 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STRING_TRAITS_HEADER_813274321443234
+#define STRING_TRAITS_HEADER_813274321443234
+
+#include "type_tools.h"
+#include "assert_static.h"
+
+
+//uniform access to string-like types: classes and character arrays
+namespace zen
+{
+/*
+strBegin():
+ std::wstring str(L"dummy");
+ char array[] = "dummy";
+ const wchar_t* iter = strBegin(str); //returns str.c_str()
+ const char* iter2 = strBegin(array); //returns array
+
+strLength():
+ strLength(str); //equals str.size()
+ strLength(array); //equals cStringLength(array)
+
+StringTraits<>::CharType:
+ StringTraits<std::wstring>::CharType //equals wchar_t
+ StringTraits<wchar_t[5]> ::CharType //equals wchar_t
+
+StringTraits<>::isStringLike:
+ StringTraits<const wchar_t*>::isStringLike; //equals "true"
+ StringTraits<const int*> ::isStringLike; //equals "false"
+
+StringTraits<>::isStringClass:
+ StringTraits<std::wstring>::isStringClass //equals "true"
+ StringTraits<wchar_t[5]> ::isStringClass //equals "false"
+*/
+
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------------- implementation ----------------------
+namespace implementation
+{
+template<typename T>
+class HasValueTypedef
+{
+ typedef char Yes[1];
+ typedef char No [2];
+
+ template <typename U> class HelperTp {};
+
+ //detect presence of a member type called value_type
+ template <class U> static Yes& hasMemberValueType(HelperTp<typename U::value_type>*);
+ template <class U> static No& hasMemberValueType(...);
+
+public:
+ enum { result = sizeof(hasMemberValueType<T>(NULL)) == sizeof(Yes)
+ };
+};
+
+
+template<typename T, bool isClassType>
+class HasStringMembers
+{
+public:
+ enum { result = false };
+};
+
+template<typename T>
+class HasStringMembers<T, true>
+{
+ typedef char Yes[1];
+ typedef char No [2];
+
+ //detect presence of member functions (without specific restriction on return type, within T or one of it's base classes)
+ template <typename U, U t> class HelperFn {};
+
+ struct Fallback
+ {
+ int c_str;
+ int length;
+ };
+
+ template <class U>
+ struct Helper2 : public U, public Fallback {}; //U must be a class-type!
+
+ //we don't know the exact declaration of the member attribute (may be in base class), but we know what NOT to expect:
+ template <class U> static No& hasMemberCstr(HelperFn<int Fallback::*, &Helper2<U>::c_str>*);
+ template <class U> static Yes& hasMemberCstr(...);
+
+ template <class U> static No& hasMemberLength(HelperFn<int Fallback::*, &Helper2<U>::length>*);
+ template <class U> static Yes& hasMemberLength(...);
+public:
+ enum { result = sizeof(hasMemberCstr <T>(NULL)) == sizeof(Yes) &&
+ sizeof(hasMemberLength<T>(NULL)) == sizeof(Yes)
+ };
+};
+
+template <class S, bool isStringClass> struct StringTraits2 { typedef EmptyType Result; }; //"StringTraits2": fix some VS bug with namespace and partial template specialization
+
+template <class S> struct StringTraits2<S, true > { typedef typename S::value_type Result; };
+template <> struct StringTraits2<char, false> { typedef char Result; };
+template <> struct StringTraits2<wchar_t, false> { typedef wchar_t Result; };
+}
+
+
+template <class S>
+struct StringTraits
+{
+private:
+ typedef typename RemoveRef <S >::Result NonRefType;
+ typedef typename RemoveConst <NonRefType >::Result NonConstType;
+ typedef typename RemoveArray <NonConstType>::Result NonArrayType;
+ typedef typename RemovePointer<NonArrayType>::Result NonPtrType;
+ typedef typename RemoveConst <NonPtrType >::Result UndecoratedType; //handle "const char* const"
+public:
+ enum
+ {
+ isStringClass = implementation::HasStringMembers<NonConstType, implementation::HasValueTypedef<NonConstType>::result>::result
+ };
+
+ typedef typename implementation::StringTraits2<UndecoratedType, isStringClass>::Result CharType;
+
+ enum
+ {
+ isStringLike = IsSameType<CharType, char>::result || IsSameType<CharType, wchar_t>::result
+ };
+};
+
+
+namespace implementation
+{
+template <class C> inline
+size_t cStringLength(const C* str) //strlen()
+{
+ assert_static((IsSameType<C, char>::result || IsSameType<C, wchar_t>::result));
+ size_t len = 0;
+ while (*str++ != 0)
+ ++len;
+ return len;
+}
+}
+
+
+template <class S> inline
+const typename StringTraits<S>::CharType* strBegin(const S& str, typename S::value_type dummy = 0) { return str.c_str(); } //SFINAE: T must be a "string"
+
+template <class Char>
+inline const typename StringTraits<Char>::CharType* strBegin(const Char* str) { return str; }
+inline const char* strBegin(const char& ch) { return &ch; }
+inline const wchar_t* strBegin(const wchar_t& ch) { return &ch; }
+
+
+template <class S> inline
+size_t strLength(const S& str, typename S::value_type dummy = 0) { return str.length(); } //SFINAE: T must be a "string"
+
+template <class Char>
+inline size_t strLength(const Char* str) { return implementation::cStringLength(str); }
+inline size_t strLength(char) { return 1; }
+inline size_t strLength(wchar_t) { return 1; }
+}
+
+#endif //STRING_TRAITS_HEADER_813274321443234
diff --git a/zen/symlink_target.h b/zen/symlink_target.h
new file mode 100644
index 00000000..3704eebe
--- /dev/null
+++ b/zen/symlink_target.h
@@ -0,0 +1,140 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef SYMLINK_WIN_H_INCLUDED
+#define SYMLINK_WIN_H_INCLUDED
+
+#include "scope_guard.h"
+#include "file_error.h"
+
+#ifdef FFS_WIN
+#include "win.h" //includes "windows.h"
+#include "WinIoCtl.h"
+#include "privilege.h"
+#include "long_path_prefix.h"
+
+#elif defined FFS_LINUX
+#include <unistd.h>
+#endif
+
+
+#ifdef _MSC_VER //I don't have Windows Driver Kit at hands right now, so unfortunately we need to redefine this structures and cross fingers...
+typedef struct _REPARSE_DATA_BUFFER //from ntifs.h
+{
+ ULONG ReparseTag;
+ USHORT ReparseDataLength;
+ USHORT Reserved;
+ union
+ {
+ struct
+ {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ ULONG Flags;
+ WCHAR PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct
+ {
+ USHORT SubstituteNameOffset;
+ USHORT SubstituteNameLength;
+ USHORT PrintNameOffset;
+ USHORT PrintNameLength;
+ WCHAR PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct
+ {
+ UCHAR DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer)
+#endif
+
+namespace
+{
+//retrieve raw target data of symlink or junction
+Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError
+{
+ using namespace zen;
+#ifdef FFS_WIN
+ //FSCTL_GET_REPARSE_POINT: http://msdn.microsoft.com/en-us/library/aa364571(VS.85).aspx
+
+ try //reading certain symlinks/junctions requires admin rights! This shall not cause an error in user mode!
+ {
+ Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError
+ }
+ catch (...) {}
+
+ const HANDLE hLink = ::CreateFile(applyLongPathPrefix(linkPath).c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ NULL);
+ if (hLink == INVALID_HANDLE_VALUE)
+ throw FileError(_("Error resolving symbolic link:") + "\n\"" + linkPath + "\"" + "\n\n" + getLastErrorFormatted());
+ ZEN_ON_BLOCK_EXIT(::CloseHandle(hLink));
+
+ //respect alignment issues...
+ const DWORD bufferSize = REPARSE_DATA_BUFFER_HEADER_SIZE + MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
+ std::vector<char> buffer(bufferSize);
+
+ DWORD bytesReturned; //dummy value required by FSCTL_GET_REPARSE_POINT!
+ if (!::DeviceIoControl(hLink, //__in HANDLE hDevice,
+ FSCTL_GET_REPARSE_POINT, //__in DWORD dwIoControlCode,
+ NULL, //__in_opt LPVOID lpInBuffer,
+ 0, //__in DWORD nInBufferSize,
+ &buffer[0], //__out_opt LPVOID lpOutBuffer,
+ bufferSize, //__in DWORD nOutBufferSize,
+ &bytesReturned, //__out_opt LPDWORD lpBytesReturned,
+ NULL)) //__inout_opt LPOVERLAPPED lpOverlapped
+ throw FileError(_("Error resolving symbolic link:") + "\n\"" + linkPath + "\"" + "\n\n" + getLastErrorFormatted());
+
+ REPARSE_DATA_BUFFER& reparseData = *reinterpret_cast<REPARSE_DATA_BUFFER*>(&buffer[0]); //REPARSE_DATA_BUFFER needs to be artificially enlarged!
+
+ Zstring output;
+ if (reparseData.ReparseTag == IO_REPARSE_TAG_SYMLINK)
+ {
+ output = Zstring(reparseData.SymbolicLinkReparseBuffer.PathBuffer + reparseData.SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR),
+ reparseData.SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
+ }
+ else if (reparseData.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
+ {
+ output = Zstring(reparseData.MountPointReparseBuffer.PathBuffer + reparseData.MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR),
+ reparseData.MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
+ }
+ else
+ throw FileError(_("Error resolving symbolic link:") + "\n\"" + linkPath + "\"" + "\n\n" + "Not a symbolic link or junction!");
+
+ //absolute symlinks and junctions technically start with \??\ while relative ones do not
+ if (startsWith(output, Zstr("\\??\\")))
+ output = Zstring(output.c_str() + 4, output.length() - 4);
+
+ return output;
+
+#elif defined FFS_LINUX
+ const int BUFFER_SIZE = 10000;
+ std::vector<char> buffer(BUFFER_SIZE);
+
+ const int bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE);
+ if (bytesWritten < 0 || bytesWritten >= BUFFER_SIZE)
+ {
+ std::wstring errorMessage = _("Error resolving symbolic link:") + "\n\"" + linkPath + "\"";
+ if (bytesWritten < 0)
+ errorMessage += L"\n\n" + getLastErrorFormatted();
+ throw FileError(errorMessage);
+ }
+ buffer[bytesWritten] = 0; //set null-terminating char
+
+ return Zstring(&buffer[0], bytesWritten);
+#endif
+}
+}
+
+#endif // SYMLINK_WIN_H_INCLUDED
diff --git a/zen/thread.h b/zen/thread.h
new file mode 100644
index 00000000..4db1e613
--- /dev/null
+++ b/zen/thread.h
@@ -0,0 +1,92 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef BOOST_THREAD_WRAP_H
+#define BOOST_THREAD_WRAP_H
+
+//temporary solution until C++11 thread becomes fully available
+
+#ifdef __MINGW32__
+#pragma GCC diagnostic push
+
+#pragma GCC diagnostic ignored "-Wswitch-enum"
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+//#pragma GCC diagnostic ignored "-Wno-attributes"
+//#pragma GCC diagnostic ignored "-Wredundant-decls"
+//#pragma GCC diagnostic ignored "-Wcast-align"
+//#pragma GCC diagnostic ignored "-Wunused-value"
+#endif
+
+#include <boost/thread.hpp>
+
+#ifdef __MINGW32__
+#pragma GCC diagnostic pop
+#endif
+
+namespace zen
+{
+//until std::async is available:
+/*
+Example:
+ Zstring dirname = ...
+ auto ft = zen::async([=](){ return zen::dirExists(dirname); });
+ if (ft.timed_wait(boost::posix_time::milliseconds(200)) && ft.get())
+ //dir exising
+*/
+template <class Function>
+auto async(Function fun) -> boost::unique_future<decltype(fun())>;
+
+template<class InputIterator, class Duration>
+void wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//###################### implementation ######################
+template <class T, class Function> inline
+auto async2(Function fun) -> boost::unique_future<T> //workaround VS2010 bug: bool (*fun)(); decltype(fun()) == int!
+{
+ boost::packaged_task<T> pt([=] { return fun(); });
+ auto fut = pt.get_future();
+ boost::thread(std::move(pt));
+ return std::move(fut);
+}
+
+
+template <class Function> inline auto async(Function fun) -> boost::unique_future<decltype(fun())> { return async2<decltype(fun())>(fun); }
+
+
+template<class InputIterator, class Duration> inline
+void wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration)
+{
+ const boost::system_time endTime = boost::get_system_time() + wait_duration;
+ while (first != last)
+ {
+ first->timed_wait_until(endTime);
+ if (boost::get_system_time() >= endTime)
+ return;
+ ++first;
+ }
+}
+}
+
+#endif //BOOST_THREAD_WRAP_H
diff --git a/zen/type_tools.h b/zen/type_tools.h
new file mode 100644
index 00000000..06ef76e1
--- /dev/null
+++ b/zen/type_tools.h
@@ -0,0 +1,74 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef TYPE_TOOLS_HEADER_45237590734254545
+#define TYPE_TOOLS_HEADER_45237590734254545
+
+namespace zen
+{
+//########## Strawman Classes ##########################
+struct EmptyType {};
+struct NullType {};
+
+//########## Type Mapping ##############################
+template <int n>
+struct Int2Type {};
+//------------------------------------------------------
+template <class T>
+struct Type2Type {};
+
+//########## Control Structures ########################
+template <bool flag, class T, class U>
+struct Select
+{
+ typedef T Result;
+};
+template <class T, class U>
+struct Select<false, T, U>
+{
+ typedef U Result;
+};
+//------------------------------------------------------
+template <class T, class U>
+struct IsSameType
+{
+ enum { result = false };
+};
+
+template <class T>
+struct IsSameType<T, T>
+{
+ enum { result = true };
+};
+
+//########## Type Cleanup ##############################
+template <class T>
+struct RemoveRef { typedef T Result; };
+
+template <class T>
+struct RemoveRef<T&> { typedef T Result; };
+//------------------------------------------------------
+template <class T>
+struct RemoveConst { typedef T Result; };
+
+template <class T>
+struct RemoveConst<const T> { typedef T Result; };
+//------------------------------------------------------
+template <class T>
+struct RemovePointer { typedef T Result; };
+
+template <class T>
+struct RemovePointer<T*> { typedef T Result; };
+//------------------------------------------------------
+template <class T>
+struct RemoveArray { typedef T Result; };
+
+template <class T, int N>
+struct RemoveArray<T[N]> { typedef T Result; };
+}
+
+#endif //TYPE_TOOLS_HEADER_45237590734254545
diff --git a/zen/type_traits.h b/zen/type_traits.h
new file mode 100644
index 00000000..0a705def
--- /dev/null
+++ b/zen/type_traits.h
@@ -0,0 +1,88 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef TYPE_TRAITS_HEADER_3425628658765467
+#define TYPE_TRAITS_HEADER_3425628658765467
+
+namespace zen
+{
+//Example: "IsSignedInt<int>::result" evaluates to "true"
+
+template <class T> struct IsUnsignedInt;
+template <class T> struct IsSignedInt;
+template <class T> struct IsFloat;
+
+template <class T> struct IsInteger; //IsSignedInt or IsUnsignedInt
+template <class T> struct IsArithmetic; //IsInteger or IsFloat
+//remaining non-arithmetic types: bool, char, wchar_t
+
+//optional: specialize new types like:
+//template <> struct IsUnsignedInt<UInt64> { enum { result = true }; };
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################ implementation ######################
+#define ZEN_SPECIALIZE_TRAIT(X, Y) template <> struct X<Y> { enum { result = true }; };
+
+template <class T>
+struct IsUnsignedInt { enum { result = false }; };
+
+ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned char);
+ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned short int);
+ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned int);
+ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned long int);
+ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned long long int); //new with C++11 - same type as unsigned __int64 in VS2010
+//------------------------------------------------------
+
+template <class T>
+struct IsSignedInt { enum { result = false }; };
+
+ZEN_SPECIALIZE_TRAIT(IsSignedInt, signed char);
+ZEN_SPECIALIZE_TRAIT(IsSignedInt, short int);
+ZEN_SPECIALIZE_TRAIT(IsSignedInt, int);
+ZEN_SPECIALIZE_TRAIT(IsSignedInt, long int);
+ZEN_SPECIALIZE_TRAIT(IsSignedInt, long long int); //new with C++11 - same type as __int64 in VS2010
+//------------------------------------------------------
+
+template <class T>
+struct IsFloat { enum { result = false }; };
+
+ZEN_SPECIALIZE_TRAIT(IsFloat, float);
+ZEN_SPECIALIZE_TRAIT(IsFloat, double);
+ZEN_SPECIALIZE_TRAIT(IsFloat, long double);
+//------------------------------------------------------
+
+#undef ZEN_SPECIALIZE_TRAIT
+
+template <class T>
+struct IsInteger { enum { result = IsUnsignedInt<T>::result || IsSignedInt<T>::result }; };
+
+template <class T>
+struct IsArithmetic { enum { result = IsInteger<T>::result || IsFloat<T>::result }; };
+}
+
+#endif //TYPE_TRAITS_HEADER_3425628658765467
diff --git a/zen/utf8.h b/zen/utf8.h
new file mode 100644
index 00000000..e72a8e3c
--- /dev/null
+++ b/zen/utf8.h
@@ -0,0 +1,336 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STRING_UTF8_HEADER_01832479146991573473545
+#define STRING_UTF8_HEADER_01832479146991573473545
+
+#include <iterator>
+#include "string_tools.h"
+//#include "type_tools.h"
+//#include "string_traits.h"
+
+namespace zen
+{
+//convert any(!) "string-like" object into target string by applying a UTF8 conversion (but only if necessary!)
+template <class TargetString, class SourceString> TargetString utf8CvrtTo(const SourceString& str);
+
+//convert wide to utf8 string; example: std::string tmp = toUtf8<std::string>(L"abc");
+template <class CharString, class WideString>
+CharString wideToUtf8(const WideString& str);
+
+//convert utf8 string to wide; example: std::wstring tmp = utf8To<std::wstring>("abc");
+template <class WideString, class CharString>
+WideString utf8ToWide(const CharString& str);
+
+const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF";
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//----------------------- implementation ----------------------------------
+namespace implementation
+{
+typedef unsigned int CodePoint;
+
+const CodePoint CODE_POINT_MAX = 0x10ffff;
+
+const CodePoint HIGH_SURROGATE = 0xd800;
+const CodePoint HIGH_SURROGATE_MAX = 0xdbff;
+
+const CodePoint LOW_SURROGATE = 0xdc00;
+const CodePoint LOW_SURROGATE_MAX = 0xdfff;
+
+
+template <class OutputIterator> inline
+OutputIterator codePointToUtf16(CodePoint cp, OutputIterator result) //http://en.wikipedia.org/wiki/UTF-16
+{
+ typedef unsigned short Char16; //this isn't necessarily 16 bit, but all we need is an unsigned type
+
+ assert(cp < HIGH_SURROGATE || LOW_SURROGATE_MAX < cp); //code points [0xd800, 0xdfff] are not allowed for UTF-16
+ assert(cp <= CODE_POINT_MAX);
+
+ if (cp < 0x10000)
+ *result++ = static_cast<Char16>(cp);
+ else
+ {
+ cp -= 0x10000;
+ *result++ = static_cast<Char16>((cp >> 10) + HIGH_SURROGATE);
+ *result++ = static_cast<Char16>((cp & 0x3ff) + LOW_SURROGATE);
+ }
+ return result;
+}
+
+
+template <class CharIterator, class Function> inline
+Function utf16ToCodePoint(CharIterator first, CharIterator last, Function f) //f is a unary function taking a CodePoint as single parameter
+{
+ assert_static(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 2);
+ typedef unsigned short Char16; //this isn't necessarily 16 bit, but all we need is an unsigned type
+
+ for ( ; first != last; ++first)
+ {
+ CodePoint cp = static_cast<Char16>(*first);
+ if (HIGH_SURROGATE <= cp && cp <= HIGH_SURROGATE_MAX)
+ {
+ if (++first == last)
+ {
+ assert(false); //low surrogate expected
+ break;
+ }
+ assert(LOW_SURROGATE <= static_cast<Char16>(*first) && static_cast<Char16>(*first) <= LOW_SURROGATE_MAX); //low surrogate expected
+ cp = ((cp - HIGH_SURROGATE) << 10) + static_cast<Char16>(*first) - LOW_SURROGATE + 0x10000;
+ }
+ else
+ assert(cp < LOW_SURROGATE || LOW_SURROGATE_MAX < cp); //NO low surrogate expected
+
+ f(cp);
+ }
+ return f;
+}
+
+
+template <class OutputIterator> inline
+OutputIterator codePointToUtf8(CodePoint cp, OutputIterator result) //http://en.wikipedia.org/wiki/UTF-8
+{
+ typedef unsigned char Char8;
+
+ assert(cp <= CODE_POINT_MAX);
+
+ if (cp < 0x80)
+ *result++ = static_cast<Char8>(cp);
+ else if (cp < 0x800)
+ {
+ *result++ = static_cast<Char8>((cp >> 6 ) | 0xc0);
+ *result++ = static_cast<Char8>((cp & 0x3f) | 0x80);
+ }
+ else if (cp < 0x10000)
+ {
+ *result++ = static_cast<Char8>((cp >> 12 ) | 0xe0);
+ *result++ = static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80);
+ *result++ = static_cast<Char8>((cp & 0x3f ) | 0x80);
+ }
+ else
+ {
+ *result++ = static_cast<Char8>((cp >> 18 ) | 0xf0);
+ *result++ = static_cast<Char8>(((cp >> 12) & 0x3f) | 0x80);
+ *result++ = static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80);
+ *result++ = static_cast<Char8>((cp & 0x3f ) | 0x80);
+ }
+ return result;
+}
+
+
+inline
+size_t getUtf8Len(unsigned char ch)
+{
+ if (ch < 0x80)
+ return 1;
+ if (ch >> 5 == 0x6)
+ return 2;
+ if (ch >> 4 == 0xe)
+ return 3;
+ if (ch >> 3 == 0x1e)
+ return 4;
+
+ assert(false); //no valid begin of UTF8 encoding
+ return 1;
+}
+
+
+template <class CharIterator, class Function> inline
+Function utf8ToCodePoint(CharIterator first, CharIterator last, Function f) //f is a unary function taking a CodePoint as single parameter
+{
+ assert_static(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 1);
+ typedef unsigned char Char8;
+
+ for ( ; first != last; ++first)
+ {
+ auto getChar = [&](Char8 & ch) -> bool
+ {
+ if (++first == last)
+ {
+ assert(false); //low surrogate expected
+ return false;
+ }
+ ch = static_cast<Char8>(*first);
+ assert(ch >> 6 == 0x2);
+ return true;
+ };
+
+ CodePoint cp = static_cast<Char8>(*first);
+ switch (getUtf8Len(static_cast<Char8>(cp)))
+ {
+ case 1:
+ break;
+ case 2:
+ {
+ cp = (cp & 0x1f) << 6;
+ Char8 ch;
+ if (!getChar(ch)) continue;
+ cp += ch & 0x3f;
+ }
+ break;
+ case 3:
+ {
+ cp = (cp & 0xf) << 12;
+ Char8 ch;
+ if (!getChar(ch)) continue;
+ cp += (ch & 0x3f) << 6;
+ if (!getChar(ch)) continue;
+ cp += ch & 0x3f;
+
+ }
+ break;
+ case 4:
+ {
+ cp = (cp & 0x7) << 18;
+ Char8 ch;
+ if (!getChar(ch)) continue;
+ cp += (ch & 0x3f) << 12;
+ if (!getChar(ch)) continue;
+ cp += (ch & 0x3f) << 6;
+ if (!getChar(ch)) continue;
+ cp += ch & 0x3f;
+ }
+ break;
+ default:
+ assert(false);
+ }
+ f(cp);
+ }
+ return f;
+}
+
+
+template <class String>
+class AppendStringIterator: public std::iterator<std::output_iterator_tag, void, void, void, void>
+{
+public:
+ explicit AppendStringIterator (String& x) : str(&x) {}
+ AppendStringIterator& operator= (typename String::value_type value) { *str += value; return *this; }
+ AppendStringIterator& operator* () { return *this; }
+ AppendStringIterator& operator++ () { return *this; }
+ AppendStringIterator operator++ (int) { return *this; }
+private:
+ String* str;
+};
+
+
+template <class WideString, class CharString> inline
+WideString utf8ToWide(const CharString& str, Int2Type<2>) //windows: convert utf8 to utf16 wchar_t
+{
+ WideString output;
+ utf8ToCodePoint(strBegin(str), strBegin(str) + strLength(str),
+ [&](CodePoint cp) { codePointToUtf16(cp, AppendStringIterator<WideString>(output)); });
+ return output;
+}
+
+
+template <class WideString, class CharString> inline
+WideString utf8ToWide(const CharString& str, Int2Type<4>) //other OS: convert utf8 to utf32 wchar_t
+{
+ WideString output;
+ utf8ToCodePoint(strBegin(str), strBegin(str) + strLength(str),
+ [&](CodePoint cp) { output += cp; });
+ return output;
+}
+
+
+template <class CharString, class WideString> inline
+CharString wideToUtf8(const WideString& str, Int2Type<2>) //windows: convert utf16-wchar_t to utf8
+{
+ CharString output;
+ utf16ToCodePoint(strBegin(str), strBegin(str) + strLength(str),
+ [&](CodePoint cp) { codePointToUtf8(cp, AppendStringIterator<CharString>(output)); });
+ return output;
+}
+
+
+template <class CharString, class WideString> inline
+CharString wideToUtf8(const WideString& str, Int2Type<4>) //other OS: convert utf32-wchar_t to utf8
+{
+ CharString output;
+ std::for_each(strBegin(str), strBegin(str) + strLength(str),
+ [&](CodePoint cp) { codePointToUtf8(cp, AppendStringIterator<CharString>(output)); });
+ return output;
+}
+}
+
+
+template <class WideString, class CharString> inline
+WideString utf8ToWide(const CharString& str)
+{
+ assert_static((IsSameType<typename StringTraits<CharString>::CharType, char >::result));
+ assert_static((IsSameType<typename StringTraits<WideString>::CharType, wchar_t>::result));
+
+ return implementation::utf8ToWide<WideString>(str, Int2Type<sizeof(wchar_t)>());
+}
+
+
+template <class CharString, class WideString> inline
+CharString wideToUtf8(const WideString& str)
+{
+ assert_static((IsSameType<typename StringTraits<CharString>::CharType, char >::result));
+ assert_static((IsSameType<typename StringTraits<WideString>::CharType, wchar_t>::result));
+
+ return implementation::wideToUtf8<CharString>(str, Int2Type<sizeof(wchar_t)>());
+}
+
+
+//-------------------------------------------------------------------------------------------
+template <class TargetString, class SourceString> inline
+TargetString utf8CvrtTo(const SourceString& str, char, wchar_t) { return utf8ToWide<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utf8CvrtTo(const SourceString& str, wchar_t, char) { return wideToUtf8<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utf8CvrtTo(const SourceString& str, char, char) { return cvrtString<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utf8CvrtTo(const SourceString& str, wchar_t, wchar_t) { return cvrtString<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utf8CvrtTo(const SourceString& str)
+{
+ return utf8CvrtTo<TargetString>(str,
+ typename StringTraits<SourceString>::CharType(),
+ typename StringTraits<TargetString>::CharType());
+}
+}
+
+#endif //STRING_UTF8_HEADER_01832479146991573473545
diff --git a/zen/warn_static.h b/zen/warn_static.h
new file mode 100644
index 00000000..db472ccd
--- /dev/null
+++ b/zen/warn_static.h
@@ -0,0 +1,34 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef WARN_STATIC_HEADER_08724567834560832745
+#define WARN_STATIC_HEADER_08724567834560832745
+
+/*
+Portable Compile-Time Warning
+-----------------------------
+Usage:
+ warn_static("my message")
+*/
+
+#ifdef _MSC_VER
+#define MAKE_STRING_SUB(NUM) #NUM
+#define MAKE_STRING(NUM) MAKE_STRING_SUB(NUM)
+
+#define warn_static(TXT) \
+ __pragma(message (__FILE__ "(" MAKE_STRING(__LINE__) "): Warning: " ## TXT))
+
+#elif defined __GNUC__
+#define ZEN_CONCAT_SUB(X, Y) X ## Y
+#define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y)
+
+#define warn_static(TXT) \
+ typedef int STATIC_WARNING __attribute__ ((deprecated)); \
+ enum { ZEN_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING) };
+#endif
+
+
+#endif //WARN_STATIC_HEADER_08724567834560832745 \ No newline at end of file
diff --git a/zen/win.h b/zen/win.h
new file mode 100644
index 00000000..618c92fa
--- /dev/null
+++ b/zen/win.h
@@ -0,0 +1,30 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H
+#define YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H
+
+//------------------------------------------------------
+#ifdef __WXMSW__ //we have wxWidgets
+#include <wx/msw/wrapwin.h> //includes "windows.h"
+//------------------------------------------------------
+#else
+//#define WIN32_LEAN_AND_MEAN
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#ifndef STRICT
+#define STRICT //improve type checking
+#endif
+
+#include <windows.h>
+
+#endif
+//------------------------------------------------------
+
+#endif //YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H
diff --git a/zen/win_ver.h b/zen/win_ver.h
new file mode 100644
index 00000000..ca075bbe
--- /dev/null
+++ b/zen/win_ver.h
@@ -0,0 +1,72 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef WINDOWS_VERSION_HEADER_238470348254325
+#define WINDOWS_VERSION_HEADER_238470348254325
+
+#include "win.h"
+
+namespace zen
+{
+bool winXpOrLater();
+bool winServer2003orLater();
+bool vistaOrLater();
+bool win7OrLater();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//######################### implementation #########################
+namespace impl
+{
+inline
+bool winXyOrLater(DWORD major, DWORD minor)
+{
+ OSVERSIONINFO osvi = {};
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ if (::GetVersionEx(&osvi))
+ return osvi.dwMajorVersion > major ||
+ (osvi.dwMajorVersion == major && osvi.dwMinorVersion >= minor);
+ return false;
+}
+}
+
+//version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx
+
+//2000 is version 5.0
+//XP is version 5.1
+//Server 2003 is version 5.2
+//Vista is version 6.0
+//Seven is version 6.1
+
+inline
+bool winXpOrLater() { return impl::winXyOrLater(5, 1); }
+
+inline
+bool winServer2003orLater() { return impl::winXyOrLater(5, 2); }
+
+inline
+bool vistaOrLater() { return impl::winXyOrLater(6, 0); }
+
+inline
+bool win7OrLater() { return impl::winXyOrLater(6, 1); }
+}
+
+#endif //WINDOWS_VERSION_HEADER_238470348254325
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
new file mode 100644
index 00000000..5331499f
--- /dev/null
+++ b/zen/zstring.cpp
@@ -0,0 +1,234 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "zstring.h"
+#include <stdexcept>
+#include <boost/thread/once.hpp>
+
+#ifdef FFS_WIN
+#include "dll.h"
+#include "win_ver.h"
+#endif
+
+#ifndef NDEBUG
+#include <iostream>
+#include <cstdlib>
+#endif
+
+using namespace zen;
+
+
+#ifndef NDEBUG
+LeakChecker::~LeakChecker()
+{
+ if (activeStrings.size() > 0)
+ {
+ int rowCount = 0;
+ std::string leakingStrings;
+
+ for (VoidPtrSizeMap::const_iterator i = activeStrings.begin();
+ i != activeStrings.end() && ++rowCount <= 20;
+ ++i)
+ leakingStrings += "\"" + rawMemToString(i->first, i->second) + "\"\n";
+
+ const std::string message = std::string("Memory leak detected!") + "\n\n"
+ + "Candidates:\n" + leakingStrings;
+#ifdef FFS_WIN
+ MessageBoxA(NULL, message.c_str(), "Error", 0);
+#else
+ std::cerr << message;
+ std::abort();
+#endif
+ }
+}
+
+
+LeakChecker& LeakChecker::instance()
+{
+ static LeakChecker inst;
+ return inst;
+}
+
+//caveat: function scope static initialization is not thread-safe in VS 2010! => make sure to call at app start!
+namespace
+{
+struct Dummy { Dummy() { LeakChecker::instance(); }} blah;
+}
+
+
+std::string LeakChecker::rawMemToString(const void* ptr, size_t size)
+{
+ std::string output = std::string(reinterpret_cast<const char*>(ptr), size);
+ output.erase(std::remove(output.begin(), output.end(), 0), output.end()); //remove intermediate 0-termination
+ if (output.size() > 100)
+ output.resize(100);
+ return output;
+}
+
+
+void LeakChecker::reportProblem(const std::string& message) //throw (std::logic_error)
+{
+#ifdef FFS_WIN
+ ::MessageBoxA(NULL, message.c_str(), "Error", 0);
+#else
+ std::cerr << message;
+#endif
+ throw std::logic_error("Memory leak! " + message);
+}
+#endif //NDEBUG
+
+
+#ifdef FFS_WIN
+namespace
+{
+#ifndef LOCALE_INVARIANT //invariant locale has been introduced with XP
+#define LOCALE_INVARIANT 0x007f
+#endif
+
+
+//warning: LOCALE_INVARIANT is NOT available with Windows 2000, so we have to make yet another distinction...
+const LCID ZSTRING_INVARIANT_LOCALE = zen::winXpOrLater() ?
+ LOCALE_INVARIANT :
+ MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); //see: http://msdn.microsoft.com/en-us/goglobal/bb688122.aspx
+
+
+//try to call "CompareStringOrdinal" for low-level string comparison: unfortunately available not before Windows Vista!
+//by a factor ~3 faster than old string comparison using "LCMapString"
+typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1,
+ int cchCount1,
+ LPCWSTR lpString2,
+ int cchCount2,
+ BOOL bIgnoreCase);
+SysDllFun<CompareStringOrdinalFunc> ordinalCompare; //caveat: function scope static initialization is not thread-safe in VS 2010!
+boost::once_flag initCmpStrOrdOnce = BOOST_ONCE_INIT;
+}
+
+
+int z_impl::compareFilenamesWin(const wchar_t* a, const wchar_t* b, size_t sizeA, size_t sizeB)
+{
+ boost::call_once(initCmpStrOrdOnce, []() { ordinalCompare = SysDllFun<CompareStringOrdinalFunc>(L"kernel32.dll", "CompareStringOrdinal"); });
+
+ if (ordinalCompare) //this additional test has no noticeable performance impact
+ {
+ const int rv = ordinalCompare(a, //pointer to first string
+ static_cast<int>(sizeA), //size, in bytes or characters, of first string
+ b, //pointer to second string
+ static_cast<int>(sizeB), //size, in bytes or characters, of second string
+ true); //ignore case
+ if (rv == 0)
+ throw std::runtime_error("Error comparing strings (ordinal)!");
+ else
+ return rv - 2; //convert to C-style string compare result
+ }
+ else //fallback
+ {
+ //do NOT use "CompareString"; this function is NOT accurate (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!!
+ //the only reliable way to compare filenames (with XP) is to call "CharUpper" or "LCMapString":
+
+ const int minSize = std::min<int>(sizeA, sizeB);
+
+ if (minSize == 0) //LCMapString does not allow input sizes of 0!
+ return static_cast<int>(sizeA) - static_cast<int>(sizeB);
+
+ int rv = 0; //always initialize...
+ if (minSize <= 5000) //performance optimization: stack
+ {
+ wchar_t bufferA[5000];
+ wchar_t bufferB[5000];
+
+ //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString()
+ if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale,
+ LCMAP_UPPERCASE, //__in DWORD dwMapFlags,
+ a, //__in LPCTSTR lpSrcStr,
+ minSize, //__in int cchSrc,
+ bufferA, //__out LPTSTR lpDestStr,
+ 5000) == 0) //__in int cchDest
+ throw std::runtime_error("Error comparing strings! (LCMapString)");
+
+ if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, bufferB, 5000) == 0)
+ throw std::runtime_error("Error comparing strings! (LCMapString)");
+
+ rv = ::wmemcmp(bufferA, bufferB, minSize);
+ }
+ else //use freestore
+ {
+ std::vector<wchar_t> bufferA(minSize);
+ std::vector<wchar_t> bufferB(minSize);
+
+ if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, a, minSize, &bufferA[0], minSize) == 0)
+ throw std::runtime_error("Error comparing strings! (LCMapString: FS)");
+
+ if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, &bufferB[0], minSize) == 0)
+ throw std::runtime_error("Error comparing strings! (LCMapString: FS)");
+
+ rv = ::wmemcmp(&bufferA[0], &bufferB[0], minSize);
+ }
+
+ return rv == 0 ?
+ static_cast<int>(sizeA) - static_cast<int>(sizeB) :
+ rv;
+ }
+
+ // const int rv = CompareString(
+ // invariantLocale, //locale independent
+ // NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options
+ // a, //pointer to first string
+ // aCount, //size, in bytes or characters, of first string
+ // b, //pointer to second string
+ // bCount); //size, in bytes or characters, of second string
+ //
+ // if (rv == 0)
+ // throw std::runtime_error("Error comparing strings!");
+ // else
+ // return rv - 2; //convert to C-style string compare result
+}
+
+
+void z_impl::makeUpperCaseWin(wchar_t* str, size_t size)
+{
+ if (size == 0) //LCMapString does not allow input sizes of 0!
+ return;
+
+ //use Windows' upper case conversion: faster than ::CharUpper()
+ if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, str, static_cast<int>(size), str, static_cast<int>(size)) == 0)
+ throw std::runtime_error("Error converting to upper case! (LCMapString)");
+}
+
+
+/*
+#include <fstream>
+ extern std::wofstream statShared;
+extern std::wofstream statLowCapacity;
+extern std::wofstream statOverwrite;
+
+std::wstring p(ptr);
+p.erase(std::remove(p.begin(), p.end(), L'\n'), p.end());
+p.erase(std::remove(p.begin(), p.end(), L','), p.end());
+
+ if (descr(ptr)->refCount > 1)
+ statShared <<
+ minCapacity << L"," <<
+ descr(ptr)->refCount << L"," <<
+ descr(ptr)->length << L"," <<
+ descr(ptr)->capacity << L"," <<
+ p << L"\n";
+else if (minCapacity > descr(ptr)->capacity)
+ statLowCapacity <<
+ minCapacity << L"," <<
+ descr(ptr)->refCount << L"," <<
+ descr(ptr)->length << L"," <<
+ descr(ptr)->capacity << L"," <<
+ p << L"\n";
+else
+ statOverwrite <<
+ minCapacity << L"," <<
+ descr(ptr)->refCount << L"," <<
+ descr(ptr)->length << L"," <<
+ descr(ptr)->capacity << L"," <<
+ p << L"\n";
+*/
+
+#endif //FFS_WIN
diff --git a/zen/zstring.h b/zen/zstring.h
new file mode 100644
index 00000000..0ba9f108
--- /dev/null
+++ b/zen/zstring.h
@@ -0,0 +1,216 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef ZSTRING_H_INCLUDED
+#define ZSTRING_H_INCLUDED
+
+#include "string_base.h"
+#include <cstring> //strcmp()
+
+#ifndef NDEBUG
+#include "thread.h" //includes <boost/thread.hpp>
+#include <map>
+#endif
+
+
+#ifndef NDEBUG
+class LeakChecker //small test for memory leaks
+{
+public:
+ void insert(const void* ptr, size_t size)
+ {
+ boost::lock_guard<boost::mutex> dummy(lockActStrings);
+ if (activeStrings.find(ptr) != activeStrings.end())
+ reportProblem(std::string("Fatal Error: New memory points into occupied space: ") + rawMemToString(ptr, size));
+
+ activeStrings[ptr] = size;
+ }
+
+ void remove(const void* ptr)
+ {
+ boost::lock_guard<boost::mutex> dummy(lockActStrings);
+ if (activeStrings.find(ptr) == activeStrings.end())
+ reportProblem(std::string("Fatal Error: No memory available for deallocation at this location!"));
+
+ activeStrings.erase(ptr);
+ }
+
+ static LeakChecker& instance();
+
+private:
+ LeakChecker() {}
+ LeakChecker(const LeakChecker&);
+ LeakChecker& operator=(const LeakChecker&);
+ ~LeakChecker();
+
+ static std::string rawMemToString(const void* ptr, size_t size);
+ void reportProblem(const std::string& message); //throw (std::logic_error)
+
+ boost::mutex lockActStrings;
+ typedef std::map<const void*, size_t> VoidPtrSizeMap;
+ VoidPtrSizeMap activeStrings;
+};
+#endif //NDEBUG
+
+
+class AllocatorFreeStoreChecked
+{
+public:
+ static void* allocate(size_t size) //throw (std::bad_alloc)
+ {
+#ifndef NDEBUG
+ void* newMem = ::operator new(size);
+ LeakChecker::instance().insert(newMem, size); //test Zbase for memory leaks
+ return newMem;
+#else
+ return ::operator new(size);
+#endif
+ }
+
+ static void deallocate(void* ptr)
+ {
+#ifndef NDEBUG
+ LeakChecker::instance().remove(ptr); //check for memory leaks
+#endif
+ ::operator delete(ptr);
+ }
+
+ static size_t calcCapacity(size_t length) { return std::max<size_t>(16, length + length / 2); } //exponential growth + min size
+};
+
+
+//############################## helper functions #############################################
+#if defined(FFS_WIN) || defined(FFS_LINUX)
+//Compare filenames: Windows does NOT distinguish between upper/lower-case, while Linux DOES
+template <class T, template <class, class> class SP, class AP> int cmpFileName(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs);
+
+struct LessFilename //case-insensitive on Windows, case-sensitive on Linux
+{
+ template <class T, template <class, class> class SP, class AP>
+ bool operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const;
+};
+
+struct EqualFilename //case-insensitive on Windows, case-sensitive on Linux
+{
+ template <class T, template <class, class> class SP, class AP>
+ bool operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const;
+};
+#endif
+
+#ifdef FFS_WIN
+template <template <class, class> class SP, class AP>
+void makeUpper(Zbase<wchar_t, SP, AP>& str);
+#endif
+
+#ifdef FFS_WIN //Windows stores filenames in wide character format
+typedef wchar_t Zchar;
+#define Zstr(x) L ## x
+const Zchar FILE_NAME_SEPARATOR = L'\\';
+
+#elif defined FFS_LINUX //Linux uses UTF-8
+typedef char Zchar;
+#define Zstr(x) x
+const Zchar FILE_NAME_SEPARATOR = '/';
+
+#else
+#error define platform you are in: FFS_WIN or FFS_LINUX
+#endif
+
+//"The reason for all the fuss above" - Loki/SmartPtr
+//a high-performant string for use as file name in multithreaded contexts
+typedef Zbase<Zchar, StorageRefCountThreadSafe, AllocatorFreeStoreChecked> Zstring;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################################# inline implementation ########################################
+#if defined(FFS_WIN) || defined(FFS_LINUX)
+namespace z_impl
+{
+int compareFilenamesWin(const wchar_t* a, const wchar_t* b, size_t sizeA, size_t sizeB);
+void makeUpperCaseWin(wchar_t* str, size_t size);
+}
+
+
+template <class T, template <class, class> class SP, class AP>
+inline
+int cmpFileName(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs)
+{
+#ifdef FFS_WIN
+ return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length());
+#elif defined FFS_LINUX
+ return ::strcmp(lhs.c_str(), rhs.c_str()); //POSIX filenames don't have embedded 0
+#endif
+}
+
+
+template <class T, template <class, class> class SP, class AP>
+inline
+bool LessFilename::operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const
+{
+#ifdef FFS_WIN
+ return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()) < 0;
+#elif defined FFS_LINUX
+ return ::strcmp(lhs.c_str(), rhs.c_str()) < 0; //POSIX filenames don't have embedded 0
+#endif
+}
+
+
+template <class T, template <class, class> class SP, class AP>
+inline
+bool EqualFilename::operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const
+{
+#ifdef FFS_WIN
+ return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()) == 0;
+#elif defined FFS_LINUX
+ return ::strcmp(lhs.c_str(), rhs.c_str()) == 0; //POSIX filenames don't have embedded 0
+#endif
+}
+#endif //defined(FFS_WIN) || defined(FFS_LINUX)
+
+
+#ifdef FFS_WIN
+template <template <class, class> class SP, class AP>
+inline
+void makeUpper(Zbase<wchar_t, SP, AP>& str)
+{
+ z_impl::makeUpperCaseWin(str.begin(), str.length());
+}
+#endif
+
+
+namespace std
+{
+template<> inline
+void swap(Zstring& rhs, Zstring& lhs)
+{
+ rhs.swap(lhs);
+}
+}
+
+#endif //ZSTRING_H_INCLUDED
bgstack15