summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorDaniel Wilhelm <shieldwed@outlook.com>2017-02-13 21:25:04 -0700
committerDaniel Wilhelm <shieldwed@outlook.com>2017-02-13 21:25:04 -0700
commit9d071d2a2cec9a7662a02669488569a017f0ea35 (patch)
treec83a623fbdff098339b66d21ea2e81f3f67344ae /zen
parent8.8 (diff)
downloadFreeFileSync-9d071d2a2cec9a7662a02669488569a017f0ea35.tar.gz
FreeFileSync-9d071d2a2cec9a7662a02669488569a017f0ea35.tar.bz2
FreeFileSync-9d071d2a2cec9a7662a02669488569a017f0ea35.zip
8.9
Diffstat (limited to 'zen')
-rwxr-xr-x[-rw-r--r--]zen/basic_math.h766
-rwxr-xr-x[-rw-r--r--]zen/build_info.h67
-rwxr-xr-x[-rw-r--r--]zen/crc.h115
-rwxr-xr-x[-rw-r--r--]zen/deprecate.h35
-rwxr-xr-x[-rw-r--r--]zen/dir_watcher.cpp784
-rwxr-xr-x[-rw-r--r--]zen/dir_watcher.h150
-rwxr-xr-x[-rw-r--r--]zen/error_log.h270
-rwxr-xr-x[-rw-r--r--]zen/file_access.cpp3265
-rwxr-xr-x[-rw-r--r--]zen/file_access.h197
-rwxr-xr-x[-rw-r--r--]zen/file_error.h123
-rwxr-xr-x[-rw-r--r--]zen/file_id_def.h118
-rwxr-xr-x[-rw-r--r--]zen/file_io.cpp663
-rwxr-xr-x[-rw-r--r--]zen/file_io.h240
-rwxr-xr-x[-rw-r--r--]zen/file_traverser.cpp299
-rwxr-xr-x[-rw-r--r--]zen/file_traverser.h94
-rwxr-xr-x[-rw-r--r--]zen/fixed_list.h478
-rwxr-xr-x[-rw-r--r--]zen/format_unit.cpp614
-rwxr-xr-x[-rw-r--r--]zen/format_unit.h111
-rwxr-xr-x[-rw-r--r--]zen/globals.h125
-rwxr-xr-x[-rw-r--r--]zen/guid.h64
-rwxr-xr-x[-rw-r--r--]zen/i18n.h243
-rwxr-xr-x[-rw-r--r--]zen/optional.h190
-rwxr-xr-x[-rw-r--r--]zen/perf.h177
-rwxr-xr-x[-rw-r--r--]zen/process_priority.cpp144
-rwxr-xr-x[-rw-r--r--]zen/process_priority.h76
-rwxr-xr-x[-rw-r--r--]zen/recycler.cpp395
-rwxr-xr-x[-rw-r--r--]zen/recycler.h94
-rwxr-xr-x[-rw-r--r--]zen/scope_guard.h251
-rwxr-xr-x[-rw-r--r--]zen/serialize.h607
-rwxr-xr-x[-rw-r--r--]zen/shell_execute.h188
-rwxr-xr-x[-rw-r--r--]zen/stl_tools.h491
-rwxr-xr-x[-rw-r--r--]zen/string_base.h1230
-rwxr-xr-x[-rw-r--r--]zen/string_tools.h1420
-rwxr-xr-x[-rw-r--r--]zen/string_traits.h432
-rwxr-xr-x[-rw-r--r--]zen/symlink_target.h315
-rwxr-xr-x[-rw-r--r--]zen/sys_error.h266
-rwxr-xr-x[-rw-r--r--]zen/thread.h892
-rwxr-xr-x[-rw-r--r--]zen/time.h725
-rwxr-xr-x[-rw-r--r--]zen/type_tools.h206
-rwxr-xr-x[-rw-r--r--]zen/type_traits.h390
-rwxr-xr-x[-rw-r--r--]zen/utf.h952
-rwxr-xr-xzen/warn_static.h24
-rwxr-xr-x[-rw-r--r--]zen/xml_io.cpp168
-rwxr-xr-x[-rw-r--r--]zen/xml_io.h52
-rwxr-xr-x[-rw-r--r--]zen/zstring.cpp151
-rwxr-xr-x[-rw-r--r--]zen/zstring.h491
46 files changed, 7716 insertions, 11432 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h
index eed23477..7836ae81 100644..100755
--- a/zen/basic_math.h
+++ b/zen/basic_math.h
@@ -1,383 +1,383 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef BASIC_MATH_H_3472639843265675
-#define BASIC_MATH_H_3472639843265675
-
-#include <algorithm>
-#include <iterator>
-#include <limits>
-#include <cmath>
-#include <functional>
-#include <cassert>
-
-
-namespace numeric
-{
-template <class T> T abs(T value);
-template <class T> auto dist(T a, T b);
-template <class T> int sign(T value); //returns one of {-1, 0, 1}
-template <class T> T min(T a, T b, T c);
-template <class T> T max(T a, T b, T c);
-template <class T> bool isNull(T value);
-
-template <class T>
-void clamp(T& val, T minVal, T maxVal); //make sure minVal <= val && val <= maxVal
-template <class T>
-T clampCpy(T val, T minVal, T maxVal);
-
-template <class T, class InputIterator> //precondition: range must be sorted!
-auto nearMatch(const T& val, InputIterator first, InputIterator last);
-
-int round(double d); //"little rounding function"
-
-template <class N, class D>
-auto integerDivideRoundUp(N numerator, D denominator);
-
-template <size_t N, class T>
-T power(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 = nullptr); //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)
-{
- //static_assert(std::is_signed<T>::value, "");
- if (value < 0)
- return -value; //operator "?:" caveat: may be different type than "value"
- else
- return value;
-}
-
-template <class T> inline
-auto dist(T a, T b) //return type might be different than T, e.g. std::chrono::duration instead of std::chrono::time_point
-{
- return a > b ? a - b : b - a;
-}
-
-
-template <class T> inline
-int sign(T value) //returns one of {-1, 0, 1}
-{
- static_assert(std::is_signed<T>::value, "");
- return value < 0 ? -1 : (value > 0 ? 1 : 0);
-}
-
-
-template <class T> inline
-T min(T a, T b, T c) //don't follow std::min's "const T&(const T&, const T&)" API
-{
- if (a < b)
- return a < c ? a : c;
- else
- return b < c ? b : c;
- //return std::min(std::min(a, b), c);
-}
-
-
-template <class T> inline
-T max(T a, T b, T c)
-{
- if (a > b)
- return a > c ? a : c;
- else
- return b > c ? b : c;
- //return std::max(std::max(a, b), c);
-}
-
-
-template <class T> inline
-T clampCpy(T val, T minVal, T maxVal)
-{
- assert(minVal <= maxVal);
- if (val < minVal)
- return minVal;
- else if (val > maxVal)
- return maxVal;
- return val;
-}
-
-template <class T> inline
-void clamp(T& val, T minVal, T maxVal)
-{
- assert(minVal <= maxVal);
- if (val < minVal)
- val = minVal;
- else if (val > maxVal)
- val = maxVal;
-}
-
-
-/*
-part of C++11 now!
-template <class InputIterator, class Compare> inline
-std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare compLess)
-{
- //by factor 1.5 to 3 faster than boost::minmax_element (=two-step algorithm) for built-in types!
-
- InputIterator lowest = first;
- InputIterator largest = first;
-
- if (first != last)
- {
- auto minVal = *lowest; //nice speedup on 64 bit!
- auto maxVal = *largest; //
- for (;;)
- {
- ++first;
- if (first == last)
- break;
- const auto val = *first;
-
- if (compLess(maxVal, val))
- {
- largest = first;
- maxVal = val;
- }
- else if (compLess(val, minVal))
- {
- lowest = first;
- minVal = val;
- }
- }
- }
- 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, class InputIterator> inline
-auto nearMatch(const T& val, InputIterator first, InputIterator last)
-{
- if (first == last)
- return static_cast<decltype(*first)>(0);
-
- assert(std::is_sorted(first, last));
- InputIterator it = std::lower_bound(first, last, val);
- if (it == last)
- return *--last;
- if (it == first)
- return *first;
-
- const auto nextVal = *it;
- const auto prevVal = *--it;
- return val - prevVal < nextVal - val ? prevVal : nextVal;
-}
-
-
-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)
-{
- assert(d - 0.5 >= std::numeric_limits<int>::min() && //if double is larger than what int can represent:
- d + 0.5 <= std::numeric_limits<int>::max()); //=> undefined behavior!
- return static_cast<int>(d < 0 ? d - 0.5 : d + 0.5);
-}
-
-
-template <class N, class D> inline
-auto integerDivideRoundUp(N numerator, D denominator)
-{
- static_assert(std::is_integral<N>::value && std::is_unsigned<N>::value, "");
- static_assert(std::is_integral<D>::value && std::is_unsigned<D>::value, "");
- assert(denominator > 0);
- return (numerator + denominator - 1) / denominator;
-}
-
-
-namespace
-{
-template <size_t N, class T> struct PowerImpl;
-/*
- template <size_t N, class T> -> let's use non-recursive specializations to help the compiler
- 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(T value) { return value * value; } };
-template <class T> struct PowerImpl<3, T> { static T result(T value) { return value * value * value; } };
-}
-
-template <size_t n, class T> inline
-T power(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)
-{
- size_t n = 0; //avoid random-access requirement for iterator!
- 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;
-}
-
-
-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 which may have less than double precision!
-
- auto lessMedAbs = [m](double lhs, double rhs) { return abs(lhs - m) < abs(rhs - m); };
-
- std::nth_element(first, first + n / 2, last, lessMedAbs); //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
- return 0.5 * (abs(*std::max_element(first, first + n / 2, lessMedAbs) - m) + 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 degradation
-
- 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_H_3472639843265675
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef BASIC_MATH_H_3472639843265675
+#define BASIC_MATH_H_3472639843265675
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <cmath>
+#include <functional>
+#include <cassert>
+
+
+namespace numeric
+{
+template <class T> T abs(T value);
+template <class T> auto dist(T a, T b);
+template <class T> int sign(T value); //returns one of {-1, 0, 1}
+template <class T> T min(T a, T b, T c);
+template <class T> T max(T a, T b, T c);
+template <class T> bool isNull(T value);
+
+template <class T>
+void clamp(T& val, T minVal, T maxVal); //make sure minVal <= val && val <= maxVal
+template <class T>
+T clampCpy(T val, T minVal, T maxVal);
+
+template <class T, class InputIterator> //precondition: range must be sorted!
+auto nearMatch(const T& val, InputIterator first, InputIterator last);
+
+int round(double d); //"little rounding function"
+
+template <class N, class D>
+auto integerDivideRoundUp(N numerator, D denominator);
+
+template <size_t N, class T>
+T power(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 = nullptr); //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)
+{
+ //static_assert(std::is_signed<T>::value, "");
+ if (value < 0)
+ return -value; //operator "?:" caveat: may be different type than "value"
+ else
+ return value;
+}
+
+template <class T> inline
+auto dist(T a, T b) //return type might be different than T, e.g. std::chrono::duration instead of std::chrono::time_point
+{
+ return a > b ? a - b : b - a;
+}
+
+
+template <class T> inline
+int sign(T value) //returns one of {-1, 0, 1}
+{
+ static_assert(std::is_signed<T>::value, "");
+ return value < 0 ? -1 : (value > 0 ? 1 : 0);
+}
+
+
+template <class T> inline
+T min(T a, T b, T c) //don't follow std::min's "const T&(const T&, const T&)" API
+{
+ if (a < b)
+ return a < c ? a : c;
+ else
+ return b < c ? b : c;
+ //return std::min(std::min(a, b), c);
+}
+
+
+template <class T> inline
+T max(T a, T b, T c)
+{
+ if (a > b)
+ return a > c ? a : c;
+ else
+ return b > c ? b : c;
+ //return std::max(std::max(a, b), c);
+}
+
+
+template <class T> inline
+T clampCpy(T val, T minVal, T maxVal)
+{
+ assert(minVal <= maxVal);
+ if (val < minVal)
+ return minVal;
+ else if (val > maxVal)
+ return maxVal;
+ return val;
+}
+
+template <class T> inline
+void clamp(T& val, T minVal, T maxVal)
+{
+ assert(minVal <= maxVal);
+ if (val < minVal)
+ val = minVal;
+ else if (val > maxVal)
+ val = maxVal;
+}
+
+
+/*
+part of C++11 now!
+template <class InputIterator, class Compare> inline
+std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare compLess)
+{
+ //by factor 1.5 to 3 faster than boost::minmax_element (=two-step algorithm) for built-in types!
+
+ InputIterator lowest = first;
+ InputIterator largest = first;
+
+ if (first != last)
+ {
+ auto minVal = *lowest; //nice speedup on 64 bit!
+ auto maxVal = *largest; //
+ for (;;)
+ {
+ ++first;
+ if (first == last)
+ break;
+ const auto val = *first;
+
+ if (compLess(maxVal, val))
+ {
+ largest = first;
+ maxVal = val;
+ }
+ else if (compLess(val, minVal))
+ {
+ lowest = first;
+ minVal = val;
+ }
+ }
+ }
+ 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, class InputIterator> inline
+auto nearMatch(const T& val, InputIterator first, InputIterator last)
+{
+ if (first == last)
+ return static_cast<decltype(*first)>(0);
+
+ assert(std::is_sorted(first, last));
+ InputIterator it = std::lower_bound(first, last, val);
+ if (it == last)
+ return *--last;
+ if (it == first)
+ return *first;
+
+ const auto nextVal = *it;
+ const auto prevVal = *--it;
+ return val - prevVal < nextVal - val ? prevVal : nextVal;
+}
+
+
+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)
+{
+ assert(d - 0.5 >= std::numeric_limits<int>::min() && //if double is larger than what int can represent:
+ d + 0.5 <= std::numeric_limits<int>::max()); //=> undefined behavior!
+ return static_cast<int>(d < 0 ? d - 0.5 : d + 0.5);
+}
+
+
+template <class N, class D> inline
+auto integerDivideRoundUp(N numerator, D denominator)
+{
+ static_assert(std::is_integral<N>::value && std::is_unsigned<N>::value, "");
+ static_assert(std::is_integral<D>::value && std::is_unsigned<D>::value, "");
+ assert(denominator > 0);
+ return (numerator + denominator - 1) / denominator;
+}
+
+
+namespace
+{
+template <size_t N, class T> struct PowerImpl;
+/*
+ template <size_t N, class T> -> let's use non-recursive specializations to help the compiler
+ 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(T value) { return value * value; } };
+template <class T> struct PowerImpl<3, T> { static T result(T value) { return value * value * value; } };
+}
+
+template <size_t n, class T> inline
+T power(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)
+{
+ size_t n = 0; //avoid random-access requirement for iterator!
+ 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;
+}
+
+
+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 which may have less than double precision!
+
+ auto lessMedAbs = [m](double lhs, double rhs) { return abs(lhs - m) < abs(rhs - m); };
+
+ std::nth_element(first, first + n / 2, last, lessMedAbs); //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
+ return 0.5 * (abs(*std::max_element(first, first + n / 2, lessMedAbs) - m) + 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 technique avoiding overflow, see: http://www.netlib.org/blas/dnrm2.f -> only 10% performance degradation
+
+ 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_H_3472639843265675
diff --git a/zen/build_info.h b/zen/build_info.h
index 31c1d456..f3f7f5b1 100644..100755
--- a/zen/build_info.h
+++ b/zen/build_info.h
@@ -1,38 +1,29 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef BUILD_INFO_H_5928539285603428657
-#define BUILD_INFO_H_5928539285603428657
-
-namespace zen
-{
-//determine build info: defines ZEN_BUILD_32BIT or ZEN_BUILD_64BIT
-
-#ifdef ZEN_WIN
- #ifdef _WIN64 //_WIN32 is defined by the compiler for both 32 and 64 bit builds, unlike _WIN64
- #define ZEN_BUILD_64BIT
- #else
- #define ZEN_BUILD_32BIT
- #endif
-
-#else
- #ifdef __LP64__
- #define ZEN_BUILD_64BIT
- #else
- #define ZEN_BUILD_32BIT
- #endif
-#endif
-
-#ifdef ZEN_BUILD_32BIT
- static_assert(sizeof(void*) == 4, "");
-#endif
-
-#ifdef ZEN_BUILD_64BIT
- static_assert(sizeof(void*) == 8, "");
-#endif
-}
-
-#endif //BUILD_INFO_H_5928539285603428657
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef BUILD_INFO_H_5928539285603428657
+#define BUILD_INFO_H_5928539285603428657
+
+namespace zen
+{
+//determine build info: defines ZEN_BUILD_32BIT or ZEN_BUILD_64BIT
+
+ #ifdef __LP64__
+ #define ZEN_BUILD_64BIT
+ #else
+ #define ZEN_BUILD_32BIT
+ #endif
+
+#ifdef ZEN_BUILD_32BIT
+ static_assert(sizeof(void*) == 4, "");
+#endif
+
+#ifdef ZEN_BUILD_64BIT
+ static_assert(sizeof(void*) == 8, "");
+#endif
+}
+
+#endif //BUILD_INFO_H_5928539285603428657
diff --git a/zen/crc.h b/zen/crc.h
index 5c950efc..b4d53d12 100644..100755
--- a/zen/crc.h
+++ b/zen/crc.h
@@ -1,61 +1,54 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef CRC_H_23489275827847235
-#define CRC_H_23489275827847235
-
-#ifdef _MSC_VER
- #pragma warning(push)
- #pragma warning(disable: 4245) //'conversion' : conversion from 'int' to 'const boost::detail::mask_uint_t<0x08>::least', signed/unsigned mismatch
-#endif
-#include <boost/crc.hpp>
-#ifdef _MSC_VER
- #pragma warning(pop)
-#endif
-
-
-namespace zen
-{
-uint16_t getCrc16(const std::string& str);
-uint32_t getCrc32(const std::string& str);
-template <class ByteIterator> uint16_t getCrc16(ByteIterator first, ByteIterator last);
-template <class ByteIterator> uint32_t getCrc32(ByteIterator first, ByteIterator last);
-
-
-
-
-//------------------------- implementation -------------------------------
-inline uint16_t getCrc16(const std::string& str) { return getCrc16(str.begin(), str.end()); }
-inline uint32_t getCrc32(const std::string& str) { return getCrc32(str.begin(), str.end()); }
-
-
-template <class ByteIterator> inline
-uint16_t getCrc16(ByteIterator first, ByteIterator last)
-{
- static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, "");
- boost::crc_16_type result;
- if (first != last)
- result.process_bytes(&*first, last - first);
- auto rv = result.checksum();
- static_assert(sizeof(rv) == sizeof(uint16_t), "");
- return rv;
-}
-
-
-template <class ByteIterator> inline
-uint32_t getCrc32(ByteIterator first, ByteIterator last)
-{
- static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, "");
- boost::crc_32_type result;
- if (first != last)
- result.process_bytes(&*first, last - first);
- auto rv = result.checksum();
- static_assert(sizeof(rv) == sizeof(uint32_t), "");
- return rv;
-}
-}
-
-#endif //CRC_H_23489275827847235
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef CRC_H_23489275827847235
+#define CRC_H_23489275827847235
+
+#include <boost/crc.hpp>
+
+
+namespace zen
+{
+uint16_t getCrc16(const std::string& str);
+uint32_t getCrc32(const std::string& str);
+template <class ByteIterator> uint16_t getCrc16(ByteIterator first, ByteIterator last);
+template <class ByteIterator> uint32_t getCrc32(ByteIterator first, ByteIterator last);
+
+
+
+
+//------------------------- implementation -------------------------------
+inline uint16_t getCrc16(const std::string& str) { return getCrc16(str.begin(), str.end()); }
+inline uint32_t getCrc32(const std::string& str) { return getCrc32(str.begin(), str.end()); }
+
+
+template <class ByteIterator> inline
+uint16_t getCrc16(ByteIterator first, ByteIterator last)
+{
+ static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, "");
+ boost::crc_16_type result;
+ if (first != last)
+ result.process_bytes(&*first, last - first);
+ auto rv = result.checksum();
+ static_assert(sizeof(rv) == sizeof(uint16_t), "");
+ return rv;
+}
+
+
+template <class ByteIterator> inline
+uint32_t getCrc32(ByteIterator first, ByteIterator last)
+{
+ static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, "");
+ boost::crc_32_type result;
+ if (first != last)
+ result.process_bytes(&*first, last - first);
+ auto rv = result.checksum();
+ static_assert(sizeof(rv) == sizeof(uint32_t), "");
+ return rv;
+}
+}
+
+#endif //CRC_H_23489275827847235
diff --git a/zen/deprecate.h b/zen/deprecate.h
index 04bac442..94273ad8 100644..100755
--- a/zen/deprecate.h
+++ b/zen/deprecate.h
@@ -1,21 +1,14 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef DEPRECATE_H_234897087787348
-#define DEPRECATE_H_234897087787348
-
-//compiler macros: http://predef.sourceforge.net/precomp.html
-#ifdef __GNUC__
- #define ZEN_DEPRECATE __attribute__ ((deprecated))
-
-#elif defined _MSC_VER
- #define ZEN_DEPRECATE __declspec(deprecated)
-
-#else
- #error add your platform here!
-#endif
-
-#endif //DEPRECATE_H_234897087787348
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef DEPRECATE_H_234897087787348
+#define DEPRECATE_H_234897087787348
+
+//compiler macros: http://predef.sourceforge.net/precomp.html
+ #define ZEN_DEPRECATE __attribute__ ((deprecated))
+
+
+#endif //DEPRECATE_H_234897087787348
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp
index 79e8aeb7..1b6f6f5c 100644..100755
--- a/zen/dir_watcher.cpp
+++ b/zen/dir_watcher.cpp
@@ -1,626 +1,158 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "dir_watcher.h"
-#include <algorithm>
-#include <set>
-#include "thread.h"
-#include "scope_guard.h"
-#include "basic_math.h"
-
-#ifdef ZEN_WIN
- #include "device_notify.h"
- #include "win.h" //includes "windows.h"
- #include "long_path_prefix.h"
- #include "optional.h"
-
-#elif defined ZEN_LINUX
- #include <map>
- #include <sys/inotify.h>
- #include <fcntl.h> //fcntl
- #include <unistd.h> //close
- #include <limits.h> //NAME_MAX
- #include "file_traverser.h"
-
-#elif defined ZEN_MAC
- #include <CoreServices/CoreServices.h>
- #include "osx_string.h"
-#endif
-
-using namespace zen;
-
-
-#ifdef ZEN_WIN
-namespace
-{
-class SharedData
-{
-public:
- //context of worker thread
- bool tryAddChanges(const char* buffer, DWORD bytesWritten, const Zstring& dirPathPf) //noexcept; return false on failure (already reported!)
- {
- std::lock_guard<std::mutex> dummy(lockAccess);
-
- if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change
- changedFiles.emplace_back(DirWatcher::ACTION_CREATE, L"Overflow.");
- else
- {
- size_t offset = 0;
- for (;;)
- {
- const FILE_NOTIFY_INFORMATION& notifyInfo = *reinterpret_cast<const FILE_NOTIFY_INFORMATION*>(buffer + offset);
-
- //access-violation crash dumps suggest that the buffer can be corrupt, so let's validate:
- if (offset + sizeof(FILE_NOTIFY_INFORMATION) > bytesWritten ||
- offset + offsetof(FILE_NOTIFY_INFORMATION, FileName) + notifyInfo.FileNameLength > bytesWritten)
- {
- reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirPathPf)), L"ReadDirectoryChangesW: corrupt data returned");
- return false;
- }
-
- const Zstring fullpath = dirPathPf + Zstring(notifyInfo.FileName, notifyInfo.FileNameLength / sizeof(WCHAR));
-
- [&]
- {
- //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately!
- //and if this child element is a .ffs_lock file we'll want to ignore all associated events!
- if (notifyInfo.Action == FILE_ACTION_MODIFIED)
- {
- //note: this check will not work if top watched directory has been renamed
- const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(fullpath).c_str());
- if (ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY)) //returns true for (dir-)symlinks also
- return;
- }
-
- //note: a move across directories will show up as FILE_ACTION_ADDED/FILE_ACTION_REMOVED!
- switch (notifyInfo.Action)
- {
- case FILE_ACTION_ADDED:
- case FILE_ACTION_RENAMED_NEW_NAME: //harmonize with "move" which is notified as "create + delete"
- changedFiles.emplace_back(DirWatcher::ACTION_CREATE, fullpath);
- break;
- case FILE_ACTION_REMOVED:
- case FILE_ACTION_RENAMED_OLD_NAME:
- changedFiles.emplace_back(DirWatcher::ACTION_DELETE, fullpath);
- break;
- case FILE_ACTION_MODIFIED:
- changedFiles.emplace_back(DirWatcher::ACTION_UPDATE, fullpath);
- break;
- }
- }();
-
- if (notifyInfo.NextEntryOffset == 0)
- break;
- offset += notifyInfo.NextEntryOffset;
- }
- }
- return true;
- }
-
- ////context of main thread
- //void addChange(const Zstring& dirPath) //throw ()
- //{
- // std::lock_guard<std::mutex> dummy(lockAccess);
- // changedFiles.insert(dirPath);
- //}
-
-
- //context of main thread
- void fetchChanges(std::vector<DirWatcher::Entry>& output) //throw FileError
- {
- std::lock_guard<std::mutex> dummy(lockAccess);
-
- //first check whether errors occurred in thread
- if (errorInfo)
- throw FileError(copyStringTo<std::wstring>(errorInfo->msg),
- copyStringTo<std::wstring>(errorInfo->descr));
-
- output.swap(changedFiles);
- changedFiles.clear();
- }
-
-
- //context of worker thread
- void reportError(const std::wstring& msg, const std::wstring& description) //throw()
- {
- std::lock_guard<std::mutex> dummy(lockAccess);
- errorInfo = ErrorInfo({ copyStringTo<BasicWString>(msg), copyStringTo<BasicWString>(description) });
- }
-
-private:
- using BasicWString = Zbase<wchar_t>; //thread safe string class for UI texts
-
- std::mutex lockAccess;
- std::vector<DirWatcher::Entry> changedFiles;
-
- struct ErrorInfo
- {
- BasicWString msg;
- BasicWString descr;
- };
- Opt<ErrorInfo> errorInfo; //non-empty if errors occurred 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),
- dirPathPf(appendSeparator(directory))
- {
- hDir = ::CreateFile(applyLongPathPrefix(dirPathPf).c_str(), //_In_ LPCTSTR lpFileName,
- FILE_LIST_DIRECTORY, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- FILE_FLAG_BACKUP_SEMANTICS |
- FILE_FLAG_OVERLAPPED, //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hDir == INVALID_HANDLE_VALUE)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(directory)), L"CreateFile");
-
- //end of constructor, no need to start managing "hDir"
- }
-
- ReadChangesAsync(ReadChangesAsync&& other) : shared_(std::move(other.shared_)),
- dirPathPf(std::move(other.dirPathPf)),
- hDir(other.hDir)
- {
- other.hDir = INVALID_HANDLE_VALUE;
- }
-
- ~ReadChangesAsync()
- {
- if (hDir != INVALID_HANDLE_VALUE) //valid hDir is NOT an invariant, see move constructor!
- ::CloseHandle(hDir);
- }
-
- void operator()() const //thread entry
- {
- 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 (;;)
- {
- interruptionPoint(); //throw ThreadInterruption
-
- //actual work
- OVERLAPPED overlapped = {};
- overlapped.hEvent = ::CreateEvent(nullptr, //__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
- true, //__in BOOL bManualReset,
- false, //__in BOOL bInitialState,
- nullptr); //__in_opt LPCTSTR lpName
- if (!overlapped.hEvent)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirPathPf)), formatSystemError(L"CreateEvent", ec));
- }
- ZEN_ON_SCOPE_EXIT(::CloseHandle(overlapped.hEvent));
-
- DWORD bytesReturned = 0; //should not be needed for async calls, still pass it to help broken drivers
-
- //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,
- &bytesReturned, // __out_opt LPDWORD lpBytesReturned,
- &overlapped, // __inout_opt LPOVERLAPPED lpOverlapped,
- nullptr)) // __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirPathPf)), formatSystemError(L"ReadDirectoryChangesW", ec));
- }
-
- //async I/O is a resource that needs to be guarded since it will write to local variable "buffer"!
- auto guardAio = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&]
- {
- //Canceling Pending I/O Operations: https://msdn.microsoft.com/en-us/library/aa363789
-#ifdef ZEN_WIN_VISTA_AND_LATER
- if (::CancelIoEx(hDir, &overlapped) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND)
-#else
- if (::CancelIo(hDir) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND)
-#endif
- {
- DWORD bytesWritten = 0;
- ::GetOverlappedResult(hDir, &overlapped, &bytesWritten, true); //must wait until cancellation is complete!
- }
- });
-
- //wait for results
- DWORD bytesWritten = 0;
- while (!::GetOverlappedResult(hDir, //__in HANDLE hFile,
- &overlapped, //__in LPOVERLAPPED lpOverlapped,
- &bytesWritten, //__out LPDWORD lpNumberOfBytesTransferred,
- false)) //__in BOOL bWait
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- if (ec != ERROR_IO_INCOMPLETE)
- return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirPathPf)), formatSystemError(L"GetOverlappedResult", ec));
-
- //execute asynchronous procedure calls (APC) queued on this thread
- ::SleepEx(50, // __in DWORD dwMilliseconds,
- true); // __in BOOL bAlertable
-
- interruptionPoint(); //throw ThreadInterruption
- }
- guardAio.dismiss();
-
- if (!shared_->tryAddChanges(&buffer[0], bytesWritten, dirPathPf)) //noexcept
- return;
- }
- }
-
- HANDLE getDirHandle() const { return hDir; } //for reading/monitoring purposes only, don't abuse (e.g. close handle)!
-
-private:
- ReadChangesAsync (const ReadChangesAsync&) = delete;
- ReadChangesAsync& operator=(const ReadChangesAsync&) = delete;
-
- //shared between main and worker:
- std::shared_ptr<SharedData> shared_;
- //worker thread only:
- Zstring dirPathPf; //thread safe!
- HANDLE hDir = INVALID_HANDLE_VALUE;
-};
-
-
-class HandleVolumeRemoval
-{
-public:
- HandleVolumeRemoval(HANDLE hDir,
- const Zstring& displayPath,
- InterruptibleThread& worker) :
- notificationHandle(registerFolderRemovalNotification(hDir, //throw FileError
- displayPath,
- [this] { this->onRequestRemoval (); }, //noexcept!
- [this](bool successful) { this->onRemovalFinished(); })), //
- worker_(worker) {}
-
- ~HandleVolumeRemoval()
- {
- unregisterDeviceNotification(notificationHandle);
- }
-
- //all functions are called by main thread!
-
- bool requestReceived() const { return removalRequested; }
- bool finished() const { return operationComplete; }
-
-private:
- void onRequestRemoval() //noexcept!
- {
- //must release hDir immediately => stop monitoring!
- if (worker_.joinable()) //= join() precondition: play safe; can't trust Windows to only call-back once
- {
- worker_.interrupt();
- worker_.join(); //we assume precondition "worker.joinable()"!!!
- //now hDir should have been released
- }
-
- removalRequested = true;
- } //don't throw!
-
- void onRemovalFinished() { operationComplete = true; } //noexcept!
-
- DeviceNotificationHandle* notificationHandle;
- InterruptibleThread& worker_;
- bool removalRequested = false;
- bool operationComplete = false;
-};
-}
-
-
-struct DirWatcher::Impl
-{
- InterruptibleThread worker;
- std::shared_ptr<SharedData> shared;
- std::unique_ptr<HandleVolumeRemoval> volRemoval;
-};
-
-
-DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
- baseDirPath(dirPath),
- pimpl_(std::make_unique<Impl>())
-{
- pimpl_->shared = std::make_shared<SharedData>();
-
- ReadChangesAsync reader(dirPath, pimpl_->shared); //throw FileError
- pimpl_->volRemoval = std::make_unique<HandleVolumeRemoval>(reader.getDirHandle(), dirPath, pimpl_->worker); //throw FileError
- pimpl_->worker = InterruptibleThread(std::move(reader));
-}
-
-
-DirWatcher::~DirWatcher()
-{
- if (pimpl_->worker.joinable()) //= thread::detach() precondition! -> may already be joined by HandleVolumeRemoval::onRequestRemoval()
- {
- pimpl_->worker.interrupt();
- pimpl_->worker.detach(); //we don't have time to wait... would take ~50ms
- //Windows caveat: exitting the app will kill the thread and leak memory!
- }
-}
-
-
-std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>& processGuiMessages) //throw FileError
-{
- std::vector<Entry> output;
- pimpl_->shared->fetchChanges(output); //throw FileError
-
- //wait until device removal is confirmed, to prevent locking hDir again by some new watch!
- if (pimpl_->volRemoval->requestReceived())
- {
- const auto startTime = std::chrono::steady_clock::now();
- //HandleVolumeRemoval::finished() not guaranteed! note: Windows gives unresponsive applications ca. 10 seconds until unmounting the usb stick in worst case
-
- while (!pimpl_->volRemoval->finished() &&
- numeric::dist(std::chrono::steady_clock::now(), startTime) < std::chrono::seconds(15)) //handle potential chrono wrap-around!
- {
- processGuiMessages(); //DBT_DEVICEREMOVECOMPLETE message is sent here!
- std::this_thread::sleep_for(std::chrono::milliseconds(50));
- }
-
- output.emplace_back(ACTION_DELETE, baseDirPath); //report removal as change to main directory
- }
-
- return output;
-}
-
-
-#elif defined ZEN_LINUX
-struct DirWatcher::Impl
-{
- int notifDescr = 0;
- std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr"
-};
-
-
-DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
- baseDirPath(dirPath),
- pimpl_(std::make_unique<Impl>())
-{
- //get all subdirectories
- std::vector<Zstring> fullFolderList { baseDirPath };
- {
- std::function<void (const Zstring& path)> traverse;
-
- traverse = [&traverse, &fullFolderList](const Zstring& path)
- {
- traverseFolder(path, nullptr,
- [&](const FolderInfo& fi ) { fullFolderList.push_back(fi.fullPath); traverse(fi.fullPath); },
- nullptr, //don't traverse into symlinks (analog to windows build)
- [&](const std::wstring& errorMsg) { throw FileError(errorMsg); });
- };
-
- traverse(baseDirPath);
- }
-
- //init
- pimpl_->notifDescr = ::inotify_init();
- if (pimpl_->notifDescr == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"inotify_init");
-
- ZEN_ON_SCOPE_FAIL( ::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_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"fcntl");
-
- //add watches
- for (const Zstring& subDirPath : fullFolderList)
- {
- int wd = ::inotify_add_watch(pimpl_->notifDescr, subDirPath.c_str(),
- IN_ONLYDIR | //"Only watch pathname if it is a directory."
- IN_DONT_FOLLOW | //don't follow symbolic links
- IN_CREATE |
- IN_MODIFY |
- IN_CLOSE_WRITE |
- IN_DELETE |
- IN_DELETE_SELF |
- IN_MOVED_FROM |
- IN_MOVED_TO |
- IN_MOVE_SELF);
- if (wd == -1)
- {
- const ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
- if (ec == ENOSPC) //fix misleading system message "No space left on device"
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)),
- formatSystemError(L"inotify_add_watch", numberTo<std::wstring>(ec), L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource."));
-
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError(L"inotify_add_watch", ec));
- }
-
- pimpl_->watchDescrs.emplace(wd, appendSeparator(subDirPath));
- }
-}
-
-
-DirWatcher::~DirWatcher()
-{
- ::close(pimpl_->notifDescr); //associated watches are removed automatically!
-}
-
-
-std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError
-{
- std::vector<char> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1));
-
- ssize_t bytesRead = 0;
- do
- {
- //non-blocking call, see O_NONBLOCK
- bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size());
- }
- while (bytesRead < 0 && errno == EINTR); //"Interrupted function call; When this happens, you should try the call again."
-
- if (bytesRead < 0)
- {
- if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found
- return std::vector<Entry>();
-
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"read");
- }
-
- std::vector<Entry> output;
-
- 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 it = pimpl_->watchDescrs.find(evt.wd);
- if (it != 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!
- const Zstring fullname = it->second + evt.name;
-
- if ((evt.mask & IN_CREATE) ||
- (evt.mask & IN_MOVED_TO))
- output.emplace_back(ACTION_CREATE, fullname);
- else if ((evt.mask & IN_MODIFY) ||
- (evt.mask & IN_CLOSE_WRITE))
- output.emplace_back(ACTION_UPDATE, fullname);
- else if ((evt.mask & IN_DELETE ) ||
- (evt.mask & IN_DELETE_SELF) ||
- (evt.mask & IN_MOVE_SELF ) ||
- (evt.mask & IN_MOVED_FROM))
- output.emplace_back(ACTION_DELETE, fullname);
- }
- }
- bytePos += sizeof(struct ::inotify_event) + evt.len;
- }
-
- return output;
-}
-
-#elif defined ZEN_MAC
-namespace
-{
-void eventCallback(ConstFSEventStreamRef streamRef,
- void* clientCallBackInfo,
- size_t numEvents,
- void* eventPaths,
- const FSEventStreamEventFlags eventFlags[],
- const FSEventStreamEventId eventIds[])
-{
- std::vector<DirWatcher::Entry>& changedFiles = *static_cast<std::vector<DirWatcher::Entry>*>(clientCallBackInfo);
-
- auto paths = static_cast<const char**>(eventPaths);
- for (size_t i = 0; i < numEvents; ++i)
- {
- //::printf("0x%08x\t%s\n", static_cast<unsigned int>(eventFlags[i]), paths[i]);
-
- //events are aggregated => it's possible to see a single event with flags
- //kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemRemoved
-
- //https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
- if (eventFlags[i] & kFSEventStreamEventFlagItemCreated ||
- eventFlags[i] & kFSEventStreamEventFlagMount)
- changedFiles.emplace_back(DirWatcher::ACTION_CREATE, paths[i]);
- if (eventFlags[i] & kFSEventStreamEventFlagItemModified || //
- eventFlags[i] & kFSEventStreamEventFlagItemXattrMod || //
- eventFlags[i] & kFSEventStreamEventFlagItemChangeOwner || //aggregate these into a single event
- eventFlags[i] & kFSEventStreamEventFlagItemInodeMetaMod || //
- eventFlags[i] & kFSEventStreamEventFlagItemFinderInfoMod || //
- eventFlags[i] & kFSEventStreamEventFlagItemRenamed || //OS X sends the same event flag for both old and new names!!!
- eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) //something changed in one of the subdirs: NOT expected due to kFSEventStreamCreateFlagFileEvents
- changedFiles.emplace_back(DirWatcher::ACTION_UPDATE, paths[i]);
- if (eventFlags[i] & kFSEventStreamEventFlagItemRemoved ||
- eventFlags[i] & kFSEventStreamEventFlagRootChanged || //root is (indirectly) deleted or renamed
- eventFlags[i] & kFSEventStreamEventFlagUnmount)
- changedFiles.emplace_back(DirWatcher::ACTION_DELETE, paths[i]);
-
- //kFSEventStreamEventFlagEventIdsWrapped -> irrelevant!
- //kFSEventStreamEventFlagHistoryDone -> not expected due to kFSEventStreamEventIdSinceNow below
- }
-}
-}
-
-
-struct DirWatcher::Impl
-{
- FSEventStreamRef eventStream = nullptr;
- std::vector<DirWatcher::Entry> changedFiles;
-};
-
-
-DirWatcher::DirWatcher(const Zstring& dirPath) :
- baseDirPath(dirPath),
- pimpl_(std::make_unique<Impl>())
-{
- CFStringRef dirpathCf = nullptr;
- try
- {
- dirpathCf = osx::createCFString(baseDirPath.c_str()); //throw SysError
- }
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), e.toString()); }
- ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCf));
-
- CFArrayRef dirpathCfArray = ::CFArrayCreate(nullptr, //CFAllocatorRef allocator,
- reinterpret_cast<const void**>(&dirpathCf), //const void** values,
- 1, //CFIndex numValues,
- nullptr); //const CFArrayCallBacks* callBacks
- if (!dirpathCfArray)
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"Function call failed: CFArrayCreate"); //no error code documented!
- ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCfArray));
-
- FSEventStreamContext context = {};
- context.info = &pimpl_->changedFiles;
-
- //can this fail?? not documented!
- pimpl_->eventStream = ::FSEventStreamCreate(nullptr, //CFAllocatorRef allocator,
- &eventCallback, //FSEventStreamCallback callback,
- &context, //FSEventStreamContext* context,
- dirpathCfArray, //CFArrayRef pathsToWatch,
- kFSEventStreamEventIdSinceNow, //FSEventStreamEventId sinceWhen,
- 0, //CFTimeInterval latency, in seconds
- kFSEventStreamCreateFlagWatchRoot |
- kFSEventStreamCreateFlagFileEvents); //FSEventStreamCreateFlags flags
- ZEN_ON_SCOPE_FAIL( ::FSEventStreamRelease(pimpl_->eventStream); );
-
- //no-fail:
- ::FSEventStreamScheduleWithRunLoop(pimpl_->eventStream, //FSEventStreamRef streamRef,
- ::CFRunLoopGetCurrent(), //CFRunLoopRef runLoop; CFRunLoopGetCurrent(): failure not documented!
- kCFRunLoopDefaultMode); //CFStringRef runLoopMode
- ZEN_ON_SCOPE_FAIL( ::FSEventStreamInvalidate(pimpl_->eventStream); );
-
- if (!::FSEventStreamStart(pimpl_->eventStream))
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"Function call failed: FSEventStreamStart"); //no error code documented!
-}
-
-
-DirWatcher::~DirWatcher()
-{
- ::FSEventStreamStop (pimpl_->eventStream);
- ::FSEventStreamInvalidate(pimpl_->eventStream);
- ::FSEventStreamRelease (pimpl_->eventStream);
-}
-
-
-std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&)
-{
- ::FSEventStreamFlushSync(pimpl_->eventStream); //flushes pending events + execs runloop
-
- std::vector<DirWatcher::Entry> changes;
- changes.swap(pimpl_->changedFiles);
- return changes;
-}
-#endif
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "dir_watcher.h"
+#include <algorithm>
+#include <set>
+#include "thread.h"
+#include "scope_guard.h"
+#include "basic_math.h"
+
+ #include <map>
+ #include <sys/inotify.h>
+ #include <fcntl.h> //fcntl
+ #include <unistd.h> //close
+ #include <limits.h> //NAME_MAX
+ #include "file_traverser.h"
+
+
+using namespace zen;
+
+
+struct DirWatcher::Impl
+{
+ int notifDescr = 0;
+ std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr"
+};
+
+
+DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
+ baseDirPath(dirPath),
+ pimpl_(std::make_unique<Impl>())
+{
+ //get all subdirectories
+ std::vector<Zstring> fullFolderList { baseDirPath };
+ {
+ std::function<void (const Zstring& path)> traverse;
+
+ traverse = [&traverse, &fullFolderList](const Zstring& path)
+ {
+ traverseFolder(path, nullptr,
+ [&](const FolderInfo& fi ) { fullFolderList.push_back(fi.fullPath); traverse(fi.fullPath); },
+ nullptr, //don't traverse into symlinks (analog to windows build)
+ [&](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+ };
+
+ traverse(baseDirPath);
+ }
+
+ //init
+ pimpl_->notifDescr = ::inotify_init();
+ if (pimpl_->notifDescr == -1)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"inotify_init");
+
+ ZEN_ON_SCOPE_FAIL( ::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_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"fcntl");
+
+ //add watches
+ for (const Zstring& subDirPath : fullFolderList)
+ {
+ int wd = ::inotify_add_watch(pimpl_->notifDescr, subDirPath.c_str(),
+ IN_ONLYDIR | //"Only watch pathname if it is a directory."
+ IN_DONT_FOLLOW | //don't follow symbolic links
+ IN_CREATE |
+ IN_MODIFY |
+ IN_CLOSE_WRITE |
+ IN_DELETE |
+ IN_DELETE_SELF |
+ IN_MOVED_FROM |
+ IN_MOVED_TO |
+ IN_MOVE_SELF);
+ if (wd == -1)
+ {
+ const ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
+ if (ec == ENOSPC) //fix misleading system message "No space left on device"
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)),
+ formatSystemError(L"inotify_add_watch", numberTo<std::wstring>(ec), L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource."));
+
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError(L"inotify_add_watch", ec));
+ }
+
+ pimpl_->watchDescrs.emplace(wd, appendSeparator(subDirPath));
+ }
+}
+
+
+DirWatcher::~DirWatcher()
+{
+ ::close(pimpl_->notifDescr); //associated watches are removed automatically!
+}
+
+
+std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError
+{
+ std::vector<char> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1));
+
+ ssize_t bytesRead = 0;
+ do
+ {
+ //non-blocking call, see O_NONBLOCK
+ bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size());
+ }
+ while (bytesRead < 0 && errno == EINTR); //"Interrupted function call; When this happens, you should try the call again."
+
+ if (bytesRead < 0)
+ {
+ if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found
+ return std::vector<Entry>();
+
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"read");
+ }
+
+ std::vector<Entry> output;
+
+ 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 it = pimpl_->watchDescrs.find(evt.wd);
+ if (it != 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!
+ const Zstring fullname = it->second + evt.name;
+
+ if ((evt.mask & IN_CREATE) ||
+ (evt.mask & IN_MOVED_TO))
+ output.emplace_back(ACTION_CREATE, fullname);
+ else if ((evt.mask & IN_MODIFY) ||
+ (evt.mask & IN_CLOSE_WRITE))
+ output.emplace_back(ACTION_UPDATE, fullname);
+ else if ((evt.mask & IN_DELETE ) ||
+ (evt.mask & IN_DELETE_SELF) ||
+ (evt.mask & IN_MOVE_SELF ) ||
+ (evt.mask & IN_MOVED_FROM))
+ output.emplace_back(ACTION_DELETE, fullname);
+ }
+ }
+ bytePos += sizeof(struct ::inotify_event) + evt.len;
+ }
+
+ return output;
+}
+
diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h
index 5676555f..b16cd417 100644..100755
--- a/zen/dir_watcher.h
+++ b/zen/dir_watcher.h
@@ -1,75 +1,75 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef DIR_WATCHER_348577025748023458
-#define DIR_WATCHER_348577025748023458
-
-#include <vector>
-#include <memory>
-#include <functional>
-#include "file_error.h"
-
-
-namespace zen
-{
-//Windows: ReadDirectoryChangesW https://msdn.microsoft.com/en-us/library/aa365465
-//Linux: inotify http://linux.die.net/man/7/inotify
-//OS X: kqueue http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/kqueue.2.html
-
-//watch directory including subdirectories
-/*
-!Note handling of directories!:
- Windows: removal of top watched directory is NOT notified when watching the dir handle, e.g. brute force usb stick removal,
- (watchting for GUID_DEVINTERFACE_WPD OTOH works fine!)
- 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(!) + additional changes in subfolders
- now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!)
-
- Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher!
- removal of top watched directory is NOT notified!
-
- OS X: everything works as expected; renaming of top level folder is also detected
-
- Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are detected
-*/
-class DirWatcher
-{
-public:
- DirWatcher(const Zstring& dirPath); //throw FileError
- ~DirWatcher();
-
- enum ActionType
- {
- ACTION_CREATE, //informal only!
- ACTION_UPDATE, //use for debugging/logging only!
- ACTION_DELETE, //
- };
-
- struct Entry
- {
- Entry() {}
- Entry(ActionType action, const Zstring& filepath) : action_(action), filepath_(filepath) {}
-
- ActionType action_ = ACTION_CREATE;
- Zstring filepath_;
- };
-
- //extract accumulated changes since last call
- std::vector<Entry> getChanges(const std::function<void()>& processGuiMessages); //throw FileError
-
-private:
- DirWatcher (const DirWatcher&) = delete;
- DirWatcher& operator=(const DirWatcher&) = delete;
-
- const Zstring baseDirPath;
-
- struct Impl;
- const std::unique_ptr<Impl> pimpl_;
-};
-
-}
-
-#endif
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef DIR_WATCHER_348577025748023458
+#define DIR_WATCHER_348577025748023458
+
+#include <vector>
+#include <memory>
+#include <functional>
+#include "file_error.h"
+
+
+namespace zen
+{
+//Windows: ReadDirectoryChangesW https://msdn.microsoft.com/en-us/library/aa365465
+//Linux: inotify http://linux.die.net/man/7/inotify
+//OS X: kqueue http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/kqueue.2.html
+
+//watch directory including subdirectories
+/*
+!Note handling of directories!:
+ Windows: removal of top watched directory is NOT notified when watching the dir handle, e.g. brute force usb stick removal,
+ (watchting for GUID_DEVINTERFACE_WPD OTOH works fine!)
+ 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(!) + additional changes in subfolders
+ now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!)
+
+ Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher!
+ removal of top watched directory is NOT notified!
+
+ OS X: everything works as expected; renaming of top level folder is also detected
+
+ Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are detected
+*/
+class DirWatcher
+{
+public:
+ DirWatcher(const Zstring& dirPath); //throw FileError
+ ~DirWatcher();
+
+ enum ActionType
+ {
+ ACTION_CREATE, //informal only!
+ ACTION_UPDATE, //use for debugging/logging only!
+ ACTION_DELETE, //
+ };
+
+ struct Entry
+ {
+ Entry() {}
+ Entry(ActionType action, const Zstring& filepath) : action_(action), filepath_(filepath) {}
+
+ ActionType action_ = ACTION_CREATE;
+ Zstring filepath_;
+ };
+
+ //extract accumulated changes since last call
+ std::vector<Entry> getChanges(const std::function<void()>& processGuiMessages); //throw FileError
+
+private:
+ DirWatcher (const DirWatcher&) = delete;
+ DirWatcher& operator=(const DirWatcher&) = delete;
+
+ const Zstring baseDirPath;
+
+ struct Impl;
+ const std::unique_ptr<Impl> pimpl_;
+};
+
+}
+
+#endif
diff --git a/zen/error_log.h b/zen/error_log.h
index 5d188e9d..1a9d2679 100644..100755
--- a/zen/error_log.h
+++ b/zen/error_log.h
@@ -1,135 +1,135 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef ERROR_LOG_H_8917590832147915
-#define ERROR_LOG_H_8917590832147915
-
-#include <cassert>
-#include <algorithm>
-#include <vector>
-#include <string>
-#include "time.h"
-#include "i18n.h"
-#include "string_base.h"
-
-
-namespace zen
-{
-enum MessageType
-{
- TYPE_INFO = 0x1,
- TYPE_WARNING = 0x2,
- TYPE_ERROR = 0x4,
- TYPE_FATAL_ERROR = 0x8,
-};
-
-using MsgString = Zbase<wchar_t>; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries" memory block below (think 1 million items)
-
-struct LogEntry
-{
- time_t time;
- MessageType type;
- MsgString message;
-};
-
-template <class String>
-String formatMessage(const LogEntry& entry);
-
-
-class ErrorLog
-{
-public:
- template <class String> //a wchar_t-based string!
- void logMsg(const String& text, MessageType type);
-
- int getItemCount(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const;
-
- //subset of std::vector<> interface:
- using const_iterator = std::vector<LogEntry>::const_iterator;
- const_iterator begin() const { return entries.begin(); }
- const_iterator end () const { return entries.end (); }
- bool empty() const { return entries.empty(); }
-
-private:
- std::vector<LogEntry> entries; //list of non-resolved errors and warnings
-};
-
-
-
-
-
-
-
-
-
-//######################## implementation ##########################
-template <class String> inline
-void ErrorLog::logMsg(const String& text, zen::MessageType type)
-{
- const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(text) };
- entries.push_back(newEntry);
-}
-
-
-inline
-int ErrorLog::getItemCount(int typeFilter) const
-{
- return static_cast<int>(std::count_if(entries.begin(), entries.end(), [&](const LogEntry& e) { return e.type & typeFilter; }));
-}
-
-
-namespace
-{
-template <class String>
-String formatMessageImpl(const LogEntry& entry) //internal linkage
-{
- auto getTypeName = [&]
- {
- switch (entry.type)
- {
- case TYPE_INFO:
- return _("Info");
- case TYPE_WARNING:
- return _("Warning");
- case TYPE_ERROR:
- return _("Error");
- case TYPE_FATAL_ERROR:
- return _("Serious Error");
- }
- assert(false);
- return std::wstring();
- };
-
- String formattedText = L"[" + formatTime<String>(FORMAT_TIME, localTime(entry.time)) + L"] " + copyStringTo<String>(getTypeName()) + L": ";
- const size_t prefixLen = formattedText.size();
-
- for (auto it = entry.message.begin(); it != entry.message.end(); )
- if (*it == L'\n')
- {
- formattedText += L'\n';
-
- String blanks;
- blanks.resize(prefixLen, L' ');
- formattedText += blanks;
-
- do //skip duplicate newlines
- {
- ++it;
- }
- while (it != entry.message.end() && *it == L'\n');
- }
- else
- formattedText += *it++;
-
- return formattedText;
-}
-}
-
-template <class String> inline
-String formatMessage(const LogEntry& entry) { return formatMessageImpl<String>(entry); }
-}
-
-#endif //ERROR_LOG_H_8917590832147915
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef ERROR_LOG_H_8917590832147915
+#define ERROR_LOG_H_8917590832147915
+
+#include <cassert>
+#include <algorithm>
+#include <vector>
+#include <string>
+#include "time.h"
+#include "i18n.h"
+#include "string_base.h"
+
+
+namespace zen
+{
+enum MessageType
+{
+ TYPE_INFO = 0x1,
+ TYPE_WARNING = 0x2,
+ TYPE_ERROR = 0x4,
+ TYPE_FATAL_ERROR = 0x8,
+};
+
+using MsgString = Zbase<wchar_t>; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries" memory block below (think 1 million items)
+
+struct LogEntry
+{
+ time_t time;
+ MessageType type;
+ MsgString message;
+};
+
+template <class String>
+String formatMessage(const LogEntry& entry);
+
+
+class ErrorLog
+{
+public:
+ template <class String> //a wchar_t-based string!
+ void logMsg(const String& text, MessageType type);
+
+ int getItemCount(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const;
+
+ //subset of std::vector<> interface:
+ using const_iterator = std::vector<LogEntry>::const_iterator;
+ const_iterator begin() const { return entries.begin(); }
+ const_iterator end () const { return entries.end (); }
+ bool empty() const { return entries.empty(); }
+
+private:
+ std::vector<LogEntry> entries; //list of non-resolved errors and warnings
+};
+
+
+
+
+
+
+
+
+
+//######################## implementation ##########################
+template <class String> inline
+void ErrorLog::logMsg(const String& text, zen::MessageType type)
+{
+ const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(text) };
+ entries.push_back(newEntry);
+}
+
+
+inline
+int ErrorLog::getItemCount(int typeFilter) const
+{
+ return static_cast<int>(std::count_if(entries.begin(), entries.end(), [&](const LogEntry& e) { return e.type & typeFilter; }));
+}
+
+
+namespace
+{
+template <class String>
+String formatMessageImpl(const LogEntry& entry) //internal linkage
+{
+ auto getTypeName = [&]
+ {
+ switch (entry.type)
+ {
+ case TYPE_INFO:
+ return _("Info");
+ case TYPE_WARNING:
+ return _("Warning");
+ case TYPE_ERROR:
+ return _("Error");
+ case TYPE_FATAL_ERROR:
+ return _("Serious Error");
+ }
+ assert(false);
+ return std::wstring();
+ };
+
+ String formattedText = L"[" + formatTime<String>(FORMAT_TIME, getLocalTime(entry.time)) + L"] " + copyStringTo<String>(getTypeName()) + L": ";
+ const size_t prefixLen = formattedText.size();
+
+ for (auto it = entry.message.begin(); it != entry.message.end(); )
+ if (*it == L'\n')
+ {
+ formattedText += L'\n';
+
+ String blanks;
+ blanks.resize(prefixLen, L' ');
+ formattedText += blanks;
+
+ do //skip duplicate newlines
+ {
+ ++it;
+ }
+ while (it != entry.message.end() && *it == L'\n');
+ }
+ else
+ formattedText += *it++;
+
+ return formattedText;
+}
+}
+
+template <class String> inline
+String formatMessage(const LogEntry& entry) { return formatMessageImpl<String>(entry); }
+}
+
+#endif //ERROR_LOG_H_8917590832147915
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index ac68330e..61a003bb 100644..100755
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -1,2589 +1,676 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "file_access.h"
-#include <map>
-#include <algorithm>
-#include <stdexcept>
-#include "file_traverser.h"
-#include "scope_guard.h"
-#include "symlink_target.h"
-#include "file_id_def.h"
-#include "file_io.h"
-
-#ifdef ZEN_WIN
- #include <Aclapi.h>
- #include "int64.h"
- #include "privilege.h"
- #include "long_path_prefix.h"
- #include "win_ver.h"
- #ifdef ZEN_WIN_VISTA_AND_LATER
- #include <zen/vista_file_op.h> //requires COM initialization!
- #endif
-
-#elif defined ZEN_LINUX
- #include <sys/vfs.h> //statfs
- #include <sys/time.h> //lutimes
- #ifdef HAVE_SELINUX
- #include <selinux/selinux.h>
- #endif
-
-#elif defined ZEN_MAC
- #include <sys/mount.h> //statfs
- #include <copyfile.h>
-#endif
-
-#if defined ZEN_LINUX || defined ZEN_MAC
- #include <fcntl.h> //open, close, AT_SYMLINK_NOFOLLOW, UTIME_OMIT
- #include <sys/stat.h>
-#endif
-
-using namespace zen;
-
-
-Opt<Zstring> zen::getParentFolderPath(const Zstring& itemPath)
-{
-#ifdef ZEN_WIN
- assert(startsWith(itemPath, L"\\\\") || //we do NOT support relative paths!
- (itemPath.size() >= 3 && isAlpha(itemPath[0]) && itemPath[1] == L':' && itemPath[2] == L'\\'));
-
- //remove trailing separator (even for C:\ root directories)
- const Zstring itemPathFmt = endsWith(itemPath, FILE_NAME_SEPARATOR) ?
- beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) :
- itemPath;
-
- if (startsWith(itemPathFmt, L"\\\\")) //UNC-name, e.g. \\server-name\share
- if (std::count(itemPathFmt.begin(), itemPathFmt.end(), FILE_NAME_SEPARATOR) <= 3)
- return NoValue();
-
- const Zstring parentDir = beforeLast(itemPathFmt, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
- if (parentDir.empty())
- return NoValue();
- if (parentDir.size() == 2 && isAlpha(parentDir[0]) && parentDir[1] == L':')
- return appendSeparator(parentDir);
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- assert(startsWith(itemPath, L"/")); //we do NOT support relative paths!
-
- if (itemPath == "/")
- return NoValue();
-
- const Zstring parentDir = beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
- if (parentDir.empty())
- return Zstring("/");
-#endif
- return parentDir;
-}
-
-
-ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
-{
-#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str());
- if (attr == INVALID_FILE_ATTRIBUTES)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- if (ec == ERROR_PATH_NOT_FOUND || //
- ec == ERROR_FILE_NOT_FOUND || //perf: short circuit for common "not existing" error codes
- ec == ERROR_BAD_NETPATH || //
- ec == ERROR_BAD_NET_NAME) //
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileAttributes");
- }
-
- if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
- return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE;
-
- //handle obscure file permission problem where ::GetFileAttributes() fails with ERROR_ACCESS_DENIED or ERROR_SHARING_VIOLATION
- //while parent directory traversal is successful: e.g. "C:\pagefile.sys"
- WIN32_FIND_DATA itemInfo = {};
- const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(itemPath).c_str(), &itemInfo);
- if (searchHandle == INVALID_HANDLE_VALUE)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"FindFirstFile");
- ::FindClose(searchHandle);
-
- if (isSymlink(itemInfo)) //not every FILE_ATTRIBUTE_REPARSE_POINT is a symlink!!!
- return ItemType::SYMLINK;
- return (itemInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE;
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat itemInfo = {};
- if (::lstat(itemPath.c_str(), &itemInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
-
- if (S_ISLNK(itemInfo.st_mode))
- return ItemType::SYMLINK;
- if (S_ISDIR(itemInfo.st_mode))
- return ItemType::FOLDER;
- return ItemType::FILE; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK
-#endif
-}
-
-
-PathDetails zen::getPathDetails(const Zstring& itemPath) //throw FileError
-{
- const Opt<Zstring> parentPath = getParentFolderPath(itemPath);
- try
- {
- return { getItemType(itemPath), itemPath, {} }; //throw FileError
- }
- catch (FileError&)
- {
- if (!parentPath) //device root
- throw;
- //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes:
- // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE,
- // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable
- }
- const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- assert(!itemName.empty());
-
- PathDetails pd = getPathDetails(*parentPath); //throw FileError
- if (!pd.relPath.empty())
- {
- pd.relPath.push_back(itemName);
- return { pd.existingType, pd.existingPath, pd.relPath };
- }
-
- try
- {
- traverseFolder(*parentPath,
- [&](const FileInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FILE; },
- [&](const FolderInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FOLDER; },
- [&](const SymlinkInfo& si) { if (equalFilePath(si.itemName, itemName)) throw ItemType::SYMLINK; },
- [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
-
- return { pd.existingType, *parentPath, { itemName } }; //throw FileError
- }
- catch (const ItemType& type) { return { type, itemPath, {} }; } //yes, exceptions for control-flow are bad design... but, but...
- //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found)
-}
-
-
-Opt<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
-{
- const PathDetails pd = getPathDetails(itemPath); //throw FileError
-#ifndef NDEBUG
- Zstring reconstructedPath = pd.existingPath;
- for (const Zstring& itemName : pd.relPath)
- reconstructedPath += endsWith(reconstructedPath, FILE_NAME_SEPARATOR) ? itemName : FILE_NAME_SEPARATOR + itemName;
- assert(itemPath == reconstructedPath);
-#endif
- if (pd.relPath.empty())
- return pd.existingType;
- return NoValue();
-}
-
-
-bool zen::fileAvailable(const Zstring& filePath) //noexcept
-{
- //symbolic links (broken or not) are also treated as existing files!
-#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str());
- if (attr != INVALID_FILE_ATTRIBUTES)
- return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks!
- return S_ISREG(fileInfo.st_mode);
-#endif
- return false;
-}
-
-
-bool zen::dirAvailable(const Zstring& dirPath) //noexcept
-{
- //symbolic links (broken or not) are also treated as existing directories!
-#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str());
- if (attr != INVALID_FILE_ATTRIBUTES)
- return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat dirInfo = {};
- if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks!
- return S_ISDIR(dirInfo.st_mode);
-#endif
- return false;
-}
-
-
-warn_static("remove following test functions after refactoring")
-
-
-
-bool zen::fileExists(const Zstring& filePath)
-{
- //symbolic links (broken or not) are also treated as existing files!
-#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str());
- if (attr != INVALID_FILE_ATTRIBUTES)
- return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks!
- return S_ISREG(fileInfo.st_mode);
-#endif
- return false;
-}
-
-
-bool zen::dirExists(const Zstring& dirPath)
-{
- //symbolic links (broken or not) are also treated as existing directories!
-#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str());
- if (attr != INVALID_FILE_ATTRIBUTES)
- return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat dirInfo = {};
- if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks!
- return S_ISDIR(dirInfo.st_mode);
-#endif
- return false;
-}
-
-
-namespace
-{
-#ifdef ZEN_WIN
-enum class FatType
-{
- NONE,
- FAT,
- FAT32,
- EXFAT,
-};
-
-FatType getFatType(const Zstring& filePath) //noexcept
-{
- const DWORD bufferSize = MAX_PATH + 1;
- std::vector<wchar_t> buffer(bufferSize);
-
- //this call is expensive: ~1.5 ms!
- if (!::GetVolumePathName(filePath.c_str(), //__in LPCTSTR lpszFileName,
- &buffer[0], //__out LPTSTR lpszVolumePathName,
- bufferSize)) //__in DWORD cchBufferLength
- {
- assert(false);
- return FatType::NONE;
- }
-
- const Zstring volumePath = appendSeparator(&buffer[0]);
-
- //suprisingly fast: ca. 0.03 ms per call!
- if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
- nullptr, //__out LPTSTR lpVolumeNameBuffer,
- 0, //__in DWORD nVolumeNameSize,
- nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
- nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
- nullptr, //__out_opt LPDWORD lpFileSystemFlags,
- &buffer[0], //__out LPTSTR lpFileSystemNameBuffer,
- bufferSize)) //__in DWORD nFileSystemNameSize
- {
- assert(false);
- return FatType::NONE;
- }
-
- if (&buffer[0] == Zstring(L"FAT"))
- return FatType::FAT;
- if (&buffer[0] == Zstring(L"FAT32"))
- return FatType::FAT32;
- if (&buffer[0] == Zstring(L"exFAT"))
- return FatType::EXFAT;
- return FatType::NONE;
-}
-
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
-FatType getFatType(HANDLE hFile) //noexcept
-{
- const DWORD bufferSize = MAX_PATH + 1; //"The length of the file system name buffer, in TCHARs. The maximum buffer size is MAX_PATH + 1"
- std::vector<wchar_t> buffer(bufferSize);
-
- if (!::GetVolumeInformationByHandleW(hFile, //_In_ HANDLE hFile,
- nullptr, //_Out_writes_opt_(nVolumeNameSize) LPWSTR lpVolumeNameBuffer,
- 0, //_In_ DWORD nVolumeNameSize,
- nullptr, //_Out_opt_ LPDWORD lpVolumeSerialNumber,
- nullptr, //_Out_opt_ LPDWORD lpMaximumComponentLength,
- nullptr, //_Out_opt_ LPDWORD lpFileSystemFlags,
- &buffer[0], //_Out_writes_opt_(nFileSystemNameSize) LPWSTR lpFileSystemNameBuffer,
- bufferSize)) //_In_ DWORD nFileSystemNameSize
- {
- assert(false);
- return FatType::NONE;
- }
-
- if (&buffer[0] == Zstring(L"FAT"))
- return FatType::FAT;
- if (&buffer[0] == Zstring(L"FAT32"))
- return FatType::FAT32;
- if (&buffer[0] == Zstring(L"exFAT"))
- return FatType::EXFAT;
- return FatType::NONE;
-}
-#endif
-#endif
-}
-
-
-std::uint64_t zen::getFilesize(const Zstring& filePath) //throw FileError
-{
-#ifdef ZEN_WIN
- {
- WIN32_FIND_DATA fileInfo = {};
- const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filePath).c_str(), &fileInfo);
- if (searchHandle == INVALID_HANDLE_VALUE)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"FindFirstFile");
- ::FindClose(searchHandle);
-
- if (!isSymlink(fileInfo))
- return get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
- }
- // WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {};
- // if (!::GetFileAttributesEx(applyLongPathPrefix(filePath).c_str(), //__in LPCTSTR lpFileName,
- // GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
- // &sourceAttr)) //__out LPVOID lpFileInformation
-
- //open handle to target of symbolic link
- const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName,
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hFile == INVALID_HANDLE_VALUE)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"CreateFile");
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
-
- LARGE_INTEGER fileSize = {};
- if (!::GetFileSizeEx(hFile, //_In_ HANDLE hFile,
- &fileSize)) //_Out_ PLARGE_INTEGER lpFileSize
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileSizeEx");
- return fileSize.QuadPart;
-
- //alternative:
- //BY_HANDLE_FILE_INFORMATION fileInfoHnd = {};
- //if (!::GetFileInformationByHandle(hFile, &fileInfoHnd))
- // THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileInformationByHandle");
- //return get64BitUInt(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh);
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"stat");
-
- return fileInfo.st_size;
-#endif
-}
-
-
-std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available
-{
-#ifdef ZEN_WIN
- ULARGE_INTEGER bytesFree = {};
- if (!::GetDiskFreeSpaceEx(appendSeparator(path).c_str(), //__in_opt LPCTSTR lpDirectoryName, -> "UNC name [...] must include a trailing backslash, for example, "\\MyServer\MyShare\"
- &bytesFree, //__out_opt PULARGE_INTEGER lpFreeBytesAvailable,
- nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes,
- nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"GetDiskFreeSpaceEx");
- //succeeds even if path is not yet existing!
-
- //return 0 if info is not available: "The GetDiskFreeSpaceEx function returns zero for lpFreeBytesAvailable for all CD requests"
- return get64BitUInt(bytesFree.LowPart, bytesFree.HighPart);
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::statfs info = {};
- if (::statfs(path.c_str(), &info) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"statfs");
-
- return static_cast<std::uint64_t>(info.f_bsize) * info.f_bavail;
-#endif
-}
-
-
-VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError
-{
-#ifdef ZEN_WIN
- //this works for:
- //- root paths "C:\", "D:\"
- //- network shares: \\share\dirname
- //- indirection: subst S: %USERPROFILE%
- // -> GetVolumePathName() + GetVolumeInformation() OTOH incorrectly resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try...
- const HANDLE hItem = ::CreateFile(zen::applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName,
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks!
- FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
- /*needed to open a directory*/
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hItem == INVALID_HANDLE_VALUE)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"CreateFile");
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hItem));
-
- BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (!::GetFileInformationByHandle(hItem, &fileInfo))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileInformationByHandle");
-
- return fileInfo.dwVolumeSerialNumber;
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat fileInfo = {};
- if (::stat(itemPath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat");
-
- return fileInfo.st_dev;
-#endif
-}
-
-
-Zstring zen::getTempFolderPath() //throw FileError
-{
-#ifdef ZEN_WIN
- const DWORD bufSize = MAX_PATH + 1; //MSDN: maximum value
- std::vector<wchar_t> buf(bufSize);
- const DWORD rv = ::GetTempPath(bufSize, //_In_ DWORD nBufferLength,
- &buf[0]); //_Out_ LPTSTR lpBuffer
- if (rv == 0)
- THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"GetTempPath");
- if (rv >= bufSize)
- throw FileError(_("Cannot get process information."), L"GetTempPath: insufficient buffer size.");
- return &buf[0];
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- const char* buf = ::getenv("TMPDIR"); //no extended error reporting
- if (!buf)
- throw FileError(_("Cannot get process information."), L"getenv: TMPDIR not found.");
- return buf;
-#endif
-}
-
-
-void zen::removeFilePlain(const Zstring& filePath) //throw FileError
-{
-#ifdef ZEN_WIN
- const wchar_t functionName[] = L"DeleteFile";
-
- if (!::DeleteFile(applyLongPathPrefix(filePath).c_str()))
-#elif defined ZEN_LINUX || defined ZEN_MAC
- const wchar_t functionName[] = L"unlink";
- if (::unlink(filePath.c_str()) != 0)
-#endif
- {
- ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
-#ifdef ZEN_WIN
- if (ec == ERROR_ACCESS_DENIED) //function fails if file is read-only
- if (::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL)) //(try to) normalize file attributes
- {
- if (::DeleteFile(applyLongPathPrefix(filePath).c_str())) //now try again...
- return;
- ec = ::GetLastError();
- }
-#endif
- //begin of "regular" error reporting
- std::wstring errorDescr = formatSystemError(functionName, ec);
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
- if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message!
- ec == ERROR_LOCK_VIOLATION)
- {
- const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept
- if (!procList.empty())
- errorDescr = _("The file is locked by another process:") + L"\n" + procList;
- }
-#endif
- throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr);
- }
-}
-
-
-void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
-{
-#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(linkPath).c_str());
- if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
- removeDirectoryPlain(linkPath); //throw FileError
- else
- removeFilePlain(linkPath); //throw FileError
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- removeFilePlain(linkPath); //throw FileError
-#endif
-}
-
-
-void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
-{
-#ifdef ZEN_WIN
- const wchar_t functionName[] = L"RemoveDirectory";
- if (!::RemoveDirectory(applyLongPathPrefix(dirPath).c_str()))
-#elif defined ZEN_LINUX || defined ZEN_MAC
- const wchar_t functionName[] = L"rmdir";
- if (::rmdir(dirPath.c_str()) != 0)
-#endif
- {
- ErrorCode ec = getLastError(); //copy before making other system calls!
-#ifdef ZEN_WIN
- if (ec == ERROR_ACCESS_DENIED) //(try to) normalize file attributes: NEEDED! even folders and symlinks can have FILE_ATTRIBUTE_READONLY set!
- if (::SetFileAttributes(applyLongPathPrefix(dirPath).c_str(), FILE_ATTRIBUTE_NORMAL))
- {
- if (::RemoveDirectory(applyLongPathPrefix(dirPath).c_str())) //now try again...
- return;
- ec = ::GetLastError();
- }
-#elif defined ZEN_LINUX || defined ZEN_MAC
- bool symlinkExists = false;
- try { symlinkExists = getItemType(dirPath) == ItemType::SYMLINK; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant
-
- if (symlinkExists)
- {
- if (::unlink(dirPath.c_str()) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
- return;
- }
-#endif
- throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec));
- }
- /*
- Windows: may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have
- successfully been *marked* for deletion, but some application still has a handle open!
- e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145
- Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html
- Alternatives: 1. move file/empty folder to some other location, then DeleteFile()/RemoveDirectory()
- 2. use CreateFile/FILE_FLAG_DELETE_ON_CLOSE *without* FILE_SHARE_DELETE instead of DeleteFile() => early failure
- */
-}
-
-
-namespace
-{
-void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
-{
-#ifndef NDEBUG //[!] no symlinks in this context!!! Do NOT traverse into it deleting contained files!!!
- try { assert(getItemType(folderPath) != ItemType::SYMLINK); /*throw FileError*/ }
- catch (FileError&) {}
-#endif
- std::vector<Zstring> filePaths;
- std::vector<Zstring> symlinkPaths;
- std::vector<Zstring> folderPaths;
-
- //get all files and directories from current directory (WITHOUT subdirectories!)
- traverseFolder(folderPath,
- [&](const FileInfo& fi) { filePaths .push_back(fi.fullPath); },
- [&](const FolderInfo& fi) { folderPaths .push_back(fi.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
- [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); },
- [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
-
- for (const Zstring& filePath : filePaths)
- removeFilePlain(filePath); //throw FileError
-
- for (const Zstring& symlinkPath : symlinkPaths)
- removeSymlinkPlain(symlinkPath); //throw FileError
-
- //delete directories recursively
- for (const Zstring& subFolderPath : folderPaths)
- removeDirectoryImpl(subFolderPath); //throw FileError; call recursively to correctly handle symbolic links
-
- removeDirectoryPlain(folderPath); //throw FileError
-}
-}
-
-
-void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError
-{
- if (getItemType(dirPath) == ItemType::SYMLINK) //throw FileError
- removeSymlinkPlain(dirPath); //throw FileError
- else
- removeDirectoryImpl(dirPath); //throw FileError
-}
-
-
-namespace
-{
-/* Usage overview: (avoid circular pattern!)
-
- renameFile() --> renameFile_sub()
- | /|\
- \|/ |
- Fix8Dot3NameClash()
-*/
-//wrapper for file system rename function:
-void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-{
-#ifdef ZEN_WIN
- const Zstring pathSourceFmt = applyLongPathPrefix(pathSource);
- const Zstring pathTargetFmt = applyLongPathPrefix(pathTarget);
-
- if (!::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName,
- pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName,
- 0)) //__in DWORD dwFlags
- {
- DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- if (ec == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this
- {
- const DWORD oldAttr = ::GetFileAttributes(pathSourceFmt.c_str());
- if (oldAttr != INVALID_FILE_ATTRIBUTES && (oldAttr & FILE_ATTRIBUTE_READONLY))
- {
- if (::SetFileAttributes(pathSourceFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute
- {
- //try again...
- if (::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName,
- pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName,
- 0)) //__in DWORD dwFlags
- {
- //(try to) restore file attributes
- ::SetFileAttributes(pathTargetFmt.c_str(), oldAttr); //don't handle error
- return;
- }
- else
- {
- ec = ::GetLastError(); //use error code from second call to ::MoveFileEx()
- //cleanup: (try to) restore file attributes: assume pathSource is still existing
- ::SetFileAttributes(pathSourceFmt.c_str(), oldAttr);
- }
- }
- }
- }
- //begin of "regular" error reporting
- const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget));
- std::wstring errorDescr = formatSystemError(L"MoveFileEx", ec);
-
-#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message
- if (ec == ERROR_SHARING_VIOLATION ||
- ec == ERROR_LOCK_VIOLATION)
- {
- const std::wstring procList = vista::getLockingProcesses(pathSource); //noexcept
- if (!procList.empty())
- errorDescr = _("The file is locked by another process:") + L"\n" + procList;
- }
-#endif
- if (ec == ERROR_NOT_SAME_DEVICE)
- throw ErrorDifferentVolume(errorMsg, errorDescr);
- if (ec == ERROR_ALREADY_EXISTS || //-> used on Win7 x64
- ec == ERROR_FILE_EXISTS) //-> used by XP???
- throw ErrorTargetExisting(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
- }
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- //rename() will never fail with EEXIST, but always (atomically) overwrite!
- //=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx + MOVEFILE_REPLACE_EXISTING
- //=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
- //=> OS X: no solution
-
- auto throwException = [&](int ec)
- {
- const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget));
- const std::wstring errorDescr = formatSystemError(L"rename", ec);
-
- if (ec == EXDEV)
- throw ErrorDifferentVolume(errorMsg, errorDescr);
- if (ec == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
- };
-
- if (!equalFilePath(pathSource, pathTarget)) //exception for OS X: changing file name case is not an "already exists" situation!
- {
- bool alreadyExists = true;
- try { /*ItemType type = */getItemType(pathTarget); } /*throw FileError*/ catch (FileError&) { alreadyExists = false; }
-
- if (alreadyExists)
- throwException(EEXIST);
- //else: nothing exists or other error (hopefully ::rename will also fail!)
- }
-
- if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0)
- throwException(errno);
-#endif
-}
-
-
-#ifdef ZEN_WIN
-/*small wrapper around
-::GetShortPathName()
-::GetLongPathName() */
-template <typename Function>
-Zstring getFilenameFmt(const Zstring& filePath, Function fun) //throw(); returns empty string on error
-{
- const Zstring filePathFmt = applyLongPathPrefix(filePath);
-
- const DWORD bufferSize = fun(filePathFmt.c_str(), nullptr, 0);
- if (bufferSize == 0)
- return Zstring();
-
- std::vector<wchar_t> buffer(bufferSize);
-
- const DWORD charsWritten = fun(filePathFmt.c_str(), //__in LPCTSTR lpszShortPath,
- &buffer[0], //__out LPTSTR lpszLongPath,
- bufferSize); //__in DWORD cchBuffer
- if (charsWritten == 0 || charsWritten >= bufferSize)
- return Zstring();
-
- return &buffer[0];
-}
-
-
-Zstring findUnused8Dot3Name(const Zstring& filePath) //throw FileError
-{
- Opt<Zstring> parentPath = getParentFolderPath(filePath);
- assert(parentPath);
- const Zstring pathPrefix = parentPath ? appendSeparator(*parentPath) : Zstring();
-
- //extension needn't contain reasonable data
- Zstring extension = getFileExtension(filePath);
- if (extension.empty())
- extension = Zstr("FFS");
- else if (extension.length() > 3)
- extension.resize(3);
-
- for (int index = 0; index < 100000000; ++index) //filePath must be representable by <= 8 characters
- {
- const Zstring testPath = pathPrefix + numberTo<Zstring>(index) + Zchar('.') + extension;
- if (!getItemTypeIfExists(testPath)) //throw FileError
- return testPath;
- }
- throw std::runtime_error(std::string("100,000,000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) +
- "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-}
-
-
-bool have8dot3NameClash(const Zstring& itemPath)
-{
- try
- {
- /*ItemType type =*/ getItemType(itemPath); //throw FileError
- }
- catch (FileError&) { return false; }
-
- const Zstring nameOrig = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- const Zstring nameShort = afterLast(getFilenameFmt(itemPath, ::GetShortPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
- const Zstring nameLong = afterLast(getFilenameFmt(itemPath, ::GetLongPathName ), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //
-
- if (!nameShort.empty() &&
- !nameLong .empty() &&
- equalFilePath(nameOrig, nameShort) &&
- !equalFilePath(nameShort, nameLong))
- {
- //for itemPath short and long file name are equal and another unrelated file happens to have the same short name
- //e.g. itemPath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
- return true;
- }
- return false;
-}
-
-class Fix8Dot3NameClash //throw FileError
-{
-public:
- Fix8Dot3NameClash(const Zstring& filePath)
- {
- const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
-
- Opt<Zstring> parentPath = getParentFolderPath(filePath);
- assert(parentPath);
- unrelatedFilePath_ = parentPath ? appendSeparator(*parentPath) : Zstring();
- unrelatedFilePath_ += longName;
-
- //find another name in short format: this ensures the actual short name WILL be renamed as well!
- parkedFilePath_ = findUnused8Dot3Name(filePath); //throw FileError
-
- //move already existing short name out of the way for now
- renameFile_sub(unrelatedFilePath_, parkedFilePath_); //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(parkedFilePath_, unrelatedFilePath_); //throw FileError, ErrorDifferentVolume
- }
- catch (FileError&) {}
- }
-private:
- Zstring unrelatedFilePath_;
- Zstring parkedFilePath_;
-};
-#endif
-}
-
-
-//rename file: no copying!!!
-void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-{
- try
- {
- renameFile_sub(pathSource, pathTarget); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
- }
- catch (const ErrorTargetExisting&)
- {
-#ifdef ZEN_WIN
- //try to handle issues with already existing short 8.3 file names on Windows
- if (have8dot3NameClash(pathTarget))
- {
- Fix8Dot3NameClash dummy(pathTarget); //throw FileError; move clashing file path to the side
- //now try again...
- renameFile_sub(pathSource, pathTarget); //throw FileError
- return;
- }
-#endif
- throw;
- }
-}
-
-
-namespace
-{
-#ifdef ZEN_WIN
-void setFileTimeByHandle(HANDLE hFile, //throw FileError
- const FILETIME* creationTime, //optional
- const FILETIME& lastWriteTime,
- const Zstring& filePath) //for error message only
-{
- if (!::SetFileTime(hFile, //__in HANDLE hFile,
- creationTime, //__in_opt const FILETIME *lpCreationTime,
- nullptr, //__in_opt const FILETIME *lpLastAccessTime,
- &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER
- {
- LARGE_INTEGER tmp = {};
- tmp.LowPart = ft.dwLowDateTime;
- tmp.HighPart = ft.dwHighDateTime;
- return tmp;
- };
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
- //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
- if (ec == ERROR_ACCESS_DENIED)
- {
- BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (::GetFileInformationByHandle(hFile, &fileInfo))
- if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
- {
- FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change"
- basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!!
- basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); //
- if (creationTime)
- basicInfo.CreationTime = toLargeInteger(*creationTime);
-
- //set file time + attributes
- if (!::SetFileInformationByHandle(hFile, //__in HANDLE hFile,
- FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
- &basicInfo, //__in LPVOID lpFileInformation,
- sizeof(basicInfo))) //__in DWORD dwBufferSize
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileInformationByHandle");
-
- //(try to) restore original file attributes
- FILE_BASIC_INFO basicInfo2 = {};
- basicInfo2.FileAttributes = fileInfo.dwFileAttributes;
- ::SetFileInformationByHandle(hFile, //__in HANDLE hFile,
- FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
- &basicInfo2, //__in LPVOID lpFileInformation,
- sizeof(basicInfo2)); //__in DWORD dwBufferSize
- return;
- }
- }
-#endif
-
- std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath));
-
- //add more meaningful message: FAT accepts only a subset of the NTFS date range
- if (ec == ERROR_INVALID_PARAMETER)
-#ifdef ZEN_WIN_VISTA_AND_LATER
- if (getFatType(hFile) != FatType::NONE) //exFAT has the same date range like FAT/FAT32: http://www.freefilesync.org/forum/viewtopic.php?t=4051
-#else
- if (getFatType(filePath) != FatType::NONE)
-#endif
- {
- //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278
- if (creationTime) //retry (single-level recursion at most!)
- return setFileTimeByHandle(hFile, nullptr, lastWriteTime, filePath); //throw FileError
-
- //if the ERROR_INVALID_PARAMETER is due to an invalid date, enhance message:
- const LARGE_INTEGER writeTimeInt = toLargeInteger(lastWriteTime);
- if (writeTimeInt.QuadPart < 0x01a8e79fe1d58000 || //1980-01-01 https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2100
- writeTimeInt.QuadPart >= 0x022f716377640000) //2100-01-01
- {
- errorMsg += L"\nA FAT volume can only store dates from 1980 to 2099:\n" "\twrite time (UTC):";
- SYSTEMTIME st = {};
- if (::FileTimeToSystemTime(&lastWriteTime, //__in const FILETIME *lpFileTime,
- &st)) //__out LPSYSTEMTIME lpSystemTime
- {
- //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!!
- int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale,
- 0, //_In_ DWORD dwFlags,
- &st, //_In_opt_ const SYSTEMTIME *lpDate,
- nullptr, //_In_opt_ LPCTSTR lpFormat,
- &buffer[0], //_Out_opt_ LPTSTR lpDateStr,
- bufferSize) > 0) //_In_ int cchDate
- errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
- }
-
- bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0)
- errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
- }
- }
- errorMsg += L" (" + numberTo<std::wstring>(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails
- }
- }
-
- throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec));
- }
-}
-
-
-void setWriteTimeNative(const Zstring& itemPath,
- const FILETIME& lastWriteTime,
- const FILETIME* creationTime, //optional
- ProcSymlink procSl) //throw FileError
-{
- //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)
- {
- */
-
- /*
- if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION)
- ::Sleep(retryInterval); //wait then retry
- else //success or unknown failure
- break;
- }
- */
- //temporarily reset read-only flag if required
- DWORD attribsToRestore = INVALID_FILE_ATTRIBUTES;
- ZEN_ON_SCOPE_EXIT(
- if (attribsToRestore != INVALID_FILE_ATTRIBUTES)
- ::SetFileAttributes(applyLongPathPrefix(itemPath).c_str(), attribsToRestore);
- );
-
- auto removeReadonly = [&]() -> bool //throw FileError; may need to remove the readonly-attribute (e.g. on FAT usb drives)
- {
- if (attribsToRestore == INVALID_FILE_ATTRIBUTES)
- {
- const DWORD attribs = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str());
- if (attribs == INVALID_FILE_ATTRIBUTES)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileAttributes");
-
- if (attribs & FILE_ATTRIBUTE_READONLY)
- {
- if (!::SetFileAttributes(applyLongPathPrefix(itemPath).c_str(), FILE_ATTRIBUTE_NORMAL))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(itemPath)), L"SetFileAttributes");
-
- attribsToRestore = attribs; //reapplied on scope exit
- return true;
- }
- }
- return false;
- };
-
- auto openFile = [&](bool conservativeApproach)
- {
- return ::CreateFile(applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName,
- (conservativeApproach ?
- //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES: they silently fail later during SetFileTime()!
- //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430
- //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first:
- GENERIC_READ | GENERIC_WRITE :
- //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call!
- //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
- //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second.
- FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- (procSl == ProcSymlink::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0) |
- FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- };
- {
- //extra scope for debug check below
-
- HANDLE hFile = INVALID_HANDLE_VALUE;
- for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :)
- {
- //1. be conservative
- hFile = openFile(true /*GENERIC_WRITE*/);
- if (hFile == INVALID_HANDLE_VALUE)
- {
- if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons)
- if (removeReadonly()) //throw FileError
- continue;
-
- //2. be a *little* fancy
- hFile = openFile(false /*FILE_WRITE_ATTRIBUTES*/);
- if (hFile == INVALID_HANDLE_VALUE)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- if (ec == ERROR_ACCESS_DENIED)
- if (removeReadonly()) //throw FileError
- continue;
-
- //3. after these herculean stunts we give up...
- throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), formatSystemError(L"CreateFile", ec));
- }
- }
- break;
- }
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
-
-#if 0 //waiting for user feedback...
-#ifdef ZEN_WIN_VISTA_AND_LATER
- //bugs, bugs, bugs.... on "SharePoint" SetFileAttributes() seems to affect file modification time: http://www.freefilesync.org/forum/viewtopic.php?t=3699
- //on Vista we can avoid reopening the file (and the SharePoint bug)
- ZEN_ON_SCOPE_EXIT(
- if (attribsToRestore != INVALID_FILE_ATTRIBUTES)
- {
- FILE_BASIC_INFO basicInfo = {};
- basicInfo.FileAttributes = attribsToRestore;
- ::SetFileInformationByHandle(hFile, //__in HANDLE hFile,
- FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
- &basicInfo, //__in LPVOID lpFileInformation,
- sizeof(basicInfo)); //__in DWORD dwBufferSize
- attribsToRestore = INVALID_FILE_ATTRIBUTES;
- }
- );
-#endif
-#endif
-
- setFileTimeByHandle(hFile, creationTime, lastWriteTime, itemPath); //throw FileError
- }
-#ifndef NDEBUG //verify written data
- FILETIME creationTimeDbg = {};
- FILETIME lastWriteTimeDbg = {};
-
- HANDLE hFile = ::CreateFile(applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName,
- FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- (procSl == ProcSymlink::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0) |
- FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- assert(hFile != INVALID_HANDLE_VALUE);
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
-
- assert(::GetFileTime(hFile, //probably more up to date than GetFileAttributesEx()!?
- &creationTimeDbg,
- nullptr,
- &lastWriteTimeDbg));
-
- assert(std::abs(filetimeToTimeT(lastWriteTimeDbg) - filetimeToTimeT(lastWriteTime)) <= 2); //respect 2 second FAT/FAT32 precision
- //assert(std::abs(filetimeToTimeT(creationTimeDbg ) - filetimeToTimeT(creationTime )) <= 2); -> creation time not available for Linux-hosted Samba shares!
-#endif
-}
-
-
-#elif defined ZEN_LINUX
-void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTime, ProcSymlink procSl) //throw FileError
-{
- /*
- [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
- => fallback to "retarded-idiot version"! -- DarkByte
-
- [2015-03-09]
- - cannot reproduce issues with NTFS and utimensat() on Ubuntu
- - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch"
- => let's give utimensat another chance:
- using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"!
- */
- struct ::timespec newTimes[2] = {};
- newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: http://www.freefilesync.org/forum/viewtopic.php?t=1701
- newTimes[1] = modTime; //modification time
-
- if (procSl == ProcSymlink::FOLLOW)
- {
- //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP:
- //http://www.freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works
- if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, 0) == 0)
- return;
-
- //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: http://www.freefilesync.org/forum/viewtopic.php?t=387
- const int fdFile = ::open(itemPath.c_str(), O_WRONLY);
- if (fdFile == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open");
- ZEN_ON_SCOPE_EXIT(::close(fdFile));
-
- if (::futimens(fdFile, newTimes) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"futimens");
- }
- else
- {
- if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"utimensat");
- }
-}
-
-
-#elif defined ZEN_MAC
-struct AttrBufFileTimes
-{
- std::uint32_t length = 0;
- struct ::timespec createTime = {}; //keep order; see docs!
- struct ::timespec writeTime = {}; //
-} __attribute__((aligned(4), packed));
-
-
-void setWriteTimeNative(const Zstring& itemPath,
- const struct ::timespec& writeTime,
- const struct ::timespec* createTime, //optional
- ProcSymlink procSl) //throw FileError
-{
- //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented
- //use ::setattrlist() instead of ::utimes() => 1. set file creation times 2. nanosecond precision
- //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/setattrlist.2.html
-
- struct ::attrlist attribs = {};
- attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
- attribs.commonattr = (createTime ? ATTR_CMN_CRTIME : 0) | ATTR_CMN_MODTIME;
-
- AttrBufFileTimes attrBuf;
- if (createTime)
- {
- attrBuf.createTime.tv_sec = createTime->tv_sec;
- attrBuf.createTime.tv_nsec = createTime->tv_nsec;
- }
- attrBuf.writeTime.tv_sec = writeTime.tv_sec;
- attrBuf.writeTime.tv_nsec = writeTime.tv_nsec;
-
- const int rv = ::setattrlist(itemPath.c_str(), //const char* path,
- &attribs, //struct ::attrlist* attrList,
- createTime ? &attrBuf.createTime : &attrBuf.writeTime, //void* attrBuf,
- (createTime ? sizeof(attrBuf.createTime) : 0) + sizeof(attrBuf.writeTime), //size_t attrBufSize,
- procSl == ProcSymlink::DIRECT ? FSOPT_NOFOLLOW : 0); //unsigned long options
- if (rv != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"setattrlist");
-}
-
-/*
-void getFileTimeRaw(int fd, //throw FileError
- const Zstring& itemPath, //for error reporting only
- struct ::timespec& createTime, //out
- struct ::timespec& writeTime) //
-{
- //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getattrlist.2.html
- struct ::attrlist attribs = {};
- attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
- attribs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME;
-
- AttrBufFileTimes attrBuf;
-
- const int rv = ::fgetattrlist(fd, //int fd,
- &attribs, //struct ::attrlist* attrList,
- &attrBuf, //void* attrBuf,
- sizeof(attrBuf), //size_t attrBufSize,
- 0); //unsigned long options
- if (rv != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"fgetattrlist");
-
- createTime.tv_sec = attrBuf.createTime.tv_sec;
- createTime.tv_nsec = attrBuf.createTime.tv_nsec;
- writeTime.tv_sec = attrBuf.writeTime.tv_sec;
- writeTime.tv_nsec = attrBuf.writeTime.tv_nsec;
-}
-*/
-
-//PERF: ~10µs per call for "int fd" variant (irrespective if file is local or on mounted USB; test case: 3000 files)
-bool hasNativeSupportForExtendedAtrributes(const Zstring& filePath, //throw FileError
- int fd = -1) //speed up!?
-{
- struct ::statfs volInfo = {};
- if ((fd != -1 ? ::fstatfs(fd, &volInfo) : ::statfs(filePath.c_str(), &volInfo)) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), fd != -1 ? L"fstatfs" : L"statfs");
-
- struct ::attrlist attribs = {};
- attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
- attribs.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES;
-
- struct
- {
- std::uint32_t length = 0;
- vol_capabilities_attr_t volCaps{};
- } __attribute__((aligned(4), packed)) attrBuf;
-
- const int rv = ::getattrlist(volInfo.f_mntonname, //const char* path,
- &attribs, //struct ::attrlist* attrList,
- &attrBuf, //void* attrBuf,
- sizeof(attrBuf), //size_t attrBufSize,
- 0); //unsigned long options
- if (rv != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(volInfo.f_mntonname)), L"getattrlist");
-
- return (attrBuf.volCaps.valid [VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) != 0 &&
- (attrBuf.volCaps.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) != 0;
-}
-#endif
-}
-
-
-void zen::setFileTime(const Zstring& filePath, std::int64_t modTime, ProcSymlink procSl) //throw FileError
-{
-#ifdef ZEN_WIN
- setWriteTimeNative(filePath, timetToFileTime(modTime), nullptr, procSl); //throw FileError
-
-#elif defined ZEN_LINUX
- struct ::timespec writeTime = {};
- writeTime.tv_sec = modTime;
- setWriteTimeNative(filePath, writeTime, procSl); //throw FileError
-
-#elif defined ZEN_MAC
- struct ::timespec writeTime = {};
- writeTime.tv_sec = modTime;
- setWriteTimeNative(filePath, writeTime, nullptr, procSl); //throw FileError
-#endif
-}
-
-
-bool zen::supportsPermissions(const Zstring& dirPath) //throw FileError
-{
-#ifdef ZEN_WIN
- const DWORD bufferSize = MAX_PATH + 1;
- std::vector<wchar_t> buffer(bufferSize);
-
- if (!::GetVolumePathName(dirPath.c_str(), //__in LPCTSTR lpszFileName,
- &buffer[0], //__out LPTSTR lpszVolumePathName,
- bufferSize)) //__in DWORD cchBufferLength
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(dirPath)), L"GetVolumePathName");
-
- const Zstring volumePath = appendSeparator(&buffer[0]);
-
- DWORD fsFlags = 0;
- if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
- nullptr, //__out LPTSTR lpVolumeNameBuffer,
- 0, //__in DWORD nVolumeNameSize,
- nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
- nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
- &fsFlags, //__out_opt LPDWORD lpFileSystemFlags,
- nullptr, //__out LPTSTR lpFileSystemNameBuffer,
- 0)) //__in DWORD nFileSystemNameSize
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(dirPath)), L"GetVolumeInformation");
-
- return (fsFlags & FILE_PERSISTENT_ACLS) != 0;
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- return true;
-#endif
-}
-
-
-namespace
-{
-#ifdef HAVE_SELINUX
-//copy SELinux security context
-void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
-{
- security_context_t contextSource = nullptr;
- const int rv = procSl == ProcSymlink::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_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), L"getfilecon");
- }
- ZEN_ON_SCOPE_EXIT(::freecon(contextSource));
-
- {
- security_context_t contextTarget = nullptr;
- const int rv2 = procSl == ProcSymlink::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_SCOPE_EXIT(::freecon(contextTarget));
-
- if (::strcmp(contextSource, contextTarget) == 0) //nothing to do
- return;
- }
- }
-
- const int rv3 = procSl == ProcSymlink::FOLLOW ?
- ::setfilecon(target.c_str(), contextSource) :
- ::lsetfilecon(target.c_str(), contextSource);
- if (rv3 < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon");
-}
-#endif
-
-
-//copy permissions for files, directories or symbolic links: requires admin rights
-void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, ProcSymlink procSl) //throw FileError
-{
-#ifdef ZEN_WIN
- //in contrast to ::SetSecurityInfo(), ::SetFileSecurity() seems to honor the "inherit DACL/SACL" flags
- //CAVEAT: if a file system does not support ACLs, GetFileSecurity() will return successfully with a *valid* security descriptor containing *no* ACL entries!
-
- const bool isSymlinkSource = getItemType(sourcePath) == ItemType::SYMLINK; //throw FileError
- const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
-
- //NOTE: ::GetFileSecurity()/::SetFileSecurity() do NOT follow Symlinks! getResolvedSymlinkPath() requires Vista or later!
- const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && isSymlinkSource ? getResolvedSymlinkPath(sourcePath) : sourcePath; //throw FileError
- const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && isSymlinkTarget ? getResolvedSymlinkPath(targetPath) : targetPath; //
-
- //setting privileges requires admin rights!
- try
- {
- //enable privilege: required to read/write SACL information (only)
- activatePrivilege(PrivilegeName::SECURITY); //throw FileError
- //Note: trying to copy SACL (SACL_SECURITY_INFORMATION) may return ERROR_PRIVILEGE_NOT_HELD (1314) on Samba shares. This is not due to missing privileges!
- //However, this is okay, since copying NTFS permissions doesn't make sense in this case anyway
-
- //the following privilege may be required according to https://msdn.microsoft.com/en-us/library/aa364399 (although not needed nor active in my tests)
- activatePrivilege(PrivilegeName::BACKUP); //throw FileError
-
- //enable privilege: required to copy owner information
- activatePrivilege(PrivilegeName::RESTORE); //throw FileError
- }
- catch (const FileError& e)//add some more context description (e.g. user is not an admin)
- {
- throw FileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourceResolved)), e.toString());
- }
-
-
- std::vector<char> buffer(10000); //example of actually required buffer size: 192 bytes
- for (;;)
- {
- DWORD bytesNeeded = 0;
- if (::GetFileSecurity(applyLongPathPrefix(sourceResolved).c_str(), //__in LPCTSTR lpFileName, -> long path prefix IS needed, although it is NOT mentioned on MSDN!!!
- DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | //__in SECURITY_INFORMATION RequestedInformation,
- OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION,
- reinterpret_cast<PSECURITY_DESCRIPTOR>(&buffer[0]), //__out_opt PSECURITY_DESCRIPTOR pSecurityDescriptor,
- static_cast<DWORD>(buffer.size()), //__in DWORD nLength,
- &bytesNeeded)) //__out LPDWORD lpnLengthNeeded
- break;
- //failure: ...
- if (bytesNeeded > buffer.size())
- buffer.resize(bytesNeeded);
- else
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourceResolved)), L"GetFileSecurity");
- }
- SECURITY_DESCRIPTOR& secDescr = reinterpret_cast<SECURITY_DESCRIPTOR&>(buffer[0]);
-
- /*
- SECURITY_DESCRIPTOR_CONTROL secCtrl = 0;
- {
- DWORD ctrlRev = 0;
- if (!::GetSecurityDescriptorControl(&secDescr, // __in PSECURITY_DESCRIPTOR pSecurityDescriptor,
- &secCtrl, // __out PSECURITY_DESCRIPTOR_CONTROL pControl,
- &ctrlRev)) //__out LPDWORD lpdwRevision
- throw FileErro
- }
- //interesting flags:
- //#define SE_DACL_PRESENT (0x0004)
- //#define SE_SACL_PRESENT (0x0010)
- //#define SE_DACL_PROTECTED (0x1000)
- //#define SE_SACL_PROTECTED (0x2000)
- */
-
- if (!::SetFileSecurity(applyLongPathPrefix(targetResolved).c_str(), //__in LPCTSTR lpFileName, -> long path prefix IS needed, although it is NOT mentioned on MSDN!!!
- OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
- DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInformation,
- &secDescr)) //__in PSECURITY_DESCRIPTOR pSecurityDescriptor
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetResolved)), L"SetFileSecurity");
-
- /*
- PSECURITY_DESCRIPTOR buffer = nullptr;
- PSID owner = nullptr;
- PSID group = nullptr;
- PACL dacl = nullptr;
- PACL sacl = nullptr;
-
- //File Security and Access Rights: https://msdn.microsoft.com/en-us/library/aa364399
- //SECURITY_INFORMATION Access Rights: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379573
- 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,
- nullptr,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS | (procSl == SymLinkHandling::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory
- nullptr);
- if (hSource == INVALID_HANDLE_VALUE)
- throw FileError
- ZEN_ON_SCOPE_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
- ZEN_ON_SCOPE_EXIT(::LocalFree(buffer));
-
- SECURITY_DESCRIPTOR_CONTROL secCtrl = 0;
- {
- DWORD ctrlRev = 0;
- if (!::GetSecurityDescriptorControl(buffer, // __in PSECURITY_DESCRIPTOR pSecurityDescriptor,
- &secCtrl, // __out PSECURITY_DESCRIPTOR_CONTROL pControl,
- &ctrlRev))//__out LPDWORD lpdwRevision
- throw FileError
- }
-
- //may need to remove the readonly-attribute
- 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
- nullptr, // lpSecurityAttributes
- OPEN_EXISTING, // dwCreationDisposition
- FILE_FLAG_BACKUP_SEMANTICS | (procSl == SymLinkHandling::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), // dwFlagsAndAttributes
- nullptr); // hTemplateFile
- });
-
- if (targetHandle.get() == INVALID_HANDLE_VALUE)
- throw FileError
-
- SECURITY_INFORMATION secFlags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION;
-
- //SACL/DACL inheritence flag is NOT copied by default: we have to tell ::SetSecurityInfo(() to enable/disable it manually!
- //if (secCtrl & SE_DACL_PRESENT)
- secFlags |= (secCtrl & SE_DACL_PROTECTED) ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION;
- //if (secCtrl & SE_SACL_PRESENT)
- secFlags |= (secCtrl & SE_SACL_PROTECTED) ? PROTECTED_SACL_SECURITY_INFORMATION : UNPROTECTED_SACL_SECURITY_INFORMATION;
-
-
- // 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,
- secFlags, //__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
- */
-
-#elif defined ZEN_LINUX
-
-#ifdef HAVE_SELINUX //copy SELinux security context
- copySecurityContext(sourcePath, targetPath, procSl); //throw FileError
-#endif
-
- struct ::stat fileInfo = {};
- if (procSl == ProcSymlink::FOLLOW)
- {
- if (::stat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat");
-
- if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown");
-
- if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
- }
- else
- {
- if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
-
- if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
-
- const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
- if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
- ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
- }
-
-#elif defined ZEN_MAC
- copyfile_flags_t flags = COPYFILE_ACL | COPYFILE_STAT; //unfortunately COPYFILE_STAT copies modtime, too!
- if (procSl == ProcSymlink::DIRECT)
- flags |= COPYFILE_NOFOLLOW;
-
- if (::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, flags) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtPath(sourcePath)), L"%y", L"\n" + fmtPath(targetPath)), L"copyfile");
-
- //owner is *not* copied with ::copyfile():
-
- struct ::stat fileInfo = {};
- if (procSl == ProcSymlink::FOLLOW)
- {
- if (::stat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat");
-
- if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown");
- }
- else
- {
- if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
-
- if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
- }
-#endif
-}
-}
-
-
-void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError
-{
- if (!getParentFolderPath(dirPath)) //device root
- return static_cast<void>(/*ItemType =*/ getItemType(dirPath)); //throw FileError
-
- try
- {
- copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting
- }
- catch (FileError&)
- {
- Opt<PathDetails> pd;
- try { pd = getPathDetails(dirPath); /*throw FileError*/ }
- catch (FileError&) {} //previous exception is more relevant
-
- if (pd && pd->existingType != ItemType::FILE)
- {
- Zstring intermediatePath = pd->existingPath;
- for (const Zstring& itemName : pd->relPath)
- {
- intermediatePath = appendSeparator(intermediatePath) + itemName;
- copyNewDirectory(Zstring(), intermediatePath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting)
- }
- return;
- }
- throw;
- }
-}
-
-
-//source path is optional (may be empty)
-void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting
- bool copyFilePermissions)
-{
-#ifdef ZEN_WIN
- auto getErrorMsg = [](const Zstring& path) { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); };
-
- //deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/
- if (endsWith(targetPath, L' ') ||
- endsWith(targetPath, L'.'))
- throw FileError(getErrorMsg(targetPath), replaceCpy(_("%x is not a regular directory name."), L"%x", fmtPath(afterLast(targetPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL))));
-
- //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(targetPath).c_str(), //__in LPCTSTR lpPathName,
- nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes
- {
- DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- //handle issues with already existing short 8.3 file names on Windows
- if (ec == ERROR_ALREADY_EXISTS)
- if (have8dot3NameClash(targetPath))
- {
- Fix8Dot3NameClash dummy(targetPath); //throw FileError; move clashing object to the side
-
- //now try again...
- if (::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), nullptr))
- ec = ERROR_SUCCESS;
- else
- ec = ::GetLastError();
- }
-
- if (ec != ERROR_SUCCESS)
- {
- const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec);
-
- if (ec == ERROR_ALREADY_EXISTS)
- throw ErrorTargetExisting(getErrorMsg(targetPath), errorDescr);
- //else if (ec == ERROR_PATH_NOT_FOUND || //the ususal suspect
- // ec == ERROR_FILE_NOT_FOUND) //Webdav incorrectly returns this one: http://www.freefilesync.org/forum/viewtopic.php?t=4053
- // throw ErrorTargetPathMissing(getErrorMsg(targetPath), errorDescr);
- throw FileError(getErrorMsg(targetPath), errorDescr);
- }
- }
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories
-
- struct ::stat dirInfo = {};
- if (!sourcePath.empty())
- if (::stat(sourcePath.c_str(), &dirInfo) == 0)
- {
- mode = dirInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
- mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
- }
- //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
-
- if (::mkdir(targetPath.c_str(), mode) != 0)
- {
- const int lastError = errno; //copy before directly or indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(targetPath));
- const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
-
- if (lastError == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
- //else if (lastError == ENOENT)
- // throw ErrorTargetPathMissing(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
- }
-#endif
-
- if (!sourcePath.empty())
- {
-#ifdef ZEN_WIN
- //optional: try to copy file attributes (dereference symlinks and junctions)
- const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(sourcePath).c_str(), //_In_ LPCTSTR lpFileName,
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks!
- FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hDirSrc != INVALID_HANDLE_VALUE) //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error...
- {
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirSrc));
-
- BY_HANDLE_FILE_INFORMATION dirInfo = {};
- if (::GetFileInformationByHandle(hDirSrc, &dirInfo))
- {
- ::SetFileAttributes(applyLongPathPrefix(targetPath).c_str(), dirInfo.dwFileAttributes);
- //copy "read-only and system attributes": https://blogs.msdn.microsoft.com/oldnewthing/20030930-00/?p=42353/
-
- const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0;
- const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
-
- if (isEncrypted)
- ::EncryptFile(targetPath.c_str()); //seems no long path is required (check passed!)
-
- HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(targetPath).c_str(), //_In_ LPCTSTR lpFileName,
- GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess,
- /*read access required for FSCTL_SET_COMPRESSION*/
- FILE_SHARE_READ |
- FILE_SHARE_WRITE |
- FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hDirTrg != INVALID_HANDLE_VALUE)
- {
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirTrg));
-
- if (isCompressed)
- {
- USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
- DWORD bytesReturned = 0;
- /*bool rv = */::DeviceIoControl(hDirTrg, //_In_ HANDLE hDevice,
- FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode,
- &cmpState, //_In_opt_ LPVOID lpInBuffer,
- sizeof(cmpState), //_In_ DWORD nInBufferSize,
- nullptr, //_Out_opt_ LPVOID lpOutBuffer,
- 0, //_In_ DWORD nOutBufferSize,
- &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned,
- nullptr); //_Inout_opt_ LPOVERLAPPED lpOverlapped
- }
-
- //(try to) set creation (and modification) time
- /*bool rv = */::SetFileTime(hDirTrg, //_In_ HANDLE hFile,
- &dirInfo.ftCreationTime, //_Out_opt_ LPFILETIME lpCreationTime,
- nullptr, //_Out_opt_ LPFILETIME lpLastAccessTime,
- &dirInfo.ftLastWriteTime); //_Out_opt_ LPFILETIME lpLastWriteTime
- }
- }
- }
-
-#elif defined ZEN_MAC
- if (hasNativeSupportForExtendedAtrributes(targetPath)) //throw FileError
- ::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, COPYFILE_XATTR); //ignore errors, see related comments in copyFileOsSpecific()
-
- //(try to) set creation (and modification) time
- if (dirInfo.st_birthtimespec.tv_sec != 0)
- try
- {
- setWriteTimeNative(targetPath, dirInfo.st_mtimespec, &dirInfo.st_birthtimespec, ProcSymlink::FOLLOW); //throw FileError
- //dirInfo.st_birthtime; -> only seconds-precision
- //dirInfo.st_mtime; ->
- }
- catch (FileError&) {}
-#endif
-
- ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPath); }
- catch (FileError&) {}); //ensure cleanup:
-
- //enforce copying file permissions: it's advertized on GUI...
- if (copyFilePermissions)
- copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError
- }
-}
-
-
-void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError
-{
- const Zstring linkPath = getSymlinkTargetRaw(sourceLink); //throw FileError; accept broken symlinks
-
-#ifdef ZEN_WIN
- WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {};
- if (!::GetFileAttributesEx(applyLongPathPrefix(sourceLink).c_str(), //__in LPCTSTR lpFileName,
- GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
- &sourceAttr)) //__out LPVOID lpFileInformation
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx");
-
- const bool isDirLink = (sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
-
- using CreateSymbolicLinkFunc = BOOLEAN (WINAPI*)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags);
- const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW");
-
- if (!createSymbolicLink)
- throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)),
- replaceCpy(_("Cannot find system function %x."), L"%x", L"\"CreateSymbolicLinkW\""));
-
- const wchar_t functionName[] = L"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 ZEN_LINUX || defined ZEN_MAC
- const wchar_t functionName[] = L"symlink";
- if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0)
-#endif
- THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName);
-
- //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist!
- ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetLink); /*throw FileError*/ }
- catch (FileError&) {});
-
- //file times: essential for sync'ing a symlink: enforce this! (don't just try!)
-#ifdef ZEN_WIN
- setWriteTimeNative(targetLink, sourceAttr.ftLastWriteTime, &sourceAttr.ftCreationTime, ProcSymlink::DIRECT); //throw FileError
-
-#else
- struct ::stat sourceInfo = {};
- if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat");
-
-#ifdef ZEN_LINUX
- setWriteTimeNative(targetLink, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError
-#elif defined ZEN_MAC
- if (hasNativeSupportForExtendedAtrributes(targetLink)) //throw FileError
- ::copyfile(sourceLink.c_str(), targetLink.c_str(), nullptr, COPYFILE_XATTR | COPYFILE_NOFOLLOW); //ignore errors, see related comments in copyFileOsSpecific()
-
- setWriteTimeNative(targetLink, sourceInfo.st_mtimespec, &sourceInfo.st_birthtimespec, ProcSymlink::DIRECT); //throw FileError
-#else
-#error WTF
-#endif
-#endif
-
- if (copyFilePermissions)
- copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
-}
-
-
-namespace
-{
-#ifdef ZEN_WIN
-/*
- CopyFileEx() BackupRead() ReadFile()
- --------------------------------------------
-Attributes YES NO NO
-create time NO NO NO
-ADS YES YES NO
-Encrypted YES NO(silent fail!) NO
-Compressed NO NO NO
-Sparse NO YES NO
-Nonstandard FS YES UNKNOWN -> error writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect.
-PERF - 6% faster
-
-Mark stream as compressed: FSCTL_SET_COMPRESSION - compatible with both BackupRead() and ReadFile()
-
-
-Current support for combinations of NTFS extended attributes:
-
-source attr | tf normal | tf compressed | tf encrypted | handled by
-============|==================================================================
- --- | --- -C- E-- copyFileWindowsDefault
- --S | --S -CS E-S copyFileWindowsStream
- -C- | -C- -C- E-- copyFileWindowsDefault
- -CS | -CS -CS E-S copyFileWindowsStream
- E-- | E-- E-- E-- copyFileWindowsDefault
- E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL for large sparse files!!
-
-tf := target folder
-E := encrypted
-C := compressed
-S := sparse
-NOK := current behavior is not optimal/OK yet.
-
-Note: - if target parent folder is compressed or encrypted, both attributes are added automatically during file creation!
- - "compressed" and "encrypted" are mutually exclusive: http://support.microsoft.com/kb/223093/en-us
-*/
-
-
-//due to issues on non-NTFS volumes, we should use the copy-as-sparse routine only if required and supported!
-template <class Function>
-bool canCopyAsSparse(DWORD fileAttrSource, Function getTargetFsFlags) //throw ()
-{
- const bool sourceIsEncrypted = (fileAttrSource & FILE_ATTRIBUTE_ENCRYPTED) != 0;
- const bool sourceIsSparse = (fileAttrSource & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
-
- if (sourceIsEncrypted || !sourceIsSparse) //BackupRead() silently fails reading encrypted files!
- return false; //small perf optimization: don't check "targetFile" if not needed
-
- DWORD targetFsFlags = 0;
- if (!getTargetFsFlags(targetFsFlags))
- {
- assert(false);
- return false;
- }
- assert(targetFsFlags != 0);
-
- return (targetFsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0;
-}
-
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
-bool canCopyAsSparse(DWORD fileAttrSource, HANDLE hTargetFile) //throw ()
-{
- return canCopyAsSparse(fileAttrSource, [&](DWORD& targetFsFlags) -> bool
- {
- return ::GetVolumeInformationByHandleW(hTargetFile, //_In_ HANDLE hFile,
- nullptr, //_Out_writes_opt_(nVolumeNameSize) LPWSTR lpVolumeNameBuffer,
- 0, //_In_ DWORD nVolumeNameSize,
- nullptr, //_Out_opt_ LPDWORD lpVolumeSerialNumber,
- nullptr, //_Out_opt_ LPDWORD lpMaximumComponentLength,
- &targetFsFlags, //_Out_opt_ LPDWORD lpFileSystemFlags,
- nullptr, //_Out_writes_opt_(nFileSystemNameSize) LPWSTR lpFileSystemNameBuffer,
- 0) != 0; //_In_ DWORD nFileSystemNameSize
- });
-}
-#endif
-
-
-bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw ()
-{
- return canCopyAsSparse(fileAttrSource, [&targetFile](DWORD& targetFsFlags) -> bool
- {
- const DWORD bufferSize = MAX_PATH + 1;
- std::vector<wchar_t> buffer(bufferSize);
-
- //full pathName need not yet exist!
- if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName,
- &buffer[0], //__out LPTSTR lpszVolumePathName,
- bufferSize)) //__in DWORD cchBufferLength
- return false;
-
- const Zstring volumePath = appendSeparator(&buffer[0]);
-
- return ::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName
- nullptr, //__out_opt LPTSTR lpVolumeNameBuffer,
- 0, //__in DWORD nVolumeNameSize,
- nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
- nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
- &targetFsFlags, //__out_opt LPDWORD lpFileSystemFlags,
- nullptr, //__out LPTSTR lpFileSystemNameBuffer,
- 0) != 0; //__in DWORD nFileSystemNameSize
- });
-}
-
-
-bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //throw ()
-{
- //follow symlinks!
- HANDLE hSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), //_In_ LPCTSTR lpFileName,
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | //all shared modes are required to read files that are open in other applications
- FILE_SHARE_WRITE |
- FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- 0, //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hSource == INVALID_HANDLE_VALUE)
- return false;
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hSource));
-
- BY_HANDLE_FILE_INFORMATION sourceInfo = {};
- if (!::GetFileInformationByHandle(hSource, &sourceInfo))
- return false;
-
- return canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile); //throw ()
-}
-
-//=============================================================================================
-
-enum class StreamCopyType
-{
- READ_FILE,
- BACKUP_READ,
-};
-
-
-InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked
- const Zstring& targetFile,
- StreamCopyType scType,
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
-{
- //try to get backup read and write privileges: help solve most "access denied" errors with FILE_FLAG_BACKUP_SEMANTICS:
- //http://www.freefilesync.org/forum/viewtopic.php?t=1714
- try { activatePrivilege(PrivilegeName::BACKUP); }
- catch (const FileError&) {}
- try { activatePrivilege(PrivilegeName::RESTORE); }
- catch (const FileError&) {}
-
- HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), //_In_ LPCTSTR lpFileName,
- GENERIC_READ, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- //FILE_FLAG_OVERLAPPED must not be used!
- //FILE_FLAG_NO_BUFFERING should not be used!
- FILE_FLAG_SEQUENTIAL_SCAN |
- FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hFileSource == INVALID_HANDLE_VALUE)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- const std::wstring errorMsg = replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile));
- std::wstring errorDescr = formatSystemError(L"CreateFile", ec);
-
- //if file is locked throw "ErrorFileLocked" instead!
- if (ec == ERROR_SHARING_VIOLATION ||
- ec == ERROR_LOCK_VIOLATION)
- {
-#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message
- const std::wstring procList = vista::getLockingProcesses(sourceFile); //noexcept
- if (!procList.empty())
- errorDescr = _("The file is locked by another process:") + L"\n" + procList;
-#endif
- throw ErrorFileLocked(errorMsg, errorDescr);
- }
-
- throw FileError(errorMsg, errorDescr);
- }
- FileInput fileIn(hFileSource, sourceFile); //pass ownership
-
- if (notifyProgress) notifyProgress(0); //throw X!
-
- //----------------------------------------------------------------------
- BY_HANDLE_FILE_INFORMATION sourceInfo = {};
- if (!::GetFileInformationByHandle(fileIn.getHandle(), &sourceInfo))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"GetFileInformationByHandle");
-
- //encrypted files cannot be read with BackupRead which would fail silently!
- const bool sourceIsEncrypted = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0;
- if (sourceIsEncrypted && scType == StreamCopyType::BACKUP_READ)
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: Source file is encrypted.");
- //----------------------------------------------------------------------
-
- const DWORD validAttribs = FILE_ATTRIBUTE_NORMAL | //"This attribute is valid only if used alone."
- 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 -> no!
-
- HANDLE hFileTarget = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), //_In_ LPCTSTR lpFileName,
- GENERIC_READ | GENERIC_WRITE | DELETE, //_In_ DWORD dwDesiredAccess,
- //GENERIC_READ required for FSCTL_SET_COMPRESSION, DELETE for ::SetFileInformationByHandle(),FileDispositionInfo
- FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- //FILE_SHARE_DELETE is required to rename file while handle is open!
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- CREATE_NEW, //_In_ DWORD dwCreationDisposition,
- //FILE_FLAG_OVERLAPPED must not be used! FILE_FLAG_NO_BUFFERING should not be used!
- (sourceInfo.dwFileAttributes & validAttribs) |
- FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes,
- FILE_FLAG_BACKUP_SEMANTICS, //-> also required by FSCTL_SET_SPARSE
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hFileTarget == INVALID_HANDLE_VALUE)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile));
- const std::wstring errorDescr = formatSystemError(L"CreateFile", ec);
-
- if (ec == ERROR_FILE_EXISTS || //confirmed to be used
- ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6
- throw ErrorTargetExisting(errorMsg, errorDescr);
-
- throw FileError(errorMsg, errorDescr);
- }
-#ifdef ZEN_WIN_VISTA_AND_LATER
- FileOutput fileOut(hFileTarget, targetFile); //pass ownership
-
- //no need for ::DeleteFile(), we already have an open handle! Maybe this also prevents needless buffer-flushing in ::CloseHandle()??? Anyway, same behavior like ::CopyFileEx()
- ZEN_ON_SCOPE_FAIL
- (
- FILE_DISPOSITION_INFO di = {};
- di.DeleteFile = true;
- if (!::SetFileInformationByHandle(fileOut.getHandle(), //_In_ HANDLE hFile,
- FileDispositionInfo, //_In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
- &di, //_In_ LPVOID lpFileInformation,
- sizeof(di))) //_In_ DWORD dwBufferSize
- assert(false);
- );
-#else
- ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
- catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget
-
- FileOutput fileOut(hFileTarget, targetFile); //pass ownership
-#endif
- if (notifyProgress) notifyProgress(0); //throw X!
-
- //----------------------------------------------------------------------
- BY_HANDLE_FILE_INFORMATION targetInfo = {};
- if (!::GetFileInformationByHandle(fileOut.getHandle(), &targetInfo))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"GetFileInformationByHandle");
-
- //return up-to-date file attributes
- InSyncAttributes newAttrib;
- newAttrib.fileSize = get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.nFileSizeHigh);
- newAttrib.modificationTime = filetimeToTimeT(sourceInfo.ftLastWriteTime);
- newAttrib.sourceFileId = extractFileId(sourceInfo);
- newAttrib.targetFileId = extractFileId(targetInfo);
-
- //#################### copy NTFS compressed attribute #########################
- const bool sourceIsCompressed = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
- const bool targetIsCompressed = (targetInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CreateFile if target parent folder is compressed!
- if (sourceIsCompressed && !targetIsCompressed)
- {
- USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
- DWORD bytesReturned = 0;
- if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice,
- FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode,
- &cmpState, //_In_opt_ LPVOID lpInBuffer,
- sizeof(cmpState), //_In_ DWORD nInBufferSize,
- nullptr, //_Out_opt_ LPVOID lpOutBuffer,
- 0, //_In_ DWORD nOutBufferSize,
- &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned,
- nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped
- {} //may legitimately fail with ERROR_INVALID_FUNCTION if:
- // - target folder is encrypted
- // - target volume does not support compressed attribute -> unlikely in this context
- }
- //#############################################################################
-
- //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
- //Quote: It is the responsibility of the backup utility to apply file attributes to a file after it is restored by using BackupWrite.
- //The application should retrieve the attributes by using GetFileAttributes prior to creating a backup with BackupRead.
- //If a file originally had the sparse attribute (FILE_ATTRIBUTE_SPARSE_FILE), the backup utility must explicitly set the
- //attribute on the restored file.
- if (scType == StreamCopyType::BACKUP_READ)
-#ifdef ZEN_WIN_VISTA_AND_LATER
- if (canCopyAsSparse(sourceInfo.dwFileAttributes, fileOut.getHandle())) //throw ()
-#else
- if (canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile)) //throw ()
-#endif
- {
- DWORD bytesReturned = 0;
- if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice,
- FSCTL_SET_SPARSE, //_In_ DWORD dwIoControlCode,
- nullptr, //_In_opt_ LPVOID lpInBuffer,
- 0, //_In_ DWORD nInBufferSize,
- nullptr, //_Out_opt_ LPVOID lpOutBuffer,
- 0, //_In_ DWORD nOutBufferSize,
- &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned,
- nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE");
- }
-
- //----------------------------------------------------------------------
- if (scType == StreamCopyType::READ_FILE)
- unbufferedStreamCopy(fileIn, fileOut, notifyProgress); //throw FileError, X
- else
- {
- const DWORD BUFFER_SIZE = std::max(128 * 1024, static_cast<int>(sizeof(WIN32_STREAM_ID))); //must be greater than sizeof(WIN32_STREAM_ID)!
- std::vector<BYTE> buffer(BUFFER_SIZE);
-
- LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite()
- LPVOID contextWrite = nullptr; //
-
- ZEN_ON_SCOPE_EXIT(
- if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored."
- if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); //
-
- //stream-copy sourceFile to targetFile
- bool eof = false;
- bool someBytesRead = false; //try to detect failure reading encrypted files
- do
- {
- DWORD bytesRead = 0;
- if (!::BackupRead(fileIn.getHandle(), //__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,
- &contextRead)) //__out LPVOID *lpContext
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead"); //better use fine-granular error messages "reading/writing"!
-
- if (bytesRead > BUFFER_SIZE)
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this
-
- if (bytesRead < BUFFER_SIZE)
- eof = true;
-
- DWORD bytesWritten = 0;
- if (!::BackupWrite(fileOut.getHandle(), //__in HANDLE hFile,
- &buffer[0], //__in LPBYTE lpBuffer,
- bytesRead, //__in DWORD nNumberOfBytesToWrite,
- &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten,
- false, //__in BOOL bAbort,
- false, //__in BOOL bProcessSecurity,
- &contextWrite)) //__out LPVOID *lpContext
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite");
-
- if (bytesWritten != bytesRead)
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite: incomplete write."); //user should never see this
-
- //total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)!
- if (notifyProgress) notifyProgress(bytesRead); //throw X!
-
- if (bytesRead > 0)
- someBytesRead = true;
- }
- while (!eof);
-
- //::BackupRead() silently fails reading encrypted files -> double check!
- if (!someBytesRead && get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.nFileSizeHigh) != 0U)
- //note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)!
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()"
- }
-
- //time needs to be set at the end: WriteFile/BackupWrite() change modification time
- setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime, sourceInfo.ftLastWriteTime, targetFile); //throw FileError
-
- return newAttrib;
-}
-
-
-DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaBackupRead);
-DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaReadFile);
-
-
-struct CallbackData
-{
- CallbackData(const std::function<void(std::int64_t bytesDelta)>& notifyProgress,
- const Zstring& sourceFile,
- const Zstring& targetFile) :
- sourceFile_(sourceFile),
- targetFile_(targetFile),
- notifyProgress_(notifyProgress) {}
-
- const Zstring& sourceFile_;
- const Zstring& targetFile_;
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress_; //optional
-
- std::exception_ptr exception; //out
- BY_HANDLE_FILE_INFORMATION fileInfoSrc{}; //out: modified by CopyFileEx() at beginning
- BY_HANDLE_FILE_INFORMATION fileInfoTrg{}; //
-
- std::int64_t bytesReported = 0; //used internally to calculate bytes transferred delta
-};
-
-
-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 depends on file size amongst others.
- Note: for 0-sized files this callback is invoked just ONCE!
-
- 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 (only) copy file modification time over from source file AFTER the last invokation of this callback
- => it is possible to adapt file creation time of target in here, but NOT file modification time!
- CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! (confirmed with Process Monitor)
-
- alternate data stream handling:
- CopyFileEx() processes multiple streams one after another, stream 1 is the file data stream and always available!
- Each stream is initialized with CALLBACK_STREAM_SWITCH and provides *new* hSourceFile, hDestinationFile.
- Calling GetFileInformationByHandle() on hDestinationFile for stream > 1 results in ERROR_ACCESS_DENIED!
- totalBytesTransferred contains size of *all* streams and so can be larger than the "file size" file attribute
- */
-
- CallbackData& cbd = *static_cast<CallbackData*>(lpData);
-
- try
- {
- if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized)
- dwStreamNumber == 1) //consider ADS!
- {
- //#################### return source file attributes ################################
- if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(cbd.sourceFile_)), L"GetFileInformationByHandle");
-
- if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(cbd.targetFile_)), L"GetFileInformationByHandle");
-
- //#################### switch to sparse file copy if req. #######################
-#ifdef ZEN_WIN_VISTA_AND_LATER
- if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, hDestinationFile)) //throw ()
-#else
- if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw ()
-#endif
- throw ErrorFallbackToCopyViaBackupRead(L"sparse, callback"); //use a different copy routine!
-
- //#################### copy file creation time ################################
- ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling!
- //=> not really needed here, creation time is set anyway at the end of copyFileWindowsDefault()!
-
- //#################### copy NTFS compressed attribute #########################
- const bool sourceIsCompressed = (cbd.fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
- const bool targetIsCompressed = (cbd.fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed!
- if (sourceIsCompressed && !targetIsCompressed)
- {
- USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
- DWORD bytesReturned = 0;
- if (!::DeviceIoControl(hDestinationFile, //_In_ HANDLE hDevice,
- FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode,
- &cmpState, //_In_opt_ LPVOID lpInBuffer,
- sizeof(cmpState), //_In_ DWORD nInBufferSize,
- nullptr, //_Out_opt_ LPVOID lpOutBuffer,
- 0, //_In_ DWORD nOutBufferSize,
- &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned
- nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped
- {} //may legitimately fail with ERROR_INVALID_FUNCTION if:
- // - target folder is encrypted
- // - target volume does not support compressed attribute
- //#############################################################################
- }
- }
-
- if (cbd.notifyProgress_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check
- {
- cbd.notifyProgress_(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X!
- cbd.bytesReported = totalBytesTransferred.QuadPart;
- }
- }
- catch (...)
- {
- cbd.exception = std::current_exception();
- return PROGRESS_CANCEL;
- }
- return PROGRESS_CONTINUE;
-}
-
-
-InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked,
- const Zstring& targetFile, // ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
-{
- //try to get backup read and write privileges: may help solve some "access denied" errors
- bool backupPrivilegesActive = true;
- try { activatePrivilege(PrivilegeName::BACKUP); }
- catch (const FileError&) { backupPrivilegesActive = false; }
- try { activatePrivilege(PrivilegeName::RESTORE); }
- catch (const FileError&) { backupPrivilegesActive = false; }
-
- auto guardTarget = zen::makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { removeFilePlain(targetFile); } catch (FileError&) {} });
- //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;)
-
- DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS;
-
- //encrypted destination is not supported with Windows 2000! -> whatever
- copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location
-
- //if (vistaOrLater()) //see https://blogs.technet.microsoft.com/askperf/2007/05/08/slow-large-file-copy-issues/
- // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, improvement for large files (20% in test NTFS -> NTFS)
- // - this flag may cause file corruption! http://www.freefilesync.org/forum/viewtopic.php?t=1857
- // - documentation on CopyFile2() even states: "It is not recommended to pause copies that are using this flag."
- //=> it's not worth it! instead of skipping buffering at kernel-level (=> also NO prefetching!!!), skip it at user-level: memory mapped files!
- // however, perf-measurements for memory mapped files show: it's also not worth it!
-
- CallbackData cbd(notifyProgress, sourceFile, targetFile);
-
- const bool success = ::CopyFileEx(applyLongPathPrefix(sourceFile).c_str(), //__in LPCTSTR lpExistingFileName,
- applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpNewFileName,
- copyCallbackInternal, //__in_opt LPPROGRESS_ROUTINE lpProgressRoutine,
- &cbd, //__in_opt LPVOID lpData,
- nullptr, //__in_opt LPBOOL pbCancel,
- copyFlags) != FALSE; //__in DWORD dwCopyFlags
- if (cbd.exception)
- std::rethrow_exception(cbd.exception); //throw X, process errors in callback first!
-
- if (!success)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user-aborted operation IS an error condition!
-
- //trying to copy huge sparse files may directly fail with ERROR_DISK_FULL before entering the callback function
- if (canCopyAsSparse(sourceFile, targetFile)) //noexcept
- throw ErrorFallbackToCopyViaBackupRead(L"sparse, copy failure");
-
- if (ec == ERROR_ACCESS_DENIED && backupPrivilegesActive)
- //chances are good this will work with copyFileWindowsStream: http://www.freefilesync.org/forum/viewtopic.php?t=1714
- throw ErrorFallbackToCopyViaReadFile(L"access denied");
-
- //- copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: http://www.freefilesync.org/forum/viewtopic.php?t=446
- //- even BackupWrite may fail => use ReadFile: http://www.freefilesync.org/forum/viewtopic.php?t=2321
- if (ec == ERROR_FILE_NOT_FOUND &&
- cbd.fileInfoSrc.nNumberOfLinks > 0 &&
- cbd.fileInfoTrg.nNumberOfLinks > 0)
- throw ErrorFallbackToCopyViaReadFile(L"bogus file not found");
-
- //assemble error message...
- const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile));
- std::wstring errorDescr = formatSystemError(L"CopyFileEx", ec);
-
- //if file is locked throw "ErrorFileLocked" instead!
- if (ec == ERROR_SHARING_VIOLATION ||
- ec == ERROR_LOCK_VIOLATION)
- {
-#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message
- const std::wstring procList = vista::getLockingProcesses(sourceFile); //noexcept
- if (!procList.empty())
- errorDescr = _("The file is locked by another process:") + L"\n" + procList;
-#endif
- throw ErrorFileLocked(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), errorDescr);
- }
-
- //if target is existing this functions is expected to throw ErrorTargetExisting!!!
- if (ec == ERROR_FILE_EXISTS || //confirmed to be used
- ec == ERROR_ALREADY_EXISTS) //not sure if used -> better be safe than sorry!!!
- {
- guardTarget.dismiss(); //don't delete file that existed previously!
- throw ErrorTargetExisting(errorMsg, errorDescr);
- }
-
- //lastError == ERROR_PATH_NOT_FOUND: could this also be source path missing!?
-
- 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 (ec == ERROR_INVALID_PARAMETER)
- {
- const FatType fatType = getFatType(targetFile);
- if ((fatType == FatType::FAT ||
- fatType == FatType::FAT32) && //no problem for exFAT (limit ca. 128 PB)
- getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError
- errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabytes.";
- //see "Limitations of the FAT32 File System": http://support.microsoft.com/kb/314463/en-us
- }
-
- //note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target file path is of a restricted type.
- }
- catch (FileError&) {}
-
- throw FileError(errorMsg, errorDescr);
- }
-
- //caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we always need to set it again but with proper error checking!
- // - perf: recent measurements show no slow down at all for buffered USB sticks!
- setWriteTimeNative(targetFile, cbd.fileInfoSrc.ftLastWriteTime, &cbd.fileInfoSrc.ftCreationTime, ProcSymlink::FOLLOW); //throw FileError
-
- InSyncAttributes newAttrib;
- newAttrib.fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh);
- newAttrib.modificationTime = filetimeToTimeT(cbd.fileInfoSrc.ftLastWriteTime);
- newAttrib.sourceFileId = extractFileId(cbd.fileInfoSrc);
- newAttrib.targetFileId = extractFileId(cbd.fileInfoTrg);
- return newAttrib;
-}
-
-
-//another layer to support copying sparse files and handle some access denied errors
-inline
-InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
-{
- try
- {
- return copyFileWindowsDefault(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead
- }
- catch (ErrorFallbackToCopyViaReadFile&)
- {
- return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::READ_FILE, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- }
- catch (ErrorFallbackToCopyViaBackupRead&)
- {
- return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::BACKUP_READ, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- }
-}
-
-
-//another layer of indirection solving 8.3 name clashes
-inline
-InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
-{
- try
- {
- return copyFileWindowsSelectRoutine(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- }
- catch (const ErrorTargetExisting&)
- {
- //try to handle issues with already existing short 8.3 file names on Windows
- if (have8dot3NameClash(targetFile))
- {
- Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing file path to the side
- return copyFileWindowsSelectRoutine(sourceFile, targetFile, notifyProgress); //throw FileError; the short file path name clash is solved, this should work now
- }
- throw;
- }
-}
-
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
-InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
-{
- FileInput fileIn(sourceFile); //throw FileError, (ErrorFileLocked -> Windows-only)
- if (notifyProgress) notifyProgress(0); //throw X!
-
- struct ::stat sourceInfo = {};
- if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat");
-
- const mode_t mode = sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
- //it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
-
- //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
- const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
- if (fdTarget == -1)
- {
- const int ec = errno; //copy before making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile));
- const std::wstring errorDescr = formatSystemError(L"open", ec);
-
- if (ec == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
-
- throw FileError(errorMsg, errorDescr);
- }
- ZEN_ON_SCOPE_FAIL( try { removeFilePlain(targetFile); }
- catch (FileError&) {} );
- //transactional behavior: place guard after ::open() and before lifetime of FileOutput:
- //=> don't delete file that existed previously!!!
- FileOutput fileOut(fdTarget, targetFile); //pass ownership
- if (notifyProgress) notifyProgress(0); //throw X!
-
- unbufferedStreamCopy(fileIn, fileOut, notifyProgress); //throw FileError, X
-
-#ifdef ZEN_MAC
- //using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying!
- //=> use ::copyfile for extended attributes only: http://www.freefilesync.org/forum/viewtopic.php?t=401
- //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/
- //docs: http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html
- //source: http://www.opensource.apple.com/source/copyfile/copyfile-103.92.1/copyfile.c
-
- //avoid creation of ._ files if target doesn't support extended attributes: http://www.freefilesync.org/forum/viewtopic.php?t=2226
- if (hasNativeSupportForExtendedAtrributes(targetFile, fileOut.getHandle())) //throw FileError
- if (::fcopyfile(fileIn.getHandle(), fileOut.getHandle(), nullptr, COPYFILE_XATTR) != 0)
- ; //THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)), L"fcopyfile");
- /*
- both problems still occur even with the "hasNativeSupportForExtendedAtrributes" check above:
- E2BIG - reference email: "Re: FFS V7.8 on Mac with 10.11.2 ElCapitan"
- EINVAL - reference email: "Error Code 22: Invalid argument (copyfile)"
- */
-#endif
- struct ::stat targetInfo = {};
- if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
-
- //close output file handle before setting file time; also good place to catch errors when closing stream!
- fileOut.close(); //throw FileError
- if (notifyProgress) notifyProgress(0); //throw X!
-
- //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
- //this triggers bugs on samba shares where the modification time is set to current time instead.
- //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
- // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
- //OS X: http://www.freefilesync.org/forum/viewtopic.php?t=356
-#ifdef ZEN_MAC
- setWriteTimeNative(targetFile, sourceInfo.st_mtimespec, &sourceInfo.st_birthtimespec, ProcSymlink::FOLLOW); //throw FileError
- //sourceInfo.st_birthtime; -> only seconds-precision
- //sourceInfo.st_mtime; ->
-#else
- setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError
-#endif
-
- InSyncAttributes newAttrib;
- newAttrib.fileSize = sourceInfo.st_size;
-#ifdef ZEN_MAC
- newAttrib.modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setWriteTimeNative() for consistency
-#else
- newAttrib.modificationTime = sourceInfo.st_mtim.tv_sec; //
-#endif
- newAttrib.sourceFileId = extractFileId(sourceInfo);
- newAttrib.targetFileId = extractFileId(targetInfo);
- return newAttrib;
-}
-#endif
-
-/*
- ------------------
- |File Copy Layers|
- ------------------
- copyNewFile
- |
- copyFileOsSpecific (solve 8.3 issue on Windows)
- |
- copyFileWindowsSelectRoutine
- / \
-copyFileWindowsDefault(::CopyFileEx) copyFileWindowsStream(::BackupRead/::BackupWrite)
-*/
-}
-
-
-InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
-{
- const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
-
- //at this point we know we created a new file, so it's fine to delete it for cleanup!
- ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
- catch (FileError&) {});
-
- if (copyFilePermissions)
- copyItemPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError
-
- return attr;
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "file_access.h"
+#include <map>
+#include <algorithm>
+#include <stdexcept>
+#include "file_traverser.h"
+#include "scope_guard.h"
+#include "symlink_target.h"
+#include "file_id_def.h"
+#include "file_io.h"
+
+ #include <sys/vfs.h> //statfs
+ #include <sys/time.h> //lutimes
+ #ifdef HAVE_SELINUX
+ #include <selinux/selinux.h>
+ #endif
+
+
+ #include <fcntl.h> //open, close, AT_SYMLINK_NOFOLLOW, UTIME_OMIT
+ #include <sys/stat.h>
+
+using namespace zen;
+
+
+Opt<PathComponents> zen::getPathComponents(const Zstring& itemPath)
+{
+ if (startsWith(itemPath, "/"))
+ {
+ Zstring relPath(itemPath.c_str() + 1);
+ if (endsWith(relPath, FILE_NAME_SEPARATOR))
+ relPath.pop_back();
+ return PathComponents({ "/", relPath });
+ }
+ //we do NOT support relative paths!
+ return NoValue();
+}
+
+
+
+Opt<Zstring> zen::getParentFolderPath(const Zstring& itemPath)
+{
+ if (const Opt<PathComponents> comp = getPathComponents(itemPath))
+ {
+ if (comp->relPath.empty())
+ return NoValue();
+
+ const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
+ if (parentRelPath.empty())
+ return comp->rootPath;
+ return appendSeparator(comp->rootPath) + parentRelPath;
+ }
+ assert(false);
+ return NoValue();
+}
+
+
+ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
+{
+ struct ::stat itemInfo = {};
+ if (::lstat(itemPath.c_str(), &itemInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+
+ if (S_ISLNK(itemInfo.st_mode))
+ return ItemType::SYMLINK;
+ if (S_ISDIR(itemInfo.st_mode))
+ return ItemType::FOLDER;
+ return ItemType::FILE; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK
+}
+
+
+PathDetails zen::getPathDetails(const Zstring& itemPath) //throw FileError
+{
+ const Opt<Zstring> parentPath = getParentFolderPath(itemPath);
+ try
+ {
+ return { getItemType(itemPath), itemPath, {} }; //throw FileError
+ }
+ catch (FileError&)
+ {
+ if (!parentPath) //device root
+ throw;
+ //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes:
+ // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE,
+ // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable
+ }
+ const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ assert(!itemName.empty());
+
+ PathDetails pd = getPathDetails(*parentPath); //throw FileError
+ if (!pd.relPath.empty())
+ {
+ pd.relPath.push_back(itemName);
+ return { pd.existingType, pd.existingPath, pd.relPath };
+ }
+
+ try
+ {
+ traverseFolder(*parentPath,
+ [&](const FileInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FILE; },
+ [&](const FolderInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FOLDER; },
+ [&](const SymlinkInfo& si) { if (equalFilePath(si.itemName, itemName)) throw ItemType::SYMLINK; },
+ [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+
+ return { pd.existingType, *parentPath, { itemName } }; //throw FileError
+ }
+ catch (const ItemType& type) { return { type, itemPath, {} }; } //yes, exceptions for control-flow are bad design... but, but...
+ //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found)
+}
+
+
+Opt<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
+{
+ const PathDetails pd = getPathDetails(itemPath); //throw FileError
+ if (pd.relPath.empty())
+ return pd.existingType;
+ return NoValue();
+}
+
+
+bool zen::fileAvailable(const Zstring& filePath) //noexcept
+{
+ //symbolic links (broken or not) are also treated as existing files!
+ struct ::stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks!
+ return S_ISREG(fileInfo.st_mode);
+ return false;
+}
+
+
+bool zen::dirAvailable(const Zstring& dirPath) //noexcept
+{
+ //symbolic links (broken or not) are also treated as existing directories!
+ struct ::stat dirInfo = {};
+ if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks!
+ return S_ISDIR(dirInfo.st_mode);
+ return false;
+}
+
+
+bool zen::itemNotExisting(const Zstring& itemPath)
+{
+ try
+ {
+ return !getItemTypeIfExists(itemPath); //throw FileError
+ }
+ catch (FileError&) { return false; }
+}
+
+
+namespace
+{
+}
+
+
+uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError
+{
+ struct ::stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"stat");
+
+ return fileInfo.st_size;
+}
+
+
+uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available
+{
+ struct ::statfs info = {};
+ if (::statfs(path.c_str(), &info) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"statfs");
+
+ return static_cast<uint64_t>(info.f_bsize) * info.f_bavail;
+}
+
+
+VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError
+{
+ struct ::stat fileInfo = {};
+ if (::stat(itemPath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat");
+
+ return fileInfo.st_dev;
+}
+
+
+Zstring zen::getTempFolderPath() //throw FileError
+{
+ const char* buf = ::getenv("TMPDIR"); //no extended error reporting
+ if (!buf)
+ throw FileError(_("Cannot get process information."), L"getenv: TMPDIR not found.");
+ return buf;
+}
+
+
+void zen::removeFilePlain(const Zstring& filePath) //throw FileError
+{
+ const wchar_t functionName[] = L"unlink";
+ if (::unlink(filePath.c_str()) != 0)
+ {
+ ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
+ //begin of "regular" error reporting
+ std::wstring errorDescr = formatSystemError(functionName, ec);
+
+ throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr);
+ }
+}
+
+
+void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
+{
+ removeFilePlain(linkPath); //throw FileError
+}
+
+
+void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
+{
+ const wchar_t functionName[] = L"rmdir";
+ if (::rmdir(dirPath.c_str()) != 0)
+ {
+ ErrorCode ec = getLastError(); //copy before making other system calls!
+ bool symlinkExists = false;
+ try { symlinkExists = getItemType(dirPath) == ItemType::SYMLINK; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant
+
+ if (symlinkExists)
+ {
+ if (::unlink(dirPath.c_str()) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
+ return;
+ }
+ throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec));
+ }
+ /*
+ Windows: may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have
+ successfully been *marked* for deletion, but some application still has a handle open!
+ e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145
+ Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html
+ Alternatives: 1. move file/empty folder to some other location, then DeleteFile()/RemoveDirectory()
+ 2. use CreateFile/FILE_FLAG_DELETE_ON_CLOSE *without* FILE_SHARE_DELETE instead of DeleteFile() => early failure
+ */
+}
+
+
+namespace
+{
+void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
+{
+ std::vector<Zstring> filePaths;
+ std::vector<Zstring> symlinkPaths;
+ std::vector<Zstring> folderPaths;
+
+ //get all files and directories from current directory (WITHOUT subdirectories!)
+ traverseFolder(folderPath,
+ [&](const FileInfo& fi) { filePaths .push_back(fi.fullPath); },
+ [&](const FolderInfo& fi) { folderPaths .push_back(fi.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
+ [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); },
+ [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+
+ for (const Zstring& filePath : filePaths)
+ removeFilePlain(filePath); //throw FileError
+
+ for (const Zstring& symlinkPath : symlinkPaths)
+ removeSymlinkPlain(symlinkPath); //throw FileError
+
+ //delete directories recursively
+ for (const Zstring& subFolderPath : folderPaths)
+ removeDirectoryImpl(subFolderPath); //throw FileError; call recursively to correctly handle symbolic links
+
+ removeDirectoryPlain(folderPath); //throw FileError
+}
+}
+
+
+void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError
+{
+ if (getItemType(dirPath) == ItemType::SYMLINK) //throw FileError
+ removeSymlinkPlain(dirPath); //throw FileError
+ else
+ removeDirectoryImpl(dirPath); //throw FileError
+}
+
+
+namespace
+{
+/* Usage overview: (avoid circular pattern!)
+
+ renameFile() --> renameFile_sub()
+ | /|\
+ \|/ |
+ Fix8Dot3NameClash()
+*/
+//wrapper for file system rename function:
+void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+{
+ //rename() will never fail with EEXIST, but always (atomically) overwrite!
+ //=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx + MOVEFILE_REPLACE_EXISTING
+ //=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
+ //=> OS X: no solution
+
+ auto throwException = [&](int ec)
+ {
+ const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget));
+ const std::wstring errorDescr = formatSystemError(L"rename", ec);
+
+ if (ec == EXDEV)
+ throw ErrorDifferentVolume(errorMsg, errorDescr);
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+ throw FileError(errorMsg, errorDescr);
+ };
+
+ if (!equalFilePath(pathSource, pathTarget)) //exception for OS X: changing file name case is not an "already exists" situation!
+ {
+ bool alreadyExists = true;
+ try { /*ItemType type = */getItemType(pathTarget); } /*throw FileError*/ catch (FileError&) { alreadyExists = false; }
+
+ if (alreadyExists)
+ throwException(EEXIST);
+ //else: nothing exists or other error (hopefully ::rename will also fail!)
+ }
+
+ if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0)
+ throwException(errno);
+}
+
+
+}
+
+
+//rename file: no copying!!!
+void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+{
+ try
+ {
+ renameFile_sub(pathSource, pathTarget); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+ }
+ catch (const ErrorTargetExisting&)
+ {
+ throw;
+ }
+}
+
+
+namespace
+{
+void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTime, ProcSymlink procSl) //throw FileError
+{
+ /*
+ [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
+ => fallback to "retarded-idiot version"! -- DarkByte
+
+ [2015-03-09]
+ - cannot reproduce issues with NTFS and utimensat() on Ubuntu
+ - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch"
+ => let's give utimensat another chance:
+ using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"!
+ */
+ struct ::timespec newTimes[2] = {};
+ newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: http://www.freefilesync.org/forum/viewtopic.php?t=1701
+ newTimes[1] = modTime; //modification time
+
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP:
+ //http://www.freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works
+ if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, 0) == 0)
+ return;
+
+ //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: http://www.freefilesync.org/forum/viewtopic.php?t=387
+ const int fdFile = ::open(itemPath.c_str(), O_WRONLY);
+ if (fdFile == -1)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open");
+ ZEN_ON_SCOPE_EXIT(::close(fdFile));
+
+ if (::futimens(fdFile, newTimes) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"futimens");
+ }
+ else
+ {
+ if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"utimensat");
+ }
+}
+
+
+}
+
+
+void zen::setFileTime(const Zstring& filePath, int64_t modTime, ProcSymlink procSl) //throw FileError
+{
+ struct ::timespec writeTime = {};
+ writeTime.tv_sec = modTime;
+ setWriteTimeNative(filePath, writeTime, procSl); //throw FileError
+
+}
+
+
+bool zen::supportsPermissions(const Zstring& dirPath) //throw FileError
+{
+ return true;
+}
+
+
+namespace
+{
+#ifdef HAVE_SELINUX
+//copy SELinux security context
+void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
+{
+ security_context_t contextSource = nullptr;
+ const int rv = procSl == ProcSymlink::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_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), L"getfilecon");
+ }
+ ZEN_ON_SCOPE_EXIT(::freecon(contextSource));
+
+ {
+ security_context_t contextTarget = nullptr;
+ const int rv2 = procSl == ProcSymlink::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_SCOPE_EXIT(::freecon(contextTarget));
+
+ if (::strcmp(contextSource, contextTarget) == 0) //nothing to do
+ return;
+ }
+ }
+
+ const int rv3 = procSl == ProcSymlink::FOLLOW ?
+ ::setfilecon(target.c_str(), contextSource) :
+ ::lsetfilecon(target.c_str(), contextSource);
+ if (rv3 < 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon");
+}
+#endif
+
+
+//copy permissions for files, directories or symbolic links: requires admin rights
+void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, ProcSymlink procSl) //throw FileError
+{
+
+#ifdef HAVE_SELINUX //copy SELinux security context
+ copySecurityContext(sourcePath, targetPath, procSl); //throw FileError
+#endif
+
+ struct ::stat fileInfo = {};
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ if (::stat(sourcePath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat");
+
+ if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown");
+
+ if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ }
+ else
+ {
+ if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
+
+ if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
+
+ const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
+ if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ }
+
+}
+}
+
+
+void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError
+{
+ if (!getParentFolderPath(dirPath)) //device root
+ return static_cast<void>(/*ItemType =*/ getItemType(dirPath)); //throw FileError
+
+ try
+ {
+ copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting
+ }
+ catch (FileError&)
+ {
+ Opt<PathDetails> pd;
+ try { pd = getPathDetails(dirPath); /*throw FileError*/ }
+ catch (FileError&) {} //previous exception is more relevant
+
+ if (pd && pd->existingType != ItemType::FILE)
+ {
+ Zstring intermediatePath = pd->existingPath;
+ for (const Zstring& itemName : pd->relPath)
+ {
+ intermediatePath = appendSeparator(intermediatePath) + itemName;
+ copyNewDirectory(Zstring(), intermediatePath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting)
+ }
+ return;
+ }
+ throw;
+ }
+}
+
+
+//source path is optional (may be empty)
+void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting
+ bool copyFilePermissions)
+{
+ mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories
+
+ struct ::stat dirInfo = {};
+ if (!sourcePath.empty())
+ if (::stat(sourcePath.c_str(), &dirInfo) == 0)
+ {
+ mode = dirInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
+ mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
+ }
+ //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
+
+ if (::mkdir(targetPath.c_str(), mode) != 0)
+ {
+ const int lastError = errno; //copy before directly or indirectly making other system calls!
+ const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(targetPath));
+ const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
+
+ if (lastError == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+ //else if (lastError == ENOENT)
+ // throw ErrorTargetPathMissing(errorMsg, errorDescr);
+ throw FileError(errorMsg, errorDescr);
+ }
+
+ if (!sourcePath.empty())
+ {
+
+ ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPath); }
+ catch (FileError&) {}); //ensure cleanup:
+
+ //enforce copying file permissions: it's advertized on GUI...
+ if (copyFilePermissions)
+ copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError
+ }
+}
+
+
+void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError
+{
+ const Zstring linkPath = getSymlinkTargetRaw(sourceLink); //throw FileError; accept broken symlinks
+
+ const wchar_t functionName[] = L"symlink";
+ if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName);
+
+ //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist!
+ ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetLink); /*throw FileError*/ }
+ catch (FileError&) {});
+
+ //file times: essential for sync'ing a symlink: enforce this! (don't just try!)
+ struct ::stat sourceInfo = {};
+ if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat");
+
+ setWriteTimeNative(targetLink, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError
+
+ if (copyFilePermissions)
+ copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
+}
+
+
+namespace
+{
+InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting
+ const Zstring& targetFile,
+ const IOCallback& notifyUnbufferedIO)
+{
+ int64_t totalUnbufferedIO = 0;
+
+ FileInput fileIn(sourceFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //throw FileError, (ErrorFileLocked -> Windows-only)
+
+ struct ::stat sourceInfo = {};
+ if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat");
+
+ const mode_t mode = sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
+ //it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
+
+ //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
+ const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (fdTarget == -1)
+ {
+ const int ec = errno; //copy before making other system calls!
+ const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile));
+ const std::wstring errorDescr = formatSystemError(L"open", ec);
+
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+
+ throw FileError(errorMsg, errorDescr);
+ }
+ ZEN_ON_SCOPE_FAIL( try { removeFilePlain(targetFile); }
+ catch (FileError&) {} );
+ //place guard AFTER ::open() and BEFORE lifetime of FileOutput:
+ //=> don't delete file that existed previously!!!
+ FileOutput fileOut(fdTarget, targetFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //pass ownership
+
+ bufferedStreamCopy(fileIn, fileOut); //throw FileError, X
+ fileOut.flushBuffers(); //throw FileError, X
+ struct ::stat targetInfo = {};
+ if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
+
+ //close output file handle before setting file time; also good place to catch errors when closing stream!
+ fileOut.finalize(); //throw FileError, (X) essentially a close() since buffers were already flushed
+
+ //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
+ //this triggers bugs on samba shares where the modification time is set to current time instead.
+ //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
+ // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
+ //OS X: http://www.freefilesync.org/forum/viewtopic.php?t=356
+ setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError
+
+ InSyncAttributes newAttrib;
+ newAttrib.fileSize = sourceInfo.st_size;
+ newAttrib.modificationTime = sourceInfo.st_mtim.tv_sec; //
+ newAttrib.sourceFileId = extractFileId(sourceInfo);
+ newAttrib.targetFileId = extractFileId(targetInfo);
+ return newAttrib;
+}
+
+/* ------------------
+ |File Copy Layers|
+ ------------------
+ copyNewFile
+ |
+ copyFileOsSpecific (solve 8.3 issue on Windows)
+ |
+ copyFileWindowsSelectRoutine
+ / \
+copyFileWindowsDefault(::CopyFileEx) copyFileWindowsStream(::BackupRead/::BackupWrite)
+*/
+}
+
+
+InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const IOCallback& notifyUnbufferedIO)
+{
+ const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyUnbufferedIO); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+
+ //at this point we know we created a new file, so it's fine to delete it for cleanup!
+ ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
+ catch (FileError&) {});
+
+ if (copyFilePermissions)
+ copyItemPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError
+
+ return attr;
+}
diff --git a/zen/file_access.h b/zen/file_access.h
index 0586ea8f..c3a52f8a 100644..100755
--- a/zen/file_access.h
+++ b/zen/file_access.h
@@ -1,96 +1,101 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FILE_ACCESS_H_8017341345614857
-#define FILE_ACCESS_H_8017341345614857
-
-#include <functional>
-#include "zstring.h"
-#include "file_error.h"
-#include "file_id_def.h"
-
-
-namespace zen
-{
-//note: certain functions require COM initialization! (vista_file_op.h)
-
-Opt<Zstring> getParentFolderPath(const Zstring& itemPath);
-
-//POSITIVE existence checks; if negative: 1. item not existing 2. different type 3.access error or similar
-bool fileAvailable(const Zstring& filePath); //noexcept
-bool dirAvailable (const Zstring& dirPath ); //
-
-
-bool fileExists (const Zstring& filePath); //noexcept; check whether file or file-symlink exists
-bool dirExists (const Zstring& dirPath ); //noexcept; check whether directory or dir-symlink exists
-
-enum class ItemType
-{
- FILE,
- FOLDER,
- SYMLINK,
-};
-//(hopefully) fast: does not distinguish between error/not existing
-ItemType getItemType (const Zstring& itemPath); //throw FileError
-//execute potentially SLOW folder traversal but distinguish error/not existing
-Opt<ItemType> getItemTypeIfExists(const Zstring& itemPath); //throw FileError
-
-struct PathDetails
-{
- ItemType existingType;
- Zstring existingPath; //itemPath =: existingPath + relPath
- std::vector<Zstring> relPath; //
-};
-PathDetails getPathDetails(const Zstring& itemPath); //throw FileError
-
-enum class ProcSymlink
-{
- DIRECT,
- FOLLOW
-};
-void setFileTime(const Zstring& filePath, std::int64_t modificationTime, ProcSymlink procSl); //throw FileError
-
-//symlink handling: always evaluate target
-std::uint64_t getFilesize(const Zstring& filePath); //throw FileError
-std::uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError, returns 0 if not available
-VolumeId getVolumeId(const Zstring& itemPath); //throw FileError
-//get per-user directory designated for temporary files:
-Zstring getTempFolderPath(); //throw FileError
-
-void removeFilePlain (const Zstring& filePath); //throw FileError; ERROR if not existing
-void removeSymlinkPlain (const Zstring& linkPath); //throw FileError; ERROR if not existing
-void removeDirectoryPlain(const Zstring& dirPath ); //throw FileError; ERROR if not existing
-void removeDirectoryPlainRecursion(const Zstring& dirPath); //throw FileError; ERROR if not existing
-
-//rename file or directory: no copying!!!
-void renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-
-bool supportsPermissions(const Zstring& dirPath); //throw FileError, dereferences symlinks
-
-//- no error if already existing
-//- create recursively if parent directory is not existing
-void createDirectoryIfMissingRecursion(const Zstring& dirPath); //throw FileError
-
-//fail if already existing or parent directory not existing:
-//source path is optional (may be empty)
-void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting
-
-void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError
-
-struct InSyncAttributes
-{
- std::uint64_t fileSize = 0;
- std::int64_t modificationTime = 0; //time_t UTC compatible
- FileId sourceFileId;
- FileId targetFileId;
-};
-
-InSyncAttributes copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
- //accummulated delta != file size! consider ADS, sparse, compressed files
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress); //may be nullptr; throw X!
-}
-
-#endif //FILE_ACCESS_H_8017341345614857
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FILE_ACCESS_H_8017341345614857
+#define FILE_ACCESS_H_8017341345614857
+
+#include <functional>
+#include "zstring.h"
+#include "file_error.h"
+#include "file_id_def.h"
+#include "serialize.h"
+
+namespace zen
+{
+//note: certain functions require COM initialization! (vista_file_op.h)
+
+struct PathComponents
+{
+ Zstring rootPath; //itemPath = rootPath + (FILE_NAME_SEPARATOR?) + relPath
+ Zstring relPath; //
+};
+Opt<PathComponents> getPathComponents(const Zstring& itemPath); //no value on failure
+
+Opt<Zstring> getParentFolderPath(const Zstring& itemPath);
+
+//POSITIVE existence checks; if false: 1. item not existing 2. different type 3.device access error or similar
+bool fileAvailable(const Zstring& filePath); //noexcept
+bool dirAvailable (const Zstring& dirPath ); //
+//NEGATIVE existence checks; if false: 1. item existing 2.device access error or similar
+bool itemNotExisting(const Zstring& itemPath);
+
+enum class ItemType
+{
+ FILE,
+ FOLDER,
+ SYMLINK,
+};
+//(hopefully) fast: does not distinguish between error/not existing
+ItemType getItemType (const Zstring& itemPath); //throw FileError
+//execute potentially SLOW folder traversal but distinguish error/not existing
+Opt<ItemType> getItemTypeIfExists(const Zstring& itemPath); //throw FileError
+
+struct PathDetails
+{
+ ItemType existingType;
+ Zstring existingPath; //itemPath =: existingPath + relPath
+ std::vector<Zstring> relPath; //
+};
+PathDetails getPathDetails(const Zstring& itemPath); //throw FileError
+
+enum class ProcSymlink
+{
+ DIRECT,
+ FOLLOW
+};
+void setFileTime(const Zstring& filePath, int64_t modificationTime, ProcSymlink procSl); //throw FileError
+
+//symlink handling: always evaluate target
+uint64_t getFileSize(const Zstring& filePath); //throw FileError
+uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError, returns 0 if not available
+VolumeId getVolumeId(const Zstring& itemPath); //throw FileError
+//get per-user directory designated for temporary files:
+Zstring getTempFolderPath(); //throw FileError
+
+void removeFilePlain (const Zstring& filePath); //throw FileError; ERROR if not existing
+void removeSymlinkPlain (const Zstring& linkPath); //throw FileError; ERROR if not existing
+void removeDirectoryPlain(const Zstring& dirPath ); //throw FileError; ERROR if not existing
+void removeDirectoryPlainRecursion(const Zstring& dirPath); //throw FileError; ERROR if not existing
+
+//rename file or directory: no copying!!!
+void renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+
+bool supportsPermissions(const Zstring& dirPath); //throw FileError, dereferences symlinks
+
+//- no error if already existing
+//- create recursively if parent directory is not existing
+void createDirectoryIfMissingRecursion(const Zstring& dirPath); //throw FileError
+
+//fail if already existing or parent directory not existing:
+//source path is optional (may be empty)
+void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting
+
+void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError
+
+struct InSyncAttributes
+{
+ uint64_t fileSize = 0;
+ int64_t modificationTime = 0; //time_t UTC compatible
+ FileId sourceFileId;
+ FileId targetFileId;
+};
+
+InSyncAttributes copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ //accummulated delta != file size! consider ADS, sparse, compressed files
+ const IOCallback& notifyUnbufferedIO); //may be nullptr; throw X!
+}
+
+#endif //FILE_ACCESS_H_8017341345614857
diff --git a/zen/file_error.h b/zen/file_error.h
index aa41040d..87f9525b 100644..100755
--- a/zen/file_error.h
+++ b/zen/file_error.h
@@ -1,70 +1,53 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FILE_ERROR_H_839567308565656789
-#define FILE_ERROR_H_839567308565656789
-
-#include <string>
-#include "zstring.h"
-#include "utf.h"
-#include "sys_error.h" //we'll need this later anyway!
-
-namespace zen
-{
-//A high-level exception class giving detailed context information for end users
-class FileError
-{
-public:
- explicit FileError(const std::wstring& msg) : msg_(msg) {}
- FileError(const std::wstring& msg, const std::wstring& details) : msg_(msg + L"\n\n" + details) {}
- virtual ~FileError() {}
-
- const std::wstring& toString() const { return msg_; }
-
-private:
- std::wstring msg_;
-};
-
-#define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& msg) : FileError(msg) {} X(const std::wstring& msg, const std::wstring& descr) : FileError(msg, descr) {} };
-
-DEFINE_NEW_FILE_ERROR(ErrorTargetExisting);
-//DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing);
-DEFINE_NEW_FILE_ERROR(ErrorFileLocked);
-DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume);
-
-
-//CAVEAT: thread-local Win32 error code is easily overwritten => evaluate *before* making any (indirect) system calls:
-//-> MinGW + Win XP: "throw" statement allocates memory to hold the exception object => error code is cleared
-//-> VC 2015, Debug: std::wstring allocator internally calls ::FlsGetValue() => error code is cleared
-// https://connect.microsoft.com/VisualStudio/feedback/details/1775690/calling-operator-new-may-set-lasterror-to-0
-#ifdef _MSC_VER
-#define THROW_LAST_FILE_ERROR(msg, functionName) \
- do \
- { \
- const ErrorCode ecInternal = getLastError(); \
- throw FileError(msg, formatSystemError(functionName, ecInternal)); \
- \
- __pragma(warning(suppress: 4127)) /*"conditional expression is constant"*/ \
- } while (false)
-
-#else //same thing witout "__pragma":
-#define THROW_LAST_FILE_ERROR(msg, functionName) \
- do { const ErrorCode ecInternal = getLastError(); throw FileError(msg, formatSystemError(functionName, ecInternal)); } while (false)
-#endif
-
-//----------- facilitate usage of std::wstring for error messages --------------------
-
-inline
-std::wstring fmtPath(const std::wstring& displayPath)
-{
- return L'\"' + displayPath + L'\"';
-}
-
-inline std::wstring fmtPath(const Zstring& displayPath) { return fmtPath(utfCvrtTo<std::wstring>(displayPath)); }
-inline std::wstring fmtPath(const wchar_t* displayPath) { return fmtPath(std::wstring(displayPath)); }
-}
-
-#endif //FILE_ERROR_H_839567308565656789
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FILE_ERROR_H_839567308565656789
+#define FILE_ERROR_H_839567308565656789
+
+#include <string>
+#include "zstring.h"
+#include "utf.h"
+#include "sys_error.h" //we'll need this later anyway!
+
+namespace zen
+{
+//A high-level exception class giving detailed context information for end users
+class FileError
+{
+public:
+ explicit FileError(const std::wstring& msg) : msg_(msg) {}
+ FileError(const std::wstring& msg, const std::wstring& details) : msg_(msg + L"\n\n" + details) {}
+ virtual ~FileError() {}
+
+ const std::wstring& toString() const { return msg_; }
+
+private:
+ std::wstring msg_;
+};
+
+#define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& msg) : FileError(msg) {} X(const std::wstring& msg, const std::wstring& descr) : FileError(msg, descr) {} };
+
+DEFINE_NEW_FILE_ERROR(ErrorTargetExisting);
+//DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing);
+DEFINE_NEW_FILE_ERROR(ErrorFileLocked);
+DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume);
+
+
+//CAVEAT: thread-local Win32 error code is easily overwritten => evaluate *before* making any (indirect) system calls:
+//-> MinGW + Win XP: "throw" statement allocates memory to hold the exception object => error code is cleared
+//-> VC 2015, Debug: std::wstring allocator internally calls ::FlsGetValue() => error code is cleared
+// https://connect.microsoft.com/VisualStudio/feedback/details/1775690/calling-operator-new-may-set-lasterror-to-0
+#define THROW_LAST_FILE_ERROR(msg, functionName) \
+ do { const ErrorCode ecInternal = getLastError(); throw FileError(msg, formatSystemError(functionName, ecInternal)); } while (false)
+
+//----------- facilitate usage of std::wstring for error messages --------------------
+
+inline std::wstring fmtPath(const std::wstring& displayPath) { return L'\"' + displayPath + L'\"'; }
+inline std::wstring fmtPath(const Zstring& displayPath) { return fmtPath(utfCvrtTo<std::wstring>(displayPath)); }
+inline std::wstring fmtPath(const wchar_t* displayPath) { return fmtPath(std::wstring(displayPath)); } //resolve overload ambiguity
+}
+
+#endif //FILE_ERROR_H_839567308565656789
diff --git a/zen/file_id_def.h b/zen/file_id_def.h
index 3eeb79a9..87a5b199 100644..100755
--- a/zen/file_id_def.h
+++ b/zen/file_id_def.h
@@ -1,77 +1,41 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FILE_ID_DEF_H_013287632486321493
-#define FILE_ID_DEF_H_013287632486321493
-
-#include <utility>
-
-#ifdef ZEN_WIN
- #include "win.h" //includes "windows.h"
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- #include <sys/stat.h>
-#endif
-
-
-namespace zen
-{
-#ifdef ZEN_WIN
-using VolumeId = DWORD;
-using FileIndex = ULONGLONG;
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
-namespace impl { typedef struct ::stat StatDummy; } //sigh...
-
-using VolumeId = decltype(impl::StatDummy::st_dev);
-using FileIndex = decltype(impl::StatDummy::st_ino);
-#endif
-
-
-struct FileId //always available on Linux, and *generally* available on Windows)
-{
- FileId() {}
- FileId(VolumeId volId, FileIndex fIdx) : volumeId(volId), fileIndex(fIdx) {}
- VolumeId volumeId = 0;
- FileIndex fileIndex = 0;
-};
-inline bool operator==(const FileId& lhs, const FileId& rhs) { return lhs.volumeId == rhs.volumeId && lhs.fileIndex == rhs.fileIndex; }
-
-
-#ifdef ZEN_WIN
-inline
-FileId extractFileId(const BY_HANDLE_FILE_INFORMATION& fileInfo)
-{
- ULARGE_INTEGER fileIndex = {};
- fileIndex.HighPart = fileInfo.nFileIndexHigh;
- fileIndex.LowPart = fileInfo.nFileIndexLow;
-
- return fileInfo.dwVolumeSerialNumber != 0 && fileIndex.QuadPart != 0 ?
- FileId(fileInfo.dwVolumeSerialNumber, fileIndex.QuadPart) : FileId();
-}
-
-inline
-FileId extractFileId(DWORD volumeSerialNumber, ULONGLONG fileIndex)
-{
- return volumeSerialNumber != 0 && fileIndex != 0 ?
- FileId(volumeSerialNumber, fileIndex) : FileId();
-}
-
-static_assert(sizeof(FileId().volumeId ) == sizeof(BY_HANDLE_FILE_INFORMATION().dwVolumeSerialNumber), "");
-static_assert(sizeof(FileId().fileIndex) == sizeof(BY_HANDLE_FILE_INFORMATION().nFileIndexHigh) + sizeof(BY_HANDLE_FILE_INFORMATION().nFileIndexLow), "");
-static_assert(sizeof(FileId().fileIndex) == sizeof(ULARGE_INTEGER), "");
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
-inline
-FileId extractFileId(const struct ::stat& fileInfo)
-{
- return fileInfo.st_dev != 0 && fileInfo.st_ino != 0 ?
- FileId(fileInfo.st_dev, fileInfo.st_ino) : FileId();
-}
-#endif
-}
-
-#endif //FILE_ID_DEF_H_013287632486321493
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FILE_ID_DEF_H_013287632486321493
+#define FILE_ID_DEF_H_013287632486321493
+
+#include <utility>
+
+ #include <sys/stat.h>
+
+
+namespace zen
+{
+namespace impl { typedef struct ::stat StatDummy; } //sigh...
+
+using VolumeId = decltype(impl::StatDummy::st_dev);
+using FileIndex = decltype(impl::StatDummy::st_ino);
+
+
+struct FileId //always available on Linux, and *generally* available on Windows)
+{
+ FileId() {}
+ FileId(VolumeId volId, FileIndex fIdx) : volumeId(volId), fileIndex(fIdx) {}
+ VolumeId volumeId = 0;
+ FileIndex fileIndex = 0;
+};
+inline bool operator==(const FileId& lhs, const FileId& rhs) { return lhs.volumeId == rhs.volumeId && lhs.fileIndex == rhs.fileIndex; }
+
+
+inline
+FileId extractFileId(const struct ::stat& fileInfo)
+{
+ return fileInfo.st_dev != 0 && fileInfo.st_ino != 0 ?
+ FileId(fileInfo.st_dev, fileInfo.st_ino) : FileId();
+}
+}
+
+#endif //FILE_ID_DEF_H_013287632486321493
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index 440a5d0d..b4affd37 100644..100755
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -1,390 +1,273 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "file_io.h"
-#include "file_access.h"
-
-#ifdef ZEN_WIN
- #include "long_path_prefix.h"
- #include "privilege.h"
- #ifdef ZEN_WIN_VISTA_AND_LATER
- #include "vista_file_op.h"
- #endif
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- #include <sys/stat.h>
- #include <fcntl.h> //open, close
- #include <unistd.h> //read, write
-#endif
-
-using namespace zen;
-
-
-namespace
-{
-#if defined ZEN_LINUX || defined ZEN_MAC
-//- "filePath" could be a named pipe which *blocks* forever for open()!
-//- open() with O_NONBLOCK avoids the block, but opens successfully
-//- create sample pipe: "sudo mkfifo named_pipe"
-void checkForUnsupportedType(const Zstring& filePath) //throw FileError
-{
- struct ::stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) != 0) //follows symlinks
- return; //let the caller handle errors like "not existing"
-
- if (!S_ISREG(fileInfo.st_mode) &&
- !S_ISLNK(fileInfo.st_mode) &&
- !S_ISDIR(fileInfo.st_mode))
- {
- auto getTypeName = [](mode_t m) -> std::wstring
- {
- const wchar_t* name =
- S_ISCHR (m) ? L"character device":
- S_ISBLK (m) ? L"block device" :
- S_ISFIFO(m) ? L"FIFO, named pipe" :
- S_ISSOCK(m) ? L"socket" : nullptr;
- const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & S_IFMT);
- return name ? numFmt + L", " + name : numFmt;
- };
- throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtPath(filePath)) + L" " + getTypeName(fileInfo.st_mode));
- }
-}
-#endif
-
-inline
-FileHandle getInvalidHandle()
-{
-#ifdef ZEN_WIN
- return INVALID_HANDLE_VALUE;
-#elif defined ZEN_LINUX || defined ZEN_MAC
- return -1;
-#endif
-}
-}
-
-
-FileInput::FileInput(FileHandle handle, const Zstring& filePath) : FileBase(filePath), fileHandle(handle) {}
-
-
-FileInput::FileInput(const Zstring& filePath) : //throw FileError, ErrorFileLocked
- FileBase(filePath), fileHandle(getInvalidHandle())
-{
-#ifdef ZEN_WIN
- try { activatePrivilege(PrivilegeName::BACKUP); }
- catch (const FileError&) {}
-
- auto createHandle = [&](DWORD dwShareMode)
- {
- return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName,
- GENERIC_READ, //_In_ DWORD dwDesiredAccess,
- dwShareMode, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes,
- /* possible values: (Reference https://msdn.microsoft.com/en-us/library/aa363858#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 the same time
- 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
- */
- FILE_FLAG_BACKUP_SEMANTICS,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- };
- fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_DELETE);
- if (fileHandle == INVALID_HANDLE_VALUE)
- {
- //=> support reading files which are open for write (e.g. Firefox .db, .sqlite files): follow CopyFileEx() by addding FILE_SHARE_WRITE only for second try:
- if (::GetLastError() == ERROR_SHARING_VIOLATION)
- fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
-
- //begin of "regular" error reporting
- if (fileHandle == INVALID_HANDLE_VALUE)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath));
- std::wstring errorDescr = formatSystemError(L"CreateFile", ec);
-
- if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message!
- ec == ERROR_LOCK_VIOLATION)
- {
-#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message
- const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept
- if (!procList.empty())
- errorDescr = _("The file is locked by another process:") + L"\n" + procList;
-#endif
- throw ErrorFileLocked(errorMsg, errorDescr);
- }
- throw FileError(errorMsg, errorDescr);
- }
- }
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- checkForUnsupportedType(filePath); //throw FileError; opening a named pipe would block forever!
-
- //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html
- fileHandle = ::open(filePath.c_str(), O_RDONLY);
- if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), L"open");
-#endif
-
- //------------------------------------------------------------------------------------------------------
-
-#ifdef ZEN_WIN //destructor call would lead to member double clean-up!!!
- ZEN_ON_SCOPE_FAIL(::CloseHandle(fileHandle));
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- ZEN_ON_SCOPE_FAIL(::close(fileHandle));
-#endif
-
-#ifdef ZEN_LINUX //handle still un-owned => need constructor guard
- //optimize read-ahead on input file:
- if (::posix_fadvise(fileHandle, 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), L"posix_fadvise");
-
-#elif defined ZEN_MAC
- //"dtruss" doesn't show use of "fcntl() F_RDAHEAD/F_RDADVISE" for "cp")
-#endif
-}
-
-
-FileInput::~FileInput()
-{
- if (fileHandle != getInvalidHandle())
-#ifdef ZEN_WIN
- ::CloseHandle(fileHandle);
-#elif defined ZEN_LINUX || defined ZEN_MAC
- ::close(fileHandle);
-#endif
-}
-
-
-size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError; may return short, only 0 means EOF!
-{
- if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-
-#ifdef ZEN_WIN
- //posix ::read() semantics: test for end of file: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365690
- 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,
- nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile");
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- ssize_t bytesRead = 0;
- do
- {
- bytesRead = ::read(fileHandle, buffer, bytesToRead);
- }
- while (bytesRead < 0 && errno == EINTR); //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz
-
- if (bytesRead < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"read");
-#endif
- if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile: buffer overflow."); //user should never see this
-
- //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead" => loop!
-
- return bytesRead; //"zero indicates end of file"
-}
-
-//----------------------------------------------------------------------------------------------------
-
-FileOutput::FileOutput(FileHandle handle, const Zstring& filePath) : FileBase(filePath), fileHandle(handle) {}
-
-
-FileOutput::FileOutput(const Zstring& filePath, AccessFlag access) : //throw FileError, ErrorTargetExisting
- FileBase(filePath), fileHandle(getInvalidHandle())
-{
-#ifdef ZEN_WIN
- try { activatePrivilege(PrivilegeName::BACKUP); }
- catch (const FileError&) {}
- try { activatePrivilege(PrivilegeName::RESTORE); }
- catch (const FileError&) {}
-
- const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW;
-
- auto createHandle = [&](DWORD dwFlagsAndAttributes)
- {
- return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName,
- GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess,
- /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files
- 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_DELETE, //_In_ DWORD dwShareMode,
- //FILE_SHARE_DELETE is required to rename file while handle is open!
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- dwCreationDisposition, //_In_ DWORD dwCreationDisposition,
- dwFlagsAndAttributes |
- FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes,
- FILE_FLAG_BACKUP_SEMANTICS,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- };
-
- fileHandle = createHandle(FILE_ATTRIBUTE_NORMAL);
- if (fileHandle == INVALID_HANDLE_VALUE)
- {
- DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system": https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files
- if (ec == ERROR_ACCESS_DENIED && dwCreationDisposition == CREATE_ALWAYS)
- {
- const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str());
- if (attrib != INVALID_FILE_ATTRIBUTES)
- {
- fileHandle = createHandle(attrib); //retry: alas this may still fail for hidden file, e.g. accessing shared folder in XP as Virtual Box guest!
- ec = ::GetLastError();
- }
- }
-
- //begin of "regular" error reporting
- if (fileHandle == INVALID_HANDLE_VALUE)
- {
- const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath));
- std::wstring errorDescr = formatSystemError(L"CreateFile", ec);
-
-#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message
- if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message!
- ec == ERROR_LOCK_VIOLATION)
- {
- const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept
- if (!procList.empty())
- errorDescr = _("The file is locked by another process:") + L"\n" + procList;
- }
-#endif
- if (ec == ERROR_FILE_EXISTS || //confirmed to be used
- ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6
- throw ErrorTargetExisting(errorMsg, errorDescr);
- //if (ec == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr);
-
- throw FileError(errorMsg, errorDescr);
- }
- }
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast
-
- fileHandle = ::open(filePath.c_str(), O_WRONLY | O_CREAT | (access == FileOutput::ACC_CREATE_NEW ? O_EXCL : O_TRUNC),
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666
- if (fileHandle == -1)
- {
- const int ec = errno; //copy before making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath));
- const std::wstring errorDescr = formatSystemError(L"open", ec);
-
- if (ec == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
- //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr);
-
- throw FileError(errorMsg, errorDescr);
- }
-#endif
-
- //------------------------------------------------------------------------------------------------------
-
- //ScopeGuard constructorGuard = zen::makeGuard
-
- //guard handle when adding code!!!
-
- //constructorGuard.dismiss();
-}
-
-
-FileOutput::FileOutput(FileOutput&& tmp) : FileBase(tmp.getFilePath()), fileHandle(tmp.fileHandle) { tmp.fileHandle = getInvalidHandle(); }
-
-
-FileOutput::~FileOutput()
-{
- if (fileHandle != getInvalidHandle())
- try
- {
- close(); //throw FileError
- }
- catch (FileError&) { assert(false); }
-}
-
-
-void FileOutput::close() //throw FileError
-{
- if (fileHandle == getInvalidHandle())
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"Contract error: close() called more than once.");
- ZEN_ON_SCOPE_EXIT(fileHandle = getInvalidHandle());
-
- //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional!
-
-#ifdef ZEN_WIN
- if (!::CloseHandle(fileHandle))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"CloseHandle");
-#elif defined ZEN_LINUX || defined ZEN_MAC
- if (::close(fileHandle) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"close");
-#endif
-}
-
-
-size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError; may return short! CONTRACT: bytesToWrite > 0
-{
- if (bytesToWrite == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-
-#ifdef ZEN_WIN
- DWORD bytesWritten = 0; //this parameter is NOT optional: https://blogs.msdn.microsoft.com/oldnewthing/20130404-00/?p=4753/
- if (!::WriteFile(fileHandle, //__in HANDLE hFile,
- buffer, //__out LPVOID lpBuffer,
- static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite,
- &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten,
- nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"WriteFile");
-
- if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes!
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"WriteFile: incomplete write."); //user should never see this
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- ssize_t bytesWritten = 0;
- do
- {
- bytesWritten = ::write(fileHandle, buffer, bytesToWrite);
- }
- while (bytesWritten < 0 && errno == EINTR);
-
- if (bytesWritten <= 0)
- {
- if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
- errno = ENOSPC;
-
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write");
- }
- if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write: buffer overflow."); //user should never see this
-
- //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
-#endif
- return bytesWritten;
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "file_io.h"
+#include "file_access.h"
+
+ #include <sys/stat.h>
+ #include <fcntl.h> //open, close
+ #include <unistd.h> //read, write
+
+using namespace zen;
+
+
+namespace
+{
+//- "filePath" could be a named pipe which *blocks* forever for open()!
+//- open() with O_NONBLOCK avoids the block, but opens successfully
+//- create sample pipe: "sudo mkfifo named_pipe"
+void checkForUnsupportedType(const Zstring& filePath) //throw FileError
+{
+ struct ::stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) != 0) //follows symlinks
+ return; //let the caller handle errors like "not existing"
+
+ if (!S_ISREG(fileInfo.st_mode) &&
+ !S_ISLNK(fileInfo.st_mode) &&
+ !S_ISDIR(fileInfo.st_mode))
+ {
+ auto getTypeName = [](mode_t m) -> std::wstring
+ {
+ const wchar_t* name =
+ S_ISCHR (m) ? L"character device":
+ S_ISBLK (m) ? L"block device" :
+ S_ISFIFO(m) ? L"FIFO, named pipe" :
+ S_ISSOCK(m) ? L"socket" : nullptr;
+ const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & S_IFMT);
+ return name ? numFmt + L", " + name : numFmt;
+ };
+ throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtPath(filePath)) + L" " + getTypeName(fileInfo.st_mode));
+ }
+}
+}
+
+
+ const FileBase::FileHandle FileBase::invalidHandleValue = -1;
+
+
+FileBase::~FileBase()
+{
+ if (fileHandle_ != invalidHandleValue)
+ try
+ {
+ close(); //throw FileError
+ }
+ catch (FileError&) { assert(false); }
+}
+
+
+void FileBase::close() //throw FileError
+{
+ if (fileHandle_ == invalidHandleValue)
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"Contract error: close() called more than once.");
+ ZEN_ON_SCOPE_EXIT(fileHandle_ = invalidHandleValue);
+
+ //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional!
+
+ if (::close(fileHandle_) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"close");
+}
+
+//----------------------------------------------------------------------------------------------------
+
+namespace
+{
+FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileError, ErrorFileLocked
+{
+ checkForUnsupportedType(filePath); //throw FileError; opening a named pipe would block forever!
+
+ //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html
+ const FileBase::FileHandle fileHandle = ::open(filePath.c_str(), O_RDONLY);
+ if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), L"open");
+ return fileHandle; //pass ownership
+}
+}
+
+
+FileInput::FileInput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) :
+ FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {}
+
+
+FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbufferedIO) :
+ FileBase(openHandleForRead(filePath), filePath), //throw FileError, ErrorFileLocked
+ notifyUnbufferedIO_(notifyUnbufferedIO)
+{
+ //optimize read-ahead on input file:
+ if (::posix_fadvise(getHandle(), 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), L"posix_fadvise");
+
+}
+
+
+size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError; may return short, only 0 means EOF!
+{
+ if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ assert(bytesToRead == getBlockSize());
+
+ ssize_t bytesRead = 0;
+ do
+ {
+ bytesRead = ::read(getHandle(), buffer, bytesToRead);
+ }
+ while (bytesRead < 0 && errno == EINTR); //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz
+
+ if (bytesRead < 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"read");
+ if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile: buffer overflow."); //user should never see this
+
+ //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"
+
+ return bytesRead; //"zero indicates end of file"
+}
+
+
+size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, X; return "bytesToRead" bytes unless end of stream!
+{
+ const size_t blockSize = getBlockSize();
+
+ while (memBuf_.size() < bytesToRead)
+ {
+ memBuf_.resize(memBuf_.size() + blockSize);
+ const size_t bytesRead = tryRead(&*(memBuf_.end() - blockSize), blockSize); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
+ memBuf_.resize(memBuf_.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
+
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X
+
+ if (bytesRead == 0) //end of file
+ bytesToRead = memBuf_.size();
+ }
+
+ std::copy(memBuf_.begin(), memBuf_.begin() + bytesToRead, static_cast<char*>(buffer));
+ memBuf_.erase(memBuf_.begin(), memBuf_.begin() + bytesToRead);
+ return bytesToRead;
+}
+
+//----------------------------------------------------------------------------------------------------
+
+namespace
+{
+FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::AccessFlag access) //throw FileError, ErrorTargetExisting
+{
+ //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast
+
+ const FileBase::FileHandle fileHandle = ::open(filePath.c_str(), O_WRONLY | O_CREAT | (access == FileOutput::ACC_CREATE_NEW ? O_EXCL : O_TRUNC),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666
+ if (fileHandle == -1)
+ {
+ const int ec = errno; //copy before making other system calls!
+ const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath));
+ const std::wstring errorDescr = formatSystemError(L"open", ec);
+
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+ //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr);
+
+ throw FileError(errorMsg, errorDescr);
+ }
+ return fileHandle; //pass ownership
+}
+}
+
+
+FileOutput::FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) :
+ FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {}
+
+
+FileOutput::FileOutput(const Zstring& filePath, AccessFlag access, const IOCallback& notifyUnbufferedIO) :
+ FileBase(openHandleForWrite(filePath, access), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting
+
+
+FileOutput::~FileOutput()
+{
+ try
+ {
+ flushBuffers(); //throw FileError, X
+ }
+ catch (...) { assert(false); }
+}
+
+
+size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+{
+ if (bytesToWrite == 0)
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ assert(bytesToWrite <= getBlockSize());
+
+ ssize_t bytesWritten = 0;
+ do
+ {
+ bytesWritten = ::write(getHandle(), buffer, bytesToWrite);
+ }
+ while (bytesWritten < 0 && errno == EINTR);
+
+ if (bytesWritten <= 0)
+ {
+ if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
+ errno = ENOSPC;
+
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write");
+ }
+ if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write: buffer overflow."); //user should never see this
+
+ //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
+ return bytesWritten;
+}
+
+
+void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError, X
+{
+ auto bufFirst = static_cast<const char*>(buffer);
+ memBuf_.insert(memBuf_.end(), bufFirst, bufFirst + bytesToWrite);
+
+ const size_t blockSize = getBlockSize();
+ size_t bytesRemaining = memBuf_.size();
+ ZEN_ON_SCOPE_EXIT(memBuf_.erase(memBuf_.begin(), memBuf_.end() - bytesRemaining));
+ while (bytesRemaining >= blockSize)
+ {
+ const size_t bytesWritten = tryWrite(&*(memBuf_.end() - bytesRemaining), blockSize); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+ bytesRemaining -= bytesWritten;
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesWritten); //throw X!
+ }
+}
+
+
+void FileOutput::flushBuffers() //throw FileError, X
+{
+ size_t bytesRemaining = memBuf_.size();
+ ZEN_ON_SCOPE_EXIT(memBuf_.erase(memBuf_.begin(), memBuf_.end() - bytesRemaining));
+ while (bytesRemaining > 0)
+ {
+ const size_t bytesWritten = tryWrite(&*(memBuf_.end() - bytesRemaining), bytesRemaining); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+ bytesRemaining -= bytesWritten;
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesWritten); //throw X!
+ }
+}
+
+
+void FileOutput::finalize() //throw FileError, X
+{
+ flushBuffers(); //throw FileError, X
+ //~FileBase() calls this one, too, but we want to propagate errors if any:
+ close(); //throw FileError
+}
+
+
+void FileOutput::preAllocateSpaceBestEffort(uint64_t expectedSize) //throw FileError
+{
+ const FileHandle fh = getHandle();
+ //don't use potentially inefficient ::posix_fallocate!
+ const int rv = ::fallocate(fh, //int fd,
+ 0, //int mode,
+ 0, //off_t offset
+ expectedSize); //off_t len
+ if (rv != 0)
+ return; //may fail with EOPNOTSUPP, unlike posix_fallocate
+
+}
diff --git a/zen/file_io.h b/zen/file_io.h
index 4a135150..8a5e0f7f 100644..100755
--- a/zen/file_io.h
+++ b/zen/file_io.h
@@ -1,117 +1,123 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FILE_IO_H_89578342758342572345
-#define FILE_IO_H_89578342758342572345
-
-#include "file_error.h"
-#include "serialize.h"
-
-#ifdef ZEN_WIN
- #include "win.h" //includes "windows.h"
-#endif
-
-
-namespace zen
-{
-#ifdef ZEN_WIN
- const char LINE_BREAK[] = "\r\n";
-#elif defined ZEN_LINUX || defined ZEN_MAC
- const char LINE_BREAK[] = "\n"; //since OS X apple uses newline, too
-#endif
-
-//OS-buffered file IO optimized for sequential read/write accesses + better error reporting + long path support + following symlinks
-
-#ifdef ZEN_WIN
- using FileHandle = HANDLE;
-#elif defined ZEN_LINUX || defined ZEN_MAC
- using FileHandle = int;
-#endif
-
-class FileBase
-{
-public:
- const Zstring& getFilePath() const { return filename_; }
-
-protected:
- FileBase(const Zstring& filename) : filename_(filename) {}
-
-private:
- FileBase (const FileBase&) = delete;
- FileBase& operator=(const FileBase&) = delete;
-
- const Zstring filename_;
-};
-
-//-----------------------------------------------------------------------------------------------
-
-class FileInput : public FileBase
-{
-public:
- FileInput(const Zstring& filePath); //throw FileError, ErrorFileLocked
- FileInput(FileHandle handle, const Zstring& filePath); //takes ownership!
- ~FileInput();
-
- //Windows: better use 64kB ?? https://technet.microsoft.com/en-us/library/cc938632
- //Linux: use st_blksize?
- size_t getBlockSize() const { return 128 * 1024; }
- size_t tryRead(void* buffer, size_t bytesToRead); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0!
-
- FileHandle getHandle() { return fileHandle; }
-
-private:
- FileHandle fileHandle;
-};
-
-
-class FileOutput : public FileBase
-{
-public:
- enum AccessFlag
- {
- ACC_OVERWRITE,
- ACC_CREATE_NEW
- };
-
- FileOutput(const Zstring& filePath, AccessFlag access); //throw FileError, ErrorTargetExisting
- FileOutput(FileHandle handle, const Zstring& filePath); //takes ownership!
- ~FileOutput();
-
- FileOutput(FileOutput&& tmp);
-
- size_t getBlockSize() const { return 128 * 1024; }
- size_t tryWrite(const void* buffer, size_t bytesToWrite); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
-
- void close(); //throw FileError -> optional, but good place to catch errors when closing stream!
- FileHandle getHandle() { return fileHandle; }
-
-private:
- FileHandle fileHandle;
-};
-
-
-//native stream I/O convenience functions:
-
-template <class BinContainer> inline
-BinContainer loadBinContainer(const Zstring& filePath, //throw FileError
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress) //optional
-{
- FileInput streamIn(filePath); //throw FileError, ErrorFileLocked
- return unbufferedLoad<BinContainer>(streamIn, notifyProgress); //throw FileError
-}
-
-
-template <class BinContainer> inline
-void saveBinContainer(const Zstring& filePath, const BinContainer& buffer, //throw FileError
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress) //optional
-{
- FileOutput fileOut(filePath, FileOutput::ACC_OVERWRITE); //
- unbufferedSave(buffer, fileOut, notifyProgress); //throw FileError
- fileOut.close(); //
-}
-}
-
-#endif //FILE_IO_H_89578342758342572345
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FILE_IO_H_89578342758342572345
+#define FILE_IO_H_89578342758342572345
+
+#include "file_error.h"
+#include "serialize.h"
+
+
+namespace zen
+{
+ const char LINE_BREAK[] = "\n"; //since OS X apple uses newline, too
+
+/*
+OS-buffered file IO optimized for
+ - sequential read/write accesses
+ - better error reporting
+ - long path support
+ - follows symlinks
+ */
+class FileBase
+{
+public:
+ const Zstring& getFilePath() const { return filePath_; }
+
+ using FileHandle = int;
+
+ FileHandle getHandle() { return fileHandle_; }
+
+ //Windows: use 64kB ?? https://technet.microsoft.com/en-us/library/cc938632
+ //Linux: use st_blksize?
+ static size_t getBlockSize() { return 128 * 1024; };
+
+protected:
+ FileBase(FileHandle handle, const Zstring& filePath) : fileHandle_(handle), filePath_(filePath) {}
+ ~FileBase();
+
+ void close(); //throw FileError -> optional, but good place to catch errors when closing stream!
+ static const FileHandle invalidHandleValue;
+
+private:
+ FileBase (const FileBase&) = delete;
+ FileBase& operator=(const FileBase&) = delete;
+
+ FileHandle fileHandle_ = invalidHandleValue;
+ const Zstring filePath_;
+};
+
+//-----------------------------------------------------------------------------------------------
+
+class FileInput : public FileBase
+{
+public:
+ FileInput(const Zstring& filePath, const IOCallback& notifyUnbufferedIO); //throw FileError, ErrorFileLocked
+ FileInput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO); //takes ownership!
+
+ size_t read(void* buffer, size_t bytesToRead); //throw FileError, X; return "bytesToRead" bytes unless end of stream!
+
+private:
+ size_t tryRead(void* buffer, size_t bytesToRead); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0!
+
+ std::vector<char> memBuf_;
+ const IOCallback notifyUnbufferedIO_; //throw X
+};
+
+
+class FileOutput : public FileBase
+{
+public:
+ enum AccessFlag
+ {
+ ACC_OVERWRITE,
+ ACC_CREATE_NEW
+ };
+ FileOutput(const Zstring& filePath, AccessFlag access, const IOCallback& notifyUnbufferedIO); //throw FileError, ErrorTargetExisting
+ FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO); //takes ownership!
+ ~FileOutput();
+
+ void preAllocateSpaceBestEffort(uint64_t expectedSize); //throw FileError
+
+ void write(const void* buffer, size_t bytesToWrite); //throw FileError, X
+ void flushBuffers(); //throw FileError, X
+ void finalize(); /*= flushBuffers() + close()*/ //throw FileError, X
+
+private:
+ size_t tryWrite(const void* buffer, size_t bytesToWrite); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+
+ std::vector<char> memBuf_;
+ const IOCallback notifyUnbufferedIO_; //throw X
+};
+
+//-----------------------------------------------------------------------------------------------
+
+//native stream I/O convenience functions:
+
+template <class BinContainer> inline
+BinContainer loadBinContainer(const Zstring& filePath, //throw FileError
+ const IOCallback& notifyUnbufferedIO)
+{
+ FileInput streamIn(filePath, notifyUnbufferedIO); //throw FileError, ErrorFileLocked
+ return bufferedLoad<BinContainer>(streamIn); //throw FileError, X;
+}
+
+
+template <class BinContainer> inline
+void saveBinContainer(const Zstring& filePath, const BinContainer& buffer, //throw FileError
+ const IOCallback& notifyUnbufferedIO)
+{
+ FileOutput fileOut(filePath, FileOutput::ACC_OVERWRITE, notifyUnbufferedIO); //throw FileError, (ErrorTargetExisting)
+ if (!buffer.empty())
+ {
+ /*snake oil?*/ fileOut.preAllocateSpaceBestEffort(buffer.size()); //throw FileError
+ fileOut.write(&*buffer.begin(), buffer.size()); //throw FileError, X
+ }
+ fileOut.finalize(); //throw FileError, X
+}
+}
+
+#endif //FILE_IO_H_89578342758342572345
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index ef6d255c..d5e3912b 100644..100755
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -1,194 +1,105 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "file_traverser.h"
-#include "file_error.h"
-
-#ifdef ZEN_WIN
- #include "int64.h"
- #include "long_path_prefix.h"
- #include "file_access.h"
- #include "symlink_target.h"
-#elif defined ZEN_MAC
- #include "osx_string.h"
-#endif
-
-#if defined ZEN_LINUX || defined ZEN_MAC
- #include <cstddef> //offsetof
- #include <unistd.h> //::pathconf()
- #include <sys/stat.h>
- #include <dirent.h>
-#endif
-
-using namespace zen;
-
-
-void zen::traverseFolder(const Zstring& dirPath,
- const std::function<void (const FileInfo& fi)>& onFile,
- const std::function<void (const FolderInfo& fi)>& onFolder,
- const std::function<void (const SymlinkInfo& si)>& onSymlink,
- const std::function<void (const std::wstring& errorMsg)>& onError)
-{
- try
- {
-#ifdef ZEN_WIN
- WIN32_FIND_DATA findData = {};
- HANDLE hDir = ::FindFirstFile(applyLongPathPrefix(appendSeparator(dirPath) + L'*').c_str(), &findData);
- if (hDir == INVALID_HANDLE_VALUE)
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- if (ec == ERROR_FILE_NOT_FOUND)
- try
- {
- //1. directory may not exist *or* 2. it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory; NetDrive
- // -> FindFirstFile() is a nice example of violating the API design principle of single responsibility
- if (getItemType(dirPath) == ItemType::FOLDER) //throw FileError
- return;
- }
- catch (FileError&) {} //previous exception is more relevant
-
- throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindFirstFile", ec));
- }
- ZEN_ON_SCOPE_EXIT(::FindClose(hDir));
-
- bool firstIteration = true;
- for (;;)
- {
- if (firstIteration) //keep ::FindNextFile at the start of the for-loop to support "continue"!
- firstIteration = false;
- else if (!::FindNextFile(hDir, &findData))
- {
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- if (ec == ERROR_NO_MORE_FILES) //not an error situation
- return;
- //else we have a problem... report it:
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindNextFile", ec));
- }
-
- //skip "." and ".."
- const wchar_t* const itemNameRaw = findData.cFileName;
-
- if (itemNameRaw[0] == L'.' &&
- (itemNameRaw[1] == 0 || (itemNameRaw[1] == L'.' && itemNameRaw[2] == 0)))
- continue;
-
- if (itemNameRaw[0] == 0)
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name.");
-
- const Zstring& itemName = itemNameRaw;
- const Zstring& itemPath = appendSeparator(dirPath) + itemName;
-
- if (zen::isSymlink(findData)) //check first!
- {
- if (onSymlink)
- onSymlink({ itemName, itemPath, filetimeToTimeT(findData.ftLastWriteTime) });
- }
- else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
- {
- if (onFolder)
- onFolder({ itemName, itemPath });
- }
- else //a file
- {
- if (onFile)
- onFile({ itemName, itemPath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) });
- }
- }
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- /* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede
- that field within the dirent structure, portable applications that use readdir_r() should allocate
- the buffer whose address is passed in entry as follows:
- len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1
- entryp = malloc(len); */
- const size_t nameMax = std::max<long>(::pathconf(dirPath.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1)
- std::vector<char> buffer(offsetof(struct ::dirent, d_name) + nameMax + 1);
-
- DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/"
- if (!folder)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir");
- ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash
-
- for (;;)
- {
- struct ::dirent* dirEntry = nullptr;
- if (::readdir_r(folder, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r");
- //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/
-
- if (!dirEntry) //no more items
- return;
-
- //don't return "." and ".."
- const char* itemNameRaw = dirEntry->d_name;
-
- if (itemNameRaw[0] == '.' &&
- (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0)))
- continue;
-
-#ifdef ZEN_MAC //normalize all text input (see see native_traverser_impl.h)
- Zstring itemName;
- try
- {
- itemName = osx::normalizeUtfForPosix(itemNameRaw); //throw SysError
- }
- catch (const SysError& e) //failure is not an item-level error since we don't know the normalized name yet!!!
- {
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)),
- L"Failed to generate normalized file name: " + fmtPath(itemNameRaw) + L"\n" + e.toString()); //too obscure to warrant translation
- }
-#else
- const Zstring& itemName = itemNameRaw;
-#endif
- if (itemName.empty()) //checks result of osx::normalizeUtfForPosix, too!
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name.");
-
- const Zstring& itemPath = appendSeparator(dirPath) + itemName;
-
- struct ::stat statData = {};
- try
- {
- if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
- }
- catch (const FileError& e)
- {
- if (onError)
- onError(e.toString());
- continue; //ignore error: skip file
- }
-
- if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks!
- {
- if (onSymlink)
- onSymlink({ itemName, itemPath, statData.st_mtime});
- }
- else if (S_ISDIR(statData.st_mode)) //a directory
- {
- if (onFolder)
- onFolder({ itemName, itemPath });
- }
- else //a file or named pipe, ect.
- {
- if (onFile)
- onFile({ itemName, itemPath, makeUnsigned(statData.st_size), statData.st_mtime });
- }
- /*
- It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios:
- - RTS setup watch (essentially wants to read directories only)
- - removeDirectory (wants to delete everything; pipes can be deleted just like files via "unlink")
-
- However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!!
- */
- }
-#endif
- }
- catch (const FileError& e)
- {
- if (onError)
- onError(e.toString());
- }
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "file_traverser.h"
+#include "file_error.h"
+
+
+ #include <cstddef> //offsetof
+ #include <unistd.h> //::pathconf()
+ #include <sys/stat.h>
+ #include <dirent.h>
+
+using namespace zen;
+
+
+void zen::traverseFolder(const Zstring& dirPath,
+ const std::function<void (const FileInfo& fi)>& onFile,
+ const std::function<void (const FolderInfo& fi)>& onFolder,
+ const std::function<void (const SymlinkInfo& si)>& onSymlink,
+ const std::function<void (const std::wstring& errorMsg)>& onError)
+{
+ try
+ {
+ /* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede
+ that field within the dirent structure, portable applications that use readdir_r() should allocate
+ the buffer whose address is passed in entry as follows:
+ len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1
+ entryp = malloc(len); */
+ const size_t nameMax = std::max<long>(::pathconf(dirPath.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1)
+ std::vector<char> buffer(offsetof(struct ::dirent, d_name) + nameMax + 1);
+
+ DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/"
+ if (!folder)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir");
+ ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash
+
+ for (;;)
+ {
+ struct ::dirent* dirEntry = nullptr;
+ if (::readdir_r(folder, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r");
+ //don't retry but restart dir traversal on error! https://blogs.msdn.microsoft.com/oldnewthing/20140612-00/?p=753/
+
+ if (!dirEntry) //no more items
+ return;
+
+ //don't return "." and ".."
+ const char* itemNameRaw = dirEntry->d_name;
+
+ if (itemNameRaw[0] == '.' &&
+ (itemNameRaw[1] == 0 || (itemNameRaw[1] == '.' && itemNameRaw[2] == 0)))
+ continue;
+
+ const Zstring& itemName = itemNameRaw;
+ if (itemName.empty()) //checks result of osx::normalizeUtfForPosix, too!
+ throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item with empty name.");
+
+ const Zstring& itemPath = appendSeparator(dirPath) + itemName;
+
+ struct ::stat statData = {};
+ try
+ {
+ if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+ }
+ catch (const FileError& e)
+ {
+ if (onError)
+ onError(e.toString());
+ continue; //ignore error: skip file
+ }
+
+ if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks!
+ {
+ if (onSymlink)
+ onSymlink({ itemName, itemPath, statData.st_mtime});
+ }
+ else if (S_ISDIR(statData.st_mode)) //a directory
+ {
+ if (onFolder)
+ onFolder({ itemName, itemPath });
+ }
+ else //a file or named pipe, ect.
+ {
+ if (onFile)
+ onFile({ itemName, itemPath, makeUnsigned(statData.st_size), statData.st_mtime });
+ }
+ /*
+ It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios:
+ - RTS setup watch (essentially wants to read directories only)
+ - removeDirectory (wants to delete everything; pipes can be deleted just like files via "unlink")
+
+ However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!!
+ */
+ }
+ }
+ catch (const FileError& e)
+ {
+ if (onError)
+ onError(e.toString());
+ }
+}
diff --git a/zen/file_traverser.h b/zen/file_traverser.h
index 1badddf9..0eb3bbee 100644..100755
--- a/zen/file_traverser.h
+++ b/zen/file_traverser.h
@@ -1,47 +1,47 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FILER_TRAVERSER_H_127463214871234
-#define FILER_TRAVERSER_H_127463214871234
-
-#include <cstdint>
-#include <functional>
-#include "zstring.h"
-
-
-namespace zen
-{
-struct FileInfo
-{
- Zstring itemName;
- Zstring fullPath;
- std::uint64_t fileSize; //[bytes]
- std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
-};
-
-struct FolderInfo
-{
- Zstring itemName;
- Zstring fullPath;
-};
-
-struct SymlinkInfo
-{
- Zstring itemName;
- Zstring fullPath;
- std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
-};
-
-//- non-recursive
-//- directory path may end with PATH_SEPARATOR
-void traverseFolder(const Zstring& dirPath, //noexcept
- const std::function<void (const FileInfo& fi)>& onFile, //
- const std::function<void (const FolderInfo& fi)>& onFolder, //optional
- const std::function<void (const SymlinkInfo& si)>& onSymlink, //
- const std::function<void (const std::wstring& errorMsg)>& onError); //
-}
-
-#endif //FILER_TRAVERSER_H_127463214871234
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FILER_TRAVERSER_H_127463214871234
+#define FILER_TRAVERSER_H_127463214871234
+
+#include <cstdint>
+#include <functional>
+#include "zstring.h"
+
+
+namespace zen
+{
+struct FileInfo
+{
+ Zstring itemName;
+ Zstring fullPath;
+ uint64_t fileSize; //[bytes]
+ int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
+};
+
+struct FolderInfo
+{
+ Zstring itemName;
+ Zstring fullPath;
+};
+
+struct SymlinkInfo
+{
+ Zstring itemName;
+ Zstring fullPath;
+ int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC
+};
+
+//- non-recursive
+//- directory path may end with PATH_SEPARATOR
+void traverseFolder(const Zstring& dirPath, //noexcept
+ const std::function<void (const FileInfo& fi)>& onFile, //
+ const std::function<void (const FolderInfo& fi)>& onFolder, //optional
+ const std::function<void (const SymlinkInfo& si)>& onSymlink, //
+ const std::function<void (const std::wstring& errorMsg)>& onError); //
+}
+
+#endif //FILER_TRAVERSER_H_127463214871234
diff --git a/zen/fixed_list.h b/zen/fixed_list.h
index 27eb488c..e143c7b7 100644..100755
--- a/zen/fixed_list.h
+++ b/zen/fixed_list.h
@@ -1,239 +1,239 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FIXED_LIST_H_01238467085684139453534
-#define FIXED_LIST_H_01238467085684139453534
-
-#include <cassert>
-#include <iterator>
-#include "stl_tools.h"
-
-namespace zen
-{
-//std::list(C++11)-like class for inplace element construction supporting non-copyable/non-movable types
-//-> no iterator invalidation after emplace_back()
-
-template <class T>
-class FixedList
-{
- struct Node
- {
- template <class... Args>
- Node(Args&& ... args) : val(std::forward<Args>(args)...) {}
-
- Node* next = nullptr; //singly-linked list is sufficient
- T val;
- };
-
-public:
- FixedList() {}
-
- ~FixedList() { clear(); }
-
- template <class NodeT, class U>
- class FixedIterator : public std::iterator<std::forward_iterator_tag, U>
- {
- public:
- FixedIterator(NodeT* it = nullptr) : it_(it) {}
- FixedIterator& operator++() { it_ = it_->next; return *this; }
- inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; }
- inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); }
- U& operator* () const { return it_->val; }
- U* operator->() const { return &it_->val; }
- private:
- NodeT* it_;
- };
-
- using value_type = T;
- using iterator = FixedIterator< Node, T>;
- using const_iterator = FixedIterator<const Node, const T>;
- using reference = T&;
- using const_reference = const T&;
-
- iterator begin() { return firstInsert_; }
- iterator end () { return iterator(); }
-
- const_iterator begin() const { return firstInsert_; }
- const_iterator end () const { return const_iterator(); }
-
- //const_iterator cbegin() const { return firstInsert_; }
- //const_iterator cend () const { return const_iterator(); }
-
- reference front() { return firstInsert_->val; }
- const_reference front() const { return firstInsert_->val; }
-
- reference& back() { return lastInsert_->val; }
- const_reference& back() const { return lastInsert_->val; }
-
- template <class... Args>
- void emplace_back(Args&& ... args)
- {
- Node* newNode = new Node(std::forward<Args>(args)...);
-
- if (!lastInsert_)
- {
- assert(!firstInsert_ && sz_ == 0);
- firstInsert_ = lastInsert_ = newNode;
- }
- else
- {
- assert(lastInsert_->next == nullptr);
- lastInsert_->next = newNode;
- lastInsert_ = newNode;
- }
- ++sz_;
- }
-
- template <class Predicate>
- void remove_if(Predicate pred)
- {
- Node* prev = nullptr;
- Node* ptr = firstInsert_;
-
- while (ptr)
- if (pred(ptr->val))
- {
- Node* next = ptr->next;
-
- delete ptr;
- assert(sz_ > 0);
- --sz_;
-
- ptr = next;
-
- if (prev)
- prev->next = next;
- else
- firstInsert_ = next;
- if (!next)
- lastInsert_ = prev;
- }
- else
- {
- prev = ptr;
- ptr = ptr->next;
- }
- }
-
- void clear()
- {
- Node* ptr = firstInsert_;
- while (ptr)
- {
- Node* next = ptr->next;
- delete ptr;
- ptr = next;
- }
-
- sz_ = 0;
- firstInsert_ = lastInsert_ = nullptr;
- }
-
- bool empty() const { return sz_ == 0; }
-
- size_t size() const { return sz_; }
-
- void swap(FixedList& other)
- {
- std::swap(firstInsert_, other.firstInsert_);
- std::swap(lastInsert_, other.lastInsert_);
- std::swap(sz_, other.sz_);
- }
-
-private:
- FixedList (const FixedList&) = delete;
- FixedList& operator=(const FixedList&) = delete;
-
- Node* firstInsert_ = nullptr;
- Node* lastInsert_ = nullptr; //point to last insertion; required by efficient emplace_back()
- size_t sz_ = 0;
-};
-
-
-//just as fast as FixedList, but simpler, more CPU-cache-friendly => superseeds FixedList!
-template <class T>
-class FixedVector
-{
-public:
- FixedVector() {}
-
- /*
- class EndIterator {}; //just like FixedList: no iterator invalidation after emplace_back()
-
- template <class V>
- class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this random-access if needed
- {
- public:
- FixedIterator(std::vector<std::unique_ptr<T>>& cont, size_t pos) : cont_(cont), pos_(pos) {}
- FixedIterator& operator++() { ++pos_; return *this; }
- inline friend bool operator==(const FixedIterator& lhs, EndIterator) { return lhs.pos_ == lhs.cont_.size(); }
- inline friend bool operator!=(const FixedIterator& lhs, EndIterator) { return !(lhs == EndIterator()); }
- V& operator* () const { return *cont_[pos_]; }
- V* operator->() const { return &*cont_[pos_]; }
- private:
- std::vector<std::unique_ptr<T>>& cont_;
- size_t pos_ = 0;
- };
- */
-
- template <class IterImpl, class V>
- class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this bidirectional if needed
- {
- public:
- FixedIterator(IterImpl it) : it_(it) {}
- FixedIterator& operator++() { ++it_; return *this; }
- inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; }
- inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); }
- V& operator* () const { return **it_; }
- V* operator->() const { return &** it_; }
- private:
- IterImpl it_; //TODO: avoid iterator invalidation after emplace_back(); caveat: end() must not store old length!
- };
-
- using value_type = T;
- using iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::iterator, T>;
- using const_iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::const_iterator, const T>;
- using reference = T&;
- using const_reference = const T&;
-
- iterator begin() { return items_.begin(); }
- iterator end () { return items_.end (); }
-
- const_iterator begin() const { return items_.begin(); }
- const_iterator end () const { return items_.end (); }
-
- reference front() { return *items_.front(); }
- const_reference front() const { return *items_.front(); }
-
- reference& back() { return *items_.back(); }
- const_reference& back() const { return *items_.back(); }
-
- template <class... Args>
- void emplace_back(Args&& ... args)
- {
- items_.push_back(std::make_unique<T>(std::forward<Args>(args)...));
- }
-
- template <class Predicate>
- void remove_if(Predicate pred)
- {
- erase_if(items_, [&](const std::unique_ptr<T>& p) { return pred(*p); });
- }
-
- void clear() { items_.clear(); }
- bool empty() const { return items_.empty(); }
- size_t size () const { return items_.size(); }
- void swap(FixedVector& other) { items_.swap(other.items_); }
-
-private:
- FixedVector (const FixedVector&) = delete;
- FixedVector& operator=(const FixedVector&) = delete;
-
- std::vector<std::unique_ptr<T>> items_;
-};
-}
-
-#endif //FIXED_LIST_H_01238467085684139453534
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FIXED_LIST_H_01238467085684139453534
+#define FIXED_LIST_H_01238467085684139453534
+
+#include <cassert>
+#include <iterator>
+#include "stl_tools.h"
+
+namespace zen
+{
+//std::list(C++11)-like class for inplace element construction supporting non-copyable/non-movable types
+//-> no iterator invalidation after emplace_back()
+
+template <class T>
+class FixedList
+{
+ struct Node
+ {
+ template <class... Args>
+ Node(Args&& ... args) : val(std::forward<Args>(args)...) {}
+
+ Node* next = nullptr; //singly-linked list is sufficient
+ T val;
+ };
+
+public:
+ FixedList() {}
+
+ ~FixedList() { clear(); }
+
+ template <class NodeT, class U>
+ class FixedIterator : public std::iterator<std::forward_iterator_tag, U>
+ {
+ public:
+ FixedIterator(NodeT* it = nullptr) : it_(it) {}
+ FixedIterator& operator++() { it_ = it_->next; return *this; }
+ inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; }
+ inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); }
+ U& operator* () const { return it_->val; }
+ U* operator->() const { return &it_->val; }
+ private:
+ NodeT* it_;
+ };
+
+ using value_type = T;
+ using iterator = FixedIterator< Node, T>;
+ using const_iterator = FixedIterator<const Node, const T>;
+ using reference = T&;
+ using const_reference = const T&;
+
+ iterator begin() { return firstInsert_; }
+ iterator end () { return iterator(); }
+
+ const_iterator begin() const { return firstInsert_; }
+ const_iterator end () const { return const_iterator(); }
+
+ //const_iterator cbegin() const { return firstInsert_; }
+ //const_iterator cend () const { return const_iterator(); }
+
+ reference front() { return firstInsert_->val; }
+ const_reference front() const { return firstInsert_->val; }
+
+ reference& back() { return lastInsert_->val; }
+ const_reference& back() const { return lastInsert_->val; }
+
+ template <class... Args>
+ void emplace_back(Args&& ... args)
+ {
+ Node* newNode = new Node(std::forward<Args>(args)...);
+
+ if (!lastInsert_)
+ {
+ assert(!firstInsert_ && sz_ == 0);
+ firstInsert_ = lastInsert_ = newNode;
+ }
+ else
+ {
+ assert(lastInsert_->next == nullptr);
+ lastInsert_->next = newNode;
+ lastInsert_ = newNode;
+ }
+ ++sz_;
+ }
+
+ template <class Predicate>
+ void remove_if(Predicate pred)
+ {
+ Node* prev = nullptr;
+ Node* ptr = firstInsert_;
+
+ while (ptr)
+ if (pred(ptr->val))
+ {
+ Node* next = ptr->next;
+
+ delete ptr;
+ assert(sz_ > 0);
+ --sz_;
+
+ ptr = next;
+
+ if (prev)
+ prev->next = next;
+ else
+ firstInsert_ = next;
+ if (!next)
+ lastInsert_ = prev;
+ }
+ else
+ {
+ prev = ptr;
+ ptr = ptr->next;
+ }
+ }
+
+ void clear()
+ {
+ Node* ptr = firstInsert_;
+ while (ptr)
+ {
+ Node* next = ptr->next;
+ delete ptr;
+ ptr = next;
+ }
+
+ sz_ = 0;
+ firstInsert_ = lastInsert_ = nullptr;
+ }
+
+ bool empty() const { return sz_ == 0; }
+
+ size_t size() const { return sz_; }
+
+ void swap(FixedList& other)
+ {
+ std::swap(firstInsert_, other.firstInsert_);
+ std::swap(lastInsert_, other.lastInsert_);
+ std::swap(sz_, other.sz_);
+ }
+
+private:
+ FixedList (const FixedList&) = delete;
+ FixedList& operator=(const FixedList&) = delete;
+
+ Node* firstInsert_ = nullptr;
+ Node* lastInsert_ = nullptr; //point to last insertion; required by efficient emplace_back()
+ size_t sz_ = 0;
+};
+
+
+//just as fast as FixedList, but simpler, more CPU-cache-friendly => superseeds FixedList!
+template <class T>
+class FixedVector
+{
+public:
+ FixedVector() {}
+
+ /*
+ class EndIterator {}; //just like FixedList: no iterator invalidation after emplace_back()
+
+ template <class V>
+ class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this random-access if needed
+ {
+ public:
+ FixedIterator(std::vector<std::unique_ptr<T>>& cont, size_t pos) : cont_(cont), pos_(pos) {}
+ FixedIterator& operator++() { ++pos_; return *this; }
+ inline friend bool operator==(const FixedIterator& lhs, EndIterator) { return lhs.pos_ == lhs.cont_.size(); }
+ inline friend bool operator!=(const FixedIterator& lhs, EndIterator) { return !(lhs == EndIterator()); }
+ V& operator* () const { return *cont_[pos_]; }
+ V* operator->() const { return &*cont_[pos_]; }
+ private:
+ std::vector<std::unique_ptr<T>>& cont_;
+ size_t pos_ = 0;
+ };
+ */
+
+ template <class IterImpl, class V>
+ class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this bidirectional if needed
+ {
+ public:
+ FixedIterator(IterImpl it) : it_(it) {}
+ FixedIterator& operator++() { ++it_; return *this; }
+ inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; }
+ inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); }
+ V& operator* () const { return **it_; }
+ V* operator->() const { return &** it_; }
+ private:
+ IterImpl it_; //TODO: avoid iterator invalidation after emplace_back(); caveat: end() must not store old length!
+ };
+
+ using value_type = T;
+ using iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::iterator, T>;
+ using const_iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::const_iterator, const T>;
+ using reference = T&;
+ using const_reference = const T&;
+
+ iterator begin() { return items_.begin(); }
+ iterator end () { return items_.end (); }
+
+ const_iterator begin() const { return items_.begin(); }
+ const_iterator end () const { return items_.end (); }
+
+ reference front() { return *items_.front(); }
+ const_reference front() const { return *items_.front(); }
+
+ reference& back() { return *items_.back(); }
+ const_reference& back() const { return *items_.back(); }
+
+ template <class... Args>
+ void emplace_back(Args&& ... args)
+ {
+ items_.push_back(std::make_unique<T>(std::forward<Args>(args)...));
+ }
+
+ template <class Predicate>
+ void remove_if(Predicate pred)
+ {
+ erase_if(items_, [&](const std::unique_ptr<T>& p) { return pred(*p); });
+ }
+
+ void clear() { items_.clear(); }
+ bool empty() const { return items_.empty(); }
+ size_t size () const { return items_.size(); }
+ void swap(FixedVector& other) { items_.swap(other.items_); }
+
+private:
+ FixedVector (const FixedVector&) = delete;
+ FixedVector& operator=(const FixedVector&) = delete;
+
+ std::vector<std::unique_ptr<T>> items_;
+};
+}
+
+#endif //FIXED_LIST_H_01238467085684139453534
diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp
index 08463778..cf17c8d4 100644..100755
--- a/zen/format_unit.cpp
+++ b/zen/format_unit.cpp
@@ -1,413 +1,201 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "format_unit.h"
-#include <cwchar> //swprintf
-#include <ctime>
-#include <cstdio>
-#include "basic_math.h"
-#include "i18n.h"
-#include "time.h"
-#include "globals.h"
-
-#ifdef ZEN_WIN
- #include "int64.h"
- #include "win.h" //includes "windows.h"
- // #include "win_ver.h"
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- #include <clocale> //thousands separator
- #include "utf.h" //
-#endif
-
-using namespace zen;
-
-
-std::wstring zen::formatTwoDigitPrecision(double value)
-{
- //print two digits: 0,1 | 1,1 | 11
- if (numeric::abs(value) < 9.95) //9.99 must not be formatted as "10.0"
- return printNumber<std::wstring>(L"%.1f", value);
- return numberTo<std::wstring>(numeric::round(value));
-}
-
-
-std::wstring zen::formatThreeDigitPrecision(double value)
-{
- //print three digits: 0,01 | 0,11 | 1,11 | 11,1 | 111
- if (numeric::abs(value) < 9.995) //9.999 must not be formatted as "10.00"
- return printNumber<std::wstring>(L"%.2f", value);
- if (numeric::abs(value) < 99.95) //99.99 must not be formatted as "100.0"
- return printNumber<std::wstring>(L"%.1f", value);
- return numberTo<std::wstring>(numeric::round(value));
-}
-
-
-std::wstring zen::filesizeToShortString(std::int64_t size)
-{
- //if (size < 0) return _("Error"); -> really?
-
- if (numeric::abs(size) <= 999)
- return _P("1 byte", "%x bytes", static_cast<int>(size));
-
- double sizeInUnit = static_cast<double>(size);
-
- auto formatUnit = [&](const std::wstring& unitTxt) { return replaceCpy(unitTxt, L"%x", formatThreeDigitPrecision(sizeInUnit)); };
-
- sizeInUnit /= 1024;
- if (numeric::abs(sizeInUnit) < 999.5)
- return formatUnit(_("%x KB"));
-
- sizeInUnit /= 1024;
- if (numeric::abs(sizeInUnit) < 999.5)
- return formatUnit(_("%x MB"));
-
- sizeInUnit /= 1024;
- if (numeric::abs(sizeInUnit) < 999.5)
- return formatUnit(_("%x GB"));
-
- sizeInUnit /= 1024;
- if (numeric::abs(sizeInUnit) < 999.5)
- return formatUnit(_("%x TB"));
-
- sizeInUnit /= 1024;
- return formatUnit(_("%x PB"));
-}
-
-
-namespace
-{
-enum UnitRemTime
-{
- URT_SEC,
- URT_MIN,
- URT_HOUR,
- URT_DAY
-};
-
-
-std::wstring formatUnitTime(int val, UnitRemTime unit)
-{
- switch (unit)
- {
- case URT_SEC:
- return _P("1 sec", "%x sec", val);
- case URT_MIN:
- return _P("1 min", "%x min", val);
- case URT_HOUR:
- return _P("1 hour", "%x hours", val);
- case URT_DAY:
- return _P("1 day", "%x days", val);
- }
- assert(false);
- return _("Error");
-}
-
-
-template <int M, int N>
-std::wstring roundToBlock(double timeInHigh,
- UnitRemTime unitHigh, const int (&stepsHigh)[M],
- int unitLowPerHigh,
- UnitRemTime unitLow, const int (&stepsLow)[N])
-{
- assert(unitLowPerHigh > 0);
- const double granularity = 0.1;
- const double timeInLow = timeInHigh * unitLowPerHigh;
- const int blockSizeLow = granularity * timeInHigh < 1 ?
- numeric::nearMatch(granularity * timeInLow, std::begin(stepsLow), std::end(stepsLow)):
- numeric::nearMatch(granularity * timeInHigh, std::begin(stepsHigh), std::end(stepsHigh)) * unitLowPerHigh;
- const int roundedtimeInLow = numeric::round(timeInLow / blockSizeLow) * blockSizeLow;
-
- std::wstring output = formatUnitTime(roundedtimeInLow / unitLowPerHigh, unitHigh);
- if (unitLowPerHigh > blockSizeLow)
- output += L" " + formatUnitTime(roundedtimeInLow % unitLowPerHigh, unitLow);
- return output;
-};
-}
-
-
-std::wstring zen::remainingTimeToString(double timeInSec)
-{
- const int steps10[] = { 1, 2, 5, 10 };
- const int steps24[] = { 1, 2, 3, 4, 6, 8, 12, 24 };
- const int steps60[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
-
- //determine preferred unit
- double timeInUnit = timeInSec;
- if (timeInUnit <= 60)
- return roundToBlock(timeInUnit, URT_SEC, steps60, 1, URT_SEC, steps60);
-
- timeInUnit /= 60;
- if (timeInUnit <= 60)
- return roundToBlock(timeInUnit, URT_MIN, steps60, 60, URT_SEC, steps60);
-
- timeInUnit /= 60;
- if (timeInUnit <= 24)
- return roundToBlock(timeInUnit, URT_HOUR, steps24, 60, URT_MIN, steps60);
-
- timeInUnit /= 24;
- return roundToBlock(timeInUnit, URT_DAY, steps10, 24, URT_HOUR, steps24);
- //note: for 10% granularity steps10 yields a valid blocksize only up to timeInUnit == 100!
- //for larger time sizes this results in a finer granularity than expected: 10 days -> should not be a problem considering "usual" remaining time for synchronization
-}
-
-
-//std::wstring zen::fractionToString1Dec(double fraction)
-//{
-// return printNumber<std::wstring>(L"%.1f", fraction * 100.0) + L'%'; //no need to internationalize fraction!?
-//}
-
-
-std::wstring zen::fractionToString(double fraction)
-{
- return printNumber<std::wstring>(L"%.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!?
-}
-
-
-#ifdef ZEN_WIN
-namespace
-{
-class IntegerFormat
-{
-public:
- static std::shared_ptr<const IntegerFormat> instance()
- {
- static Global<const IntegerFormat> inst(std::make_unique<const IntegerFormat>());
- return inst.get();
- }
-
- bool isValid() const { return valid; }
- const NUMBERFMT& get() const { return fmt; }
-
- IntegerFormat()
- {
- //all we want is default NUMBERFMT, but set NumDigits to 0
- fmt.NumDigits = 0;
-
- //what a disgrace:
- std::wstring grouping;
- if (getUserSetting(LOCALE_ILZERO, fmt.LeadingZero) &&
- getUserSetting(LOCALE_SGROUPING, grouping) &&
- getUserSetting(LOCALE_SDECIMAL, decimalSep) &&
- getUserSetting(LOCALE_STHOUSAND, thousandSep) &&
- getUserSetting(LOCALE_INEGNUMBER, fmt.NegativeOrder))
- {
- fmt.lpDecimalSep = &decimalSep[0]; //don't need it
- fmt.lpThousandSep = &thousandSep[0];
-
- //convert LOCALE_SGROUPING to Grouping: https://blogs.msdn.microsoft.com/oldnewthing/20060418-11/?p=31493/
- replace(grouping, L';', L"");
- if (endsWith(grouping, L'0'))
- grouping.pop_back();
- else
- grouping += L'0';
- fmt.Grouping = stringTo<UINT>(grouping);
- valid = true;
- }
- }
-
-private:
- IntegerFormat (const IntegerFormat&) = delete;
- IntegerFormat& operator=(const IntegerFormat&) = delete;
-
- static bool getUserSetting(LCTYPE lt, UINT& setting)
- {
- return ::GetLocaleInfo(LOCALE_USER_DEFAULT, //__in LCID Locale,
- lt | LOCALE_RETURN_NUMBER, //__in LCTYPE LCType,
- reinterpret_cast<LPTSTR>(&setting), //__out LPTSTR lpLCData,
- sizeof(setting) / sizeof(TCHAR)) > 0; //__in int cchData
- }
-
- static bool getUserSetting(LCTYPE lt, std::wstring& setting)
- {
- const int bufferSize = ::GetLocaleInfo(LOCALE_USER_DEFAULT, lt, nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetLocaleInfo(LOCALE_USER_DEFAULT, //__in LCID Locale,
- lt, //__in LCTYPE LCType,
- &buffer[0], //__out LPTSTR lpLCData,
- bufferSize) > 0) //__in int cchData
- {
- setting = &buffer[0]; //GetLocaleInfo() returns char count *including* 0-termination!
- return true;
- }
- }
- return false;
- }
-
- NUMBERFMT fmt = {};
- std::wstring thousandSep;
- std::wstring decimalSep;
- bool valid = false;
-};
-}
-#endif
-
-
-std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number)
-{
-#ifdef ZEN_WIN
- if (std::shared_ptr<const IntegerFormat> fmt = IntegerFormat::instance())
- if (fmt->isValid())
- {
- const int bufferSize = ::GetNumberFormat(LOCALE_USER_DEFAULT, 0, number.c_str(), &fmt->get(), nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetNumberFormat(LOCALE_USER_DEFAULT, //__in LCID Locale,
- 0, //__in DWORD dwFlags,
- number.c_str(), //__in LPCTSTR lpValue,
- &fmt->get(), //__in_opt const NUMBERFMT *lpFormat,
- &buffer[0], //__out_opt LPTSTR lpNumberStr,
- bufferSize) > 0) //__in int cchNumber
- return &buffer[0]; //GetNumberFormat() returns char count *including* 0-termination!
- }
- }
- assert(false); //what's the problem?
- return number;
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- //we have to include thousands separator ourselves; this doesn't work for all countries (e.g india), but is better than nothing
-
- //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale
- const lconv* localInfo = ::localeconv(); //always bound according to doc
- const std::wstring& thousandSep = utfCvrtTo<std::wstring>(localInfo->thousands_sep);
-
- // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).thousands_sep(); - why not working?
- // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).decimal_point();
-
- std::wstring output(number);
- size_t i = output.size();
- for (;;)
- {
- if (i <= 3)
- break;
- i -= 3;
- if (!isDigit(output[i - 1])) //stop on +, - signs
- break;
- output.insert(i, thousandSep);
- }
- return output;
-#endif
-}
-
-
-std::wstring zen::utcToLocalTimeString(std::int64_t utcTime)
-{
- auto errorMsg = [&] { return _("Error") + L" (time_t: " + numberTo<std::wstring>(utcTime) + L")"; };
-
-#ifdef ZEN_WIN
- const FILETIME lastWriteTimeUtc = timetToFileTime(utcTime); //convert ansi C time to FILETIME
-
- SYSTEMTIME systemTimeLocal = {};
-
- //https://msdn.microsoft.com/en-us/library/ms724277
-#ifdef ZEN_WIN_VISTA_AND_LATER
- //DST conversion like in Vista and later: NTFS stays fixed, but FAT jumps by one hour
- SYSTEMTIME systemTimeUtc = {};
- if (!::FileTimeToSystemTime(&lastWriteTimeUtc, //__in const FILETIME *lpFileTime,
- &systemTimeUtc)) //__out LPSYSTEMTIME lpSystemTime
- return errorMsg();
-
- if (!::SystemTimeToTzSpecificLocalTime(nullptr, //__in_opt LPTIME_ZONE_INFORMATION lpTimeZone,
- &systemTimeUtc, //__in LPSYSTEMTIME lpUniversalTime,
- &systemTimeLocal)) //__out LPSYSTEMTIME lpLocalTime
- return errorMsg();
-#else
- //DST conversion like in Windows 2000 and XP: FAT times stay fixed, while NTFS jumps
- FILETIME fileTimeLocal = {};
- if (!::FileTimeToLocalFileTime(&lastWriteTimeUtc, //_In_ const FILETIME *lpFileTime,
- &fileTimeLocal)) //_Out_ LPFILETIME lpLocalFileTime
- return errorMsg();
-
- if (!::FileTimeToSystemTime(&fileTimeLocal, //__in const FILETIME *lpFileTime,
- &systemTimeLocal)) //__out LPSYSTEMTIME lpSystemTime
- return errorMsg();
-#endif
-
- zen::TimeComp loc;
- loc.year = systemTimeLocal.wYear;
- loc.month = systemTimeLocal.wMonth;
- loc.day = systemTimeLocal.wDay;
- loc.hour = systemTimeLocal.wHour;
- loc.minute = systemTimeLocal.wMinute;
- loc.second = systemTimeLocal.wSecond;
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- zen::TimeComp loc = zen::localTime(utcTime);
-#endif
-
- std::wstring dateString = formatTime<std::wstring>(L"%x %X", loc);
- return !dateString.empty() ? dateString : errorMsg();
-}
-
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
-Opt<std::int64_t> zen::mtpVariantTimetoUtc(double localVarTime) //returns empty on error
-{
- SYSTEMTIME localSystemTime = {};
- if (!::VariantTimeToSystemTime(localVarTime, //_In_ DOUBLE vtime,
- &localSystemTime)) //_Out_ LPSYSTEMTIME lpSystemTime
- return NoValue();
-
- /*
- Windows Explorer isn't even consistent within itself: the modification time shown in the details list is calculated differently than the
- one shown in MTP file properties => different result shown for files from a DST interval distinct from the current one.
- -> Variant 1 matches the calculation of Explorer's details list and produces stable results irrespective of currently selected DST
- -> Explorer uses different algorithms for MTP and FAT file systems! FAT's local time jumps between DST intervals in Explorer (since Vista)!
- */
-#if 1
- SYSTEMTIME utcSystemTime = {};
- if (!::TzSpecificLocalTimeToSystemTime(nullptr, //_In_opt_ LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
- &localSystemTime, //_In_ LPSYSTEMTIME lpLocalTime,
- &utcSystemTime)) //_Out_ LPSYSTEMTIME lpUniversalTime
- return NoValue();
-
- FILETIME utcFiletime = {};
- if (!::SystemTimeToFileTime(&utcSystemTime, //_In_ const SYSTEMTIME *lpSystemTime,
- &utcFiletime)) //_Out_ LPFILETIME lpFileTime
- return NoValue();
-
-#else
- FILETIME localFiletime = {};
- if (!::SystemTimeToFileTime(&localSystemTime, //_In_ const SYSTEMTIME *lpSystemTime,
- &localFiletime)) //_Out_ LPFILETIME lpFileTime
- return NoValue();
-
- FILETIME utcFiletime = {};
- if (!LocalFileTimeToFileTime(&localFiletime, //_In_ const FILETIME *lpLocalFileTime,
- &utcFiletime)) //_Out_ LPFILETIME lpFileTime
- return NoValue();
-
-#endif
- return filetimeToTimeT(utcFiletime);
-}
-
-
-Opt<double> zen::utcToMtpVariantTime(std::int64_t utcTime) //returns empty on error
-{
- const FILETIME lastWriteTimeUtc = timetToFileTime(utcTime); //convert ansi C time to FILETIME
-
- SYSTEMTIME systemTimeUtc = {};
- if (!::FileTimeToSystemTime(&lastWriteTimeUtc, //__in const FILETIME *lpFileTime,
- &systemTimeUtc)) //__out LPSYSTEMTIME lpSystemTime
- return NoValue();
-
- SYSTEMTIME systemTimeLocal = {};
- if (!::SystemTimeToTzSpecificLocalTime(nullptr, //__in_opt LPTIME_ZONE_INFORMATION lpTimeZone,
- &systemTimeUtc, //__in LPSYSTEMTIME lpUniversalTime,
- &systemTimeLocal)) //__out LPSYSTEMTIME lpLocalTime
- return NoValue();
-
- double localVarTime = 0;
- if (!::SystemTimeToVariantTime(&systemTimeLocal, //_In_ LPSYSTEMTIME lpSystemTime,
- &localVarTime)) //_Out_ DOUBLE *pvtime
- return NoValue();
-
- return localVarTime;
-}
-#endif \ No newline at end of file
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "format_unit.h"
+#include <cwchar> //swprintf
+#include <ctime>
+#include <cstdio>
+#include "basic_math.h"
+#include "i18n.h"
+#include "time.h"
+#include "globals.h"
+
+ #include <clocale> //thousands separator
+ #include "utf.h" //
+
+using namespace zen;
+
+
+std::wstring zen::formatTwoDigitPrecision(double value)
+{
+ //print two digits: 0,1 | 1,1 | 11
+ if (numeric::abs(value) < 9.95) //9.99 must not be formatted as "10.0"
+ return printNumber<std::wstring>(L"%.1f", value);
+ return numberTo<std::wstring>(numeric::round(value));
+}
+
+
+std::wstring zen::formatThreeDigitPrecision(double value)
+{
+ //print three digits: 0,01 | 0,11 | 1,11 | 11,1 | 111
+ if (numeric::abs(value) < 9.995) //9.999 must not be formatted as "10.00"
+ return printNumber<std::wstring>(L"%.2f", value);
+ if (numeric::abs(value) < 99.95) //99.99 must not be formatted as "100.0"
+ return printNumber<std::wstring>(L"%.1f", value);
+ return numberTo<std::wstring>(numeric::round(value));
+}
+
+
+std::wstring zen::filesizeToShortString(int64_t size)
+{
+ //if (size < 0) return _("Error"); -> really?
+
+ if (numeric::abs(size) <= 999)
+ return _P("1 byte", "%x bytes", static_cast<int>(size));
+
+ double sizeInUnit = static_cast<double>(size);
+
+ auto formatUnit = [&](const std::wstring& unitTxt) { return replaceCpy(unitTxt, L"%x", formatThreeDigitPrecision(sizeInUnit)); };
+
+ sizeInUnit /= 1024;
+ if (numeric::abs(sizeInUnit) < 999.5)
+ return formatUnit(_("%x KB"));
+
+ sizeInUnit /= 1024;
+ if (numeric::abs(sizeInUnit) < 999.5)
+ return formatUnit(_("%x MB"));
+
+ sizeInUnit /= 1024;
+ if (numeric::abs(sizeInUnit) < 999.5)
+ return formatUnit(_("%x GB"));
+
+ sizeInUnit /= 1024;
+ if (numeric::abs(sizeInUnit) < 999.5)
+ return formatUnit(_("%x TB"));
+
+ sizeInUnit /= 1024;
+ return formatUnit(_("%x PB"));
+}
+
+
+namespace
+{
+enum UnitRemTime
+{
+ URT_SEC,
+ URT_MIN,
+ URT_HOUR,
+ URT_DAY
+};
+
+
+std::wstring formatUnitTime(int val, UnitRemTime unit)
+{
+ switch (unit)
+ {
+ case URT_SEC:
+ return _P("1 sec", "%x sec", val);
+ case URT_MIN:
+ return _P("1 min", "%x min", val);
+ case URT_HOUR:
+ return _P("1 hour", "%x hours", val);
+ case URT_DAY:
+ return _P("1 day", "%x days", val);
+ }
+ assert(false);
+ return _("Error");
+}
+
+
+template <int M, int N>
+std::wstring roundToBlock(double timeInHigh,
+ UnitRemTime unitHigh, const int (&stepsHigh)[M],
+ int unitLowPerHigh,
+ UnitRemTime unitLow, const int (&stepsLow)[N])
+{
+ assert(unitLowPerHigh > 0);
+ const double granularity = 0.1;
+ const double timeInLow = timeInHigh * unitLowPerHigh;
+ const int blockSizeLow = granularity * timeInHigh < 1 ?
+ numeric::nearMatch(granularity * timeInLow, std::begin(stepsLow), std::end(stepsLow)):
+ numeric::nearMatch(granularity * timeInHigh, std::begin(stepsHigh), std::end(stepsHigh)) * unitLowPerHigh;
+ const int roundedtimeInLow = numeric::round(timeInLow / blockSizeLow) * blockSizeLow;
+
+ std::wstring output = formatUnitTime(roundedtimeInLow / unitLowPerHigh, unitHigh);
+ if (unitLowPerHigh > blockSizeLow)
+ output += L" " + formatUnitTime(roundedtimeInLow % unitLowPerHigh, unitLow);
+ return output;
+};
+}
+
+
+std::wstring zen::remainingTimeToString(double timeInSec)
+{
+ const int steps10[] = { 1, 2, 5, 10 };
+ const int steps24[] = { 1, 2, 3, 4, 6, 8, 12, 24 };
+ const int steps60[] = { 1, 2, 5, 10, 15, 20, 30, 60 };
+
+ //determine preferred unit
+ double timeInUnit = timeInSec;
+ if (timeInUnit <= 60)
+ return roundToBlock(timeInUnit, URT_SEC, steps60, 1, URT_SEC, steps60);
+
+ timeInUnit /= 60;
+ if (timeInUnit <= 60)
+ return roundToBlock(timeInUnit, URT_MIN, steps60, 60, URT_SEC, steps60);
+
+ timeInUnit /= 60;
+ if (timeInUnit <= 24)
+ return roundToBlock(timeInUnit, URT_HOUR, steps24, 60, URT_MIN, steps60);
+
+ timeInUnit /= 24;
+ return roundToBlock(timeInUnit, URT_DAY, steps10, 24, URT_HOUR, steps24);
+ //note: for 10% granularity steps10 yields a valid blocksize only up to timeInUnit == 100!
+ //for larger time sizes this results in a finer granularity than expected: 10 days -> should not be a problem considering "usual" remaining time for synchronization
+}
+
+
+//std::wstring zen::fractionToString1Dec(double fraction)
+//{
+// return printNumber<std::wstring>(L"%.1f", fraction * 100.0) + L'%'; //no need to internationalize fraction!?
+//}
+
+
+std::wstring zen::fractionToString(double fraction)
+{
+ return printNumber<std::wstring>(L"%.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!?
+}
+
+
+
+
+std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number)
+{
+ //we have to include thousands separator ourselves; this doesn't work for all countries (e.g india), but is better than nothing
+
+ //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale
+ const lconv* localInfo = ::localeconv(); //always bound according to doc
+ const std::wstring& thousandSep = utfCvrtTo<std::wstring>(localInfo->thousands_sep);
+
+ // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).thousands_sep(); - why not working?
+ // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t>>(std::locale("")).decimal_point();
+
+ std::wstring output(number);
+ size_t i = output.size();
+ for (;;)
+ {
+ if (i <= 3)
+ break;
+ i -= 3;
+ if (!isDigit(output[i - 1])) //stop on +, - signs
+ break;
+ output.insert(i, thousandSep);
+ }
+ return output;
+}
+
+
+std::wstring zen::utcToLocalTimeString(int64_t utcTime)
+{
+ auto errorMsg = [&] { return _("Error") + L" (time_t: " + numberTo<std::wstring>(utcTime) + L")"; };
+
+ TimeComp loc = getLocalTime(utcTime);
+
+ std::wstring dateString = formatTime<std::wstring>(L"%x %X", loc);
+ return !dateString.empty() ? dateString : errorMsg();
+}
+
+
diff --git a/zen/format_unit.h b/zen/format_unit.h
index 1f9177f0..fc79738a 100644..100755
--- a/zen/format_unit.h
+++ b/zen/format_unit.h
@@ -1,58 +1,53 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef FMT_UNIT_8702184019487324
-#define FMT_UNIT_8702184019487324
-
-#include <string>
-#include <cstdint>
-#include "optional.h"
-#include "string_tools.h"
-
-
-namespace zen
-{
-std::wstring filesizeToShortString(std::int64_t filesize);
-std::wstring remainingTimeToString(double timeInSec);
-std::wstring fractionToString(double fraction); //within [0, 1]
-std::wstring utcToLocalTimeString(std::int64_t utcTime); //like Windows Explorer would...
-
-std::wstring formatTwoDigitPrecision (double value); //format with fixed number of digits
-std::wstring formatThreeDigitPrecision(double value); //(unless value is too large)
-
-template <class NumberType>
-std::wstring toGuiString(NumberType number); //format integer number including thousands separator
-
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
- //reverse calculation of utcToLocalTimeString()
- Opt<std::int64_t> mtpVariantTimetoUtc(double localTime); //returns empty on error
- Opt<double> utcToMtpVariantTime(int64_t utcTime); //
-#endif
-
-
-
-
-
-
-
-
-
-//--------------- inline impelementation -------------------------------------------
-namespace ffs_Impl
-{
-std::wstring includeNumberSeparator(const std::wstring& number);
-}
-
-template <class NumberType> inline
-std::wstring toGuiString(NumberType number)
-{
- static_assert(IsInteger<NumberType>::value, "");
- return ffs_Impl::includeNumberSeparator(zen::numberTo<std::wstring>(number));
-}
-}
-
-#endif
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef FMT_UNIT_8702184019487324
+#define FMT_UNIT_8702184019487324
+
+#include <string>
+#include <cstdint>
+#include "optional.h"
+#include "string_tools.h"
+
+
+namespace zen
+{
+std::wstring filesizeToShortString(int64_t filesize);
+std::wstring remainingTimeToString(double timeInSec);
+std::wstring fractionToString(double fraction); //within [0, 1]
+std::wstring utcToLocalTimeString(int64_t utcTime); //like Windows Explorer would...
+
+std::wstring formatTwoDigitPrecision (double value); //format with fixed number of digits
+std::wstring formatThreeDigitPrecision(double value); //(unless value is too large)
+
+template <class NumberType>
+std::wstring toGuiString(NumberType number); //format integer number including thousands separator
+
+
+
+
+
+
+
+
+
+
+
+//--------------- inline impelementation -------------------------------------------
+namespace ffs_Impl
+{
+std::wstring includeNumberSeparator(const std::wstring& number);
+}
+
+template <class NumberType> inline
+std::wstring toGuiString(NumberType number)
+{
+ static_assert(IsInteger<NumberType>::value, "");
+ return ffs_Impl::includeNumberSeparator(zen::numberTo<std::wstring>(number));
+}
+}
+
+#endif
diff --git a/zen/globals.h b/zen/globals.h
index 123028c7..a1fd2764 100644..100755
--- a/zen/globals.h
+++ b/zen/globals.h
@@ -1,64 +1,61 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef GLOBALS_H_8013740213748021573485
-#define GLOBALS_H_8013740213748021573485
-
-#include <atomic>
-#include <memory>
-#include "scope_guard.h"
-
-namespace zen
-{
-//solve static destruction order fiasco by providing shared ownership and serialized access to global variables
-template <class T>
-class Global
-{
-public:
- Global() { static_assert(std::is_trivially_destructible<Pod>::value, "this memory needs to live forever"); }
- explicit Global(std::unique_ptr<T>&& newInst) { set(std::move(newInst)); }
- ~Global() { set(nullptr); }
-
- std::shared_ptr<T> get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!)
- {
- while (pod.spinLock.exchange(true)) ;
- ZEN_ON_SCOPE_EXIT(pod.spinLock = false);
- if (pod.inst)
- return *pod.inst;
- return nullptr;
- }
-
- void set(std::unique_ptr<T>&& newInst)
- {
- std::shared_ptr<T>* tmpInst = nullptr;
- if (newInst)
- tmpInst = new std::shared_ptr<T>(std::move(newInst));
- {
- while (pod.spinLock.exchange(true)) ;
- ZEN_ON_SCOPE_EXIT(pod.spinLock = false);
- std::swap(pod.inst, tmpInst);
- }
- delete tmpInst;
- }
-
-private:
- //avoid static destruction order fiasco: there may be accesses to "Global<T>::get()" during process shutdown
- //e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message!
- //=> use trivially-destructible POD only!!!
- struct Pod
- {
- std::shared_ptr<T>* inst = nullptr;
- //serialize access; can't use std::mutex: has non-trival destructor
- std::atomic<bool> spinLock { false };
- } pod;
-};
-
-#if defined _MSC_VER && _MSC_VER < 1900
- #error function scope static initialization is not yet thread-safe!
-#endif
-}
-
-#endif //GLOBALS_H_8013740213748021573485
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef GLOBALS_H_8013740213748021573485
+#define GLOBALS_H_8013740213748021573485
+
+#include <atomic>
+#include <memory>
+#include "scope_guard.h"
+
+namespace zen
+{
+//solve static destruction order fiasco by providing shared ownership and serialized access to global variables
+template <class T>
+class Global
+{
+public:
+ Global() { static_assert(std::is_trivially_destructible<Pod>::value, "this memory needs to live forever"); }
+ explicit Global(std::unique_ptr<T>&& newInst) { set(std::move(newInst)); }
+ ~Global() { set(nullptr); }
+
+ std::shared_ptr<T> get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!)
+ {
+ while (pod.spinLock.exchange(true)) ;
+ ZEN_ON_SCOPE_EXIT(pod.spinLock = false);
+ if (pod.inst)
+ return *pod.inst;
+ return nullptr;
+ }
+
+ void set(std::unique_ptr<T>&& newInst)
+ {
+ std::shared_ptr<T>* tmpInst = nullptr;
+ if (newInst)
+ tmpInst = new std::shared_ptr<T>(std::move(newInst));
+ {
+ while (pod.spinLock.exchange(true)) ;
+ ZEN_ON_SCOPE_EXIT(pod.spinLock = false);
+ std::swap(pod.inst, tmpInst);
+ }
+ delete tmpInst;
+ }
+
+private:
+ //avoid static destruction order fiasco: there may be accesses to "Global<T>::get()" during process shutdown
+ //e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message!
+ //=> use trivially-destructible POD only!!!
+ struct Pod
+ {
+ std::shared_ptr<T>* inst = nullptr;
+ //serialize access; can't use std::mutex: has non-trival destructor
+ std::atomic<bool> spinLock { false };
+ } pod;
+};
+
+}
+
+#endif //GLOBALS_H_8013740213748021573485
diff --git a/zen/guid.h b/zen/guid.h
index 63007707..052f3fdc 100644..100755
--- a/zen/guid.h
+++ b/zen/guid.h
@@ -1,34 +1,30 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef GUID_H_80425780237502345
-#define GUID_H_80425780237502345
-
-#include <string>
-
-#ifdef __GNUC__ //boost should clean this mess up
- #pragma GCC diagnostic push
- #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-#include <boost/uuid/uuid_generators.hpp>
-#ifdef __GNUC__
- #pragma GCC diagnostic pop
-#endif
-
-
-namespace zen
-{
-inline
-std::string generateGUID() //creates a 16-byte GUID
-{
- boost::uuids::uuid nativeRep = boost::uuids::random_generator()();
- //generator is only thread-safe like an int, so we keep it local until we need to optimize perf
- //perf: generator: 0.22ms per call; retrieve GUID: 0.12µs per call
- return std::string(nativeRep.begin(), nativeRep.end());
-}
-}
-
-#endif //GUID_H_80425780237502345
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef GUID_H_80425780237502345
+#define GUID_H_80425780237502345
+
+#include <string>
+
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#include <boost/uuid/uuid_generators.hpp>
+ #pragma GCC diagnostic pop
+
+
+namespace zen
+{
+inline
+std::string generateGUID() //creates a 16-byte GUID
+{
+ boost::uuids::uuid nativeRep = boost::uuids::random_generator()();
+ //generator is only thread-safe like an int, so we keep it local until we need to optimize perf
+ //perf: generator: 0.22ms per call; retrieve GUID: 0.12µs per call
+ return std::string(nativeRep.begin(), nativeRep.end());
+}
+}
+
+#endif //GUID_H_80425780237502345
diff --git a/zen/i18n.h b/zen/i18n.h
index e5b0ab2c..ef3f3706 100644..100755
--- a/zen/i18n.h
+++ b/zen/i18n.h
@@ -1,124 +1,119 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef I18_N_H_3843489325044253425456
-#define I18_N_H_3843489325044253425456
-
-#include <string>
-#include <cstdint>
-#include "globals.h"
-#include "string_tools.h"
-#include "format_unit.h"
-
-//minimal layer enabling text translation - without platform/library dependencies!
-#ifdef __WXMSW__ //we have wxWidgets
- #ifndef WXINTL_NO_GETTEXT_MACRO
- #error WXINTL_NO_GETTEXT_MACRO must be defined to deactivate wxWidgets underscore macro
- #endif
-#endif
-
-#define ZEN_TRANS_CONCAT_SUB(X, Y) X ## Y
-#define _(s) zen::implementation::translate(ZEN_TRANS_CONCAT_SUB(L, s))
-#define _P(s, p, n) zen::implementation::translate(ZEN_TRANS_CONCAT_SUB(L, s), ZEN_TRANS_CONCAT_SUB(L, p), n)
-//source and translation are required to use %x as number placeholder
-//for plural form, which will be substituted automatically!!!
-
-namespace zen
-{
-//implement handler to enable program-wide localizations:
-struct TranslationHandler
-{
- //THREAD-SAFETY: "const" member must model thread-safe access!
- TranslationHandler() {}
- virtual ~TranslationHandler() {}
-
- //C++11: std::wstring should be thread-safe like an int
- virtual std::wstring translate(const std::wstring& text) const = 0; //simple translation
- virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, std::int64_t n) const = 0;
-
-private:
- TranslationHandler (const TranslationHandler&) = delete;
- TranslationHandler& operator=(const TranslationHandler&) = delete;
-};
-
-void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler); //take ownership
-std::shared_ptr<const TranslationHandler> getTranslator();
-
-
-
-
-
-
-
-
-
-
-
-
-
-//######################## implementation ##############################
-namespace implementation
-{
-inline
-std::wstring translate(const std::wstring& text)
-{
- if (std::shared_ptr<const TranslationHandler> t = getTranslator()) //std::shared_ptr => temporarily take (shared) ownership while using the interface!
- return t->translate(text);
- return text;
-}
-
-
-//translate plural forms: "%x day" "%x days"
-//returns "1 day" if n == 1; "123 days" if n == 123 for english language
-inline
-std::wstring translate(const std::wstring& singular, const std::wstring& plural, std::int64_t n)
-{
- assert(contains(plural, L"%x"));
-
- if (std::shared_ptr<const TranslationHandler> t = getTranslator())
- {
- std::wstring translation = t->translate(singular, plural, n);
- assert(!contains(translation, L"%x"));
- return translation;
- }
-
- return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", toGuiString(n));
-}
-
-
-template <class T> inline
-std::wstring translate(const std::wstring& singular, const std::wstring& plural, T n)
-{
- static_assert(sizeof(n) <= sizeof(std::int64_t), "");
- return translate(singular, plural, static_cast<std::int64_t>(n));
-}
-
-
-inline
-Global<const TranslationHandler>& refGlobalTranslationHandler()
-{
- //getTranslator() may be called even after static objects of this translation unit are destroyed!
- static Global<const TranslationHandler> inst; //external linkage even in header!
- return inst;
-}
-}
-
-
-inline
-void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler)
-{
- implementation::refGlobalTranslationHandler().set(std::move(newHandler));
-}
-
-
-inline
-std::shared_ptr<const TranslationHandler> getTranslator()
-{
- return implementation::refGlobalTranslationHandler().get();
-}
-}
-
-#endif //I18_N_H_3843489325044253425456
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef I18_N_H_3843489325044253425456
+#define I18_N_H_3843489325044253425456
+
+#include <string>
+#include <cstdint>
+#include "globals.h"
+#include "string_tools.h"
+#include "format_unit.h"
+
+//minimal layer enabling text translation - without platform/library dependencies!
+
+#define ZEN_TRANS_CONCAT_SUB(X, Y) X ## Y
+#define _(s) zen::implementation::translate(ZEN_TRANS_CONCAT_SUB(L, s))
+#define _P(s, p, n) zen::implementation::translate(ZEN_TRANS_CONCAT_SUB(L, s), ZEN_TRANS_CONCAT_SUB(L, p), n)
+//source and translation are required to use %x as number placeholder
+//for plural form, which will be substituted automatically!!!
+
+namespace zen
+{
+//implement handler to enable program-wide localizations:
+struct TranslationHandler
+{
+ //THREAD-SAFETY: "const" member must model thread-safe access!
+ TranslationHandler() {}
+ virtual ~TranslationHandler() {}
+
+ //C++11: std::wstring should be thread-safe like an int
+ virtual std::wstring translate(const std::wstring& text) const = 0; //simple translation
+ virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, int64_t n) const = 0;
+
+private:
+ TranslationHandler (const TranslationHandler&) = delete;
+ TranslationHandler& operator=(const TranslationHandler&) = delete;
+};
+
+void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler); //take ownership
+std::shared_ptr<const TranslationHandler> getTranslator();
+
+
+
+
+
+
+
+
+
+
+
+
+
+//######################## implementation ##############################
+namespace implementation
+{
+inline
+std::wstring translate(const std::wstring& text)
+{
+ if (std::shared_ptr<const TranslationHandler> t = getTranslator()) //std::shared_ptr => temporarily take (shared) ownership while using the interface!
+ return t->translate(text);
+ return text;
+}
+
+
+//translate plural forms: "%x day" "%x days"
+//returns "1 day" if n == 1; "123 days" if n == 123 for english language
+inline
+std::wstring translate(const std::wstring& singular, const std::wstring& plural, int64_t n)
+{
+ assert(contains(plural, L"%x"));
+
+ if (std::shared_ptr<const TranslationHandler> t = getTranslator())
+ {
+ std::wstring translation = t->translate(singular, plural, n);
+ assert(!contains(translation, L"%x"));
+ return translation;
+ }
+
+ return replaceCpy(std::abs(n) == 1 ? singular : plural, L"%x", toGuiString(n));
+}
+
+
+template <class T> inline
+std::wstring translate(const std::wstring& singular, const std::wstring& plural, T n)
+{
+ static_assert(sizeof(n) <= sizeof(int64_t), "");
+ return translate(singular, plural, static_cast<int64_t>(n));
+}
+
+
+inline
+Global<const TranslationHandler>& refGlobalTranslationHandler()
+{
+ //getTranslator() may be called even after static objects of this translation unit are destroyed!
+ static Global<const TranslationHandler> inst; //external linkage even in header!
+ return inst;
+}
+}
+
+
+inline
+void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler)
+{
+ implementation::refGlobalTranslationHandler().set(std::move(newHandler));
+}
+
+
+inline
+std::shared_ptr<const TranslationHandler> getTranslator()
+{
+ return implementation::refGlobalTranslationHandler().get();
+}
+}
+
+#endif //I18_N_H_3843489325044253425456
diff --git a/zen/optional.h b/zen/optional.h
index 83915110..a4c67984 100644..100755
--- a/zen/optional.h
+++ b/zen/optional.h
@@ -1,90 +1,100 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef OPTIONAL_H_2857428578342203589
-#define OPTIONAL_H_2857428578342203589
-
-#include <cassert>
-#include <type_traits>
-
-namespace zen
-{
-/*
-Optional return value without heap memory allocation!
- -> interface like a pointer, performance like a value
-
- Usage:
- ------
- Opt<MyEnum> someFunction();
-{
- if (allIsWell)
- return enumVal;
- else
- return NoValue();
-}
-
- Opt<MyEnum> optValue = someFunction();
- if (optValue)
- ... use *optValue ...
-*/
-
-struct NoValue {};
-
-template <class T>
-class Opt
-{
-public:
- Opt() {}
- Opt(NoValue) {}
- Opt(const T& val) : valid(true) { new (&rawMem) T(val); } //throw X
-
- Opt(const Opt& other) : valid(other.valid)
- {
- if (const T* val = other.get())
- new (&rawMem) T(*val); //throw X
- }
-
- ~Opt() { if (T* val = get()) val->~T(); }
-
- Opt& operator=(const Opt& other) //strong exception-safety iff T::operator=() is strongly exception-safe
- {
- if (T* val = get())
- {
- if (const T* valOther = other.get())
- *val = *valOther; //throw X
- else
- {
- valid = false;
- val->~T();
- }
- }
- else if (const T* valOther = other.get())
- {
- new (&rawMem) T(*valOther); //throw X
- valid = true;
- }
- return *this;
- }
-
- explicit operator bool() const { return valid; } //thank you C++11!!!
-
- const T* get() const { return valid ? reinterpret_cast<const T*>(&rawMem) : nullptr; }
- T* get() { return valid ? reinterpret_cast< T*>(&rawMem) : nullptr; }
-
- const T& operator*() const { return *get(); }
- /**/ T& operator*() { return *get(); }
-
- const T* operator->() const { return get(); }
- /**/ T* operator->() { return get(); }
-
-private:
- std::aligned_storage_t<sizeof(T), alignof(T)> rawMem; //don't require T to be default-constructible!
- bool valid = false;
-};
-
-}
-
-#endif //OPTIONAL_H_2857428578342203589
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef OPTIONAL_H_2857428578342203589
+#define OPTIONAL_H_2857428578342203589
+
+#include <cassert>
+#include <type_traits>
+
+namespace zen
+{
+/*
+Optional return value without heap memory allocation!
+ -> interface like a pointer, performance like a value
+
+ Usage:
+ ------
+ Opt<MyEnum> someFunction();
+{
+ if (allIsWell)
+ return enumVal;
+ else
+ return NoValue();
+}
+
+ Opt<MyEnum> optValue = someFunction();
+ if (optValue)
+ ... use *optValue ...
+*/
+
+struct NoValue {};
+
+template <class T>
+class Opt
+{
+public:
+ Opt() {}
+ Opt(NoValue) {}
+ Opt(const T& val) : valid_(true) { new (&rawMem_) T(val); } //throw X
+ Opt( T&& tmp) : valid_(true) { new (&rawMem_) T(std::move(tmp)); }
+
+ Opt(const Opt& other) : valid_(other.valid_)
+ {
+ if (const T* val = other.get())
+ new (&rawMem_) T(*val); //throw X
+ }
+
+ ~Opt() { if (T* val = get()) val->~T(); }
+
+ Opt& operator=(NoValue) //support assignment to Opt<const T>
+ {
+ if (T* val = get())
+ {
+ valid_ = false;
+ val->~T();
+ }
+ return *this;
+ }
+
+ Opt& operator=(const Opt& other) //strong exception-safety iff T::operator=() is strongly exception-safe
+ {
+ if (T* val = get())
+ {
+ if (const T* valOther = other.get())
+ *val = *valOther; //throw X
+ else
+ {
+ valid_ = false;
+ val->~T();
+ }
+ }
+ else if (const T* valOther = other.get())
+ {
+ new (&rawMem_) T(*valOther); //throw X
+ valid_ = true;
+ }
+ return *this;
+ }
+
+ explicit operator bool() const { return valid_; } //thank you, C++11!!!
+
+ const T* get() const { return valid_ ? reinterpret_cast<const T*>(&rawMem_) : nullptr; }
+ T* get() { return valid_ ? reinterpret_cast< T*>(&rawMem_) : nullptr; }
+
+ const T& operator*() const { return *get(); }
+ /**/ T& operator*() { return *get(); }
+
+ const T* operator->() const { return get(); }
+ /**/ T* operator->() { return get(); }
+
+private:
+ std::aligned_storage_t<sizeof(T), alignof(T)> rawMem_; //don't require T to be default-constructible!
+ bool valid_ = false;
+};
+}
+
+#endif //OPTIONAL_H_2857428578342203589
diff --git a/zen/perf.h b/zen/perf.h
index 6cd874b8..ada59691 100644..100755
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -1,94 +1,83 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef PERF_H_83947184145342652456
-#define PERF_H_83947184145342652456
-
-#include <chrono>
-#include "deprecate.h"
-#include "scope_guard.h"
-
-#ifdef ZEN_WIN
- #include <sstream>
- #include "win.h"
-#else
- #include <iostream>
-#endif
-
-
-//############# two macros for quick performance measurements ###############
-#define PERF_START zen::PerfTimer perfTest;
-#define PERF_STOP perfTest.showResult();
-//###########################################################################
-
-namespace zen
-{
-class PerfTimer
-{
-public:
- ZEN_DEPRECATE PerfTimer() {}
-
- ~PerfTimer() { if (!resultShown_) showResult(); }
-
- void pause()
- {
- if (!paused_)
- {
- paused_ = true;
- elapsedUntilPause_ += std::chrono::steady_clock::now() - startTime_; //ignore potential ::QueryPerformanceCounter() wrap-around!
- }
- }
-
- void resume()
- {
- if (paused_)
- {
- paused_ = false;
- startTime_ = std::chrono::steady_clock::now();
- }
- }
-
- void restart()
- {
- paused_ = false;
- startTime_ = std::chrono::steady_clock::now();
- elapsedUntilPause_ = std::chrono::nanoseconds::zero();
- }
-
- int64_t timeMs() const
- {
- auto elapsedTotal = elapsedUntilPause_;
- if (!paused_)
- elapsedTotal += std::chrono::steady_clock::now() - startTime_;
-
- return std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTotal).count();
- }
-
- void showResult()
- {
- const bool wasRunning = !paused_;
- if (wasRunning) pause(); //don't include call to MessageBox()!
- ZEN_ON_SCOPE_EXIT(if (wasRunning) resume());
-
-#ifdef ZEN_WIN
- std::wostringstream ss;
- ss << timeMs() << L" ms";
- ::MessageBox(nullptr, ss.str().c_str(), L"Timer", MB_OK);
-#else
- std::clog << "Perf: duration: " << timeMs() << " ms\n";
-#endif
- resultShown_ = true;
- }
-
-private:
- bool resultShown_ = false;
- bool paused_ = false;
- std::chrono::steady_clock::time_point startTime_ = std::chrono::steady_clock::now(); //uses ::QueryPerformanceCounter()
- std::chrono::nanoseconds elapsedUntilPause_{}; //std::chrono::duration is uninitialized by default! WTF! When will this stupidity end???
-};
-}
-
-#endif //PERF_H_83947184145342652456
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef PERF_H_83947184145342652456
+#define PERF_H_83947184145342652456
+
+#include <chrono>
+#include "deprecate.h"
+#include "scope_guard.h"
+
+ #include <iostream>
+
+
+//############# two macros for quick performance measurements ###############
+#define PERF_START zen::PerfTimer perfTest;
+#define PERF_STOP perfTest.showResult();
+//###########################################################################
+
+namespace zen
+{
+class PerfTimer
+{
+public:
+ ZEN_DEPRECATE PerfTimer() {}
+
+ ~PerfTimer() { if (!resultShown_) showResult(); }
+
+ void pause()
+ {
+ if (!paused_)
+ {
+ paused_ = true;
+ elapsedUntilPause_ += std::chrono::steady_clock::now() - startTime_; //ignore potential ::QueryPerformanceCounter() wrap-around!
+ }
+ }
+
+ void resume()
+ {
+ if (paused_)
+ {
+ paused_ = false;
+ startTime_ = std::chrono::steady_clock::now();
+ }
+ }
+
+ void restart()
+ {
+ paused_ = false;
+ startTime_ = std::chrono::steady_clock::now();
+ elapsedUntilPause_ = std::chrono::nanoseconds::zero();
+ }
+
+ int64_t timeMs() const
+ {
+ auto elapsedTotal = elapsedUntilPause_;
+ if (!paused_)
+ elapsedTotal += std::chrono::steady_clock::now() - startTime_;
+
+ return std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTotal).count();
+ }
+
+ void showResult()
+ {
+ const bool wasRunning = !paused_;
+ if (wasRunning) pause(); //don't include call to MessageBox()!
+ ZEN_ON_SCOPE_EXIT(if (wasRunning) resume());
+
+ std::clog << "Perf: duration: " << timeMs() << " ms\n";
+ resultShown_ = true;
+ }
+
+private:
+ bool resultShown_ = false;
+ bool paused_ = false;
+ std::chrono::steady_clock::time_point startTime_ = std::chrono::steady_clock::now(); //uses ::QueryPerformanceCounter()
+ std::chrono::nanoseconds elapsedUntilPause_{}; //std::chrono::duration is uninitialized by default! WTF! When will this stupidity end???
+};
+}
+
+#endif //PERF_H_83947184145342652456
diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp
index 826cf857..50c975fe 100644..100755
--- a/zen/process_priority.cpp
+++ b/zen/process_priority.cpp
@@ -1,90 +1,54 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "process_priority.h"
-#include "i18n.h"
-
-#ifdef ZEN_WIN
- #include "win.h" //includes "windows.h"
-#endif
-
-using namespace zen;
-
-
-#ifdef ZEN_WIN
-struct PreventStandby::Impl {};
-
-PreventStandby::PreventStandby()
-{
- if (::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) == 0)
- throw FileError(_("Unable to suspend system sleep mode.")); //no GetLastError() support?
-}
-
-
-PreventStandby::~PreventStandby()
-{
- ::SetThreadExecutionState(ES_CONTINUOUS);
-}
-
-
-struct ScheduleForBackgroundProcessing::Impl {};
-
-
-ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing()
-{
- if (!::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN)) //this call lowers CPU priority, too!!
- THROW_LAST_FILE_ERROR(_("Cannot change process I/O priorities."), L"SetPriorityClass");
-}
-
-
-ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing()
-{
- ::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_END);
-}
-
-#elif defined ZEN_LINUX
-struct PreventStandby::Impl {};
-PreventStandby::PreventStandby() {}
-PreventStandby::~PreventStandby() {}
-
-//solution for GNOME?: http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit
-
-struct ScheduleForBackgroundProcessing::Impl {};
-ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() {};
-ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() {};
-
-/*
-struct ScheduleForBackgroundProcessing
-{
- - required functions ioprio_get/ioprio_set are not part of glibc: http://linux.die.net/man/2/ioprio_set
- - and probably never will: http://sourceware.org/bugzilla/show_bug.cgi?id=4464
- - /usr/include/linux/ioprio.h not available on Ubuntu, so we can't use it instead
-
- ScheduleForBackgroundProcessing() : oldIoPrio(getIoPriority(IOPRIO_WHO_PROCESS, ::getpid()))
- {
- if (oldIoPrio != -1)
- setIoPriority(::getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0));
- }
- ~ScheduleForBackgroundProcessing()
- {
- if (oldIoPrio != -1)
- setIoPriority(::getpid(), oldIoPrio);
- }
-
-private:
- static int getIoPriority(pid_t pid)
- {
- return ::syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, pid);
- }
- static int setIoPriority(pid_t pid, int ioprio)
- {
- return ::syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, pid, ioprio);
- }
-
- const int oldIoPrio;
-};
-*/
-#endif
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "process_priority.h"
+#include "i18n.h"
+
+
+using namespace zen;
+
+
+struct PreventStandby::Impl {};
+PreventStandby::PreventStandby() {}
+PreventStandby::~PreventStandby() {}
+
+//solution for GNOME?: http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit
+
+struct ScheduleForBackgroundProcessing::Impl {};
+ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() {};
+ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() {};
+
+/*
+struct ScheduleForBackgroundProcessing
+{
+ - required functions ioprio_get/ioprio_set are not part of glibc: http://linux.die.net/man/2/ioprio_set
+ - and probably never will: http://sourceware.org/bugzilla/show_bug.cgi?id=4464
+ - /usr/include/linux/ioprio.h not available on Ubuntu, so we can't use it instead
+
+ ScheduleForBackgroundProcessing() : oldIoPrio(getIoPriority(IOPRIO_WHO_PROCESS, ::getpid()))
+ {
+ if (oldIoPrio != -1)
+ setIoPriority(::getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0));
+ }
+ ~ScheduleForBackgroundProcessing()
+ {
+ if (oldIoPrio != -1)
+ setIoPriority(::getpid(), oldIoPrio);
+ }
+
+private:
+ static int getIoPriority(pid_t pid)
+ {
+ return ::syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, pid);
+ }
+ static int setIoPriority(pid_t pid, int ioprio)
+ {
+ return ::syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, pid, ioprio);
+ }
+
+ const int oldIoPrio;
+};
+*/
diff --git a/zen/process_priority.h b/zen/process_priority.h
index 07679b0c..b876dc92 100644..100755
--- a/zen/process_priority.h
+++ b/zen/process_priority.h
@@ -1,38 +1,38 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-#ifndef PROCESS_PRIORITY_H_83421759082143245
-#define PROCESS_PRIORITY_H_83421759082143245
-
-#include <memory>
-#include "file_error.h"
-
-
-namespace zen
-{
-//signal a "busy" state to the operating system
-class PreventStandby
-{
-public:
- PreventStandby(); //throw FileError
- ~PreventStandby();
-private:
- struct Impl;
- const std::unique_ptr<Impl> pimpl;
-};
-
-//lower CPU and file I/O priorities
-class ScheduleForBackgroundProcessing
-{
-public:
- ScheduleForBackgroundProcessing(); //throw FileError
- ~ScheduleForBackgroundProcessing();
-private:
- struct Impl;
- const std::unique_ptr<Impl> pimpl;
-};
-}
-
-#endif //PROCESS_PRIORITY_H_83421759082143245
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+#ifndef PROCESS_PRIORITY_H_83421759082143245
+#define PROCESS_PRIORITY_H_83421759082143245
+
+#include <memory>
+#include "file_error.h"
+
+
+namespace zen
+{
+//signal a "busy" state to the operating system
+class PreventStandby
+{
+public:
+ PreventStandby(); //throw FileError
+ ~PreventStandby();
+private:
+ struct Impl;
+ const std::unique_ptr<Impl> pimpl;
+};
+
+//lower CPU and file I/O priorities
+class ScheduleForBackgroundProcessing
+{
+public:
+ ScheduleForBackgroundProcessing(); //throw FileError
+ ~ScheduleForBackgroundProcessing();
+private:
+ struct Impl;
+ const std::unique_ptr<Impl> pimpl;
+};
+}
+
+#endif //PROCESS_PRIORITY_H_83421759082143245
diff --git a/zen/recycler.cpp b/zen/recycler.cpp
index d8ee58c4..02ea026a 100644..100755
--- a/zen/recycler.cpp
+++ b/zen/recycler.cpp
@@ -1,323 +1,72 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "recycler.h"
-#include "file_access.h"
-
-#ifdef ZEN_WIN
- #include "thread.h"
-
- #ifdef ZEN_WIN_VISTA_AND_LATER
- #include "vista_file_op.h"
- #else
- #include "com_tools.h"
- #endif
-
-#elif defined ZEN_LINUX
- #include <sys/stat.h>
- #include <gio/gio.h>
- #include "scope_guard.h"
-
-#elif defined ZEN_MAC
- #include <CoreServices/CoreServices.h>
-#endif
-
-using namespace zen;
-
-
-#ifdef ZEN_WIN
-void zen::recycleOrDeleteIfExists(const std::vector<Zstring>& itemPaths, const std::function<void (const std::wstring& displayPath)>& onRecycleItem)
-{
- //warning: moving long file paths to recycler does not work!
- //both ::SHFileOperation() and ::IFileOperation cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success
- //both ::SHFileOperation() and ::IFileOperation can't handle \\?\-prefix!
-
- /*
- Performance test: delete 1000 files
- ------------------------------------
- SHFileOperation - single file 33s
- SHFileOperation - multiple files 2,1s
- IFileOperation - single file 33s
- IFileOperation - multiple files 2,1s
-
- => SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics!
-
- Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)!
- */
-
-#ifdef ZEN_WIN_VISTA_AND_LATER //Win Vista recycle bin usage: 1. good error reporting 2. late failure
- vista::moveToRecycleBinIfExists(itemPaths, onRecycleItem); //throw FileError
-
-#else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure
- //TODO: this XP version fails if any item is not existing violating this function's API
- if (itemPaths.empty()) return;
-
- Zstring itemPathsDoubleNull;
- for (const Zstring& itemPath : itemPaths)
- {
- itemPathsDoubleNull += itemPath;
- itemPathsDoubleNull += L'\0';
- }
-
- SHFILEOPSTRUCT fileOp = {};
- fileOp.hwnd = nullptr;
- fileOp.wFunc = FO_DELETE;
- fileOp.pFrom = itemPathsDoubleNull.c_str();
- fileOp.pTo = nullptr;
- fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
- fileOp.fAnyOperationsAborted = false;
- fileOp.hNameMappings = nullptr;
- fileOp.lpszProgressTitle = nullptr;
-
- //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe."
- if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) //fails if items are not existing!
- {
- std::wstring itempathFmt = fmtPath(itemPaths[0]); //probably not the correct file name for file lists larger than 1!
- if (itemPaths.size() > 1)
- itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one
- throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt));
- }
-#endif
-}
-#endif
-
-
-bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError
-{
-#ifdef ZEN_WIN
-#ifdef ZEN_WIN_VISTA_AND_LATER
- return vista::moveToRecycleBinIfExists({ itemPath }, nullptr) != 0; //throw FileError
-
-#else
- Zstring itemPathDoubleNull = itemPath;
- itemPathDoubleNull += L'\0';
-
- SHFILEOPSTRUCT fileOp = {};
- fileOp.wFunc = FO_DELETE;
- fileOp.pFrom = itemPathDoubleNull.c_str();
- fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
-
- if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) //fails if item is not existing!
- {
- try
- {
- if (!getItemTypeIfExists(itemPath)) //throw FileError
- return false;
- }
- catch (FileError&) {} //previous exception is more relevant
-
- throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)));
- }
- return true;
-#endif
-
-#elif defined ZEN_LINUX
- GFile* file = ::g_file_new_for_path(itemPath.c_str()); //never fails according to docu
- ZEN_ON_SCOPE_EXIT(g_object_unref(file);)
-
- GError* error = nullptr;
- ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error););
-
- if (!::g_file_trash(file, nullptr, &error))
- {
- const Opt<ItemType> type = getItemTypeIfExists(itemPath); //throw FileError
- if (!type)
- return false;
-
- const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath));
- if (!error)
- throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this
-
- //implement same behavior as in Windows: if recycler is not existing, delete permanently
- if (error->code == G_IO_ERROR_NOT_SUPPORTED)
- {
- if (*type == ItemType::FOLDER)
- removeDirectoryPlainRecursion(itemPath); //throw FileError
- else
- removeFilePlain(itemPath); //throw FileError
- return true;
- }
-
- throw FileError(errorMsg, replaceCpy<std::wstring>(L"Glib Error Code %x:", L"%x", numberTo<std::wstring>(error->code)) + L" " + utfCvrtTo<std::wstring>(error->message));
- //g_quark_to_string(error->domain)
- }
- return true;
-
-#elif defined ZEN_MAC
- //we cannot use FSPathMoveObjectToTrashSync directly since it follows symlinks!
-
- static_assert(sizeof(Zchar) == sizeof(char), "");
- const UInt8* itemPathUtf8 = reinterpret_cast<const UInt8*>(itemPath.c_str());
-
- auto throwFileError = [&](OSStatus oss)
- {
- const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath));
- std::wstring errorDescr = L"OSStatus Code " + numberTo<std::wstring>(oss);
-
- if (const char* description = ::GetMacOSStatusCommentString(oss)) //found no documentation for proper use of GetMacOSStatusCommentString
- errorDescr += L": " + utfCvrtTo<std::wstring>(description);
- throw FileError(errorMsg, errorDescr);
- };
-
- //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin unsupported"
- //both "not supported" and "item missing" let FSMoveObjectToTrashSync fail with -120
- const Opt<ItemType> type = getItemTypeIfExists(itemPath); //throw FileError
- if (!type)
- return false;
-
- FSRef objectRef = {}; //= POD structure not a pointer type!
- OSStatus rv = ::FSPathMakeRefWithOptions(itemPathUtf8, //const UInt8 *path,
- kFSPathMakeRefDoNotFollowLeafSymlink, //OptionBits options,
- &objectRef, //FSRef *ref,
- nullptr); //Boolean *isDirectory
- if (rv != noErr)
- throwFileError(rv);
-
- //deprecated since OS X 10.8!!! "trashItemAtURL" should be used instead
- OSStatus rv2 = ::FSMoveObjectToTrashSync(&objectRef, //const FSRef *source,
- nullptr, //FSRef *target,
- kFSFileOperationDefaultOptions); //OptionBits options
- if (rv2 != noErr)
- {
- //implement same behavior as in Windows: if recycler is not existing, delete permanently
- if (rv2 == -120) //=="Directory not found or incomplete pathname." but should really be "recycle bin directory not found"!
- {
- if (*type == ItemType::FOLDER)
- removeDirectoryPlainRecursion(itemPath); //throw FileError
- else
- removeFilePlain(itemPath); //throw FileError
- return true;
- }
-
- throwFileError(rv2);
- }
- return true;
-#endif
-}
-
-
-#ifdef ZEN_WIN
-bool zen::recycleBinExists(const Zstring& dirPath, const std::function<void ()>& onUpdateGui) //throw FileError
-{
-#ifdef ZEN_WIN_VISTA_AND_LATER
- return vista::supportsRecycleBin(dirPath); //throw FileError
-
-#else
- //excessive runtime if recycle bin exists, is full and drive is slow:
- auto ft = runAsync([dirPath]() -> HRESULT
- {
- try
- {
- ComInitializer ci; //throw SysError
-
- SHQUERYRBINFO recInfo = {};
- recInfo.cbSize = sizeof(recInfo);
- return ::SHQueryRecycleBin(dirPath.c_str(), //__in_opt LPCTSTR pszRootPath,
- &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo
- }
- catch (SysError&) { assert(false); return ERROR_GEN_FAILURE; }
- });
-
- while (ft.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready)
- if (onUpdateGui)
- onUpdateGui(); //may throw!
-
- return ft.get() == S_OK;
-#endif
-
- //1. ::SHQueryRecycleBin() is excessive: traverses whole $Recycle.Bin directory tree each time!!!! But it's safe and correct.
-
- //2. we can't simply buffer the ::SHQueryRecycleBin() based on volume serial number:
- // "subst S:\ C:\" => GetVolumeInformation() returns same serial for C:\ and S:\, but S:\ does not support recycle bin!
-
- //3. we would prefer to use CLSID_RecycleBinManager beginning with Vista... if only this interface were documented!!!
-
- //4. check directory existence of "C:\$Recycle.Bin, C:\RECYCLER, C:\RECYCLED"
- // -> not upward-compatible, wrong result for subst-alias: recycler assumed existing, although it is not!
-
- //5. alternative approach a'la Raymond Chen: https://blogs.msdn.microsoft.com/oldnewthing/20080918-00/?p=20843/
- //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although recycler is not available!
-
- /*
- Zstring rootPathPf = appendSeparator(&buffer[0]);
-
- const bool canUseFastCheckForRecycler = winXpOrLater();
- if (!canUseFastCheckForRecycler) //== "checkForRecycleBin"
- return STATUS_REC_UNKNOWN;
-
- using namespace fileop;
- const DllFun<FunType_checkRecycler> checkRecycler(getDllName(), funName_checkRecycler);
-
- if (!checkRecycler)
- return STATUS_REC_UNKNOWN; //actually an error since we're >= XP
-
- //search root directories for recycle bin folder...
-
- WIN32_FIND_DATA dataRoot = {};
- HANDLE hFindRoot = ::FindFirstFile(applyLongPathPrefix(rootPathPf + L'*').c_str(), &dataRoot);
- if (hFindRoot == INVALID_HANDLE_VALUE)
- return STATUS_REC_UNKNOWN;
- ZEN_ON_SCOPE_EXIT(FindClose(hFindRoot));
-
- auto shouldSkip = [](const Zstring& shortname) { return shortname == L"." || shortname == L".."; };
-
- do
- {
- if (!shouldSkip(dataRoot.cFileName) &&
- (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 &&
- (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM ) != 0 && //risky to rely on these attributes!!!
- (dataRoot.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ) != 0) //
- {
- WIN32_FIND_DATA dataChild = {};
- const Zstring childDirPf = rootPathPf + dataRoot.cFileName + L"\\";
-
- HANDLE hFindChild = ::FindFirstFile(applyLongPathPrefix(childDirPf + L'*').c_str(), &dataChild);
- if (hFindChild != INVALID_HANDLE_VALUE) //if we can't access a subdir, it's probably not the recycler
- {
- ZEN_ON_SCOPE_EXIT(FindClose(hFindChild));
- do
- {
- if (!shouldSkip(dataChild.cFileName) &&
- (dataChild.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
- {
- bool isRecycler = false;
- if (checkRecycler((childDirPf + dataChild.cFileName).c_str(), isRecycler))
- {
- if (isRecycler)
- return STATUS_REC_EXISTS;
- }
- else assert(false);
- }
- }
- while (::FindNextFile(hFindChild, &dataChild)); //ignore errors other than ERROR_NO_MORE_FILES
- }
- }
- }
- while (::FindNextFile(hFindRoot, &dataRoot)); //
-
- return STATUS_REC_MISSING;
- */
-}
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
-/*
-We really need access to a similar function to check whether a directory supports trashing and emit a warning if it does not!
-
-The following function looks perfect, alas it is restricted to local files and to the implementation of GIO only:
-
- gboolean _g_local_file_has_trash_dir(const char* dirpath, dev_t dir_dev);
- See: http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.h
-
- Just checking for "G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH" is not correct, since we find in
- http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.c
-
- g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
- writable && parent_info->has_trash_dir);
-
- => We're NOT interested in whether the specified folder can be trashed, but whether it supports thrashing its child elements! (Only support, not actual write access!)
- This renders G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH useless for this purpose.
-*/
-#endif
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "recycler.h"
+#include "file_access.h"
+
+ #include <sys/stat.h>
+ #include <gio/gio.h>
+ #include "scope_guard.h"
+
+
+using namespace zen;
+
+
+
+
+bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError
+{
+ GFile* file = ::g_file_new_for_path(itemPath.c_str()); //never fails according to docu
+ ZEN_ON_SCOPE_EXIT(g_object_unref(file);)
+
+ GError* error = nullptr;
+ ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error););
+
+ if (!::g_file_trash(file, nullptr, &error))
+ {
+ const Opt<ItemType> type = getItemTypeIfExists(itemPath); //throw FileError
+ if (!type)
+ return false;
+
+ const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath));
+ if (!error)
+ throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this
+
+ //implement same behavior as in Windows: if recycler is not existing, delete permanently
+ if (error->code == G_IO_ERROR_NOT_SUPPORTED)
+ {
+ if (*type == ItemType::FOLDER)
+ removeDirectoryPlainRecursion(itemPath); //throw FileError
+ else
+ removeFilePlain(itemPath); //throw FileError
+ return true;
+ }
+
+ throw FileError(errorMsg, replaceCpy<std::wstring>(L"Glib Error Code %x:", L"%x", numberTo<std::wstring>(error->code)) + L" " + utfCvrtTo<std::wstring>(error->message));
+ //g_quark_to_string(error->domain)
+ }
+ return true;
+
+}
+
+
+/*
+We really need access to a similar function to check whether a directory supports trashing and emit a warning if it does not!
+
+The following function looks perfect, alas it is restricted to local files and to the implementation of GIO only:
+
+ gboolean _g_local_file_has_trash_dir(const char* dirpath, dev_t dir_dev);
+ See: http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.h
+
+ Just checking for "G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH" is not correct, since we find in
+ http://www.netmite.com/android/mydroid/2.0/external/bluetooth/glib/gio/glocalfileinfo.c
+
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH,
+ writable && parent_info->has_trash_dir);
+
+ => We're NOT interested in whether the specified folder can be trashed, but whether it supports thrashing its child elements! (Only support, not actual write access!)
+ This renders G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH useless for this purpose.
+*/
diff --git a/zen/recycler.h b/zen/recycler.h
index ec2a8672..0f1b0023 100644..100755
--- a/zen/recycler.h
+++ b/zen/recycler.h
@@ -1,51 +1,43 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef RECYCLER_H_18345067341545
-#define RECYCLER_H_18345067341545
-
-#include <vector>
-#include <functional>
-#include "file_error.h"
-
-
-namespace zen
-{
-/*
---------------------
-|Recycle Bin Access|
---------------------
-
-Windows
--------
--> Recycler API always available: during runtime either SHFileOperation or IFileOperation (since Vista) will be dynamically selected
--> COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize
-
-Linux
------
-Compiler flags: `pkg-config --cflags gio-2.0`
-Linker flags: `pkg-config --libs gio-2.0`
-
-Already included in package "gtk+-2.0"!
-*/
-
-
-
-//move a file or folder to Recycle Bin (deletes permanently if recycler is not available) -> crappy semantics, but we have no choice thanks to Windows' design
-bool recycleOrDeleteIfExists(const Zstring& itemPath); //throw FileError, return "true" if file/dir was actually deleted
-
-
-#ifdef ZEN_WIN
-//Win XP: can take a long time if recycle bin is full and drive is slow!!! => buffer result!
-//Vista and later: dirPath must exist for a valid check!
-bool recycleBinExists(const Zstring& dirPath, const std::function<void ()>& onUpdateGui); //throw FileError
-
-void recycleOrDeleteIfExists(const std::vector<Zstring>& filePaths, //throw FileError
- const std::function<void (const std::wstring& displayPath)>& onRecycleItem); //optional; currentItem may be empty
-#endif
-}
-
-#endif //RECYCLER_H_18345067341545
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef RECYCLER_H_18345067341545
+#define RECYCLER_H_18345067341545
+
+#include <vector>
+#include <functional>
+#include "file_error.h"
+
+
+namespace zen
+{
+/*
+--------------------
+|Recycle Bin Access|
+--------------------
+
+Windows
+-------
+-> Recycler API always available: during runtime either SHFileOperation or IFileOperation (since Vista) will be dynamically selected
+-> COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize
+
+Linux
+-----
+Compiler flags: `pkg-config --cflags gio-2.0`
+Linker flags: `pkg-config --libs gio-2.0`
+
+Already included in package "gtk+-2.0"!
+*/
+
+
+
+//move a file or folder to Recycle Bin (deletes permanently if recycler is not available) -> crappy semantics, but we have no choice thanks to Windows' design
+bool recycleOrDeleteIfExists(const Zstring& itemPath); //throw FileError, return "true" if file/dir was actually deleted
+
+
+}
+
+#endif //RECYCLER_H_18345067341545
diff --git a/zen/scope_guard.h b/zen/scope_guard.h
index 853f51b9..09a7fbdb 100644..100755
--- a/zen/scope_guard.h
+++ b/zen/scope_guard.h
@@ -1,130 +1,121 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef SCOPE_GUARD_H_8971632487321434
-#define SCOPE_GUARD_H_8971632487321434
-
-#include <cassert>
-#include <exception>
-#include "type_tools.h"
-
-
-#ifdef ZEN_WIN
-inline int getUncaughtExceptionCount() { return std::uncaught_exceptions(); }
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
-//std::uncaught_exceptions() currently unsupported on GCC and Clang => clean up ASAP
-#ifdef ZEN_LINUX
- static_assert(__GNUC__ < 6 || (__GNUC__ == 6 && (__GNUC_MINOR__ < 2 || (__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ <= 1))), "check std::uncaught_exceptions support");
-#else //std::uncaught_exceptions() requires "mmacosx-version-min=10.12"
- static_assert(__clang_major__ < 8 || (__clang_major__ == 8 && __clang_minor__ <= 0), "check std::uncaught_exceptions support");
-#endif
-
-namespace __cxxabiv1
-{
-struct __cxa_eh_globals;
-extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept;
-}
-
-inline int getUncaughtExceptionCount()
-{
- return *(reinterpret_cast<unsigned int*>(static_cast<char*>(static_cast<void*>(__cxxabiv1::__cxa_get_globals())) + sizeof(void*)));
-}
-#endif
-
-//best of Zen, Loki and C++17
-
-namespace zen
-{
-//Scope Guard
-/*
- auto guardAio = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { ::CloseHandle(hDir); });
- ...
- guardAio.dismiss();
-
-Scope Exit:
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir));
- ZEN_ON_SCOPE_FAIL(UndoPreviousWork());
- ZEN_ON_SCOPE_SUCCESS(NotifySuccess());
-*/
-
-enum class ScopeGuardRunMode
-{
- ON_EXIT,
- ON_SUCCESS,
- ON_FAIL
-};
-
-
-//partially specialize scope guard destructor code and get rid of those pesky MSVC "4127 conditional expression is constant"
-template <typename F> inline
-void runScopeGuardDestructor(F& fun, int /*exeptionCountOld*/, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_EXIT>)
-{
- try { fun(); }
- catch (...) { assert(false); } //consistency: don't expect exceptions for ON_EXIT even if "!failed"!
-}
-
-
-template <typename F> inline
-void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_SUCCESS>)
-{
- const bool failed = getUncaughtExceptionCount() > exeptionCountOld;
- if (!failed)
- fun(); //throw X
-}
-
-
-template <typename F> inline
-void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_FAIL>)
-{
- const bool failed = getUncaughtExceptionCount() > exeptionCountOld;
- if (failed)
- try { fun(); }
- catch (...) { assert(false); }
-}
-
-
-template <ScopeGuardRunMode runMode, typename F>
-class ScopeGuard
-{
-public:
- explicit ScopeGuard(const F& fun) : fun_(fun) {}
- explicit ScopeGuard( F&& fun) : fun_(std::move(fun)) {}
-
- ScopeGuard(ScopeGuard&& other) : fun_(std::move(other.fun_)),
- exeptionCount_(other.exeptionCount_),
- dismissed_(other.dismissed_) { other.dismissed_ = true; }
-
- ~ScopeGuard() noexcept(runMode != ScopeGuardRunMode::ON_SUCCESS)
- {
- if (!dismissed_)
- runScopeGuardDestructor(fun_, exeptionCount_, StaticEnum<ScopeGuardRunMode, runMode>());
- }
-
- void dismiss() { dismissed_ = true; }
-
-private:
- ScopeGuard (const ScopeGuard&) = delete;
- ScopeGuard& operator=(const ScopeGuard&) = delete;
-
- F fun_;
- const int exeptionCount_ = getUncaughtExceptionCount();
- bool dismissed_ = false;
-};
-
-
-template <ScopeGuardRunMode runMode, class F> inline
-auto makeGuard(F&& fun) { return ScopeGuard<runMode, std::decay_t<F>>(std::forward<F>(fun)); }
-}
-
-#define ZEN_CONCAT_SUB(X, Y) X ## Y
-#define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y)
-
-#define ZEN_ON_SCOPE_EXIT(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_EXIT >([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
-#define ZEN_ON_SCOPE_FAIL(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_FAIL >([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
-#define ZEN_ON_SCOPE_SUCCESS(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_SUCCESS>([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
-
-#endif //SCOPE_GUARD_H_8971632487321434
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef SCOPE_GUARD_H_8971632487321434
+#define SCOPE_GUARD_H_8971632487321434
+
+#include <cassert>
+#include <exception>
+#include "type_tools.h"
+
+
+//std::uncaught_exceptions() currently unsupported on GCC and Clang => clean up ASAP
+ static_assert(__GNUC__ < 6 || (__GNUC__ == 6 && (__GNUC_MINOR__ < 2 || (__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ <= 1))), "check std::uncaught_exceptions support");
+
+namespace __cxxabiv1
+{
+struct __cxa_eh_globals;
+extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept;
+}
+
+inline int getUncaughtExceptionCount()
+{
+ return *(reinterpret_cast<unsigned int*>(static_cast<char*>(static_cast<void*>(__cxxabiv1::__cxa_get_globals())) + sizeof(void*)));
+}
+
+//best of Zen, Loki and C++17
+
+namespace zen
+{
+//Scope Guard
+/*
+ auto guardAio = zen::makeGuard<ScopeGuardRunMode::ON_EXIT>([&] { ::CloseHandle(hDir); });
+ ...
+ guardAio.dismiss();
+
+Scope Exit:
+ ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir));
+ ZEN_ON_SCOPE_FAIL(UndoPreviousWork());
+ ZEN_ON_SCOPE_SUCCESS(NotifySuccess());
+*/
+
+enum class ScopeGuardRunMode
+{
+ ON_EXIT,
+ ON_SUCCESS,
+ ON_FAIL
+};
+
+
+//partially specialize scope guard destructor code and get rid of those pesky MSVC "4127 conditional expression is constant"
+template <typename F> inline
+void runScopeGuardDestructor(F& fun, int /*exeptionCountOld*/, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_EXIT>)
+{
+ try { fun(); }
+ catch (...) { assert(false); } //consistency: don't expect exceptions for ON_EXIT even if "!failed"!
+}
+
+
+template <typename F> inline
+void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_SUCCESS>)
+{
+ const bool failed = getUncaughtExceptionCount() > exeptionCountOld;
+ if (!failed)
+ fun(); //throw X
+}
+
+
+template <typename F> inline
+void runScopeGuardDestructor(F& fun, int exeptionCountOld, StaticEnum<ScopeGuardRunMode, ScopeGuardRunMode::ON_FAIL>)
+{
+ const bool failed = getUncaughtExceptionCount() > exeptionCountOld;
+ if (failed)
+ try { fun(); }
+ catch (...) { assert(false); }
+}
+
+
+template <ScopeGuardRunMode runMode, typename F>
+class ScopeGuard
+{
+public:
+ explicit ScopeGuard(const F& fun) : fun_(fun) {}
+ explicit ScopeGuard( F&& fun) : fun_(std::move(fun)) {}
+
+ ScopeGuard(ScopeGuard&& other) : fun_(std::move(other.fun_)),
+ exeptionCount_(other.exeptionCount_),
+ dismissed_(other.dismissed_) { other.dismissed_ = true; }
+
+ ~ScopeGuard() noexcept(runMode != ScopeGuardRunMode::ON_SUCCESS)
+ {
+ if (!dismissed_)
+ runScopeGuardDestructor(fun_, exeptionCount_, StaticEnum<ScopeGuardRunMode, runMode>());
+ }
+
+ void dismiss() { dismissed_ = true; }
+
+private:
+ ScopeGuard (const ScopeGuard&) = delete;
+ ScopeGuard& operator=(const ScopeGuard&) = delete;
+
+ F fun_;
+ const int exeptionCount_ = getUncaughtExceptionCount();
+ bool dismissed_ = false;
+};
+
+
+template <ScopeGuardRunMode runMode, class F> inline
+auto makeGuard(F&& fun) { return ScopeGuard<runMode, std::decay_t<F>>(std::forward<F>(fun)); }
+}
+
+#define ZEN_CONCAT_SUB(X, Y) X ## Y
+#define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y)
+
+#define ZEN_ON_SCOPE_EXIT(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_EXIT >([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
+#define ZEN_ON_SCOPE_FAIL(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_FAIL >([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
+#define ZEN_ON_SCOPE_SUCCESS(X) auto ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard<zen::ScopeGuardRunMode::ON_SUCCESS>([&]{ X; }); (void)ZEN_CONCAT(dummy, __LINE__);
+
+#endif //SCOPE_GUARD_H_8971632487321434
diff --git a/zen/serialize.h b/zen/serialize.h
index 290d9200..bb2f7a45 100644..100755
--- a/zen/serialize.h
+++ b/zen/serialize.h
@@ -1,327 +1,280 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef SERIALIZE_H_839405783574356
-#define SERIALIZE_H_839405783574356
-
-#include <functional>
-#include <cstdint>
-#include "string_base.h"
-//keep header clean from specific stream implementations! (e.g.file_io.h)! used by abstract.h!
-
-
-namespace zen
-{
-//high-performance unformatted serialization (avoiding wxMemoryOutputStream/wxMemoryInputStream inefficiencies)
-
-/*
---------------------------
-|Binary Container Concept|
---------------------------
-binary container for data storage: must support "basic" std::vector interface (e.g. std::vector<char>, std::string, Zbase<char>)
-*/
-
-//binary container reference implementations
-using Utf8String = Zbase<char>; //ref-counted + COW text stream + guaranteed performance: exponential growth
-class ByteArray; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?)
-
-
-class ByteArray //essentially a std::vector<char> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite
-{
-public:
- using value_type = std::vector<char>::value_type;
- using iterator = std::vector<char>::iterator;
- using const_iterator = std::vector<char>::const_iterator;
-
- iterator begin() { return buffer->begin(); }
- iterator end () { return buffer->end (); }
-
- const_iterator begin() const { return buffer->begin(); }
- const_iterator end () const { return buffer->end (); }
-
- void resize(size_t len) { buffer->resize(len); }
- size_t size() const { return buffer->size(); }
- bool empty() const { return buffer->empty(); }
-
- inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return *lhs.buffer == *rhs.buffer; }
-
-private:
- std::shared_ptr<std::vector<char>> buffer { std::make_shared<std::vector<char>>() }; //always bound!
- //perf: shared_ptr indirection irrelevant: less than 1% slower!
-};
-
-/*
----------------------------------
-|Unbuffered Input Stream Concept|
----------------------------------
-struct UnbufferedInputStream
-{
- size_t getBlockSize();
- size_t tryRead(void* buffer, size_t bytesToRead); //may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
-};
-
-----------------------------------
-|Unbuffered Output Stream Concept|
-----------------------------------
-struct UnbufferedOutputStream
-{
- size_t getBlockSize();
- size_t tryWrite(const void* buffer, size_t bytesToWrite); //may return short! CONTRACT: bytesToWrite > 0
-};
-*/
-
-//functions based on unbuffered stream abstraction
-template <class UnbufferedInputStream, class UnbufferedOutputStream>
-void unbufferedStreamCopy(UnbufferedInputStream& streamIn, UnbufferedOutputStream& streamOut, const std::function<void(std::int64_t bytesDelta)>& notifyProgress); //throw X
-
-template <class BinContainer, class UnbufferedOutputStream>
-void unbufferedSave(const BinContainer& buffer, UnbufferedOutputStream& streamOut, const std::function<void(std::int64_t bytesDelta)>& notifyProgress); //throw X
-
-template <class BinContainer, class UnbufferedInputStream>
-BinContainer unbufferedLoad(UnbufferedInputStream& streamIn, const std::function<void(std::int64_t bytesDelta)>& notifyProgress); //throw X
-
-/*
--------------------------------
-|Buffered Input Stream Concept|
--------------------------------
-struct BufferedInputStream
-{
- size_t read(void* buffer, size_t bytesToRead); //return "len" bytes unless end of stream! throw ?
-};
-
---------------------------------
-|Buffered Output Stream Concept|
---------------------------------
-struct BufferedOutputStream
-{
- void write(const void* buffer, size_t bytesToWrite); //throw ?
-};
-*/
-//functions based on buffered stream abstraction
-template <class N, class BufferedOutputStream> void writeNumber (BufferedOutputStream& stream, const N& num); //
-template <class C, class BufferedOutputStream> void writeContainer(BufferedOutputStream& stream, const C& str); //throw ()
-template < class BufferedOutputStream> void writeArray (BufferedOutputStream& stream, const void* data, size_t len); //
-//----------------------------------------------------------------------
-class UnexpectedEndOfStreamError {};
-template <class N, class BufferedInputStream> N readNumber (BufferedInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data)
-template <class C, class BufferedInputStream> C readContainer(BufferedInputStream& stream); //
-template < class BufferedInputStream> void readArray (BufferedInputStream& stream, void* data, size_t len); //
-
-//buffered input/output stream reference implementations:
-template <class BinContainer>
-struct MemoryStreamIn
-{
- MemoryStreamIn(const BinContainer& cont) : buffer_(cont) {} //this better be cheap!
-
- size_t read(void* data, size_t len) //return "len" bytes unless end of stream!
- {
- static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
- const size_t bytesRead = std::min(len, buffer_.size() - pos_);
- auto itFirst = buffer_.begin() + pos_;
- std::copy(itFirst, itFirst + bytesRead, static_cast<char*>(data));
- pos_ += bytesRead;
- return bytesRead;
- }
-
- size_t pos() const { return pos_; }
-
-private:
- const BinContainer buffer_;
- size_t pos_ = 0;
-};
-
-template <class BinContainer>
-struct MemoryStreamOut
-{
- void write(const void* data, size_t len)
- {
- static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
- const size_t oldSize = buffer_.size();
- buffer_.resize(oldSize + len);
- std::copy(static_cast<const char*>(data), static_cast<const char*>(data) + len, buffer_.begin() + oldSize);
- }
-
- const BinContainer& ref() const { return buffer_; }
-
-private:
- BinContainer buffer_;
-};
-
-
-
-
-
-
-
-
-//-----------------------implementation-------------------------------
-template <class UnbufferedInputStream, class UnbufferedOutputStream> inline
-void unbufferedStreamCopy(UnbufferedInputStream& streamIn, //throw X
- UnbufferedOutputStream& streamOut, //
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress) //optional
-{
- size_t unevenBytes = 0;
- auto reportBytesProcessed = [&](size_t bytesReadOrWritten)
- {
- if (notifyProgress)
- {
- const size_t bytesToReport = (unevenBytes + bytesReadOrWritten) / 2;
- notifyProgress(bytesToReport); //throw X!
- unevenBytes = (unevenBytes + bytesReadOrWritten) - bytesToReport * 2; //unsigned arithmetics!
- }
- };
-
- const size_t blockSizeIn = streamIn .getBlockSize();
- const size_t blockSizeOut = streamOut.getBlockSize();
- if (blockSizeIn == 0 || blockSizeOut == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-
- std::vector<char> buffer;
- for (;;)
- {
- buffer.resize(buffer.size() + blockSizeIn);
- const size_t bytesRead = streamIn.tryRead(&*(buffer.end() - blockSizeIn), blockSizeIn); //throw X; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
- buffer.resize(buffer.size() - blockSizeIn + bytesRead); //caveat: unsigned arithmetics
-
- reportBytesProcessed(bytesRead); //throw X!
-
- size_t bytesRemaining = buffer.size();
- while (bytesRemaining >= blockSizeOut)
- {
- const size_t bytesWritten = streamOut.tryWrite(&*(buffer.end() - bytesRemaining), blockSizeOut); //throw X; may return short! CONTRACT: bytesToWrite > 0
- bytesRemaining -= bytesWritten;
- reportBytesProcessed(bytesWritten); //throw X!
- }
- buffer.erase(buffer.begin(), buffer.end() - bytesRemaining);
-
- if (bytesRead == 0) //end of file
- break;
- }
-
- for (size_t bytesRemaining = buffer.size(); bytesRemaining > 0;)
- {
- const size_t bytesWritten = streamOut.tryWrite(&*(buffer.end() - bytesRemaining), bytesRemaining); //throw X; may return short! CONTRACT: bytesToWrite > 0
- bytesRemaining -= bytesWritten;
- reportBytesProcessed(bytesWritten); //throw X!
- }
-
- if (unevenBytes != 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-}
-
-
-template <class BinContainer, class UnbufferedOutputStream> inline
-void unbufferedSave(const BinContainer& buffer,
- UnbufferedOutputStream& streamOut, //throw X
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress) //optional
-{
- const size_t blockSize = streamOut.getBlockSize();
- if (blockSize == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-
- static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
-
- for (size_t bytesRemaining = buffer.size(); bytesRemaining > 0;)
- {
- const size_t bytesToWrite = std::min(bytesRemaining, blockSize);
- const size_t bytesWritten = streamOut.tryWrite(&*(buffer.end() - bytesRemaining), bytesToWrite); //throw X; may return short! CONTRACT: bytesToWrite > 0
- bytesRemaining -= bytesWritten;
- if (notifyProgress) notifyProgress(bytesWritten); //throw X!
- }
-}
-
-
-template <class BinContainer, class UnbufferedInputStream> inline
-BinContainer unbufferedLoad(UnbufferedInputStream& streamIn, //throw X
- const std::function<void(std::int64_t bytesDelta)>& notifyProgress) //optional
-{
- const size_t blockSize = streamIn.getBlockSize();
- if (blockSize == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-
- static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
-
- BinContainer buffer;
- for (;;)
- {
- buffer.resize(buffer.size() + blockSize);
- const size_t bytesRead = streamIn.tryRead(&*(buffer.end() - blockSize), blockSize); //throw X; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
- buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
-
- if (notifyProgress) notifyProgress(bytesRead); //throw X!
-
- if (bytesRead == 0) //end of file
- return buffer;
- }
-}
-
-
-template <class BufferedOutputStream> inline
-void writeArray(BufferedOutputStream& stream, const void* data, size_t len)
-{
- stream.write(data, len);
-}
-
-
-template <class N, class BufferedOutputStream> inline
-void writeNumber(BufferedOutputStream& stream, const N& num)
-{
- static_assert(IsArithmetic<N>::value || IsSameType<N, bool>::value, "not a number!");
- writeArray(stream, &num, sizeof(N));
-}
-
-
-template <class C, class BufferedOutputStream> inline
-void writeContainer(BufferedOutputStream& stream, const C& cont) //don't even consider UTF8 conversions here, we're handling arbitrary binary data!
-{
- const auto len = cont.size();
- writeNumber(stream, static_cast<std::uint32_t>(len));
- if (len > 0)
- writeArray(stream, &*cont.begin(), sizeof(typename C::value_type) * len); //don't use c_str(), but access uniformly via STL interface
-}
-
-
-template <class BufferedInputStream> inline
-void readArray(BufferedInputStream& stream, void* data, size_t len) //throw UnexpectedEndOfStreamError
-{
- const size_t bytesRead = stream.read(data, len);
- if (bytesRead < len)
- throw UnexpectedEndOfStreamError();
-}
-
-
-template <class N, class BufferedInputStream> inline
-N readNumber(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError
-{
- static_assert(IsArithmetic<N>::value || IsSameType<N, bool>::value, "");
- N num = 0;
- readArray(stream, &num, sizeof(N)); //throw UnexpectedEndOfStreamError
- return num;
-}
-
-
-template <class C, class BufferedInputStream> inline
-C readContainer(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError
-{
- C cont;
- auto strLength = readNumber<std::uint32_t>(stream);
- if (strLength > 0)
- {
- try
- {
- cont.resize(strLength); //throw std::bad_alloc
- }
- catch (std::bad_alloc&) //most likely this is due to data corruption!
- {
- throw UnexpectedEndOfStreamError();
- }
- readArray(stream, &*cont.begin(), sizeof(typename C::value_type) * strLength); //throw UnexpectedEndOfStreamError
- }
- return cont;
-}
-}
-
-#endif //SERIALIZE_H_839405783574356
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef SERIALIZE_H_839405783574356
+#define SERIALIZE_H_839405783574356
+
+#include <functional>
+#include <cstdint>
+#include "string_base.h"
+//keep header clean from specific stream implementations! (e.g.file_io.h)! used by abstract.h!
+
+
+namespace zen
+{
+//high-performance unformatted serialization (avoiding wxMemoryOutputStream/wxMemoryInputStream inefficiencies)
+
+/*
+--------------------------
+|Binary Container Concept|
+--------------------------
+binary container for data storage: must support "basic" std::vector interface (e.g. std::vector<char>, std::string, Zbase<char>)
+*/
+
+//binary container reference implementations
+using Utf8String = Zbase<char>; //ref-counted + COW text stream + guaranteed performance: exponential growth
+class ByteArray; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?)
+
+
+class ByteArray //essentially a std::vector<char> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite
+{
+public:
+ using value_type = std::vector<char>::value_type;
+ using iterator = std::vector<char>::iterator;
+ using const_iterator = std::vector<char>::const_iterator;
+
+ iterator begin() { return buffer->begin(); }
+ iterator end () { return buffer->end (); }
+
+ const_iterator begin() const { return buffer->begin(); }
+ const_iterator end () const { return buffer->end (); }
+
+ void resize(size_t len) { buffer->resize(len); }
+ size_t size() const { return buffer->size(); }
+ bool empty() const { return buffer->empty(); }
+
+ inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return *lhs.buffer == *rhs.buffer; }
+
+private:
+ std::shared_ptr<std::vector<char>> buffer { std::make_shared<std::vector<char>>() }; //always bound!
+ //perf: shared_ptr indirection irrelevant: less than 1% slower!
+};
+
+/*
+-------------------------------
+|Buffered Input Stream Concept|
+-------------------------------
+struct BufferedInputStream
+{
+ size_t read(void* buffer, size_t bytesToRead); //throw X; return "bytesToRead" bytes unless end of stream!
+
+Optional: support stream-copying
+--------------------------------
+ size_t getBlockSize() const;
+ const IOCallback& notifyUnbufferedIO
+};
+
+--------------------------------
+|Buffered Output Stream Concept|
+--------------------------------
+struct BufferedOutputStream
+{
+ void write(const void* buffer, size_t bytesToWrite); //throw X
+
+Optional: support stream-copying
+--------------------------------
+ const IOCallback& notifyUnbufferedIO
+};
+*/
+using IOCallback = std::function<void(int64_t bytesDelta)>; //throw X
+
+
+//functions based on buffered stream abstraction
+template <class BufferedInputStream, class BufferedOutputStream>
+void bufferedStreamCopy(BufferedInputStream& streamIn, BufferedOutputStream& streamOut); //throw X
+
+template <class BinContainer, class BufferedInputStream> BinContainer
+bufferedLoad(BufferedInputStream& streamIn); //throw X
+
+template <class N, class BufferedOutputStream> void writeNumber (BufferedOutputStream& stream, const N& num); //
+template <class C, class BufferedOutputStream> void writeContainer(BufferedOutputStream& stream, const C& str); //throw ()
+template < class BufferedOutputStream> void writeArray (BufferedOutputStream& stream, const void* buffer, size_t len); //
+//----------------------------------------------------------------------
+class UnexpectedEndOfStreamError {};
+template <class N, class BufferedInputStream> N readNumber (BufferedInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data)
+template <class C, class BufferedInputStream> C readContainer(BufferedInputStream& stream); //
+template < class BufferedInputStream> void readArray (BufferedInputStream& stream, void* buffer, size_t len); //
+
+
+struct IOCallbackDivider
+{
+ IOCallbackDivider(const IOCallback& notifyUnbufferedIO, int64_t& totalUnbufferedIO) : totalUnbufferedIO_(totalUnbufferedIO), notifyUnbufferedIO_(notifyUnbufferedIO) {}
+
+ void operator()(int64_t bytesDelta)
+ {
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_((totalUnbufferedIO_ - totalUnbufferedIO_ / 2 * 2 + bytesDelta) / 2); //throw X!
+ totalUnbufferedIO_ += bytesDelta;
+ }
+
+private:
+ int64_t& totalUnbufferedIO_;
+ const IOCallback& notifyUnbufferedIO_;
+};
+
+//buffered input/output stream reference implementations:
+template <class BinContainer>
+struct MemoryStreamIn
+{
+ MemoryStreamIn(const BinContainer& cont) : buffer_(cont) {} //this better be cheap!
+
+ size_t read(void* buffer, size_t bytesToRead) //return "bytesToRead" bytes unless end of stream!
+ {
+ static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
+ const size_t bytesRead = std::min(bytesToRead, buffer_.size() - pos_);
+ auto itFirst = buffer_.begin() + pos_;
+ std::copy(itFirst, itFirst + bytesRead, static_cast<char*>(buffer));
+ pos_ += bytesRead;
+ return bytesRead;
+ }
+
+ size_t pos() const { return pos_; }
+
+private:
+ MemoryStreamIn (const MemoryStreamIn&) = delete;
+ MemoryStreamIn& operator=(const MemoryStreamIn&) = delete;
+
+ const BinContainer buffer_;
+ size_t pos_ = 0;
+};
+
+template <class BinContainer>
+struct MemoryStreamOut
+{
+ MemoryStreamOut() = default;
+
+ void write(const void* buffer, size_t bytesToWrite)
+ {
+ static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
+ const size_t oldSize = buffer_.size();
+ buffer_.resize(oldSize + bytesToWrite);
+ std::copy(static_cast<const char*>(buffer), static_cast<const char*>(buffer) + bytesToWrite, buffer_.begin() + oldSize);
+ }
+
+ const BinContainer& ref() const { return buffer_; }
+
+private:
+ MemoryStreamOut (const MemoryStreamOut&) = delete;
+ MemoryStreamOut& operator=(const MemoryStreamOut&) = delete;
+
+ BinContainer buffer_;
+};
+
+
+
+
+
+
+
+
+//-----------------------implementation-------------------------------
+template <class BufferedInputStream, class BufferedOutputStream> inline
+void bufferedStreamCopy(BufferedInputStream& streamIn, //throw X
+ BufferedOutputStream& streamOut) //
+{
+ const size_t blockSize = streamIn.getBlockSize();
+ if (blockSize == 0)
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+
+ std::vector<char> buffer(blockSize);
+ for (;;)
+ {
+ const size_t bytesRead = streamIn.read(&buffer[0], blockSize); //throw X; return "bytesToRead" bytes unless end of stream!
+ streamOut.write(&buffer[0], bytesRead); //throw X
+
+ if (bytesRead < blockSize) //end of file
+ break;
+ }
+}
+
+
+template <class BinContainer, class BufferedInputStream> inline
+BinContainer bufferedLoad(BufferedInputStream& streamIn) //throw X
+{
+ static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes
+
+ const size_t blockSize = streamIn.getBlockSize();
+ if (blockSize == 0)
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+
+ BinContainer buffer;
+ for (;;)
+ {
+ buffer.resize(buffer.size() + blockSize);
+ const size_t bytesRead = streamIn.read(&*(buffer.end() - blockSize), blockSize); //throw X; return "bytesToRead" bytes unless end of stream!
+ buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
+
+ if (bytesRead < blockSize) //end of file
+ return buffer;
+ }
+}
+
+
+template <class BufferedOutputStream> inline
+void writeArray(BufferedOutputStream& stream, const void* buffer, size_t len)
+{
+ stream.write(buffer, len);
+}
+
+
+template <class N, class BufferedOutputStream> inline
+void writeNumber(BufferedOutputStream& stream, const N& num)
+{
+ static_assert(IsArithmetic<N>::value || IsSameType<N, bool>::value, "not a number!");
+ writeArray(stream, &num, sizeof(N));
+}
+
+
+template <class C, class BufferedOutputStream> inline
+void writeContainer(BufferedOutputStream& stream, const C& cont) //don't even consider UTF8 conversions here, we're handling arbitrary binary data!
+{
+ const auto len = cont.size();
+ writeNumber(stream, static_cast<uint32_t>(len));
+ if (len > 0)
+ writeArray(stream, &*cont.begin(), sizeof(typename C::value_type) * len); //don't use c_str(), but access uniformly via STL interface
+}
+
+
+template <class BufferedInputStream> inline
+void readArray(BufferedInputStream& stream, void* buffer, size_t len) //throw UnexpectedEndOfStreamError
+{
+ const size_t bytesRead = stream.read(buffer, len);
+ if (bytesRead < len)
+ throw UnexpectedEndOfStreamError();
+}
+
+
+template <class N, class BufferedInputStream> inline
+N readNumber(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError
+{
+ static_assert(IsArithmetic<N>::value || IsSameType<N, bool>::value, "");
+ N num = 0;
+ readArray(stream, &num, sizeof(N)); //throw UnexpectedEndOfStreamError
+ return num;
+}
+
+
+template <class C, class BufferedInputStream> inline
+C readContainer(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError
+{
+ C cont;
+ auto strLength = readNumber<uint32_t>(stream); //throw UnexpectedEndOfStreamError
+ if (strLength > 0)
+ {
+ try
+ {
+ cont.resize(strLength); //throw std::bad_alloc
+ }
+ catch (std::bad_alloc&) //most likely this is due to data corruption!
+ {
+ throw UnexpectedEndOfStreamError();
+ }
+ readArray(stream, &*cont.begin(), sizeof(typename C::value_type) * strLength); //throw UnexpectedEndOfStreamError
+ }
+ return cont;
+}
+}
+
+#endif //SERIALIZE_H_839405783574356
diff --git a/zen/shell_execute.h b/zen/shell_execute.h
index b5e04469..9ba0aef0 100644..100755
--- a/zen/shell_execute.h
+++ b/zen/shell_execute.h
@@ -1,136 +1,52 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef SHELL_EXECUTE_H_23482134578134134
-#define SHELL_EXECUTE_H_23482134578134134
-
-#include "file_error.h"
-
-#ifdef ZEN_WIN
- #include "scope_guard.h"
- #include "win.h" //includes "windows.h"
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- #include "thread.h"
- #include <stdlib.h> //::system()
-#endif
-
-
-namespace zen
-{
-//launch commandline and report errors via popup dialog
-//windows: COM needs to be initialized before calling this function!
-enum ExecutionType
-{
- EXEC_TYPE_SYNC,
- EXEC_TYPE_ASYNC
-};
-
-namespace
-{
-#ifdef ZEN_WIN
-template <class Function>
-bool shellExecuteImpl(Function fillExecInfo, ExecutionType type)
-{
- SHELLEXECUTEINFO execInfo = {};
- execInfo.cbSize = sizeof(execInfo);
- execInfo.lpVerb = nullptr;
- execInfo.nShow = SW_SHOW;
- execInfo.fMask = type == EXEC_TYPE_SYNC ? (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC) : 0;
- //don't use SEE_MASK_ASYNCOK -> different async mode than the default which returns successful despite errors!
- execInfo.fMask |= SEE_MASK_FLAG_NO_UI; //::ShellExecuteEx() shows a non-blocking pop-up dialog on errors -> we want a blocking one
- //for the record, SEE_MASK_UNICODE does nothing: https://blogs.msdn.microsoft.com/oldnewthing/20140227-00/?p=1643/
-
- fillExecInfo(execInfo);
-
- if (!::ShellExecuteEx(&execInfo)) //__inout LPSHELLEXECUTEINFO lpExecInfo
- return false;
-
- if (execInfo.hProcess)
- {
- ZEN_ON_SCOPE_EXIT(::CloseHandle(execInfo.hProcess));
-
- if (type == EXEC_TYPE_SYNC)
- ::WaitForSingleObject(execInfo.hProcess, INFINITE);
- }
- return true;
-}
-
-
-void shellExecute(const void* /*PCIDLIST_ABSOLUTE*/ shellItemPidl, const std::wstring& displayPath, ExecutionType type) //throw FileError
-{
- auto fillExecInfo = [&](SHELLEXECUTEINFO& execInfo)
- {
- execInfo.fMask |= SEE_MASK_IDLIST;
- execInfo.lpIDList = const_cast<void*>(shellItemPidl); //lpIDList is documented as PCIDLIST_ABSOLUTE!
- };
-
- if (!shellExecuteImpl(fillExecInfo, type)) //throw FileError
- THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\n" + fmtPath(displayPath), L"ShellExecuteEx");
-}
-#endif
-
-
-void shellExecute(const Zstring& command, ExecutionType type) //throw FileError
-{
-#ifdef ZEN_WIN
- //parse commandline
- Zstring commandTmp = command;
- trim(commandTmp, true, false); //CommandLineToArgvW() does not like leading spaces
-
- std::vector<Zstring> argv;
- if (!commandTmp.empty()) //::CommandLineToArgvW returns the path to the current executable if empty string is passed
- {
- int argc = 0;
- LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc);
- if (!tmp)
- THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\n" + commandTmp.c_str(), L"CommandLineToArgvW");
- ZEN_ON_SCOPE_EXIT(::LocalFree(tmp));
- std::copy(tmp, tmp + argc, std::back_inserter(argv));
- }
-
- Zstring filePath;
- Zstring arguments;
- if (!argv.empty())
- {
- filePath = argv[0];
- for (auto it = argv.begin() + 1; it != argv.end(); ++it)
- arguments += (it == argv.begin() + 1 ? L"" : L" ") +
- (it->empty() || std::any_of(it->begin(), it->end(), &isWhiteSpace<wchar_t>) ? L"\"" + *it + L"\"" : *it);
- }
-
- auto fillExecInfo = [&](SHELLEXECUTEINFO& execInfo)
- {
- execInfo.lpFile = filePath.c_str();
- execInfo.lpParameters = arguments.c_str();
- };
-
- if (!shellExecuteImpl(fillExecInfo, type))
- THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\nFile: " + fmtPath(filePath) + L"\nArg: " + arguments.c_str(), L"ShellExecuteEx");
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- /*
- we cannot use wxExecute due to various issues:
- - screws up encoding on OS X for non-ASCII characters
- - does not provide any reasonable error information
- - uses a zero-sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list in Windows
- */
-
- if (type == EXEC_TYPE_SYNC)
- {
- //Posix::system - execute a shell command
- int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect...
- if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
- throw FileError(_("Incorrect command line:") + L"\n" + utfCvrtTo<std::wstring>(command));
- }
- else
- runAsync([=] { int rv = ::system(command.c_str()); (void)rv; });
-#endif
-}
-}
-}
-
-#endif //SHELL_EXECUTE_H_23482134578134134
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef SHELL_EXECUTE_H_23482134578134134
+#define SHELL_EXECUTE_H_23482134578134134
+
+#include "file_error.h"
+
+ #include "thread.h"
+ #include <stdlib.h> //::system()
+
+
+namespace zen
+{
+//launch commandline and report errors via popup dialog
+//windows: COM needs to be initialized before calling this function!
+enum ExecutionType
+{
+ EXEC_TYPE_SYNC,
+ EXEC_TYPE_ASYNC
+};
+
+namespace
+{
+
+
+void shellExecute(const Zstring& command, ExecutionType type) //throw FileError
+{
+ /*
+ we cannot use wxExecute due to various issues:
+ - screws up encoding on OS X for non-ASCII characters
+ - does not provide any reasonable error information
+ - uses a zero-sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list in Windows
+ */
+
+ if (type == EXEC_TYPE_SYNC)
+ {
+ //Posix::system - execute a shell command
+ int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect...
+ if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
+ throw FileError(_("Incorrect command line:") + L"\n" + utfCvrtTo<std::wstring>(command));
+ }
+ else
+ runAsync([=] { int rv = ::system(command.c_str()); (void)rv; });
+}
+}
+}
+
+#endif //SHELL_EXECUTE_H_23482134578134134
diff --git a/zen/stl_tools.h b/zen/stl_tools.h
index 48f475f3..74a2b360 100644..100755
--- a/zen/stl_tools.h
+++ b/zen/stl_tools.h
@@ -1,247 +1,244 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef STL_TOOLS_H_84567184321434
-#define STL_TOOLS_H_84567184321434
-
-#include <set>
-#include <map>
-#include <vector>
-#include <memory>
-#include <algorithm>
-#include "type_tools.h"
-#include "build_info.h"
-
-
-//enhancements for <algorithm>
-namespace zen
-{
-//erase selected elements from any container:
-template <class T, class Alloc, class Predicate>
-void erase_if(std::vector<T, Alloc>& v, Predicate p);
-
-template <class T, class LessType, class Alloc, class Predicate>
-void erase_if(std::set<T, LessType, Alloc>& s, Predicate p);
-
-template <class KeyType, class ValueType, class LessType, class Alloc, class Predicate>
-void erase_if(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p);
-
-//append STL containers
-template <class T, class Alloc, class C>
-void append(std::vector<T, Alloc>& v, const C& c);
-
-template <class T, class LessType, class Alloc, class C>
-void append(std::set<T, LessType, Alloc>& s, const C& c);
-
-template <class KeyType, class ValueType, class LessType, class Alloc, class C>
-void append(std::map<KeyType, ValueType, LessType, Alloc>& m, const C& c);
-
-template <class M, class K, class V>
-V& map_add_or_update(M& map, const K& key, const V& value); //efficient add or update without "default-constructible" requirement (Effective STL, item 24)
-
-template <class T, class Alloc>
-void removeDuplicates(std::vector<T, Alloc>& v);
-
-//binary search returning an iterator
-template <class ForwardIterator, class T, typename CompLess>
-ForwardIterator binary_search(ForwardIterator first, ForwardIterator last, const T& value, CompLess less);
-
-template <class BidirectionalIterator, class T>
-BidirectionalIterator find_last(BidirectionalIterator first, BidirectionalIterator last, const T& value);
-
-//replacement for std::find_end taking advantage of bidirectional iterators (and giving the algorithm a reasonable name)
-template <class BidirectionalIterator1, class BidirectionalIterator2>
-BidirectionalIterator1 search_last(BidirectionalIterator1 first1, BidirectionalIterator1 last1,
- BidirectionalIterator2 first2, BidirectionalIterator2 last2);
-
-template <class InputIterator1, class InputIterator2>
-bool equal(InputIterator1 first1, InputIterator1 last1,
- InputIterator2 first2, InputIterator2 last2);
-
-template <class ByteIterator> size_t hashBytes (ByteIterator first, ByteIterator last);
-template <class ByteIterator> size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last);
-
-
-//support for custom string classes in std::unordered_set/map
-struct StringHash
-{
- template <class String>
- size_t operator()(const String& str) const
- {
- const auto* strFirst = strBegin(str);
- return hashBytes(reinterpret_cast<const char*>(strFirst),
- reinterpret_cast<const char*>(strFirst + strLength(str)));
- }
-};
-
-
-
-
-
-
-//######################## implementation ########################
-
-template <class T, class Alloc, class Predicate> inline
-void erase_if(std::vector<T, Alloc>& v, Predicate p)
-{
- v.erase(std::remove_if(v.begin(), v.end(), p), v.end());
-}
-
-
-namespace impl
-{
-template <class S, class Predicate> inline
-void set_or_map_erase_if(S& s, Predicate p)
-{
- for (auto it = s.begin(); it != s.end();)
- if (p(*it))
- s.erase(it++);
- else
- ++it;
-}
-}
-
-
-template <class T, class LessType, class Alloc, class Predicate> inline
-void erase_if(std::set<T, LessType, Alloc>& s, Predicate p) { impl::set_or_map_erase_if(s, p); } //don't make this any more generic! e.g. must not compile for std::vector!!!
-
-
-template <class KeyType, class ValueType, class LessType, class Alloc, class Predicate> inline
-void erase_if(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p) { impl::set_or_map_erase_if(m, p); }
-
-
-template <class T, class Alloc, class C> inline
-void append(std::vector<T, Alloc>& v, const C& c) { v.insert(v.end(), c.begin(), c.end()); }
-
-
-template <class T, class LessType, class Alloc, class C> inline
-void append(std::set<T, LessType, Alloc>& s, const C& c) { s.insert(c.begin(), c.end()); }
-
-
-template <class KeyType, class ValueType, class LessType, class Alloc, class C> inline
-void append(std::map<KeyType, ValueType, LessType, Alloc>& m, const C& c) { m.insert(c.begin(), c.end()); }
-
-
-template <class M, class K, class V> inline
-V& map_add_or_update(M& map, const K& key, const V& value) //efficient add or update without "default-constructible" requirement (Effective STL, item 24)
-{
- auto it = map.lower_bound(key);
- if (it != map.end() && !(map.key_comp()(key, it->first)))
- {
- it->second = value;
- return it->second;
- }
- else
- return map.insert(it, typename M::value_type(key, value))->second;
-}
-
-
-template <class T, class Alloc> inline
-void removeDuplicates(std::vector<T, Alloc>& v)
-{
- std::sort(v.begin(), v.end());
- v.erase(std::unique(v.begin(), v.end()), v.end());
-}
-
-
-template <class ForwardIterator, class T, typename CompLess> inline
-ForwardIterator binary_search(ForwardIterator first, ForwardIterator last, const T& value, CompLess less)
-{
- first = std::lower_bound(first, last, value, less);
- if (first != last && !less(value, *first))
- return first;
- else
- return last;
-}
-
-
-template <class BidirectionalIterator, class T> inline
-BidirectionalIterator find_last(const BidirectionalIterator first, const BidirectionalIterator last, const T& value)
-{
- for (BidirectionalIterator it = last; it != first;) //reverse iteration: 1. check 2. decrement 3. evaluate
- {
- --it; //
-
- if (*it == value)
- return it;
- }
- return last;
-}
-
-
-template <class BidirectionalIterator1, class BidirectionalIterator2> inline
-BidirectionalIterator1 search_last(const BidirectionalIterator1 first1, BidirectionalIterator1 last1,
- const BidirectionalIterator2 first2, const BidirectionalIterator2 last2)
-{
- const BidirectionalIterator1 itNotFound = last1;
-
- //reverse iteration: 1. check 2. decrement 3. evaluate
- for (;;)
- {
- BidirectionalIterator1 it1 = last1;
- BidirectionalIterator2 it2 = last2;
-
- for (;;)
- {
- if (it2 == first2) return it1;
- if (it1 == first1) return itNotFound;
-
- --it1;
- --it2;
-
- if (*it1 != *it2) break;
- }
- --last1;
- }
-}
-
-
-template <class InputIterator1, class InputIterator2> inline
-bool equal(InputIterator1 first1, InputIterator1 last1,
- InputIterator2 first2, InputIterator2 last2)
-{
- return last1 - first1 == last2 - first2 && std::equal(first1, last1, first2);
-}
-
-
-#if defined _MSC_VER && _MSC_VER <= 1600
- static_assert(false, "VS2010 performance bug in std::unordered_set<>: http://drdobbs.com/blogs/cpp/232200410 -> should be fixed in VS11");
-#endif
-
-
-template <class ByteIterator> inline
-size_t hashBytes(ByteIterator first, ByteIterator last)
-{
- //FNV-1a: http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
-#ifdef ZEN_BUILD_32BIT
- const size_t basis = 2166136261U;
-#elif defined ZEN_BUILD_64BIT
- const size_t basis = 14695981039346656037ULL;
-#endif
- return hashBytesAppend(basis, first, last);
-}
-
-
-template <class ByteIterator> inline
-size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last)
-{
-#ifdef ZEN_BUILD_32BIT
- const size_t prime = 16777619U;
-#elif defined ZEN_BUILD_64BIT
- const size_t prime = 1099511628211ULL;
-#endif
- static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, "");
-
- for (; first != last; ++first)
- {
- hashVal ^= static_cast<size_t>(*first);
- hashVal *= prime;
- }
- return hashVal;
-}
-}
-
-#endif //STL_TOOLS_H_84567184321434
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef STL_TOOLS_H_84567184321434
+#define STL_TOOLS_H_84567184321434
+
+#include <set>
+#include <map>
+#include <vector>
+#include <memory>
+#include <algorithm>
+#include "type_tools.h"
+#include "build_info.h"
+
+
+//enhancements for <algorithm>
+namespace zen
+{
+//erase selected elements from any container:
+template <class T, class Alloc, class Predicate>
+void erase_if(std::vector<T, Alloc>& v, Predicate p);
+
+template <class T, class LessType, class Alloc, class Predicate>
+void erase_if(std::set<T, LessType, Alloc>& s, Predicate p);
+
+template <class KeyType, class ValueType, class LessType, class Alloc, class Predicate>
+void erase_if(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p);
+
+//append STL containers
+template <class T, class Alloc, class C>
+void append(std::vector<T, Alloc>& v, const C& c);
+
+template <class T, class LessType, class Alloc, class C>
+void append(std::set<T, LessType, Alloc>& s, const C& c);
+
+template <class KeyType, class ValueType, class LessType, class Alloc, class C>
+void append(std::map<KeyType, ValueType, LessType, Alloc>& m, const C& c);
+
+template <class M, class K, class V>
+V& map_add_or_update(M& map, const K& key, const V& value); //efficient add or update without "default-constructible" requirement (Effective STL, item 24)
+
+template <class T, class Alloc>
+void removeDuplicates(std::vector<T, Alloc>& v);
+
+//binary search returning an iterator
+template <class ForwardIterator, class T, typename CompLess>
+ForwardIterator binary_search(ForwardIterator first, ForwardIterator last, const T& value, CompLess less);
+
+template <class BidirectionalIterator, class T>
+BidirectionalIterator find_last(BidirectionalIterator first, BidirectionalIterator last, const T& value);
+
+//replacement for std::find_end taking advantage of bidirectional iterators (and giving the algorithm a reasonable name)
+template <class BidirectionalIterator1, class BidirectionalIterator2>
+BidirectionalIterator1 search_last(BidirectionalIterator1 first1, BidirectionalIterator1 last1,
+ BidirectionalIterator2 first2, BidirectionalIterator2 last2);
+
+template <class InputIterator1, class InputIterator2>
+bool equal(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2);
+
+template <class ByteIterator> size_t hashBytes (ByteIterator first, ByteIterator last);
+template <class ByteIterator> size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last);
+
+
+//support for custom string classes in std::unordered_set/map
+struct StringHash
+{
+ template <class String>
+ size_t operator()(const String& str) const
+ {
+ const auto* strFirst = strBegin(str);
+ return hashBytes(reinterpret_cast<const char*>(strFirst),
+ reinterpret_cast<const char*>(strFirst + strLength(str)));
+ }
+};
+
+
+
+
+
+
+//######################## implementation ########################
+
+template <class T, class Alloc, class Predicate> inline
+void erase_if(std::vector<T, Alloc>& v, Predicate p)
+{
+ v.erase(std::remove_if(v.begin(), v.end(), p), v.end());
+}
+
+
+namespace impl
+{
+template <class S, class Predicate> inline
+void set_or_map_erase_if(S& s, Predicate p)
+{
+ for (auto it = s.begin(); it != s.end();)
+ if (p(*it))
+ s.erase(it++);
+ else
+ ++it;
+}
+}
+
+
+template <class T, class LessType, class Alloc, class Predicate> inline
+void erase_if(std::set<T, LessType, Alloc>& s, Predicate p) { impl::set_or_map_erase_if(s, p); } //don't make this any more generic! e.g. must not compile for std::vector!!!
+
+
+template <class KeyType, class ValueType, class LessType, class Alloc, class Predicate> inline
+void erase_if(std::map<KeyType, ValueType, LessType, Alloc>& m, Predicate p) { impl::set_or_map_erase_if(m, p); }
+
+
+template <class T, class Alloc, class C> inline
+void append(std::vector<T, Alloc>& v, const C& c) { v.insert(v.end(), c.begin(), c.end()); }
+
+
+template <class T, class LessType, class Alloc, class C> inline
+void append(std::set<T, LessType, Alloc>& s, const C& c) { s.insert(c.begin(), c.end()); }
+
+
+template <class KeyType, class ValueType, class LessType, class Alloc, class C> inline
+void append(std::map<KeyType, ValueType, LessType, Alloc>& m, const C& c) { m.insert(c.begin(), c.end()); }
+
+
+template <class M, class K, class V> inline
+V& map_add_or_update(M& map, const K& key, const V& value) //efficient add or update without "default-constructible" requirement (Effective STL, item 24)
+{
+ auto it = map.lower_bound(key);
+ if (it != map.end() && !(map.key_comp()(key, it->first)))
+ {
+ it->second = value;
+ return it->second;
+ }
+ else
+ return map.insert(it, typename M::value_type(key, value))->second;
+}
+
+
+template <class T, class Alloc> inline
+void removeDuplicates(std::vector<T, Alloc>& v)
+{
+ std::sort(v.begin(), v.end());
+ v.erase(std::unique(v.begin(), v.end()), v.end());
+}
+
+
+template <class ForwardIterator, class T, typename CompLess> inline
+ForwardIterator binary_search(ForwardIterator first, ForwardIterator last, const T& value, CompLess less)
+{
+ first = std::lower_bound(first, last, value, less);
+ if (first != last && !less(value, *first))
+ return first;
+ else
+ return last;
+}
+
+
+template <class BidirectionalIterator, class T> inline
+BidirectionalIterator find_last(const BidirectionalIterator first, const BidirectionalIterator last, const T& value)
+{
+ for (BidirectionalIterator it = last; it != first;) //reverse iteration: 1. check 2. decrement 3. evaluate
+ {
+ --it; //
+
+ if (*it == value)
+ return it;
+ }
+ return last;
+}
+
+
+template <class BidirectionalIterator1, class BidirectionalIterator2> inline
+BidirectionalIterator1 search_last(const BidirectionalIterator1 first1, BidirectionalIterator1 last1,
+ const BidirectionalIterator2 first2, const BidirectionalIterator2 last2)
+{
+ const BidirectionalIterator1 itNotFound = last1;
+
+ //reverse iteration: 1. check 2. decrement 3. evaluate
+ for (;;)
+ {
+ BidirectionalIterator1 it1 = last1;
+ BidirectionalIterator2 it2 = last2;
+
+ for (;;)
+ {
+ if (it2 == first2) return it1;
+ if (it1 == first1) return itNotFound;
+
+ --it1;
+ --it2;
+
+ if (*it1 != *it2) break;
+ }
+ --last1;
+ }
+}
+
+
+template <class InputIterator1, class InputIterator2> inline
+bool equal(InputIterator1 first1, InputIterator1 last1,
+ InputIterator2 first2, InputIterator2 last2)
+{
+ return last1 - first1 == last2 - first2 && std::equal(first1, last1, first2);
+}
+
+
+
+
+template <class ByteIterator> inline
+size_t hashBytes(ByteIterator first, ByteIterator last)
+{
+ //FNV-1a: http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
+#ifdef ZEN_BUILD_32BIT
+ const size_t basis = 2166136261U;
+#elif defined ZEN_BUILD_64BIT
+ const size_t basis = 14695981039346656037ULL;
+#endif
+ return hashBytesAppend(basis, first, last);
+}
+
+
+template <class ByteIterator> inline
+size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last)
+{
+#ifdef ZEN_BUILD_32BIT
+ const size_t prime = 16777619U;
+#elif defined ZEN_BUILD_64BIT
+ const size_t prime = 1099511628211ULL;
+#endif
+ static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, "");
+
+ for (; first != last; ++first)
+ {
+ hashVal ^= static_cast<size_t>(*first);
+ hashVal *= prime;
+ }
+ return hashVal;
+}
+}
+
+#endif //STL_TOOLS_H_84567184321434
diff --git a/zen/string_base.h b/zen/string_base.h
index 1ee37563..3afa66c6 100644..100755
--- a/zen/string_base.h
+++ b/zen/string_base.h
@@ -1,620 +1,610 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef STRING_BASE_H_083217454562342526
-#define STRING_BASE_H_083217454562342526
-
-#include <algorithm>
-#include <cassert>
-#include <cstdint>
-#include <atomic>
-#include "string_tools.h"
-
-//Zbase - a policy based string class optimizing performance and flexibility
-
-namespace zen
-{
-/*
-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
-{
-protected:
- //::operator new/ ::operator delete show same performance characterisics like malloc()/free()!
- static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc
- static void deallocate(void* ptr) { ::free(ptr); }
- static size_t calcCapacity(size_t length) { return std::max<size_t>(16, std::max(length + length / 2, length)); }
- //- size_t might overflow! => better catch here than return a too small size covering up the real error: a way too large length!
- //- any growth rate should not exceed golden ratio: 1.618033989
-};
-
-
-class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string
-{
-protected:
- static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc
- static void deallocate(void* ptr) { ::free(ptr); }
- static size_t calcCapacity(size_t length) { return length; }
-};
-
-/*
-Storage Policy:
----------------
-template <typename Char, //Character Type
- class AP> //Allocator Policy
-
- Char* create(size_t size)
- Char* create(size_t size, size_t minCapacity)
- Char* clone(Char* ptr)
- void destroy(Char* ptr) //must handle "destroy(nullptr)"!
- bool canWrite(const Char* ptr, size_t minCapacity) //needs to be checked before writing to "ptr"
- size_t length(const Char* ptr)
- void setLength(Char* ptr, size_t newLength)
-*/
-
-template <class Char, //Character Type
- class AP> //Allocator Policy
-class StorageDeepCopy : public AP
-{
-protected:
- ~StorageDeepCopy() {}
-
- Char* create(size_t size) { return create(size, size); }
- Char* create(size_t size, size_t minCapacity)
- {
- assert(size <= minCapacity);
- const size_t newCapacity = AP::calcCapacity(minCapacity);
- assert(newCapacity >= minCapacity);
-
- Descriptor* const newDescr = static_cast<Descriptor*>(this->allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(Char))); //throw std::bad_alloc
- new (newDescr) Descriptor(size, newCapacity);
-
- return reinterpret_cast<Char*>(newDescr + 1); //alignment note: "newDescr + 1" is Descriptor-aligned, which is larger than alignment for Char-array! => no problem!
- }
-
- Char* clone(Char* ptr)
- {
- const size_t len = length(ptr);
- Char* newData = create(len); //throw std::bad_alloc
- std::copy(ptr, ptr + len + 1, newData);
- return newData;
- }
-
- void destroy(Char* ptr)
- {
- if (!ptr) return; //support "destroy(nullptr)"
-
- Descriptor* const d = descr(ptr);
- d->~Descriptor();
- this->deallocate(d);
- }
-
- //this needs to be checked before writing to "ptr"
- static bool canWrite(const Char* ptr, size_t minCapacity) { return minCapacity <= descr(ptr)->capacity; }
- static size_t length(const Char* ptr) { return descr(ptr)->length; }
-
- static void setLength(Char* ptr, size_t newLength)
- {
- assert(canWrite(ptr, newLength));
- descr(ptr)->length = newLength;
- }
-
-private:
- struct Descriptor
- {
- Descriptor(size_t len, size_t cap) :
- length (static_cast<std::uint32_t>(len)),
- capacity(static_cast<std::uint32_t>(cap)) {}
-
- std::uint32_t length;
- std::uint32_t capacity; //allocated size without null-termination
- };
-
- static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; }
- static const Descriptor* descr(const Char* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; }
-};
-
-
-template <class Char, //Character Type
- class AP> //Allocator Policy
-class StorageRefCountThreadSafe : public AP
-{
-protected:
- ~StorageRefCountThreadSafe() {}
-
- Char* create(size_t size) { return create(size, size); }
- Char* create(size_t size, size_t minCapacity)
- {
- assert(size <= minCapacity);
-
- const size_t newCapacity = AP::calcCapacity(minCapacity);
- assert(newCapacity >= minCapacity);
-
- Descriptor* const newDescr = static_cast<Descriptor*>(this->allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(Char))); //throw std::bad_alloc
- new (newDescr) Descriptor(size, newCapacity);
-
- return reinterpret_cast<Char*>(newDescr + 1);
- }
-
- static Char* clone(Char* ptr)
- {
- ++descr(ptr)->refCount;
- return ptr;
- }
-
-#ifdef NDEBUG
- void destroy(Char* ptr)
-#else
- void destroy(Char*& ptr)
-#endif
- {
- assert(ptr != reinterpret_cast<Char*>(0x1)); //detect double-deletion
-
- if (!ptr) //support "destroy(nullptr)"
- {
-#ifndef NDEBUG
- ptr = reinterpret_cast<Char*>(0x1);
-#endif
- return;
- }
-
- Descriptor* const d = descr(ptr);
-
- if (--(d->refCount) == 0) //operator--() is overloaded to decrement and evaluate in a single atomic operation!
- {
- d->~Descriptor();
- this->deallocate(d);
-#ifndef NDEBUG
- ptr = reinterpret_cast<Char*>(0x1);
-#endif
- }
- }
-
- static bool canWrite(const Char* ptr, size_t minCapacity) //needs to be checked before writing to "ptr"
- {
- const Descriptor* const d = descr(ptr);
- assert(d->refCount > 0);
- return d->refCount == 1 && minCapacity <= d->capacity;
- }
-
- static size_t length(const Char* ptr) { return descr(ptr)->length; }
-
- static void setLength(Char* ptr, size_t newLength)
- {
- assert(canWrite(ptr, newLength));
- descr(ptr)->length = static_cast<std::uint32_t>(newLength);
- }
-
-private:
- struct Descriptor
- {
- Descriptor(size_t len, size_t cap) :
- length (static_cast<std::uint32_t>(len)),
- capacity(static_cast<std::uint32_t>(cap)) { static_assert(ATOMIC_INT_LOCK_FREE == 2, ""); } //2: "The atomic type is always lock-free"
-
- std::atomic<unsigned int> refCount { 1 }; //std:atomic is uninitialized by default!
- std::uint32_t length;
- std::uint32_t capacity; //allocated size without null-termination
- };
-
- static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; }
- static const Descriptor* descr(const Char* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; }
-};
-
-
-template <class Char>
-using DefaultStoragePolicy = StorageRefCountThreadSafe<Char, AllocatorOptimalSpeed>;
-
-
-//################################################################################################################################################################
-
-//perf note: interestingly StorageDeepCopy and StorageRefCountThreadSafe show same performance in FFS comparison
-
-template <class Char, //Character Type
- template <class> class SP = DefaultStoragePolicy> //Storage Policy
-class Zbase : public SP<Char>
-{
-public:
- Zbase();
- Zbase(const Char* str) : Zbase(str, str + strLength(str)) {} //implicit conversion from a C-string!
- Zbase(const Char* str, size_t len) : Zbase(str, str + len) {}
- Zbase(const Zbase& str);
- Zbase(Zbase&& tmp) noexcept;
- template <class InputIterator>
- Zbase(InputIterator first, InputIterator last);
- //explicit Zbase(Char ch); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! //-> non-standard extension!!!
-
- ~Zbase();
-
- //operator const Char* () 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 Char*...
-
- //STL accessors
- using iterator = Char*;
- using const_iterator = const Char*;
- using reference = Char&;
- using const_reference = const Char&;
- using value_type = Char;
-
- iterator begin();
- iterator end ();
- const_iterator begin () const { return rawStr_; }
- const_iterator end () const { return rawStr_ + length(); }
- const_iterator cbegin() const { return begin(); }
- const_iterator cend () const { return end (); }
-
- //std::string functions
- size_t length() const;
- size_t size () const { return length(); }
- const Char* c_str() const { return rawStr_; } //C-string format with 0-termination
- const Char operator[](size_t pos) const;
- bool empty() const { return length() == 0; }
- void clear();
- size_t find (const Zbase& str, size_t pos = 0) const; //
- size_t find (const Char* str, size_t pos = 0) const; //
- size_t find (Char ch, size_t pos = 0) const; //returns "npos" if not found
- size_t rfind(Char ch, size_t pos = npos) const; //
- size_t rfind(const Char* 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 Char* str, size_t len) { return assign(str, str + len); }
- Zbase& append(const Char* str, size_t len) { return append(str, str + len); }
-
- template <class InputIterator> Zbase& assign(InputIterator first, InputIterator last);
- template <class InputIterator> Zbase& append(InputIterator first, InputIterator last);
-
- void resize(size_t newSize, Char fillChar = 0);
- void swap(Zbase& str) { std::swap(rawStr_, str.rawStr_); }
- void push_back(Char val) { operator+=(val); } //STL access
- void pop_back();
-
- Zbase& operator=(const Zbase& str);
- Zbase& operator=(Zbase&& tmp) noexcept;
- Zbase& operator=(const Char* str) { return assign(str, strLength(str)); }
- Zbase& operator=(Char ch) { return assign(&ch, 1); }
- Zbase& operator+=(const Zbase& str) { return append(str.c_str(), str.length()); }
- Zbase& operator+=(const Char* str) { return append(str, strLength(str)); }
- Zbase& operator+=(Char ch) { return append(&ch, 1); }
-
- static const size_t npos = static_cast<size_t>(-1);
-
-private:
- Zbase (int) = delete; //
- Zbase& operator= (int) = delete; //detect usage errors by creating an intentional ambiguity with "Char"
- Zbase& operator+=(int) = delete; //
- void push_back (int) = delete; //
-
- Char* rawStr_;
-};
-
-template <class Char, template <class> class SP> bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs);
-template <class Char, template <class> class SP> bool operator==(const Zbase<Char, SP>& lhs, const Char* rhs);
-template <class Char, template <class> class SP> inline bool operator==(const Char* lhs, const Zbase<Char, SP>& rhs) { return operator==(rhs, lhs); }
-
-template <class Char, template <class> class SP> inline bool operator!=(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return !operator==(lhs, rhs); }
-template <class Char, template <class> class SP> inline bool operator!=(const Zbase<Char, SP>& lhs, const Char* rhs) { return !operator==(lhs, rhs); }
-template <class Char, template <class> class SP> inline bool operator!=(const Char* lhs, const Zbase<Char, SP>& rhs) { return !operator==(lhs, rhs); }
-
-template <class Char, template <class> class SP> bool operator<(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs);
-template <class Char, template <class> class SP> bool operator<(const Zbase<Char, SP>& lhs, const Char* rhs);
-template <class Char, template <class> class SP> bool operator<(const Char* lhs, const Zbase<Char, SP>& rhs);
-
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(lhs) += rhs; }
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, const Char* rhs) { return Zbase<Char, SP>(lhs) += rhs; }
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, Char rhs) { return Zbase<Char, SP>(lhs) += rhs; }
-
-//don't use unified first argument but save one move-construction in the r-value case instead!
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, const Zbase<Char, SP>& rhs) { return std::move(lhs += rhs); } //the move *is* needed!!!
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-value parameter...
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision
-
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+( Char lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(&lhs, 1) += rhs; }
-template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Char* lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(lhs ) += rhs; }
-
-
-
-
-
-
-
-
-
-
-
-
-
-//################################# implementation ########################################
-template <class Char, template <class> class SP> inline
-Zbase<Char, SP>::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 Char, template <class> class SP>
-template <class InputIterator> inline
-Zbase<Char, SP>::Zbase(InputIterator first, InputIterator last)
-{
- rawStr_ = this->create(std::distance(first, last));
- *std::copy(first, last, rawStr_) = 0;
-}
-
-
-template <class Char, template <class> class SP> inline
-Zbase<Char, SP>::Zbase(const Zbase<Char, SP>& str)
-{
- rawStr_ = this->clone(str.rawStr_);
-}
-
-
-template <class Char, template <class> class SP> inline
-Zbase<Char, SP>::Zbase(Zbase<Char, SP>&& tmp) noexcept
-{
- rawStr_ = tmp.rawStr_;
- tmp.rawStr_ = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor!
- //caveat: do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory!
-}
-
-
-template <class Char, template <class> class SP> inline
-Zbase<Char, SP>::~Zbase()
-{
- static_assert(noexcept(this->~Zbase()), ""); //has exception spec of compiler-generated destructor by default
-
- this->destroy(rawStr_); //rawStr_ may be nullptr; see move constructor!
-}
-
-
-template <class Char, template <class> class SP> inline
-size_t Zbase<Char, SP>::find(const Zbase& str, size_t pos) const
-{
- assert(pos <= length());
- const size_t len = length();
- const Char* thisEnd = begin() + len; //respect embedded 0
- const Char* it = std::search(begin() + std::min(pos, len), thisEnd,
- str.begin(), str.end());
- return it == thisEnd ? npos : it - begin();
-}
-
-
-template <class Char, template <class> class SP> inline
-size_t Zbase<Char, SP>::find(const Char* str, size_t pos) const
-{
- assert(pos <= length());
- const size_t len = length();
- const Char* thisEnd = begin() + len; //respect embedded 0
- const Char* it = std::search(begin() + std::min(pos, len), thisEnd,
- str, str + strLength(str));
- return it == thisEnd ? npos : it - begin();
-}
-
-
-template <class Char, template <class> class SP> inline
-size_t Zbase<Char, SP>::find(Char ch, size_t pos) const
-{
- assert(pos <= length());
- const size_t len = length();
- const Char* thisEnd = begin() + len; //respect embedded 0
- const Char* it = std::find(begin() + std::min(pos, len), thisEnd, ch);
- return it == thisEnd ? npos : it - begin();
-}
-
-
-template <class Char, template <class> class SP> inline
-size_t Zbase<Char, SP>::rfind(Char ch, size_t pos) const
-{
- assert(pos == npos || pos <= length());
- const size_t len = length();
- const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + 1, len));
- const Char* it = find_last(begin(), currEnd, ch);
- return it == currEnd ? npos : it - begin();
-}
-
-
-template <class Char, template <class> class SP> inline
-size_t Zbase<Char, SP>::rfind(const Char* str, size_t pos) const
-{
- assert(pos == npos || pos <= length());
- const size_t strLen = strLength(str);
- const size_t len = length();
- const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + strLen, len));
- const Char* it = search_last(begin(), currEnd,
- str, str + strLen);
- return it == currEnd ? npos : it - begin();
-}
-
-
-template <class Char, template <class> class SP> inline
-void Zbase<Char, SP>::resize(size_t newSize, Char fillChar)
-{
- const size_t oldSize = length();
- if (this->canWrite(rawStr_, newSize))
- {
- if (oldSize < newSize)
- std::fill(rawStr_ + oldSize, rawStr_ + newSize, fillChar);
- rawStr_[newSize] = 0;
- this->setLength(rawStr_, newSize);
- }
- else
- {
- Char* newStr = this->create(newSize);
- if (oldSize < newSize)
- {
- std::copy(rawStr_, rawStr_ + oldSize, newStr);
- std::fill(newStr + oldSize, newStr + newSize, fillChar);
- }
- else
- std::copy(rawStr_, rawStr_ + newSize, newStr);
- newStr[newSize] = 0;
-
- this->destroy(rawStr_);
- rawStr_ = newStr;
- }
-}
-
-
-template <class Char, template <class> class SP> inline
-bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs)
-{
- return lhs.length() == rhs.length() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); //respect embedded 0
-}
-
-
-template <class Char, template <class> class SP> inline
-bool operator==(const Zbase<Char, SP>& lhs, const Char* rhs)
-{
- return lhs.length() == strLength(rhs) && std::equal(lhs.begin(), lhs.end(), rhs); //respect embedded 0
-}
-
-
-template <class Char, template <class> class SP> inline
-bool operator<(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs)
-{
- return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0
- rhs.begin(), rhs.end());
-}
-
-
-template <class Char, template <class> class SP> inline
-bool operator<(const Zbase<Char, SP>& lhs, const Char* rhs)
-{
- return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0
- rhs, rhs + strLength(rhs));
-}
-
-
-template <class Char, template <class> class SP> inline
-bool operator<(const Char* lhs, const Zbase<Char, SP>& rhs)
-{
- return std::lexicographical_compare(lhs, lhs + strLength(lhs), //respect embedded 0
- rhs.begin(), rhs.end());
-}
-
-
-template <class Char, template <class> class SP> inline
-size_t Zbase<Char, SP>::length() const
-{
- return SP<Char>::length(rawStr_);
-}
-
-
-template <class Char, template <class> class SP> inline
-const Char Zbase<Char, SP>::operator[](size_t pos) const
-{
- assert(pos < length()); //design by contract! no runtime check!
- return rawStr_[pos];
-}
-
-
-template <class Char, template <class> class SP> inline
-auto Zbase<Char, SP>::begin() -> iterator
-{
- reserve(length()); //make unshared!
- return rawStr_;
-}
-
-
-template <class Char, template <class> class SP> inline
-auto Zbase<Char, SP>::end() -> iterator
-{
- return begin() + length();
-}
-
-
-template <class Char, template <class> class SP> inline
-void Zbase<Char, SP>::clear()
-{
- if (!empty())
- {
- if (this->canWrite(rawStr_, 0))
- {
- rawStr_[0] = 0; //keep allocated memory
- this->setLength(rawStr_, 0); //
- }
- else
- *this = Zbase();
- }
-}
-
-
-template <class Char, template <class> class SP> inline
-void Zbase<Char, SP>::reserve(size_t minCapacity) //make unshared and check capacity
-{
- if (!this->canWrite(rawStr_, minCapacity))
- {
- //allocate a new string
- const size_t len = length();
- Char* newStr = this->create(len, std::max(len, minCapacity)); //reserve() must NEVER shrink the string: logical const!
- std::copy(rawStr_, rawStr_ + len + 1, newStr); //include 0-termination
-
- this->destroy(rawStr_);
- rawStr_ = newStr;
- }
-}
-
-
-template <class Char, template <class> class SP>
-template <class InputIterator> inline
-Zbase<Char, SP>& Zbase<Char, SP>::assign(InputIterator first, InputIterator last)
-{
- const size_t len = std::distance(first, last);
- if (this->canWrite(rawStr_, len))
- {
- *std::copy(first, last, rawStr_) = 0;
- this->setLength(rawStr_, len);
- }
- else
- *this = Zbase(first, last);
-
- return *this;
-}
-
-
-template <class Char, template <class> class SP>
-template <class InputIterator> inline
-Zbase<Char, SP>& Zbase<Char, SP>::append(InputIterator first, InputIterator last)
-{
- const size_t len = std::distance(first, last);
- const size_t thisLen = length();
- reserve(thisLen + len); //make unshared and check capacity
-
- *std::copy(first, last, rawStr_ + thisLen) = 0;
- this->setLength(rawStr_, thisLen + len);
- return *this;
-}
-
-
-template <class Char, template <class> class SP> inline
-Zbase<Char, SP>& Zbase<Char, SP>::operator=(const Zbase<Char, SP>& str)
-{
- Zbase<Char, SP>(str).swap(*this);
- return *this;
-}
-
-
-template <class Char, template <class> class SP> inline
-Zbase<Char, SP>& Zbase<Char, SP>::operator=(Zbase<Char, SP>&& tmp) noexcept
-{
- swap(tmp); //don't use unifying assignment but save one move-construction in the r-value case instead!
- return *this;
-}
-
-template <class Char, template <class> class SP> inline
-void Zbase<Char, SP>::pop_back()
-{
- const size_t len = length();
- assert(len > 0);
- if (len > 0)
- resize(len - 1);
-}
-}
-
-#endif //STRING_BASE_H_083217454562342526
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef STRING_BASE_H_083217454562342526
+#define STRING_BASE_H_083217454562342526
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <atomic>
+#include "string_tools.h"
+
+//Zbase - a policy based string class optimizing performance and flexibility
+
+namespace zen
+{
+/*
+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
+{
+protected:
+ //::operator new/ ::operator delete show same performance characterisics like malloc()/free()!
+ static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc
+ static void deallocate(void* ptr) { ::free(ptr); }
+ static size_t calcCapacity(size_t length) { return std::max<size_t>(16, std::max(length + length / 2, length)); }
+ //- size_t might overflow! => better catch here than return a too small size covering up the real error: a way too large length!
+ //- any growth rate should not exceed golden ratio: 1.618033989
+};
+
+
+class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string
+{
+protected:
+ static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc
+ static void deallocate(void* ptr) { ::free(ptr); }
+ static size_t calcCapacity(size_t length) { return length; }
+};
+
+/*
+Storage Policy:
+---------------
+template <typename Char, //Character Type
+ class AP> //Allocator Policy
+
+ Char* create(size_t size)
+ Char* create(size_t size, size_t minCapacity)
+ Char* clone(Char* ptr)
+ void destroy(Char* ptr) //must handle "destroy(nullptr)"!
+ bool canWrite(const Char* ptr, size_t minCapacity) //needs to be checked before writing to "ptr"
+ size_t length(const Char* ptr)
+ void setLength(Char* ptr, size_t newLength)
+*/
+
+template <class Char, //Character Type
+ class AP> //Allocator Policy
+class StorageDeepCopy : public AP
+{
+protected:
+ ~StorageDeepCopy() {}
+
+ Char* create(size_t size) { return create(size, size); }
+ Char* create(size_t size, size_t minCapacity)
+ {
+ assert(size <= minCapacity);
+ const size_t newCapacity = AP::calcCapacity(minCapacity);
+ assert(newCapacity >= minCapacity);
+
+ Descriptor* const newDescr = static_cast<Descriptor*>(this->allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(Char))); //throw std::bad_alloc
+ new (newDescr) Descriptor(size, newCapacity);
+
+ return reinterpret_cast<Char*>(newDescr + 1); //alignment note: "newDescr + 1" is Descriptor-aligned, which is larger than alignment for Char-array! => no problem!
+ }
+
+ Char* clone(Char* ptr)
+ {
+ const size_t len = length(ptr);
+ Char* newData = create(len); //throw std::bad_alloc
+ std::copy(ptr, ptr + len + 1, newData);
+ return newData;
+ }
+
+ void destroy(Char* ptr)
+ {
+ if (!ptr) return; //support "destroy(nullptr)"
+
+ Descriptor* const d = descr(ptr);
+ d->~Descriptor();
+ this->deallocate(d);
+ }
+
+ //this needs to be checked before writing to "ptr"
+ static bool canWrite(const Char* ptr, size_t minCapacity) { return minCapacity <= descr(ptr)->capacity; }
+ static size_t length(const Char* ptr) { return descr(ptr)->length; }
+
+ static void setLength(Char* ptr, size_t newLength)
+ {
+ assert(canWrite(ptr, newLength));
+ descr(ptr)->length = newLength;
+ }
+
+private:
+ struct Descriptor
+ {
+ Descriptor(size_t len, size_t cap) :
+ length (static_cast<uint32_t>(len)),
+ capacity(static_cast<uint32_t>(cap)) {}
+
+ uint32_t length;
+ uint32_t capacity; //allocated size without null-termination
+ };
+
+ static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; }
+ static const Descriptor* descr(const Char* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; }
+};
+
+
+template <class Char, //Character Type
+ class AP> //Allocator Policy
+class StorageRefCountThreadSafe : public AP
+{
+protected:
+ ~StorageRefCountThreadSafe() {}
+
+ Char* create(size_t size) { return create(size, size); }
+ Char* create(size_t size, size_t minCapacity)
+ {
+ assert(size <= minCapacity);
+
+ const size_t newCapacity = AP::calcCapacity(minCapacity);
+ assert(newCapacity >= minCapacity);
+
+ Descriptor* const newDescr = static_cast<Descriptor*>(this->allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(Char))); //throw std::bad_alloc
+ new (newDescr) Descriptor(size, newCapacity);
+
+ return reinterpret_cast<Char*>(newDescr + 1);
+ }
+
+ static Char* clone(Char* ptr)
+ {
+ ++descr(ptr)->refCount;
+ return ptr;
+ }
+
+ void destroy(Char* ptr)
+ {
+ assert(ptr != reinterpret_cast<Char*>(0x1)); //detect double-deletion
+
+ if (!ptr) //support "destroy(nullptr)"
+ {
+ return;
+ }
+
+ Descriptor* const d = descr(ptr);
+
+ if (--(d->refCount) == 0) //operator--() is overloaded to decrement and evaluate in a single atomic operation!
+ {
+ d->~Descriptor();
+ this->deallocate(d);
+ }
+ }
+
+ static bool canWrite(const Char* ptr, size_t minCapacity) //needs to be checked before writing to "ptr"
+ {
+ const Descriptor* const d = descr(ptr);
+ assert(d->refCount > 0);
+ return d->refCount == 1 && minCapacity <= d->capacity;
+ }
+
+ static size_t length(const Char* ptr) { return descr(ptr)->length; }
+
+ static void setLength(Char* ptr, size_t newLength)
+ {
+ assert(canWrite(ptr, newLength));
+ descr(ptr)->length = static_cast<uint32_t>(newLength);
+ }
+
+private:
+ struct Descriptor
+ {
+ Descriptor(size_t len, size_t cap) :
+ length (static_cast<uint32_t>(len)),
+ capacity(static_cast<uint32_t>(cap)) { static_assert(ATOMIC_INT_LOCK_FREE == 2, ""); } //2: "The atomic type is always lock-free"
+
+ std::atomic<unsigned int> refCount { 1 }; //std:atomic is uninitialized by default!
+ uint32_t length;
+ uint32_t capacity; //allocated size without null-termination
+ };
+
+ static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; }
+ static const Descriptor* descr(const Char* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; }
+};
+
+
+template <class Char>
+using DefaultStoragePolicy = StorageRefCountThreadSafe<Char, AllocatorOptimalSpeed>;
+
+
+//################################################################################################################################################################
+
+//perf note: interestingly StorageDeepCopy and StorageRefCountThreadSafe show same performance in FFS comparison
+
+template <class Char, //Character Type
+ template <class> class SP = DefaultStoragePolicy> //Storage Policy
+class Zbase : public SP<Char>
+{
+public:
+ Zbase();
+ Zbase(const Char* str) : Zbase(str, str + strLength(str)) {} //implicit conversion from a C-string!
+ Zbase(const Char* str, size_t len) : Zbase(str, str + len) {}
+ Zbase(const Zbase& str);
+ Zbase(Zbase&& tmp) noexcept;
+ template <class InputIterator>
+ Zbase(InputIterator first, InputIterator last);
+ //explicit Zbase(Char ch); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! //-> non-standard extension!!!
+
+ ~Zbase();
+
+ //operator const Char* () 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 Char*...
+
+ //STL accessors
+ using iterator = Char*;
+ using const_iterator = const Char*;
+ using reference = Char&;
+ using const_reference = const Char&;
+ using value_type = Char;
+
+ iterator begin();
+ iterator end ();
+ const_iterator begin () const { return rawStr_; }
+ const_iterator end () const { return rawStr_ + length(); }
+ const_iterator cbegin() const { return begin(); }
+ const_iterator cend () const { return end (); }
+
+ //std::string functions
+ size_t length() const;
+ size_t size () const { return length(); }
+ const Char* c_str() const { return rawStr_; } //C-string format with 0-termination
+ const Char operator[](size_t pos) const;
+ bool empty() const { return length() == 0; }
+ void clear();
+ size_t find (const Zbase& str, size_t pos = 0) const; //
+ size_t find (const Char* str, size_t pos = 0) const; //
+ size_t find (Char ch, size_t pos = 0) const; //returns "npos" if not found
+ size_t rfind(Char ch, size_t pos = npos) const; //
+ size_t rfind(const Char* 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 Char* str, size_t len) { return assign(str, str + len); }
+ Zbase& append(const Char* str, size_t len) { return append(str, str + len); }
+
+ template <class InputIterator> Zbase& assign(InputIterator first, InputIterator last);
+ template <class InputIterator> Zbase& append(InputIterator first, InputIterator last);
+
+ void resize(size_t newSize, Char fillChar = 0);
+ void swap(Zbase& str) { std::swap(rawStr_, str.rawStr_); }
+ void push_back(Char val) { operator+=(val); } //STL access
+ void pop_back();
+
+ Zbase& operator=(const Zbase& str);
+ Zbase& operator=(Zbase&& tmp) noexcept;
+ Zbase& operator=(const Char* str) { return assign(str, strLength(str)); }
+ Zbase& operator=(Char ch) { return assign(&ch, 1); }
+ Zbase& operator+=(const Zbase& str) { return append(str.c_str(), str.length()); }
+ Zbase& operator+=(const Char* str) { return append(str, strLength(str)); }
+ Zbase& operator+=(Char ch) { return append(&ch, 1); }
+
+ static const size_t npos = static_cast<size_t>(-1);
+
+private:
+ Zbase (int) = delete; //
+ Zbase& operator= (int) = delete; //detect usage errors by creating an intentional ambiguity with "Char"
+ Zbase& operator+=(int) = delete; //
+ void push_back (int) = delete; //
+
+ Char* rawStr_;
+};
+
+template <class Char, template <class> class SP> bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs);
+template <class Char, template <class> class SP> bool operator==(const Zbase<Char, SP>& lhs, const Char* rhs);
+template <class Char, template <class> class SP> inline bool operator==(const Char* lhs, const Zbase<Char, SP>& rhs) { return operator==(rhs, lhs); }
+
+template <class Char, template <class> class SP> inline bool operator!=(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return !operator==(lhs, rhs); }
+template <class Char, template <class> class SP> inline bool operator!=(const Zbase<Char, SP>& lhs, const Char* rhs) { return !operator==(lhs, rhs); }
+template <class Char, template <class> class SP> inline bool operator!=(const Char* lhs, const Zbase<Char, SP>& rhs) { return !operator==(lhs, rhs); }
+
+template <class Char, template <class> class SP> bool operator<(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs);
+template <class Char, template <class> class SP> bool operator<(const Zbase<Char, SP>& lhs, const Char* rhs);
+template <class Char, template <class> class SP> bool operator<(const Char* lhs, const Zbase<Char, SP>& rhs);
+
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(lhs) += rhs; }
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, const Char* rhs) { return Zbase<Char, SP>(lhs) += rhs; }
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, Char rhs) { return Zbase<Char, SP>(lhs) += rhs; }
+
+//don't use unified first argument but save one move-construction in the r-value case instead!
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, const Zbase<Char, SP>& rhs) { return std::move(lhs += rhs); } //the move *is* needed!!!
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-value parameter...
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision
+
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+( Char lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(&lhs, 1) += rhs; }
+template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Char* lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(lhs ) += rhs; }
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################################# implementation ########################################
+template <class Char, template <class> class SP> inline
+Zbase<Char, SP>::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 Char, template <class> class SP>
+template <class InputIterator> inline
+Zbase<Char, SP>::Zbase(InputIterator first, InputIterator last)
+{
+ rawStr_ = this->create(std::distance(first, last));
+ *std::copy(first, last, rawStr_) = 0;
+}
+
+
+template <class Char, template <class> class SP> inline
+Zbase<Char, SP>::Zbase(const Zbase<Char, SP>& str)
+{
+ rawStr_ = this->clone(str.rawStr_);
+}
+
+
+template <class Char, template <class> class SP> inline
+Zbase<Char, SP>::Zbase(Zbase<Char, SP>&& tmp) noexcept
+{
+ rawStr_ = tmp.rawStr_;
+ tmp.rawStr_ = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor!
+ //caveat: do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory!
+}
+
+
+template <class Char, template <class> class SP> inline
+Zbase<Char, SP>::~Zbase()
+{
+ static_assert(noexcept(this->~Zbase()), ""); //has exception spec of compiler-generated destructor by default
+
+ this->destroy(rawStr_); //rawStr_ may be nullptr; see move constructor!
+}
+
+
+template <class Char, template <class> class SP> inline
+size_t Zbase<Char, SP>::find(const Zbase& str, size_t pos) const
+{
+ assert(pos <= length());
+ const size_t len = length();
+ const Char* thisEnd = begin() + len; //respect embedded 0
+ const Char* it = std::search(begin() + std::min(pos, len), thisEnd,
+ str.begin(), str.end());
+ return it == thisEnd ? npos : it - begin();
+}
+
+
+template <class Char, template <class> class SP> inline
+size_t Zbase<Char, SP>::find(const Char* str, size_t pos) const
+{
+ assert(pos <= length());
+ const size_t len = length();
+ const Char* thisEnd = begin() + len; //respect embedded 0
+ const Char* it = std::search(begin() + std::min(pos, len), thisEnd,
+ str, str + strLength(str));
+ return it == thisEnd ? npos : it - begin();
+}
+
+
+template <class Char, template <class> class SP> inline
+size_t Zbase<Char, SP>::find(Char ch, size_t pos) const
+{
+ assert(pos <= length());
+ const size_t len = length();
+ const Char* thisEnd = begin() + len; //respect embedded 0
+ const Char* it = std::find(begin() + std::min(pos, len), thisEnd, ch);
+ return it == thisEnd ? npos : it - begin();
+}
+
+
+template <class Char, template <class> class SP> inline
+size_t Zbase<Char, SP>::rfind(Char ch, size_t pos) const
+{
+ assert(pos == npos || pos <= length());
+ const size_t len = length();
+ const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + 1, len));
+ const Char* it = find_last(begin(), currEnd, ch);
+ return it == currEnd ? npos : it - begin();
+}
+
+
+template <class Char, template <class> class SP> inline
+size_t Zbase<Char, SP>::rfind(const Char* str, size_t pos) const
+{
+ assert(pos == npos || pos <= length());
+ const size_t strLen = strLength(str);
+ const size_t len = length();
+ const Char* currEnd = begin() + (pos == npos ? len : std::min(pos + strLen, len));
+ const Char* it = search_last(begin(), currEnd,
+ str, str + strLen);
+ return it == currEnd ? npos : it - begin();
+}
+
+
+template <class Char, template <class> class SP> inline
+void Zbase<Char, SP>::resize(size_t newSize, Char fillChar)
+{
+ const size_t oldSize = length();
+ if (this->canWrite(rawStr_, newSize))
+ {
+ if (oldSize < newSize)
+ std::fill(rawStr_ + oldSize, rawStr_ + newSize, fillChar);
+ rawStr_[newSize] = 0;
+ this->setLength(rawStr_, newSize);
+ }
+ else
+ {
+ Char* newStr = this->create(newSize);
+ if (oldSize < newSize)
+ {
+ std::copy(rawStr_, rawStr_ + oldSize, newStr);
+ std::fill(newStr + oldSize, newStr + newSize, fillChar);
+ }
+ else
+ std::copy(rawStr_, rawStr_ + newSize, newStr);
+ newStr[newSize] = 0;
+
+ this->destroy(rawStr_);
+ rawStr_ = newStr;
+ }
+}
+
+
+template <class Char, template <class> class SP> inline
+bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs)
+{
+ return lhs.length() == rhs.length() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); //respect embedded 0
+}
+
+
+template <class Char, template <class> class SP> inline
+bool operator==(const Zbase<Char, SP>& lhs, const Char* rhs)
+{
+ return lhs.length() == strLength(rhs) && std::equal(lhs.begin(), lhs.end(), rhs); //respect embedded 0
+}
+
+
+template <class Char, template <class> class SP> inline
+bool operator<(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs)
+{
+ return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0
+ rhs.begin(), rhs.end());
+}
+
+
+template <class Char, template <class> class SP> inline
+bool operator<(const Zbase<Char, SP>& lhs, const Char* rhs)
+{
+ return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0
+ rhs, rhs + strLength(rhs));
+}
+
+
+template <class Char, template <class> class SP> inline
+bool operator<(const Char* lhs, const Zbase<Char, SP>& rhs)
+{
+ return std::lexicographical_compare(lhs, lhs + strLength(lhs), //respect embedded 0
+ rhs.begin(), rhs.end());
+}
+
+
+template <class Char, template <class> class SP> inline
+size_t Zbase<Char, SP>::length() const
+{
+ return SP<Char>::length(rawStr_);
+}
+
+
+template <class Char, template <class> class SP> inline
+const Char Zbase<Char, SP>::operator[](size_t pos) const
+{
+ assert(pos < length()); //design by contract! no runtime check!
+ return rawStr_[pos];
+}
+
+
+template <class Char, template <class> class SP> inline
+auto Zbase<Char, SP>::begin() -> iterator
+{
+ reserve(length()); //make unshared!
+ return rawStr_;
+}
+
+
+template <class Char, template <class> class SP> inline
+auto Zbase<Char, SP>::end() -> iterator
+{
+ return begin() + length();
+}
+
+
+template <class Char, template <class> class SP> inline
+void Zbase<Char, SP>::clear()
+{
+ if (!empty())
+ {
+ if (this->canWrite(rawStr_, 0))
+ {
+ rawStr_[0] = 0; //keep allocated memory
+ this->setLength(rawStr_, 0); //
+ }
+ else
+ *this = Zbase();
+ }
+}
+
+
+template <class Char, template <class> class SP> inline
+void Zbase<Char, SP>::reserve(size_t minCapacity) //make unshared and check capacity
+{
+ if (!this->canWrite(rawStr_, minCapacity))
+ {
+ //allocate a new string
+ const size_t len = length();
+ Char* newStr = this->create(len, std::max(len, minCapacity)); //reserve() must NEVER shrink the string: logical const!
+ std::copy(rawStr_, rawStr_ + len + 1, newStr); //include 0-termination
+
+ this->destroy(rawStr_);
+ rawStr_ = newStr;
+ }
+}
+
+
+template <class Char, template <class> class SP>
+template <class InputIterator> inline
+Zbase<Char, SP>& Zbase<Char, SP>::assign(InputIterator first, InputIterator last)
+{
+ const size_t len = std::distance(first, last);
+ if (this->canWrite(rawStr_, len))
+ {
+ *std::copy(first, last, rawStr_) = 0;
+ this->setLength(rawStr_, len);
+ }
+ else
+ *this = Zbase(first, last);
+
+ return *this;
+}
+
+
+template <class Char, template <class> class SP>
+template <class InputIterator> inline
+Zbase<Char, SP>& Zbase<Char, SP>::append(InputIterator first, InputIterator last)
+{
+ const size_t len = std::distance(first, last);
+ const size_t thisLen = length();
+ reserve(thisLen + len); //make unshared and check capacity
+
+ *std::copy(first, last, rawStr_ + thisLen) = 0;
+ this->setLength(rawStr_, thisLen + len);
+ return *this;
+}
+
+
+template <class Char, template <class> class SP> inline
+Zbase<Char, SP>& Zbase<Char, SP>::operator=(const Zbase<Char, SP>& str)
+{
+ Zbase<Char, SP>(str).swap(*this);
+ return *this;
+}
+
+
+template <class Char, template <class> class SP> inline
+Zbase<Char, SP>& Zbase<Char, SP>::operator=(Zbase<Char, SP>&& tmp) noexcept
+{
+ swap(tmp); //don't use unifying assignment but save one move-construction in the r-value case instead!
+ return *this;
+}
+
+template <class Char, template <class> class SP> inline
+void Zbase<Char, SP>::pop_back()
+{
+ const size_t len = length();
+ assert(len > 0);
+ if (len > 0)
+ resize(len - 1);
+}
+}
+
+#endif //STRING_BASE_H_083217454562342526
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 5292dfc6..5a82e0ed 100644..100755
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -1,713 +1,707 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef STRING_TOOLS_H_213458973046
-#define STRING_TOOLS_H_213458973046
-
-#include <cctype> //isspace
-#include <cwctype> //iswspace
-#include <cstdio> //sprintf
-#include <cwchar> //swprintf
-#include <algorithm>
-#include <cassert>
-#include <vector>
-#include <sstream>
-#include "stl_tools.h"
-#include "string_traits.h"
-
-
-//enhance arbitray string class with useful non-member functions:
-namespace zen
-{
-template <class Char> bool isWhiteSpace(Char ch);
-template <class Char> bool isDigit (Char ch); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only!
-template <class Char> bool isHexDigit (Char ch);
-template <class Char> bool isAlpha (Char ch);
-
-template <class S, class T> bool startsWith(const S& str, const T& prefix); //
-template <class S, class T> bool endsWith (const S& str, const T& postfix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t
-template <class S, class T> bool contains (const S& str, const T& term); //
-
-enum FailureReturnVal
-{
- IF_MISSING_RETURN_ALL,
- IF_MISSING_RETURN_NONE
-};
-
-template <class S, class T> S afterLast (const S& str, const T& term, FailureReturnVal rv);
-template <class S, class T> S beforeLast (const S& str, const T& term, FailureReturnVal rv);
-template <class S, class T> S afterFirst (const S& str, const T& term, FailureReturnVal rv);
-template <class S, class T> S beforeFirst(const S& str, const T& term, FailureReturnVal rv);
-
-template <class S, class T> std::vector<S> split(const S& str, const T& delimiter);
-template <class S> void trim (S& str, bool fromLeft = true, bool fromRight = true);
-template <class S> S trimCpy(S str, bool fromLeft = true, bool fromRight = true);
-template <class S, class T, class U> void replace ( S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
-template <class S, class T, class U> S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
-
-//high-performance conversion between numbers and strings
-template <class S, class Num> S numberTo(const Num& number);
-template <class Num, class S > Num stringTo(const S& str);
-
-std::pair<char, char> hexify (unsigned char c, bool upperCase = true);
-char unhexify(char high, char low);
-
-template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf()
-
-//string to string conversion: converts string-like type into char-compatible target string class
-template <class T, class S> T copyStringTo(S&& str);
-
-//case-sensitive comparison
-template <class S, class T> int cmpString(const S& lhs, const T& rhs);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//---------------------- implementation ----------------------
-template <> inline
-bool isWhiteSpace(char ch)
-{
- assert(ch != 0); //std C++ does not consider 0 as white space
- //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 isWhiteSpace(wchar_t ch)
-{
- assert(ch != 0); //std C++ does not consider 0 as white space
- return std::iswspace(ch) != 0;
-}
-
-
-template <class Char> inline
-bool isDigit(Char ch) //similar to implmenetation of std::::isdigit()!
-{
- static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, "");
- return static_cast<Char>('0') <= ch && ch <= static_cast<Char>('9');
-}
-
-
-template <class Char> inline
-bool isHexDigit(Char c)
-{
- static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, "");
- return (static_cast<Char>('0') <= c && c <= static_cast<Char>('9')) ||
- (static_cast<Char>('A') <= c && c <= static_cast<Char>('F')) ||
- (static_cast<Char>('a') <= c && c <= static_cast<Char>('f'));
-}
-
-
-template <> bool isAlpha(char ch) = delete; //probably not a good idea with UTF-8 anyway...
-
-template <> inline bool isAlpha(wchar_t ch) { return std::iswalpha(ch) != 0; }
-
-
-template <class S, class T> inline
-bool startsWith(const S& str, const T& prefix)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const size_t pfLen = strLength(prefix);
- if (strLength(str) < pfLen)
- return false;
-
- const auto* const cmpFirst = strBegin(str);
- return std::equal(cmpFirst, cmpFirst + pfLen,
- strBegin(prefix));
-}
-
-
-template <class S, class T> inline
-bool endsWith(const S& str, const T& postfix)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const size_t strLen = strLength(str);
- const size_t pfLen = strLength(postfix);
- if (strLen < pfLen)
- return false;
-
- const auto* const cmpFirst = strBegin(str) + strLen - pfLen;
- return std::equal(cmpFirst, cmpFirst + pfLen,
- strBegin(postfix));
-}
-
-
-template <class S, class T> inline
-bool contains(const S& str, const T& term)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const size_t strLen = strLength(str);
- const size_t termLen = strLength(term);
- if (strLen < termLen)
- return false;
-
- const auto* const strFirst = strBegin(str);
- const auto* const strLast = strFirst + strLen;
- const auto* const termFirst = strBegin(term);
-
- return std::search(strFirst, strLast,
- termFirst, termFirst + termLen) != strLast;
-}
-
-
-template <class S, class T> inline
-S afterLast(const S& str, const T& term, FailureReturnVal rv)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const size_t termLen = strLength(term);
-
- const auto* const strFirst = strBegin(str);
- const auto* const strLast = strFirst + strLength(str);
- const auto* const termFirst = strBegin(term);
-
- const auto* it = search_last(strFirst, strLast,
- termFirst, termFirst + termLen);
- if (it == strLast)
- return rv == IF_MISSING_RETURN_ALL ? str : S();
-
- it += termLen;
- return S(it, strLast - it);
-}
-
-
-template <class S, class T> inline
-S beforeLast(const S& str, const T& term, FailureReturnVal rv)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const auto* const strFirst = strBegin(str);
- const auto* const strLast = strFirst + strLength(str);
- const auto* const termFirst = strBegin(term);
-
- const auto* it = search_last(strFirst, strLast,
- termFirst, termFirst + strLength(term));
- if (it == strLast)
- return rv == IF_MISSING_RETURN_ALL ? str : S();
-
- return S(strFirst, it - strFirst);
-}
-
-
-template <class S, class T> inline
-S afterFirst(const S& str, const T& term, FailureReturnVal rv)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const size_t termLen = strLength(term);
- const auto* const strFirst = strBegin(str);
- const auto* const strLast = strFirst + strLength(str);
- const auto* const termFirst = strBegin(term);
-
- const auto* it = std::search(strFirst, strLast,
- termFirst, termFirst + termLen);
- if (it == strLast)
- return rv == IF_MISSING_RETURN_ALL ? str : S();
-
- it += termLen;
- return S(it, strLast - it);
-}
-
-
-template <class S, class T> inline
-S beforeFirst(const S& str, const T& term, FailureReturnVal rv)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const auto* const strFirst = strBegin(str);
- const auto* const strLast = strFirst + strLength(str);
- const auto* const termFirst = strBegin(term);
-
- auto it = std::search(strFirst, strLast,
- termFirst, termFirst + strLength(term));
- if (it == strLast)
- return rv == IF_MISSING_RETURN_ALL ? str : S();
-
- return S(strFirst, it - strFirst);
-}
-
-
-template <class S, class T> inline
-std::vector<S> split(const S& str, const T& delimiter)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
-
- const size_t delimLen = strLength(delimiter);
-
- if (delimLen == 0)
- return { str };
- else
- {
- const auto* const delimFirst = strBegin(delimiter);
- const auto* const delimLast = delimFirst + delimLen;
-
- const auto* blockStart = strBegin(str);
- const auto* const strLast = blockStart + strLength(str);
-
- std::vector<S> output;
-
- for (;;)
- {
- const auto* const blockEnd = std::search(blockStart, strLast,
- delimFirst, delimLast);
-
- output.emplace_back(blockStart, blockEnd - blockStart);
- if (blockEnd == strLast) //clients expect: if delimiter not found, return str
- return output;
- blockStart = blockEnd + delimLen;
- }
- }
-}
-
-
-namespace impl
-{
-ZEN_INIT_DETECT_MEMBER(append);
-
-//either call operator+=(S(str, len)) or append(str, len)
-template <class S, class InputIterator> inline
-typename EnableIf<HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str.append(first, last); }
-
-template <class S, class InputIterator> inline
-typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str += S(first, last); }
-}
-
-
-template <class S, class T, class U> inline
-S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- static_assert(IsSameType<typename GetCharType<T>::Type, typename GetCharType<U>::Type>::value, "");
- const size_t oldLen = strLength(oldTerm);
- if (oldLen == 0)
- {
- assert(false);
- return str;
- }
-
- const auto* strPos = strBegin(str);
- const auto* const strEnd = strPos + strLength(str);
-
- const auto* const oldBegin = strBegin(oldTerm);
- const auto* const oldEnd = oldBegin + oldLen;
-
- //optimize "oldTerm not found": return ref-counted copy
- const auto* strMatch = std::search(strPos, strEnd,
- oldBegin, oldEnd);
- if (strMatch == strEnd)
- return str;
-
- const auto* const newBegin = strBegin(newTerm);
- const auto* const newEnd = newBegin + strLength(newTerm);
- S output;
-
- for (;;)
- {
- impl::stringAppend(output, strPos, strMatch);
- impl::stringAppend(output, newBegin, newEnd);
-
- strPos = strMatch + oldLen;
-
- if (!replaceAll)
- break;
-
- strMatch = std::search(strPos, strEnd,
- oldBegin, oldEnd);
- if (strMatch == strEnd)
- break;
- }
- impl::stringAppend(output, strPos, strEnd);
-
- return output;
-}
-
-
-template <class S, class T, class U> inline
-void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll)
-{
- str = replaceCpy(str, oldTerm, newTerm, replaceAll);
-}
-
-
-template <class S> inline
-void trim(S& str, bool fromLeft, bool fromRight)
-{
- assert(fromLeft || fromRight);
-
- const auto* const oldBegin = strBegin(str);
- const auto* newBegin = oldBegin;
- const auto* newEnd = oldBegin + strLength(str);
-
- if (fromRight)
- while (newBegin != newEnd && isWhiteSpace(newEnd[-1]))
- --newEnd;
-
- if (fromLeft)
- while (newBegin != newEnd && isWhiteSpace(*newBegin))
- ++newBegin;
-
- if (newBegin != oldBegin)
- str = S(newBegin, newEnd - newBegin); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only
- else
- str.resize(newEnd - newBegin);
-}
-
-
-template <class S> inline
-S trimCpy(S str, bool fromLeft, bool fromRight)
-{
- //implementing trimCpy() in terms of trim(), instead of the other way round, avoids memory allocations when trimming from right!
- trim(str, fromLeft, fromRight);
- return std::move(str); //"str" is an l-value parameter => no copy elision!
-}
-
-
-namespace impl
-{
-template <class S, class T>
-struct CopyStringToString
-{
- T copy(const S& src) const { return T(strBegin(src), strLength(src)); }
-};
-
-template <class T>
-struct CopyStringToString<T, T> //perf: we don't need a deep copy if string types match
-{
- template <class S>
- T copy(S&& str) const { return std::forward<S>(str); }
-};
-}
-
-template <class T, class S> inline
-T copyStringTo(S&& str) { return impl::CopyStringToString<std::decay_t<S>, T>().copy(std::forward<S>(str)); }
-
-
-template <class S, class T> inline
-int cmpString(const S& lhs, const T& rhs)
-{
- const size_t lenL = strLength(lhs);
- const size_t lenR = strLength(rhs);
-
- const auto* strPosL = strBegin(lhs);
- const auto* strPosR = strBegin(rhs);
-
- const auto* const strPosLLast = strPosL + std::min(lenL, lenR);
-
- while (strPosL != strPosLLast)
- {
- const auto charL = static_cast<unsigned int>(*strPosL++); //unsigned char-comparison is the convention!
- const auto charR = static_cast<unsigned int>(*strPosR++);
- if (charL != charR)
- return static_cast<int>(charL) - static_cast<int>(charR);
- }
- return static_cast<int>(lenL) - static_cast<int>(lenR);
-}
-
-
-namespace impl
-{
-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 ;)
-{
-#if defined _MSC_VER || defined __MINGW32__
- return ::_snprintf(buffer, bufferSize, format, number); //by factor 10 faster than "std::snprintf" on MinGW and on par with std::sprintf()!!!
-#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__
- return ::_snwprintf(buffer, bufferSize, format, number); //MinGW doesn't respect ISO C
-#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
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- using CharType = typename GetCharType<S>::Type;
-
- const int BUFFER_SIZE = 128;
- CharType buffer[BUFFER_SIZE]; //zero-initialize?
- const int charsWritten = impl::saferPrintf(buffer, BUFFER_SIZE, strBegin(format), number);
-
- return charsWritten > 0 ? S(buffer, charsWritten) : S();
-}
-
-
-namespace impl
-{
-enum NumberType
-{
- NUM_TYPE_SIGNED_INT,
- NUM_TYPE_UNSIGNED_INT,
- NUM_TYPE_FLOATING_POINT,
- NUM_TYPE_OTHER,
-};
-
-
-template <class S, class Num> inline
-S numberTo(const Num& number, Int2Type<NUM_TYPE_OTHER>) //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20)
-{
- using CharType = typename GetCharType<S>::Type;
-
- std::basic_ostringstream<CharType> ss;
- ss << number;
- return copyStringTo<S>(ss.str());
-}
-
-
-template <class S, class Num> inline S floatToString(const Num& number, char ) { return printNumber<S>( "%g", static_cast<double>(number)); }
-template <class S, class Num> inline S floatToString(const Num& number, wchar_t) { return printNumber<S>(L"%g", static_cast<double>(number)); }
-
-template <class S, class Num> inline
-S numberTo(const Num& number, Int2Type<NUM_TYPE_FLOATING_POINT>)
-{
- return floatToString<S>(number, typename GetCharType<S>::Type());
-}
-
-
-/*
-perf: integer to string: (executed 10 mio. times)
- std::stringstream - 14796 ms
- std::sprintf - 3086 ms
- formatInteger - 778 ms
-*/
-
-template <class OutputIterator, class Num> inline
-void formatNegativeInteger(Num n, OutputIterator& it)
-{
- assert(n < 0);
- using CharType = typename std::iterator_traits<OutputIterator>::value_type;
- do
- {
- const Num tmp = n / 10;
- *--it = static_cast<CharType>('0' + (tmp * 10 - n)); //8% faster than using modulus operator!
- n = tmp;
- }
- while (n != 0);
-
- *--it = static_cast<CharType>('-');
-}
-
-template <class OutputIterator, class Num> inline
-void formatPositiveInteger(Num n, OutputIterator& it)
-{
- assert(n >= 0);
- using CharType = typename std::iterator_traits<OutputIterator>::value_type;
- do
- {
- const Num tmp = n / 10;
- *--it = static_cast<CharType>('0' + (n - tmp * 10)); //8% faster than using modulus operator!
- n = tmp;
- }
- while (n != 0);
-}
-
-
-template <class S, class Num> inline
-S numberTo(const Num& number, Int2Type<NUM_TYPE_SIGNED_INT>)
-{
- using CharType = typename GetCharType<S>::Type;
- CharType buffer[2 + sizeof(Num) * 241 / 100]; //zero-initialize?
- //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency
- //required chars (+ sign char): 1 + ceil(ln_10(256^sizeof(n) / 2 + 1)) -> divide by 2 for signed half-range; second +1 since one half starts with 1!
- // <= 1 + ceil(ln_10(256^sizeof(n))) =~ 1 + ceil(sizeof(n) * 2.4082) <= 2 + floor(sizeof(n) * 2.41)
-
- //caveat: consider INT_MIN: technically -INT_MIN == INT_MIN
- auto it = std::end(buffer);
- if (number < 0)
- formatNegativeInteger(number, it);
- else
- formatPositiveInteger(number, it);
- assert(it >= std::begin(buffer));
-
- return S(&*it, std::end(buffer) - it);
-}
-
-
-template <class S, class Num> inline
-S numberTo(const Num& number, Int2Type<NUM_TYPE_UNSIGNED_INT>)
-{
- using CharType = typename GetCharType<S>::Type;
- CharType buffer[1 + sizeof(Num) * 241 / 100]; //zero-initialize?
- //required chars: ceil(ln_10(256^sizeof(n))) =~ ceil(sizeof(n) * 2.4082) <= 1 + floor(sizeof(n) * 2.41)
-
- auto it = std::end(buffer);
- formatPositiveInteger(number, it);
- assert(it >= std::begin(buffer));
-
- return S(&*it, std::end(buffer) - it);
-}
-
-//--------------------------------------------------------------------------------
-
-template <class Num, class S> inline
-Num stringTo(const S& str, Int2Type<NUM_TYPE_OTHER>) //default string to number conversion using streams: convenient, but SLOW
-{
- using CharType = typename GetCharType<S>::Type;
- Num number = 0;
- std::basic_istringstream<CharType>(copyStringTo<std::basic_string<CharType>>(str)) >> number;
- return number;
-}
-
-
-template <class Num> inline Num stringToFloat(const char* str) { return std::strtod(str, nullptr); }
-template <class Num> inline Num stringToFloat(const wchar_t* str) { return std::wcstod(str, nullptr); }
-
-template <class Num, class S> inline
-Num stringTo(const S& str, Int2Type<NUM_TYPE_FLOATING_POINT>)
-{
- return stringToFloat<Num>(strBegin(str));
-}
-
-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
-{
- using CharType = typename GetCharType<S>::Type;
-
- const CharType* first = strBegin(str);
- const CharType* last = first + strLength(str);
-
- while (first != last && isWhiteSpace(*first)) //skip leading whitespace
- ++first;
-
- //handle minus sign
- hasMinusSign = false;
- if (first != last)
- {
- if (*first == static_cast<CharType>('-'))
- {
- hasMinusSign = true;
- ++first;
- }
- else if (*first == static_cast<CharType>('+'))
- ++first;
- }
-
- Num number = 0;
- for (const CharType* it = first; it != last; ++it)
- {
- const CharType c = *it;
- if (static_cast<CharType>('0') <= c && c <= static_cast<CharType>('9'))
- {
- number *= 10;
- number += c - static_cast<CharType>('0');
- }
- else
- {
- //rest of string should contain whitespace only, it's NOT a bug if there is something else!
- //assert(std::all_of(iter, last, &isWhiteSpace<CharType>)); -> this is NO assert situation
- break;
- }
- }
- return number;
-}
-
-
-template <class Num, class S> inline
-Num stringTo(const S& str, Int2Type<NUM_TYPE_SIGNED_INT>)
-{
- bool hasMinusSign = false; //handle minus sign
- const Num number = extractInteger<Num>(str, hasMinusSign);
- return hasMinusSign ? -number : number;
-}
-
-
-template <class Num, class S> inline
-Num stringTo(const S& str, Int2Type<NUM_TYPE_UNSIGNED_INT>) //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 numberTo(const Num& number)
-{
- using TypeTag = Int2Type<
- IsSignedInt <Num>::value ? impl::NUM_TYPE_SIGNED_INT :
- IsUnsignedInt<Num>::value ? impl::NUM_TYPE_UNSIGNED_INT :
- IsFloat <Num>::value ? impl::NUM_TYPE_FLOATING_POINT :
- impl::NUM_TYPE_OTHER>;
-
- return impl::numberTo<S>(number, TypeTag());
-}
-
-
-template <class Num, class S> inline
-Num stringTo(const S& str)
-{
- using TypeTag = Int2Type<
- IsSignedInt <Num>::value ? impl::NUM_TYPE_SIGNED_INT :
- IsUnsignedInt<Num>::value ? impl::NUM_TYPE_UNSIGNED_INT :
- IsFloat <Num>::value ? impl::NUM_TYPE_FLOATING_POINT :
- impl::NUM_TYPE_OTHER>;
-
- return impl::stringTo<Num>(str, TypeTag());
-}
-
-
-inline //hexify beats "printNumber<std::string>("%02X", c)" by a nice factor of 3!
-std::pair<char, char> hexify(unsigned char c, bool upperCase)
-{
- auto hexifyDigit = [upperCase](int num) -> char //input [0, 15], output 0-9, A-F
- {
- assert(0 <= num&& num <= 15); //guaranteed by design below!
- if (num <= 9)
- return static_cast<char>('0' + num); //no signed/unsigned char problem here!
-
- if (upperCase)
- return static_cast<char>('A' + (num - 10));
- else
- return static_cast<char>('a' + (num - 10));
- };
- return std::make_pair(hexifyDigit(c / 16), hexifyDigit(c % 16));
-}
-
-
-inline //unhexify beats "::sscanf(&it[3], "%02X", &tmp)" by a factor of 3000 for ~250000 calls!!!
-char unhexify(char high, char low)
-{
- auto unhexifyDigit = [](char hex) -> int //input 0-9, a-f, A-F; output range: [0, 15]
- {
- if ('0' <= hex && hex <= '9') //no signed/unsigned char problem here!
- return hex - '0';
- else if ('A' <= hex && hex <= 'F')
- return (hex - 'A') + 10;
- else if ('a' <= hex && hex <= 'f')
- return (hex - 'a') + 10;
- assert(false);
- return 0;
- };
- return static_cast<unsigned char>(16 * unhexifyDigit(high) + unhexifyDigit(low)); //[!] convert to unsigned char first, then to char (which may be signed)
-}
-}
-
-#endif //STRING_TOOLS_H_213458973046
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef STRING_TOOLS_H_213458973046
+#define STRING_TOOLS_H_213458973046
+
+#include <cctype> //isspace
+#include <cwctype> //iswspace
+#include <cstdio> //sprintf
+#include <cwchar> //swprintf
+#include <algorithm>
+#include <cassert>
+#include <vector>
+#include <sstream>
+#include "stl_tools.h"
+#include "string_traits.h"
+
+
+//enhance arbitray string class with useful non-member functions:
+namespace zen
+{
+template <class Char> bool isWhiteSpace(Char ch);
+template <class Char> bool isDigit (Char ch); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only!
+template <class Char> bool isHexDigit (Char ch);
+template <class Char> bool isAlpha (Char ch);
+
+template <class S, class T> bool startsWith(const S& str, const T& prefix); //
+template <class S, class T> bool endsWith (const S& str, const T& postfix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t
+template <class S, class T> bool contains (const S& str, const T& term); //
+
+enum FailureReturnVal
+{
+ IF_MISSING_RETURN_ALL,
+ IF_MISSING_RETURN_NONE
+};
+
+template <class S, class T> S afterLast (const S& str, const T& term, FailureReturnVal rv);
+template <class S, class T> S beforeLast (const S& str, const T& term, FailureReturnVal rv);
+template <class S, class T> S afterFirst (const S& str, const T& term, FailureReturnVal rv);
+template <class S, class T> S beforeFirst(const S& str, const T& term, FailureReturnVal rv);
+
+template <class S, class T> std::vector<S> split(const S& str, const T& delimiter);
+template <class S> S trimCpy(S str, bool fromLeft = true, bool fromRight = true);
+template <class S> void trim (S& str, bool fromLeft = true, bool fromRight = true);
+template <class S, class Function> void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar);
+template <class S, class T, class U> void replace ( S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
+template <class S, class T, class U> S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
+
+//high-performance conversion between numbers and strings
+template <class S, class Num> S numberTo(const Num& number);
+template <class Num, class S > Num stringTo(const S& str);
+
+std::pair<char, char> hexify (unsigned char c, bool upperCase = true);
+char unhexify(char high, char low);
+
+template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf()
+
+//string to string conversion: converts string-like type into char-compatible target string class
+template <class T, class S> T copyStringTo(S&& str);
+
+//case-sensitive comparison
+template <class S, class T> int cmpString(const S& lhs, const T& rhs);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------------- implementation ----------------------
+template <> inline
+bool isWhiteSpace(char ch)
+{
+ assert(ch != 0); //std C++ does not consider 0 as white space
+ //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 isWhiteSpace(wchar_t ch)
+{
+ assert(ch != 0); //std C++ does not consider 0 as white space
+ return std::iswspace(ch) != 0;
+}
+
+
+template <class Char> inline
+bool isDigit(Char ch) //similar to implmenetation of std::::isdigit()!
+{
+ static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, "");
+ return static_cast<Char>('0') <= ch && ch <= static_cast<Char>('9');
+}
+
+
+template <class Char> inline
+bool isHexDigit(Char c)
+{
+ static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, "");
+ return (static_cast<Char>('0') <= c && c <= static_cast<Char>('9')) ||
+ (static_cast<Char>('A') <= c && c <= static_cast<Char>('F')) ||
+ (static_cast<Char>('a') <= c && c <= static_cast<Char>('f'));
+}
+
+
+template <> bool isAlpha(char ch) = delete; //probably not a good idea with UTF-8 anyway...
+
+template <> inline bool isAlpha(wchar_t ch) { return std::iswalpha(ch) != 0; }
+
+
+template <class S, class T> inline
+bool startsWith(const S& str, const T& prefix)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ const size_t pfLen = strLength(prefix);
+ if (strLength(str) < pfLen)
+ return false;
+
+ const auto* const cmpFirst = strBegin(str);
+ return std::equal(cmpFirst, cmpFirst + pfLen,
+ strBegin(prefix));
+}
+
+
+template <class S, class T> inline
+bool endsWith(const S& str, const T& postfix)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ const size_t strLen = strLength(str);
+ const size_t pfLen = strLength(postfix);
+ if (strLen < pfLen)
+ return false;
+
+ const auto* const cmpFirst = strBegin(str) + strLen - pfLen;
+ return std::equal(cmpFirst, cmpFirst + pfLen,
+ strBegin(postfix));
+}
+
+
+template <class S, class T> inline
+bool contains(const S& str, const T& term)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ const size_t strLen = strLength(str);
+ const size_t termLen = strLength(term);
+ if (strLen < termLen)
+ return false;
+
+ const auto* const strFirst = strBegin(str);
+ const auto* const strLast = strFirst + strLen;
+ const auto* const termFirst = strBegin(term);
+
+ return std::search(strFirst, strLast,
+ termFirst, termFirst + termLen) != strLast;
+}
+
+
+template <class S, class T> inline
+S afterLast(const S& str, const T& term, FailureReturnVal rv)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ const size_t termLen = strLength(term);
+
+ const auto* const strFirst = strBegin(str);
+ const auto* const strLast = strFirst + strLength(str);
+ const auto* const termFirst = strBegin(term);
+
+ const auto* it = search_last(strFirst, strLast,
+ termFirst, termFirst + termLen);
+ if (it == strLast)
+ return rv == IF_MISSING_RETURN_ALL ? str : S();
+
+ it += termLen;
+ return S(it, strLast - it);
+}
+
+
+template <class S, class T> inline
+S beforeLast(const S& str, const T& term, FailureReturnVal rv)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ const auto* const strFirst = strBegin(str);
+ const auto* const strLast = strFirst + strLength(str);
+ const auto* const termFirst = strBegin(term);
+
+ const auto* it = search_last(strFirst, strLast,
+ termFirst, termFirst + strLength(term));
+ if (it == strLast)
+ return rv == IF_MISSING_RETURN_ALL ? str : S();
+
+ return S(strFirst, it - strFirst);
+}
+
+
+template <class S, class T> inline
+S afterFirst(const S& str, const T& term, FailureReturnVal rv)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ const size_t termLen = strLength(term);
+ const auto* const strFirst = strBegin(str);
+ const auto* const strLast = strFirst + strLength(str);
+ const auto* const termFirst = strBegin(term);
+
+ const auto* it = std::search(strFirst, strLast,
+ termFirst, termFirst + termLen);
+ if (it == strLast)
+ return rv == IF_MISSING_RETURN_ALL ? str : S();
+
+ it += termLen;
+ return S(it, strLast - it);
+}
+
+
+template <class S, class T> inline
+S beforeFirst(const S& str, const T& term, FailureReturnVal rv)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ const auto* const strFirst = strBegin(str);
+ const auto* const strLast = strFirst + strLength(str);
+ const auto* const termFirst = strBegin(term);
+
+ auto it = std::search(strFirst, strLast,
+ termFirst, termFirst + strLength(term));
+ if (it == strLast)
+ return rv == IF_MISSING_RETURN_ALL ? str : S();
+
+ return S(strFirst, it - strFirst);
+}
+
+
+template <class S, class T> inline
+std::vector<S> split(const S& str, const T& delimiter)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+
+ const size_t delimLen = strLength(delimiter);
+
+ if (delimLen == 0)
+ return { str };
+ else
+ {
+ const auto* const delimFirst = strBegin(delimiter);
+ const auto* const delimLast = delimFirst + delimLen;
+
+ const auto* blockStart = strBegin(str);
+ const auto* const strLast = blockStart + strLength(str);
+
+ std::vector<S> output;
+
+ for (;;)
+ {
+ const auto* const blockEnd = std::search(blockStart, strLast,
+ delimFirst, delimLast);
+
+ output.emplace_back(blockStart, blockEnd - blockStart);
+ if (blockEnd == strLast) //clients expect: if delimiter not found, return str
+ return output;
+ blockStart = blockEnd + delimLen;
+ }
+ }
+}
+
+
+namespace impl
+{
+ZEN_INIT_DETECT_MEMBER(append);
+
+//either call operator+=(S(str, len)) or append(str, len)
+template <class S, class InputIterator> inline
+typename EnableIf<HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str.append(first, last); }
+
+template <class S, class InputIterator> inline
+typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str += S(first, last); }
+}
+
+
+template <class S, class T, class U> inline
+S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ static_assert(IsSameType<typename GetCharType<T>::Type, typename GetCharType<U>::Type>::value, "");
+ const size_t oldLen = strLength(oldTerm);
+ if (oldLen == 0)
+ return str;
+
+ const auto* const oldBegin = strBegin(oldTerm);
+ const auto* const oldEnd = oldBegin + oldLen;
+
+ const auto* const newBegin = strBegin(newTerm);
+ const auto* const newEnd = newBegin + strLength(newTerm);
+
+ S output;
+
+ for (auto it = str.begin();;)
+ {
+ const auto itFound = std::search(it, str.end(),
+ oldBegin, oldEnd);
+ if (itFound == str.end() && it == str.begin())
+ return str; //optimize "oldTerm not found": return ref-counted copy
+
+ impl::stringAppend(output, it, itFound);
+ if (itFound == str.end())
+ return output;
+
+ impl::stringAppend(output, newBegin, newEnd);
+ it = itFound + oldLen;
+
+ if (!replaceAll)
+ {
+ impl::stringAppend(output, it, str.end());
+ return output;
+ }
+ }
+}
+
+
+template <class S, class T, class U> inline
+void replace(S& str, const T& oldTerm, const U& newTerm, bool replaceAll)
+{
+ str = replaceCpy(str, oldTerm, newTerm, replaceAll);
+}
+
+
+template <class S, class Function> inline
+void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar)
+{
+ assert(fromLeft || fromRight);
+
+ const auto* const oldBegin = strBegin(str);
+ const auto* newBegin = oldBegin;
+ const auto* newEnd = oldBegin + strLength(str);
+
+ if (fromRight)
+ while (newBegin != newEnd && trimThisChar(newEnd[-1]))
+ --newEnd;
+
+ if (fromLeft)
+ while (newBegin != newEnd && trimThisChar(*newBegin))
+ ++newBegin;
+
+ if (newBegin != oldBegin)
+ str = S(newBegin, newEnd - newBegin); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only
+ else
+ str.resize(newEnd - newBegin);
+}
+
+
+template <class S> inline
+void trim(S& str, bool fromLeft, bool fromRight)
+{
+ using CharType = typename GetCharType<S>::Type;
+ trim(str, fromLeft, fromRight, [](CharType c) { return isWhiteSpace(c); });
+}
+
+
+template <class S> inline
+S trimCpy(S str, bool fromLeft, bool fromRight)
+{
+ //implementing trimCpy() in terms of trim(), instead of the other way round, avoids memory allocations when trimming from right!
+ trim(str, fromLeft, fromRight);
+ return std::move(str); //"str" is an l-value parameter => no copy elision!
+}
+
+
+namespace impl
+{
+template <class S, class T>
+struct CopyStringToString
+{
+ T copy(const S& src) const { return T(strBegin(src), strLength(src)); }
+};
+
+template <class T>
+struct CopyStringToString<T, T> //perf: we don't need a deep copy if string types match
+{
+ template <class S>
+ T copy(S&& str) const { return std::forward<S>(str); }
+};
+}
+
+template <class T, class S> inline
+T copyStringTo(S&& str) { return impl::CopyStringToString<std::decay_t<S>, T>().copy(std::forward<S>(str)); }
+
+
+template <class S, class T> inline
+int cmpString(const S& lhs, const T& rhs)
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+
+ const size_t lenL = strLength(lhs);
+ const size_t lenR = strLength(rhs);
+
+ const auto* strPosL = strBegin(lhs);
+ const auto* strPosR = strBegin(rhs);
+
+ const auto* const strPosLLast = strPosL + std::min(lenL, lenR);
+
+ while (strPosL != strPosLLast)
+ {
+ const auto charL = static_cast<unsigned int>(*strPosL++); //unsigned char-comparison is the convention!
+ const auto charR = static_cast<unsigned int>(*strPosR++);
+ if (charL != charR)
+ return static_cast<int>(charL) - static_cast<int>(charR);
+ }
+ return static_cast<int>(lenL) - static_cast<int>(lenR);
+}
+
+
+namespace impl
+{
+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 ;)
+{
+ return std::snprintf(buffer, bufferSize, format, number); //C99
+}
+
+template <class Num> inline
+int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const Num& number)
+{
+ return std::swprintf(buffer, bufferSize, format, number); //C99
+}
+}
+
+template <class S, class T, class Num> inline
+S printNumber(const T& format, const Num& number) //format a single number using ::sprintf
+{
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ using CharType = typename GetCharType<S>::Type;
+
+ const int BUFFER_SIZE = 128;
+ CharType buffer[BUFFER_SIZE]; //zero-initialize?
+ const int charsWritten = impl::saferPrintf(buffer, BUFFER_SIZE, strBegin(format), number);
+
+ return charsWritten > 0 ? S(buffer, charsWritten) : S();
+}
+
+
+namespace impl
+{
+enum NumberType
+{
+ NUM_TYPE_SIGNED_INT,
+ NUM_TYPE_UNSIGNED_INT,
+ NUM_TYPE_FLOATING_POINT,
+ NUM_TYPE_OTHER,
+};
+
+
+template <class S, class Num> inline
+S numberTo(const Num& number, Int2Type<NUM_TYPE_OTHER>) //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20)
+{
+ using CharType = typename GetCharType<S>::Type;
+
+ std::basic_ostringstream<CharType> ss;
+ ss << number;
+ return copyStringTo<S>(ss.str());
+}
+
+
+template <class S, class Num> inline S floatToString(const Num& number, char ) { return printNumber<S>( "%g", static_cast<double>(number)); }
+template <class S, class Num> inline S floatToString(const Num& number, wchar_t) { return printNumber<S>(L"%g", static_cast<double>(number)); }
+
+template <class S, class Num> inline
+S numberTo(const Num& number, Int2Type<NUM_TYPE_FLOATING_POINT>)
+{
+ return floatToString<S>(number, typename GetCharType<S>::Type());
+}
+
+
+/*
+perf: integer to string: (executed 10 mio. times)
+ std::stringstream - 14796 ms
+ std::sprintf - 3086 ms
+ formatInteger - 778 ms
+*/
+
+template <class OutputIterator, class Num> inline
+void formatNegativeInteger(Num n, OutputIterator& it)
+{
+ assert(n < 0);
+ using CharType = typename std::iterator_traits<OutputIterator>::value_type;
+ do
+ {
+ const Num tmp = n / 10;
+ *--it = static_cast<CharType>('0' + (tmp * 10 - n)); //8% faster than using modulus operator!
+ n = tmp;
+ }
+ while (n != 0);
+
+ *--it = static_cast<CharType>('-');
+}
+
+template <class OutputIterator, class Num> inline
+void formatPositiveInteger(Num n, OutputIterator& it)
+{
+ assert(n >= 0);
+ using CharType = typename std::iterator_traits<OutputIterator>::value_type;
+ do
+ {
+ const Num tmp = n / 10;
+ *--it = static_cast<CharType>('0' + (n - tmp * 10)); //8% faster than using modulus operator!
+ n = tmp;
+ }
+ while (n != 0);
+}
+
+
+template <class S, class Num> inline
+S numberTo(const Num& number, Int2Type<NUM_TYPE_SIGNED_INT>)
+{
+ using CharType = typename GetCharType<S>::Type;
+ CharType buffer[2 + sizeof(Num) * 241 / 100]; //zero-initialize?
+ //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency
+ //required chars (+ sign char): 1 + ceil(ln_10(256^sizeof(n) / 2 + 1)) -> divide by 2 for signed half-range; second +1 since one half starts with 1!
+ // <= 1 + ceil(ln_10(256^sizeof(n))) =~ 1 + ceil(sizeof(n) * 2.4082) <= 2 + floor(sizeof(n) * 2.41)
+
+ //caveat: consider INT_MIN: technically -INT_MIN == INT_MIN
+ auto it = std::end(buffer);
+ if (number < 0)
+ formatNegativeInteger(number, it);
+ else
+ formatPositiveInteger(number, it);
+ assert(it >= std::begin(buffer));
+
+ return S(&*it, std::end(buffer) - it);
+}
+
+
+template <class S, class Num> inline
+S numberTo(const Num& number, Int2Type<NUM_TYPE_UNSIGNED_INT>)
+{
+ using CharType = typename GetCharType<S>::Type;
+ CharType buffer[1 + sizeof(Num) * 241 / 100]; //zero-initialize?
+ //required chars: ceil(ln_10(256^sizeof(n))) =~ ceil(sizeof(n) * 2.4082) <= 1 + floor(sizeof(n) * 2.41)
+
+ auto it = std::end(buffer);
+ formatPositiveInteger(number, it);
+ assert(it >= std::begin(buffer));
+
+ return S(&*it, std::end(buffer) - it);
+}
+
+//--------------------------------------------------------------------------------
+
+template <class Num, class S> inline
+Num stringTo(const S& str, Int2Type<NUM_TYPE_OTHER>) //default string to number conversion using streams: convenient, but SLOW
+{
+ using CharType = typename GetCharType<S>::Type;
+ Num number = 0;
+ std::basic_istringstream<CharType>(copyStringTo<std::basic_string<CharType>>(str)) >> number;
+ return number;
+}
+
+
+template <class Num> inline Num stringToFloat(const char* str) { return std::strtod(str, nullptr); }
+template <class Num> inline Num stringToFloat(const wchar_t* str) { return std::wcstod(str, nullptr); }
+
+template <class Num, class S> inline
+Num stringTo(const S& str, Int2Type<NUM_TYPE_FLOATING_POINT>)
+{
+ return stringToFloat<Num>(strBegin(str));
+}
+
+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
+{
+ using CharType = typename GetCharType<S>::Type;
+
+ const CharType* first = strBegin(str);
+ const CharType* last = first + strLength(str);
+
+ while (first != last && isWhiteSpace(*first)) //skip leading whitespace
+ ++first;
+
+ //handle minus sign
+ hasMinusSign = false;
+ if (first != last)
+ {
+ if (*first == static_cast<CharType>('-'))
+ {
+ hasMinusSign = true;
+ ++first;
+ }
+ else if (*first == static_cast<CharType>('+'))
+ ++first;
+ }
+
+ Num number = 0;
+ for (const CharType* it = first; it != last; ++it)
+ {
+ const CharType c = *it;
+ if (static_cast<CharType>('0') <= c && c <= static_cast<CharType>('9'))
+ {
+ number *= 10;
+ number += c - static_cast<CharType>('0');
+ }
+ else
+ {
+ //rest of string should contain whitespace only, it's NOT a bug if there is something else!
+ //assert(std::all_of(iter, last, &isWhiteSpace<CharType>)); -> this is NO assert situation
+ break;
+ }
+ }
+ return number;
+}
+
+
+template <class Num, class S> inline
+Num stringTo(const S& str, Int2Type<NUM_TYPE_SIGNED_INT>)
+{
+ bool hasMinusSign = false; //handle minus sign
+ const Num number = extractInteger<Num>(str, hasMinusSign);
+ return hasMinusSign ? -number : number;
+}
+
+
+template <class Num, class S> inline
+Num stringTo(const S& str, Int2Type<NUM_TYPE_UNSIGNED_INT>) //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 numberTo(const Num& number)
+{
+ using TypeTag = Int2Type<
+ IsSignedInt <Num>::value ? impl::NUM_TYPE_SIGNED_INT :
+ IsUnsignedInt<Num>::value ? impl::NUM_TYPE_UNSIGNED_INT :
+ IsFloat <Num>::value ? impl::NUM_TYPE_FLOATING_POINT :
+ impl::NUM_TYPE_OTHER>;
+
+ return impl::numberTo<S>(number, TypeTag());
+}
+
+
+template <class Num, class S> inline
+Num stringTo(const S& str)
+{
+ using TypeTag = Int2Type<
+ IsSignedInt <Num>::value ? impl::NUM_TYPE_SIGNED_INT :
+ IsUnsignedInt<Num>::value ? impl::NUM_TYPE_UNSIGNED_INT :
+ IsFloat <Num>::value ? impl::NUM_TYPE_FLOATING_POINT :
+ impl::NUM_TYPE_OTHER>;
+
+ return impl::stringTo<Num>(str, TypeTag());
+}
+
+
+inline //hexify beats "printNumber<std::string>("%02X", c)" by a nice factor of 3!
+std::pair<char, char> hexify(unsigned char c, bool upperCase)
+{
+ auto hexifyDigit = [upperCase](int num) -> char //input [0, 15], output 0-9, A-F
+ {
+ assert(0 <= num&& num <= 15); //guaranteed by design below!
+ if (num <= 9)
+ return static_cast<char>('0' + num); //no signed/unsigned char problem here!
+
+ if (upperCase)
+ return static_cast<char>('A' + (num - 10));
+ else
+ return static_cast<char>('a' + (num - 10));
+ };
+ return std::make_pair(hexifyDigit(c / 16), hexifyDigit(c % 16));
+}
+
+
+inline //unhexify beats "::sscanf(&it[3], "%02X", &tmp)" by a factor of 3000 for ~250000 calls!!!
+char unhexify(char high, char low)
+{
+ auto unhexifyDigit = [](char hex) -> int //input 0-9, a-f, A-F; output range: [0, 15]
+ {
+ if ('0' <= hex && hex <= '9') //no signed/unsigned char problem here!
+ return hex - '0';
+ else if ('A' <= hex && hex <= 'F')
+ return (hex - 'A') + 10;
+ else if ('a' <= hex && hex <= 'f')
+ return (hex - 'a') + 10;
+ assert(false);
+ return 0;
+ };
+ return static_cast<unsigned char>(16 * unhexifyDigit(high) + unhexifyDigit(low)); //[!] convert to unsigned char first, then to char (which may be signed)
+}
+}
+
+#endif //STRING_TOOLS_H_213458973046
diff --git a/zen/string_traits.h b/zen/string_traits.h
index f17e5e0d..5ae5733c 100644..100755
--- a/zen/string_traits.h
+++ b/zen/string_traits.h
@@ -1,216 +1,216 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef STRING_TRAITS_H_813274321443234
-#define STRING_TRAITS_H_813274321443234
-
-#include <cstring> //strlen
-#include "type_tools.h"
-
-
-//uniform access to string-like types, both classes and character arrays
-namespace zen
-{
-/*
-IsStringLike<>::value:
- IsStringLike<const wchar_t*>::value; //equals "true"
- IsStringLike<const int*> ::value; //equals "false"
-
-GetCharType<>::Type:
- GetCharType<std::wstring>::Type //equals wchar_t
- GetCharType<wchar_t[5]> ::Type //equals wchar_t
-
-strLength():
- strLength(str); //equals str.length()
- strLength(array); //equals cStringLength(array)
-
-strBegin(): -> not null-terminated! -> may be nullptr if length is 0!
- std::wstring str(L"dummy");
- char array[] = "dummy";
- strBegin(str); //returns str.c_str()
- strBegin(array); //returns array
-*/
-
-//reference a sub-string for consumption by zen string_tools
-template <class Char>
-class StringRef
-{
-public:
- template <class Iterator>
- StringRef(Iterator first, Iterator last) : len_(last - first), str_(first != last ? &*first : nullptr) {}
- //StringRef(const Char* str, size_t len) : str_(str), len_(len) {} -> needless constraint! Char* not available for empty range!
-
- Char* data () const { return str_; } //1. no null-termination! 2. may be nullptr!
- size_t length() const { return len_; }
-
-private:
- const size_t len_;
- Char* str_;
-};
-
-
-
-
-
-
-
-
-
-
-
-
-//---------------------- implementation ----------------------
-namespace implementation
-{
-template<class S, class Char> //test if result of S::c_str() can convert to const Char*
-class HasConversion
-{
- using Yes = char[1];
- using No = char[2];
-
- static Yes& hasConversion(const Char*);
- static No& hasConversion(...);
-
-public:
- enum { value = sizeof(hasConversion(std::declval<S>().c_str())) == sizeof(Yes) };
-};
-
-
-template <class S, bool isStringClass> struct GetCharTypeImpl : ResultType<NullType> {};
-
-template <class S>
-struct GetCharTypeImpl<S, true> :
- ResultType<
- typename SelectIf<HasConversion<S, wchar_t>::value, wchar_t,
- typename SelectIf<HasConversion<S, char >::value, char, NullType>::Type
- >::Type>
-{
- //using Type = typename S::value_type;
- /*DON'T use S::value_type:
- 1. support Glib::ustring: value_type is "unsigned int" but c_str() returns "const char*"
- 2. wxString, wxWidgets v2.9, has some questionable string design: wxString::c_str() returns a proxy (wxCStrData) which
- is implicitly convertible to *both* "const char*" and "const wchar_t*" while wxString::value_type is a wrapper around an unsigned int
- */
-};
-
-template <> struct GetCharTypeImpl<char, false> : ResultType<char > {};
-template <> struct GetCharTypeImpl<wchar_t, false> : ResultType<wchar_t> {};
-
-template <> struct GetCharTypeImpl<StringRef<char >, false> : ResultType<char > {};
-template <> struct GetCharTypeImpl<StringRef<wchar_t >, false> : ResultType<wchar_t> {};
-template <> struct GetCharTypeImpl<StringRef<const char >, false> : ResultType<char > {};
-template <> struct GetCharTypeImpl<StringRef<const wchar_t>, false> : ResultType<wchar_t> {};
-
-
-ZEN_INIT_DETECT_MEMBER_TYPE(value_type);
-ZEN_INIT_DETECT_MEMBER(c_str); //we don't know the exact declaration of the member attribute and it may be in a base class!
-ZEN_INIT_DETECT_MEMBER(length); //
-
-template <class S>
-class StringTraits
-{
- using NonRefType = typename RemoveRef <S >::Type;
- using NonConstType = typename RemoveConst <NonRefType >::Type;
- using NonArrayType = typename RemoveArray <NonConstType>::Type;
- using NonPtrType = typename RemovePointer<NonArrayType>::Type;
- using UndecoratedType = typename RemoveConst <NonPtrType >::Type ; //handle "const char* const"
-
-public:
- enum
- {
- isStringClass = HasMemberType_value_type<NonConstType>::value &&
- HasMember_c_str <NonConstType>::value &&
- HasMember_length <NonConstType>::value
- };
-
- using CharType = typename GetCharTypeImpl<UndecoratedType, isStringClass>::Type;
-
- enum
- {
- isStringLike = IsSameType<CharType, char>::value ||
- IsSameType<CharType, wchar_t>::value
- };
-};
-}
-
-template <class T>
-struct IsStringLike : StaticBool<implementation::StringTraits<T>::isStringLike> {};
-
-template <class T>
-struct GetCharType : ResultType<typename implementation::StringTraits<T>::CharType> {};
-
-
-namespace implementation
-{
-//strlen/wcslen are vectorized since VS14 CTP3
-inline size_t cStringLength(const char* str) { return std::strlen(str); }
-inline size_t cStringLength(const wchar_t* str) { return std::wcslen(str); }
-
-//no significant perf difference for "comparison" test case between cStringLength/wcslen:
-#if 0
-template <class C> inline
-size_t cStringLength(const C* str)
-{
- static_assert(IsSameType<C, char>::value || IsSameType<C, wchar_t>::value, "");
- size_t len = 0;
- while (*str++ != 0)
- ++len;
- return len;
-}
-#endif
-
-template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline
-const typename GetCharType<S>::Type* strBegin(const S& str) //SFINAE: T must be a "string"
-{
- return str.c_str();
-}
-
-inline const char* strBegin(const char* str) { return str; }
-inline const wchar_t* strBegin(const wchar_t* str) { return str; }
-inline const char* strBegin(const char& ch) { return &ch; }
-inline const wchar_t* strBegin(const wchar_t& ch) { return &ch; }
-
-inline const char* strBegin(const StringRef<char >& ref) { return ref.data(); }
-inline const wchar_t* strBegin(const StringRef<wchar_t >& ref) { return ref.data(); }
-inline const char* strBegin(const StringRef<const char >& ref) { return ref.data(); }
-inline const wchar_t* strBegin(const StringRef<const wchar_t>& ref) { return ref.data(); }
-
-
-template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline
-size_t strLength(const S& str) //SFINAE: T must be a "string"
-{
- return str.length();
-}
-
-inline size_t strLength(const char* str) { return cStringLength(str); }
-inline size_t strLength(const wchar_t* str) { return cStringLength(str); }
-inline size_t strLength(char) { return 1; }
-inline size_t strLength(wchar_t) { return 1; }
-
-inline size_t strLength(const StringRef<char >& ref) { return ref.length(); }
-inline size_t strLength(const StringRef<wchar_t >& ref) { return ref.length(); }
-inline size_t strLength(const StringRef<const char >& ref) { return ref.length(); }
-inline size_t strLength(const StringRef<const wchar_t>& ref) { return ref.length(); }
-}
-
-
-template <class S> inline
-auto strBegin(S&& str) -> const typename GetCharType<S>::Type*
-{
- static_assert(IsStringLike<S>::value, "");
- return implementation::strBegin(std::forward<S>(str));
-}
-
-
-template <class S> inline
-size_t strLength(S&& str)
-{
- static_assert(IsStringLike<S>::value, "");
- return implementation::strLength(std::forward<S>(str));
-}
-}
-
-#endif //STRING_TRAITS_H_813274321443234
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef STRING_TRAITS_H_813274321443234
+#define STRING_TRAITS_H_813274321443234
+
+#include <cstring> //strlen
+#include "type_tools.h"
+
+
+//uniform access to string-like types, both classes and character arrays
+namespace zen
+{
+/*
+IsStringLike<>::value:
+ IsStringLike<const wchar_t*>::value; //equals "true"
+ IsStringLike<const int*> ::value; //equals "false"
+
+GetCharType<>::Type:
+ GetCharType<std::wstring>::Type //equals wchar_t
+ GetCharType<wchar_t[5]> ::Type //equals wchar_t
+
+strLength():
+ strLength(str); //equals str.length()
+ strLength(array); //equals cStringLength(array)
+
+strBegin(): -> not null-terminated! -> may be nullptr if length is 0!
+ std::wstring str(L"dummy");
+ char array[] = "dummy";
+ strBegin(str); //returns str.c_str()
+ strBegin(array); //returns array
+*/
+
+//reference a sub-string for consumption by zen string_tools
+template <class Char>
+class StringRef
+{
+public:
+ template <class Iterator>
+ StringRef(Iterator first, Iterator last) : len_(last - first), str_(first != last ? &*first : nullptr) {}
+ //StringRef(const Char* str, size_t len) : str_(str), len_(len) {} -> needless constraint! Char* not available for empty range!
+
+ Char* data () const { return str_; } //1. no null-termination! 2. may be nullptr!
+ size_t length() const { return len_; }
+
+private:
+ const size_t len_;
+ Char* str_;
+};
+
+
+
+
+
+
+
+
+
+
+
+
+//---------------------- implementation ----------------------
+namespace implementation
+{
+template<class S, class Char> //test if result of S::c_str() can convert to const Char*
+class HasConversion
+{
+ using Yes = char[1];
+ using No = char[2];
+
+ static Yes& hasConversion(const Char*);
+ static No& hasConversion(...);
+
+public:
+ enum { value = sizeof(hasConversion(std::declval<S>().c_str())) == sizeof(Yes) };
+};
+
+
+template <class S, bool isStringClass> struct GetCharTypeImpl : ResultType<NullType> {};
+
+template <class S>
+struct GetCharTypeImpl<S, true> :
+ ResultType<
+ typename SelectIf<HasConversion<S, wchar_t>::value, wchar_t,
+ typename SelectIf<HasConversion<S, char >::value, char, NullType>::Type
+ >::Type>
+{
+ //using Type = typename S::value_type;
+ /*DON'T use S::value_type:
+ 1. support Glib::ustring: value_type is "unsigned int" but c_str() returns "const char*"
+ 2. wxString, wxWidgets v2.9, has some questionable string design: wxString::c_str() returns a proxy (wxCStrData) which
+ is implicitly convertible to *both* "const char*" and "const wchar_t*" while wxString::value_type is a wrapper around an unsigned int
+ */
+};
+
+template <> struct GetCharTypeImpl<char, false> : ResultType<char > {};
+template <> struct GetCharTypeImpl<wchar_t, false> : ResultType<wchar_t> {};
+
+template <> struct GetCharTypeImpl<StringRef<char >, false> : ResultType<char > {};
+template <> struct GetCharTypeImpl<StringRef<wchar_t >, false> : ResultType<wchar_t> {};
+template <> struct GetCharTypeImpl<StringRef<const char >, false> : ResultType<char > {};
+template <> struct GetCharTypeImpl<StringRef<const wchar_t>, false> : ResultType<wchar_t> {};
+
+
+ZEN_INIT_DETECT_MEMBER_TYPE(value_type);
+ZEN_INIT_DETECT_MEMBER(c_str); //we don't know the exact declaration of the member attribute and it may be in a base class!
+ZEN_INIT_DETECT_MEMBER(length); //
+
+template <class S>
+class StringTraits
+{
+ using NonRefType = typename RemoveRef <S >::Type;
+ using NonConstType = typename RemoveConst <NonRefType >::Type;
+ using NonArrayType = typename RemoveArray <NonConstType>::Type;
+ using NonPtrType = typename RemovePointer<NonArrayType>::Type;
+ using UndecoratedType = typename RemoveConst <NonPtrType >::Type ; //handle "const char* const"
+
+public:
+ enum
+ {
+ isStringClass = HasMemberType_value_type<NonConstType>::value &&
+ HasMember_c_str <NonConstType>::value &&
+ HasMember_length <NonConstType>::value
+ };
+
+ using CharType = typename GetCharTypeImpl<UndecoratedType, isStringClass>::Type;
+
+ enum
+ {
+ isStringLike = IsSameType<CharType, char>::value ||
+ IsSameType<CharType, wchar_t>::value
+ };
+};
+}
+
+template <class T>
+struct IsStringLike : StaticBool<implementation::StringTraits<T>::isStringLike> {};
+
+template <class T>
+struct GetCharType : ResultType<typename implementation::StringTraits<T>::CharType> {};
+
+
+namespace implementation
+{
+//strlen/wcslen are vectorized since VS14 CTP3
+inline size_t cStringLength(const char* str) { return std::strlen(str); }
+inline size_t cStringLength(const wchar_t* str) { return std::wcslen(str); }
+
+//no significant perf difference for "comparison" test case between cStringLength/wcslen:
+#if 0
+template <class C> inline
+size_t cStringLength(const C* str)
+{
+ static_assert(IsSameType<C, char>::value || IsSameType<C, wchar_t>::value, "");
+ size_t len = 0;
+ while (*str++ != 0)
+ ++len;
+ return len;
+}
+#endif
+
+template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline
+const typename GetCharType<S>::Type* strBegin(const S& str) //SFINAE: T must be a "string"
+{
+ return str.c_str();
+}
+
+inline const char* strBegin(const char* str) { return str; }
+inline const wchar_t* strBegin(const wchar_t* str) { return str; }
+inline const char* strBegin(const char& ch) { return &ch; }
+inline const wchar_t* strBegin(const wchar_t& ch) { return &ch; }
+
+inline const char* strBegin(const StringRef<char >& ref) { return ref.data(); }
+inline const wchar_t* strBegin(const StringRef<wchar_t >& ref) { return ref.data(); }
+inline const char* strBegin(const StringRef<const char >& ref) { return ref.data(); }
+inline const wchar_t* strBegin(const StringRef<const wchar_t>& ref) { return ref.data(); }
+
+
+template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline
+size_t strLength(const S& str) //SFINAE: T must be a "string"
+{
+ return str.length();
+}
+
+inline size_t strLength(const char* str) { return cStringLength(str); }
+inline size_t strLength(const wchar_t* str) { return cStringLength(str); }
+inline size_t strLength(char) { return 1; }
+inline size_t strLength(wchar_t) { return 1; }
+
+inline size_t strLength(const StringRef<char >& ref) { return ref.length(); }
+inline size_t strLength(const StringRef<wchar_t >& ref) { return ref.length(); }
+inline size_t strLength(const StringRef<const char >& ref) { return ref.length(); }
+inline size_t strLength(const StringRef<const wchar_t>& ref) { return ref.length(); }
+}
+
+
+template <class S> inline
+auto strBegin(S&& str) -> const typename GetCharType<S>::Type*
+{
+ static_assert(IsStringLike<S>::value, "");
+ return implementation::strBegin(std::forward<S>(str));
+}
+
+
+template <class S> inline
+size_t strLength(S&& str)
+{
+ static_assert(IsStringLike<S>::value, "");
+ return implementation::strLength(std::forward<S>(str));
+}
+}
+
+#endif //STRING_TRAITS_H_813274321443234
diff --git a/zen/symlink_target.h b/zen/symlink_target.h
index e3f526ba..4620e7cc 100644..100755
--- a/zen/symlink_target.h
+++ b/zen/symlink_target.h
@@ -1,240 +1,75 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef SYMLINK_TARGET_H_801783470198357483
-#define SYMLINK_TARGET_H_801783470198357483
-
-#include "scope_guard.h"
-#include "file_error.h"
-
-#ifdef ZEN_WIN
- #include "win.h" //includes "windows.h"
- #include "privilege.h"
- #include "long_path_prefix.h"
- #include "dll.h"
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- #include <unistd.h>
- #include <stdlib.h> //realpath
-#endif
-
-
-namespace zen
-{
-#ifdef ZEN_WIN
- bool isSymlink(const WIN32_FIND_DATA& data); //checking FILE_ATTRIBUTE_REPARSE_POINT is insufficient!
- bool isSymlink(DWORD fileAttributes, DWORD reparseTag);
-#endif
-
-Zstring getResolvedSymlinkPath(const Zstring& linkPath); //throw FileError; Win: requires Vista or later!
-Zstring getSymlinkTargetRaw (const Zstring& linkPath); //throw FileError
-}
-
-
-
-
-
-
-
-
-
-//################################ implementation ################################
-#ifdef ZEN_WIN
-//I don't have Windows Driver Kit at hands right now, so unfortunately we need to redefine this structure 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_impl(const Zstring& linkPath) //throw FileError
-{
- using namespace zen;
-#ifdef ZEN_WIN
- //FSCTL_GET_REPARSE_POINT: https://msdn.microsoft.com/en-us/library/aa364571
-
- //reading certain symlinks/junctions requires admin rights!
- try
- { activatePrivilege(PrivilegeName::BACKUP); } //throw FileError
- catch (FileError&) {} //This shall not cause an error in user mode!
-
- const HANDLE hLink = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName,
- //it seems we do not even need GENERIC_READ!
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hLink == INVALID_HANDLE_VALUE)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"CreateFile");
- ZEN_ON_SCOPE_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 = 0; //dummy value required by FSCTL_GET_REPARSE_POINT!
- if (!::DeviceIoControl(hLink, //__in HANDLE hDevice,
- FSCTL_GET_REPARSE_POINT, //__in DWORD dwIoControlCode,
- nullptr, //__in_opt LPVOID lpInBuffer,
- 0, //__in DWORD nInBufferSize,
- &buffer[0], //__out_opt LPVOID lpOutBuffer,
- bufferSize, //__in DWORD nOutBufferSize,
- &bytesReturned, //__out_opt LPDWORD lpBytesReturned,
- nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"DeviceIoControl, FSCTL_GET_REPARSE_POINT");
-
- 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(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"Not a symbolic link or junction.");
-
- //absolute symlinks and junctions use NT namespace naming convention while relative ones do not:
- //https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247#NT_Namespaces
- return ntPathToWin32Path(output);
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- const size_t BUFFER_SIZE = 10000;
- std::vector<char> buffer(BUFFER_SIZE);
-
- const ssize_t bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE);
- if (bytesWritten < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink");
- if (bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation, not an error for readlink!
- throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink: buffer truncated.");
-
- return Zstring(&buffer[0], bytesWritten); //readlink does not append 0-termination!
-#endif
-}
-
-
-Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError
-{
- using namespace zen;
-#ifdef ZEN_WIN
- //GetFinalPathNameByHandle() is not available before Vista!
- using GetFinalPathNameByHandleWFunc = DWORD (WINAPI*)(HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags);
- const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW");
- if (!getFinalPathNameByHandle)
- throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\""));
-
- const HANDLE hFile = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName,
- 0, //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- //needed to open a directory:
- FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- if (hFile == INVALID_HANDLE_VALUE)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"CreateFile");
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
-
- const DWORD bufferSize = getFinalPathNameByHandle(hFile, nullptr, 0, 0);
- if (bufferSize == 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"GetFinalPathNameByHandle");
-
- std::vector<wchar_t> targetPath(bufferSize);
- const DWORD charsWritten = getFinalPathNameByHandle(hFile, //__in HANDLE hFile,
- &targetPath[0], //__out LPTSTR lpszFilePath,
- bufferSize, //__in DWORD cchFilePath,
- 0); //__in DWORD dwFlags
- if (charsWritten == 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"GetFinalPathNameByHandle");
- if (charsWritten >= bufferSize)
- throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"GetFinalPathNameByHandle: insufficient buffer size.");
-
- return removeLongPathPrefix(Zstring(&targetPath[0], charsWritten)); //MSDN: GetFinalPathNameByHandle() always prepends "\\?\"
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- char* targetPath = ::realpath(linkPath.c_str(), nullptr);
- if (!targetPath)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"realpath");
- ZEN_ON_SCOPE_EXIT(::free(targetPath));
- return targetPath;
-#endif
-}
-}
-
-
-namespace zen
-{
-inline
-Zstring getSymlinkTargetRaw(const Zstring& linkPath) { return getSymlinkRawTargetString_impl(linkPath); }
-
-inline
-Zstring getResolvedSymlinkPath(const Zstring& linkPath) { return getResolvedSymlinkPath_impl(linkPath); }
-
-#ifdef ZEN_WIN
-/*
- Reparse Point Tags
- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365511
- WIN32_FIND_DATA structure
- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365740
-
- The only surrogate reparse points are;
- IO_REPARSE_TAG_MOUNT_POINT
- IO_REPARSE_TAG_SYMLINK
-*/
-
-inline
-bool isSymlink(DWORD fileAttributes, DWORD reparseTag)
-{
- return (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 &&
- IsReparseTagNameSurrogate(reparseTag);
-}
-
-inline
-bool isSymlink(const WIN32_FIND_DATA& data)
-{
- return isSymlink(data.dwFileAttributes, data.dwReserved0);
-}
-#endif
-}
-
-#endif //SYMLINK_TARGET_H_801783470198357483
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef SYMLINK_TARGET_H_801783470198357483
+#define SYMLINK_TARGET_H_801783470198357483
+
+#include "scope_guard.h"
+#include "file_error.h"
+
+ #include <unistd.h>
+ #include <stdlib.h> //realpath
+
+
+namespace zen
+{
+
+Zstring getResolvedSymlinkPath(const Zstring& linkPath); //throw FileError; Win: requires Vista or later!
+Zstring getSymlinkTargetRaw (const Zstring& linkPath); //throw FileError
+}
+
+
+
+
+
+
+
+
+
+//################################ implementation ################################
+
+namespace
+{
+//retrieve raw target data of symlink or junction
+Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileError
+{
+ using namespace zen;
+ const size_t BUFFER_SIZE = 10000;
+ std::vector<char> buffer(BUFFER_SIZE);
+
+ const ssize_t bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE);
+ if (bytesWritten < 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink");
+ if (bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation, not an error for readlink!
+ throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink: buffer truncated.");
+
+ return Zstring(&buffer[0], bytesWritten); //readlink does not append 0-termination!
+}
+
+
+Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError
+{
+ using namespace zen;
+ char* targetPath = ::realpath(linkPath.c_str(), nullptr);
+ if (!targetPath)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"realpath");
+ ZEN_ON_SCOPE_EXIT(::free(targetPath));
+ return targetPath;
+}
+}
+
+
+namespace zen
+{
+inline
+Zstring getSymlinkTargetRaw(const Zstring& linkPath) { return getSymlinkRawTargetString_impl(linkPath); }
+
+inline
+Zstring getResolvedSymlinkPath(const Zstring& linkPath) { return getResolvedSymlinkPath_impl(linkPath); }
+
+}
+
+#endif //SYMLINK_TARGET_H_801783470198357483
diff --git a/zen/sys_error.h b/zen/sys_error.h
index 4798b959..a19409ab 100644..100755
--- a/zen/sys_error.h
+++ b/zen/sys_error.h
@@ -1,161 +1,105 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef SYS_ERROR_H_3284791347018951324534
-#define SYS_ERROR_H_3284791347018951324534
-
-#include <string>
-#include "utf.h"
-#include "i18n.h"
-#include "scope_guard.h"
-
-#ifdef ZEN_WIN
- #include "win.h" //tame WinINet.h include
- #include <WinINet.h>
-#elif defined ZEN_LINUX || defined ZEN_MAC
- #include <cstring>
- #include <cerrno>
-#endif
-
-
-namespace zen
-{
-//evaluate GetLastError()/errno and assemble specific error message
-#ifdef ZEN_WIN
- using ErrorCode = DWORD;
-#elif defined ZEN_LINUX || defined ZEN_MAC
- using ErrorCode = int;
-#endif
-
-ErrorCode getLastError();
-
-std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec);
-std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg);
-
-//A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"!
-class SysError
-{
-public:
- explicit SysError(const std::wstring& msg) : msg_(msg) {}
- const std::wstring& toString() const { return msg_; }
-
-private:
- std::wstring msg_;
-};
-
-#define DEFINE_NEW_SYS_ERROR(X) struct X : public SysError { X(const std::wstring& msg) : SysError(msg) {} };
-
-
-
-#ifdef _MSC_VER
-#define THROW_LAST_SYS_ERROR(functionName) \
- do \
- { \
- const ErrorCode ecInternal = getLastError(); \
- throw SysError(formatSystemError(functionName, ecInternal)); \
- \
- __pragma(warning(suppress: 4127)) /*"conditional expression is constant"*/ \
- } while (false)
-
-#else //same thing witout "__pragma":
-#define THROW_LAST_SYS_ERROR(functionName) \
- do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false)
-#endif
-
-
-
-
-
-
-//######################## implementation ########################
-inline
-ErrorCode getLastError()
-{
-#ifdef ZEN_WIN
- return ::GetLastError();
-#elif defined ZEN_LINUX || defined ZEN_MAC
- return errno; //don't use "::", errno is a macro!
-#endif
-}
-
-
-std::wstring formatSystemErrorRaw(long long) = delete; //intentional overload ambiguity to catch usage errors
-
-inline
-std::wstring formatSystemErrorRaw(ErrorCode ec) //return empty string on error
-{
- const ErrorCode currentError = getLastError(); //not necessarily == lastError
-
- std::wstring errorMsg;
-#ifdef ZEN_WIN
- ZEN_ON_SCOPE_EXIT(::SetLastError(currentError)); //this function must not change active system error variable!
-
- LPWSTR buffer = nullptr;
- const DWORD rv = [&]
- {
- if (INTERNET_ERROR_BASE <= ec && ec <= INTERNET_ERROR_LAST)
- return ::FormatMessage(FORMAT_MESSAGE_FROM_HMODULE |
- FORMAT_MESSAGE_MAX_WIDTH_MASK |
- FORMAT_MESSAGE_IGNORE_INSERTS |
- FORMAT_MESSAGE_ALLOCATE_BUFFER, ::GetModuleHandle(L"WinINet.dll"), ec, 0, reinterpret_cast<LPWSTR>(&buffer), 0, nullptr);
- else
- return ::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, //_In_ DWORD dwFlags,
- nullptr, //_In_opt_ LPCVOID lpSource,
- ec, //_In_ DWORD dwMessageId,
- 0, //_In_ DWORD dwLanguageId,
- reinterpret_cast<LPWSTR>(&buffer), //_Out_ LPTSTR lpBuffer,
- 0, //_In_ DWORD nSize,
- nullptr); //_In_opt_ va_list *Arguments
- }();
- if (rv != 0)
- if (buffer) //"don't trust nobody"
- {
- ZEN_ON_SCOPE_EXIT(::LocalFree(buffer));
- errorMsg = buffer;
- }
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
- ZEN_ON_SCOPE_EXIT(errno = currentError);
-
- errorMsg = utfCvrtTo<std::wstring>(::strerror(ec));
-#endif
- trim(errorMsg); //Windows messages seem to end with a blank...
-
- return errorMsg;
-}
-
-
-std::wstring formatSystemError(const std::wstring& functionName, long long lastError) = delete; //intentional overload ambiguity to catch usage errors with HRESULT!
-
-inline
-std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec)
-{
- return formatSystemError(functionName, numberTo<std::wstring>(ec), formatSystemErrorRaw(ec));
-}
-
-
-inline
-std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg)
-{
- std::wstring output = replaceCpy(_("Error Code %x:"), L"%x", errorCode);
-
- if (!errorMsg.empty())
- {
- output += L" ";
- output += errorMsg;
- }
-
- output += L" (" + functionName + L")";
-
- return output;
-}
-
-}
-
-#endif //SYS_ERROR_H_3284791347018951324534
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef SYS_ERROR_H_3284791347018951324534
+#define SYS_ERROR_H_3284791347018951324534
+
+#include <string>
+#include "utf.h"
+#include "i18n.h"
+#include "scope_guard.h"
+
+ #include <cstring>
+ #include <cerrno>
+
+
+namespace zen
+{
+//evaluate GetLastError()/errno and assemble specific error message
+ using ErrorCode = int;
+
+ErrorCode getLastError();
+
+std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec);
+std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg);
+
+//A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"!
+class SysError
+{
+public:
+ explicit SysError(const std::wstring& msg) : msg_(msg) {}
+ const std::wstring& toString() const { return msg_; }
+
+private:
+ std::wstring msg_;
+};
+
+#define DEFINE_NEW_SYS_ERROR(X) struct X : public SysError { X(const std::wstring& msg) : SysError(msg) {} };
+
+
+
+#define THROW_LAST_SYS_ERROR(functionName) \
+ do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false)
+
+
+
+
+
+
+//######################## implementation ########################
+inline
+ErrorCode getLastError()
+{
+ return errno; //don't use "::", errno is a macro!
+}
+
+
+std::wstring formatSystemErrorRaw(long long) = delete; //intentional overload ambiguity to catch usage errors
+
+inline
+std::wstring formatSystemErrorRaw(ErrorCode ec) //return empty string on error
+{
+ const ErrorCode currentError = getLastError(); //not necessarily == lastError
+
+ std::wstring errorMsg;
+ ZEN_ON_SCOPE_EXIT(errno = currentError);
+
+ errorMsg = utfCvrtTo<std::wstring>(::strerror(ec));
+ trim(errorMsg); //Windows messages seem to end with a blank...
+
+ return errorMsg;
+}
+
+
+std::wstring formatSystemError(const std::wstring& functionName, long long lastError) = delete; //intentional overload ambiguity to catch usage errors with HRESULT!
+
+inline
+std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec)
+{
+ return formatSystemError(functionName, replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(ec)), formatSystemErrorRaw(ec));
+}
+
+
+inline
+std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg)
+{
+ std::wstring output = errorCode + L":";
+
+ const std::wstring errorMsgFmt = trimCpy(errorMsg);
+ if (!errorMsgFmt.empty())
+ {
+ output += L" ";
+ output += errorMsgFmt;
+ }
+
+ output += L" [" + functionName + L"]";
+
+ return output;
+}
+
+}
+
+#endif //SYS_ERROR_H_3284791347018951324534
diff --git a/zen/thread.h b/zen/thread.h
index f6c3ae01..a59f3807 100644..100755
--- a/zen/thread.h
+++ b/zen/thread.h
@@ -1,477 +1,415 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef THREAD_H_7896323423432235246427
-#define THREAD_H_7896323423432235246427
-
-#include <thread>
-#include <future>
-#include "scope_guard.h"
-#include "type_traits.h"
-#include "optional.h"
-#ifdef ZEN_WIN
- #include "win.h"
-#endif
-
-
-namespace zen
-{
-class InterruptionStatus;
-
-class InterruptibleThread
-{
-public:
- InterruptibleThread() {}
- InterruptibleThread (InterruptibleThread&& tmp) = default;
- InterruptibleThread& operator=(InterruptibleThread&& tmp) = default;
-
- template <class Function>
- InterruptibleThread(Function&& f);
-
- bool joinable () const { return stdThread.joinable(); }
- void interrupt();
- void join () { stdThread.join(); }
- void detach () { stdThread.detach(); }
-
- template <class Rep, class Period>
- bool tryJoinFor(const std::chrono::duration<Rep, Period>& relTime)
- {
- if (threadCompleted.wait_for(relTime) == std::future_status::ready)
- {
- stdThread.join(); //runs thread-local destructors => this better be fast!!!
- return true;
- }
- return false;
- }
-
-private:
- std::thread stdThread;
- std::shared_ptr<InterruptionStatus> intStatus_;
- std::future<void> threadCompleted;
-};
-
-//context of worker thread:
-void interruptionPoint(); //throw ThreadInterruption
-
-template<class Predicate>
-void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadInterruption
-
-template <class Rep, class Period>
-void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadInterruption
-
-#ifdef ZEN_WIN
- void setCurrentThreadName(const char* threadName);
-#endif
-
-std::uint64_t getThreadId(); //simple integer thread id, unlike boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754
-
-//------------------------------------------------------------------------------------------
-
-/*
-std::async replacement without crappy semantics:
- 1. guaranteed to run asynchronously
- 2. does not follow C++11 [futures.async], Paragraph 5, where std::future waits for thread in destructor
-
-Example:
- Zstring dirpath = ...
- auto ft = zen::runAsync([=](){ return zen::dirExists(dirpath); });
- if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get())
- //dir exising
-*/
-template <class Function>
-auto runAsync(Function&& fun);
-
-//wait for all with a time limit: return true if *all* results are available!
-template<class InputIterator, class Duration>
-bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration);
-
-template<typename T> inline
-bool isReady(const std::future<T>& f) { return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
-//------------------------------------------------------------------------------------------
-
-//wait until first job is successful or all failed: substitute until std::when_any is available
-template <class T>
-class GetFirstResult
-{
-public:
- GetFirstResult();
-
- template <class Fun>
- void addJob(Fun&& f); //f must return a zen::Opt<T> containing a value if successful
-
- template <class Duration>
- bool timedWait(const Duration& duration) const; //true: "get()" is ready, false: time elapsed
-
- //return first value or none if all jobs failed; blocks until result is ready!
- Opt<T> get() const; //may be called only once!
-
-private:
- class AsyncResult;
- std::shared_ptr<AsyncResult> asyncResult_;
- size_t jobsTotal_ = 0;
-};
-
-//------------------------------------------------------------------------------------------
-
-//value associated with mutex and guaranteed protected access:
-template <class T>
-class Protected
-{
-public:
- Protected() {}
- Protected(const T& value) : value_(value) {}
-
- template <class Function>
- void access(Function fun)
- {
- std::lock_guard<std::mutex> dummy(lockValue);
- fun(value_);
- }
-
-private:
- Protected (const Protected&) = delete;
- Protected& operator=(const Protected&) = delete;
-
- std::mutex lockValue;
- T value_{};
-};
-
-
-
-
-
-
-
-
-
-//###################### implementation ######################
-
-namespace impl
-{
-template <class Function> inline
-auto runAsync(Function&& fun, TrueType /*copy-constructible*/)
-{
- using ResultType = decltype(fun());
-
- //note: std::packaged_task does NOT support move-only function objects!
- std::packaged_task<ResultType()> pt(std::forward<Function>(fun));
- auto fut = pt.get_future();
- std::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
- return fut;
-}
-
-
-template <class Function> inline
-auto runAsync(Function&& fun, FalseType /*copy-constructible*/)
-{
- //support move-only function objects!
- auto sharedFun = std::make_shared<Function>(std::forward<Function>(fun));
- return runAsync([sharedFun] { return (*sharedFun)(); }, TrueType());
-}
-}
-
-
-template <class Function> inline
-auto runAsync(Function&& fun)
-{
- return impl::runAsync(std::forward<Function>(fun), StaticBool<std::is_copy_constructible<Function>::value>());
-}
-
-
-template<class InputIterator, class Duration> inline
-bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& duration)
-{
- const std::chrono::steady_clock::time_point stopTime = std::chrono::steady_clock::now() + duration;
- for (; first != last; ++first)
- if (first->wait_until(stopTime) != std::future_status::ready)
- return false; //time elapsed
- return true;
-}
-
-
-template <class T>
-class GetFirstResult<T>::AsyncResult
-{
-public:
- //context: worker threads
- void reportFinished(Opt<T>&& result)
- {
- {
- std::lock_guard<std::mutex> dummy(lockResult);
- ++jobsFinished;
- if (!result_)
- result_ = std::move(result);
- }
- conditionJobDone.notify_all(); //better notify all, considering bugs like: https://svn.boost.org/trac/boost/ticket/7796
- }
-
- //context: main thread
- template <class Duration>
- bool waitForResult(size_t jobsTotal, const Duration& duration)
- {
- std::unique_lock<std::mutex> dummy(lockResult);
- return conditionJobDone.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); });
- }
-
- Opt<T> getResult(size_t jobsTotal)
- {
- std::unique_lock<std::mutex> dummy(lockResult);
- conditionJobDone.wait(dummy, [&] { return this->jobDone(jobsTotal); });
-
-#ifndef NDEBUG
- assert(!returnedResult);
- returnedResult = true;
-#endif
- return std::move(result_);
- }
-
-private:
- bool jobDone(size_t jobsTotal) const { return result_ || (jobsFinished >= jobsTotal); } //call while locked!
-
-#ifndef NDEBUG
- bool returnedResult = false;
-#endif
-
- std::mutex lockResult;
- size_t jobsFinished = 0; //
- Opt<T> result_; //our condition is: "have result" or "jobsFinished == jobsTotal"
- std::condition_variable conditionJobDone;
-};
-
-
-
-template <class T> inline
-GetFirstResult<T>::GetFirstResult() : asyncResult_(std::make_shared<AsyncResult>()) {}
-
-
-template <class T>
-template <class Fun> inline
-void GetFirstResult<T>::addJob(Fun&& f) //f must return a zen::Opt<T> containing a value on success
-{
- std::thread t([asyncResult = this->asyncResult_, f = std::forward<Fun>(f)] { asyncResult->reportFinished(f()); });
- ++jobsTotal_;
- t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
-}
-
-
-template <class T>
-template <class Duration> inline
-bool GetFirstResult<T>::timedWait(const Duration& duration) const { return asyncResult_->waitForResult(jobsTotal_, duration); }
-
-
-template <class T> inline
-Opt<T> GetFirstResult<T>::get() const { return asyncResult_->getResult(jobsTotal_); }
-
-//------------------------------------------------------------------------------------------
-
-//thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine:
-#ifdef _MSC_VER
- #define ZEN_THREAD_LOCAL_SPECIFIER __declspec(thread)
-#elif defined __GNUC__ || defined __clang__
- #define ZEN_THREAD_LOCAL_SPECIFIER __thread
-#else
- #error "Game over!"
-#endif
-
-
-class ThreadInterruption {};
-
-
-class InterruptionStatus
-{
-public:
- //context of InterruptibleThread instance:
- void interrupt()
- {
- interrupted = true;
-
- {
- std::lock_guard<std::mutex> dummy(lockSleep); //needed! makes sure the following signal is not lost!
- //usually we'd make "interrupted" non-atomic, but this is already given due to interruptibleWait() handling
- }
- conditionSleepInterruption.notify_all();
-
- std::lock_guard<std::mutex> dummy(lockConditionPtr);
- if (activeCondition)
- activeCondition->notify_all(); //signal may get lost!
- //alternative design locking the cv's mutex here could be dangerous: potential for dead lock!
- }
-
- //context of worker thread:
- void checkInterruption() //throw ThreadInterruption
- {
- if (interrupted)
- throw ThreadInterruption();
- }
-
- //context of worker thread:
- template<class Predicate>
- void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption
- {
- setConditionVar(&cv);
- ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr));
-
- //"interrupted" is not protected by cv's mutex => signal may get lost!!! => add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out!
- while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->interrupted || pred(); }))
- ;
-
- checkInterruption(); //throw ThreadInterruption
- }
-
- //context of worker thread:
- template <class Rep, class Period>
- void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption
- {
- std::unique_lock<std::mutex> lock(lockSleep);
- if (conditionSleepInterruption.wait_for(lock, relTime, [this] { return static_cast<bool>(this->interrupted); }))
- throw ThreadInterruption();
- }
-
-private:
- void setConditionVar(std::condition_variable* cv)
- {
- std::lock_guard<std::mutex> dummy(lockConditionPtr);
- activeCondition = cv;
- }
-
- std::atomic<bool> interrupted{ false }; //std:atomic is uninitialized by default!!!
- //"The default constructor is trivial: no initialization takes place other than zero initialization of static and thread-local objects."
-
- std::condition_variable* activeCondition = nullptr;
- std::mutex lockConditionPtr; //serialize pointer access (only!)
-
- std::condition_variable conditionSleepInterruption;
- std::mutex lockSleep;
-};
-
-
-namespace impl
-{
-inline
-InterruptionStatus*& refThreadLocalInterruptionStatus()
-{
- static ZEN_THREAD_LOCAL_SPECIFIER InterruptionStatus* threadLocalInterruptionStatus = nullptr;
- return threadLocalInterruptionStatus;
-}
-}
-
-//context of worker thread:
-inline
-void interruptionPoint() //throw ThreadInterruption
-{
- assert(impl::refThreadLocalInterruptionStatus());
- if (impl::refThreadLocalInterruptionStatus())
- impl::refThreadLocalInterruptionStatus()->checkInterruption(); //throw ThreadInterruption
-}
-
-
-//context of worker thread:
-template<class Predicate> inline
-void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption
-{
- assert(impl::refThreadLocalInterruptionStatus());
- if (impl::refThreadLocalInterruptionStatus())
- impl::refThreadLocalInterruptionStatus()->interruptibleWait(cv, lock, pred);
- else
- cv.wait(lock, pred);
-}
-
-//context of worker thread:
-template <class Rep, class Period> inline
-void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption
-{
- assert(impl::refThreadLocalInterruptionStatus());
- if (impl::refThreadLocalInterruptionStatus())
- impl::refThreadLocalInterruptionStatus()->interruptibleSleep(relTime);
- else
- std::this_thread::sleep_for(relTime);
-}
-
-
-template <class Function> inline
-InterruptibleThread::InterruptibleThread(Function&& f) : intStatus_(std::make_shared<InterruptionStatus>())
-{
- std::promise<void> pFinished;
- threadCompleted = pFinished.get_future();
-
- stdThread = std::thread([f = std::forward<Function>(f),
- intStatus = this->intStatus_,
- pFinished = std::move(pFinished)]() mutable
- {
- assert(!impl::refThreadLocalInterruptionStatus());
- impl::refThreadLocalInterruptionStatus() = intStatus.get();
- ZEN_ON_SCOPE_EXIT(impl::refThreadLocalInterruptionStatus() = nullptr);
- ZEN_ON_SCOPE_EXIT(pFinished.set_value());
-
- try
- {
- f(); //throw ThreadInterruption
- }
- catch (ThreadInterruption&) {}
- });
-}
-
-
-inline
-void InterruptibleThread::interrupt() { intStatus_->interrupt(); }
-
-
-#ifdef ZEN_WIN
-//https://randomascii.wordpress.com/2015/10/26/thread-naming-in-windows-time-for-something-better/
-
-#pragma pack(push,8)
-struct THREADNAME_INFO
-{
- DWORD dwType; // Must be 0x1000.
- LPCSTR szName; // Pointer to name (in user addr space).
- DWORD dwThreadID; // Thread ID (-1=caller thread).
- DWORD dwFlags; // Reserved for future use, must be zero.
-};
-#pragma pack(pop)
-
-
-inline
-void setCurrentThreadName(const char* threadName)
-{
- const DWORD MS_VC_EXCEPTION = 0x406D1388;
-
- THREADNAME_INFO info = {};
- info.dwType = 0x1000;
- info.szName = threadName;
- info.dwThreadID = GetCurrentThreadId();
-
- __try
- {
- ::RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), reinterpret_cast<ULONG_PTR*>(&info));
- }
- __except (EXCEPTION_EXECUTE_HANDLER) {}
-}
-#endif
-
-
-inline
-std::uint64_t getThreadId()
-{
-#ifdef ZEN_WIN
- static_assert(sizeof(std::uint64_t) >= sizeof(DWORD), "");
- return ::GetCurrentThreadId(); //no-fail
-
-#elif defined ZEN_LINUX
- //obviously "gettid()" is not available on Ubuntu/Debian/Suse => use the OpenSSL approach:
- static_assert(sizeof(std::uint64_t) >= sizeof(void*), "");
- return reinterpret_cast<std::uint64_t>(static_cast<void*>(&errno));
-
-#elif defined ZEN_MAC
- uint64_t tid = 0;
- const int rv = ::pthread_threadid_np(nullptr, &tid); //yeah, theoretically no-fail, too :> http://opensource.apple.com//source/Libc/Libc-583/pthreads/pthread.c
- assert(rv == 0);
- (void)rv;
- return tid;
-#endif
-}
-}
-
-#endif //THREAD_H_7896323423432235246427
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef THREAD_H_7896323423432235246427
+#define THREAD_H_7896323423432235246427
+
+#include <thread>
+#include <future>
+#include "scope_guard.h"
+#include "type_traits.h"
+#include "optional.h"
+
+
+namespace zen
+{
+class InterruptionStatus;
+
+class InterruptibleThread
+{
+public:
+ InterruptibleThread() {}
+ InterruptibleThread (InterruptibleThread&& tmp) = default;
+ InterruptibleThread& operator=(InterruptibleThread&& tmp) = default;
+
+ template <class Function>
+ InterruptibleThread(Function&& f);
+
+ bool joinable () const { return stdThread.joinable(); }
+ void interrupt();
+ void join () { stdThread.join(); }
+ void detach () { stdThread.detach(); }
+
+ template <class Rep, class Period>
+ bool tryJoinFor(const std::chrono::duration<Rep, Period>& relTime)
+ {
+ if (threadCompleted.wait_for(relTime) == std::future_status::ready)
+ {
+ stdThread.join(); //runs thread-local destructors => this better be fast!!!
+ return true;
+ }
+ return false;
+ }
+
+private:
+ std::thread stdThread;
+ std::shared_ptr<InterruptionStatus> intStatus_;
+ std::future<void> threadCompleted;
+};
+
+//context of worker thread:
+void interruptionPoint(); //throw ThreadInterruption
+
+template<class Predicate>
+void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadInterruption
+
+template <class Rep, class Period>
+void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadInterruption
+
+
+uint64_t getThreadId(); //simple integer thread id, unlike boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754
+
+//------------------------------------------------------------------------------------------
+
+/*
+std::async replacement without crappy semantics:
+ 1. guaranteed to run asynchronously
+ 2. does not follow C++11 [futures.async], Paragraph 5, where std::future waits for thread in destructor
+
+Example:
+ Zstring dirpath = ...
+ auto ft = zen::runAsync([=](){ return zen::dirExists(dirpath); });
+ if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get())
+ //dir exising
+*/
+template <class Function>
+auto runAsync(Function&& fun);
+
+//wait for all with a time limit: return true if *all* results are available!
+template<class InputIterator, class Duration>
+bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration);
+
+template<typename T> inline
+bool isReady(const std::future<T>& f) { return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
+//------------------------------------------------------------------------------------------
+
+//wait until first job is successful or all failed: substitute until std::when_any is available
+template <class T>
+class GetFirstResult
+{
+public:
+ GetFirstResult();
+
+ template <class Fun>
+ void addJob(Fun&& f); //f must return a zen::Opt<T> containing a value if successful
+
+ template <class Duration>
+ bool timedWait(const Duration& duration) const; //true: "get()" is ready, false: time elapsed
+
+ //return first value or none if all jobs failed; blocks until result is ready!
+ Opt<T> get() const; //may be called only once!
+
+private:
+ class AsyncResult;
+ std::shared_ptr<AsyncResult> asyncResult_;
+ size_t jobsTotal_ = 0;
+};
+
+//------------------------------------------------------------------------------------------
+
+//value associated with mutex and guaranteed protected access:
+template <class T>
+class Protected
+{
+public:
+ Protected() {}
+ Protected(const T& value) : value_(value) {}
+
+ template <class Function>
+ void access(Function fun)
+ {
+ std::lock_guard<std::mutex> dummy(lockValue);
+ fun(value_);
+ }
+
+private:
+ Protected (const Protected&) = delete;
+ Protected& operator=(const Protected&) = delete;
+
+ std::mutex lockValue;
+ T value_{};
+};
+
+
+
+
+
+
+
+
+
+//###################### implementation ######################
+
+namespace impl
+{
+template <class Function> inline
+auto runAsync(Function&& fun, TrueType /*copy-constructible*/)
+{
+ using ResultType = decltype(fun());
+
+ //note: std::packaged_task does NOT support move-only function objects!
+ std::packaged_task<ResultType()> pt(std::forward<Function>(fun));
+ auto fut = pt.get_future();
+ std::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
+ return fut;
+}
+
+
+template <class Function> inline
+auto runAsync(Function&& fun, FalseType /*copy-constructible*/)
+{
+ //support move-only function objects!
+ auto sharedFun = std::make_shared<Function>(std::forward<Function>(fun));
+ return runAsync([sharedFun] { return (*sharedFun)(); }, TrueType());
+}
+}
+
+
+template <class Function> inline
+auto runAsync(Function&& fun)
+{
+ return impl::runAsync(std::forward<Function>(fun), StaticBool<std::is_copy_constructible<Function>::value>());
+}
+
+
+template<class InputIterator, class Duration> inline
+bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& duration)
+{
+ const std::chrono::steady_clock::time_point stopTime = std::chrono::steady_clock::now() + duration;
+ for (; first != last; ++first)
+ if (first->wait_until(stopTime) != std::future_status::ready)
+ return false; //time elapsed
+ return true;
+}
+
+
+template <class T>
+class GetFirstResult<T>::AsyncResult
+{
+public:
+ //context: worker threads
+ void reportFinished(Opt<T>&& result)
+ {
+ {
+ std::lock_guard<std::mutex> dummy(lockResult);
+ ++jobsFinished;
+ if (!result_)
+ result_ = std::move(result);
+ }
+ conditionJobDone.notify_all(); //better notify all, considering bugs like: https://svn.boost.org/trac/boost/ticket/7796
+ }
+
+ //context: main thread
+ template <class Duration>
+ bool waitForResult(size_t jobsTotal, const Duration& duration)
+ {
+ std::unique_lock<std::mutex> dummy(lockResult);
+ return conditionJobDone.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); });
+ }
+
+ Opt<T> getResult(size_t jobsTotal)
+ {
+ std::unique_lock<std::mutex> dummy(lockResult);
+ conditionJobDone.wait(dummy, [&] { return this->jobDone(jobsTotal); });
+
+ return std::move(result_);
+ }
+
+private:
+ bool jobDone(size_t jobsTotal) const { return result_ || (jobsFinished >= jobsTotal); } //call while locked!
+
+
+ std::mutex lockResult;
+ size_t jobsFinished = 0; //
+ Opt<T> result_; //our condition is: "have result" or "jobsFinished == jobsTotal"
+ std::condition_variable conditionJobDone;
+};
+
+
+
+template <class T> inline
+GetFirstResult<T>::GetFirstResult() : asyncResult_(std::make_shared<AsyncResult>()) {}
+
+
+template <class T>
+template <class Fun> inline
+void GetFirstResult<T>::addJob(Fun&& f) //f must return a zen::Opt<T> containing a value on success
+{
+ std::thread t([asyncResult = this->asyncResult_, f = std::forward<Fun>(f)] { asyncResult->reportFinished(f()); });
+ ++jobsTotal_;
+ t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!!
+}
+
+
+template <class T>
+template <class Duration> inline
+bool GetFirstResult<T>::timedWait(const Duration& duration) const { return asyncResult_->waitForResult(jobsTotal_, duration); }
+
+
+template <class T> inline
+Opt<T> GetFirstResult<T>::get() const { return asyncResult_->getResult(jobsTotal_); }
+
+//------------------------------------------------------------------------------------------
+
+//thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine:
+ #define ZEN_THREAD_LOCAL_SPECIFIER __thread
+
+
+class ThreadInterruption {};
+
+
+class InterruptionStatus
+{
+public:
+ //context of InterruptibleThread instance:
+ void interrupt()
+ {
+ interrupted = true;
+
+ {
+ std::lock_guard<std::mutex> dummy(lockSleep); //needed! makes sure the following signal is not lost!
+ //usually we'd make "interrupted" non-atomic, but this is already given due to interruptibleWait() handling
+ }
+ conditionSleepInterruption.notify_all();
+
+ std::lock_guard<std::mutex> dummy(lockConditionPtr);
+ if (activeCondition)
+ activeCondition->notify_all(); //signal may get lost!
+ //alternative design locking the cv's mutex here could be dangerous: potential for dead lock!
+ }
+
+ //context of worker thread:
+ void checkInterruption() //throw ThreadInterruption
+ {
+ if (interrupted)
+ throw ThreadInterruption();
+ }
+
+ //context of worker thread:
+ template<class Predicate>
+ void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption
+ {
+ setConditionVar(&cv);
+ ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr));
+
+ //"interrupted" is not protected by cv's mutex => signal may get lost!!! => add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out!
+ while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->interrupted || pred(); }))
+ ;
+
+ checkInterruption(); //throw ThreadInterruption
+ }
+
+ //context of worker thread:
+ template <class Rep, class Period>
+ void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption
+ {
+ std::unique_lock<std::mutex> lock(lockSleep);
+ if (conditionSleepInterruption.wait_for(lock, relTime, [this] { return static_cast<bool>(this->interrupted); }))
+ throw ThreadInterruption();
+ }
+
+private:
+ void setConditionVar(std::condition_variable* cv)
+ {
+ std::lock_guard<std::mutex> dummy(lockConditionPtr);
+ activeCondition = cv;
+ }
+
+ std::atomic<bool> interrupted{ false }; //std:atomic is uninitialized by default!!!
+ //"The default constructor is trivial: no initialization takes place other than zero initialization of static and thread-local objects."
+
+ std::condition_variable* activeCondition = nullptr;
+ std::mutex lockConditionPtr; //serialize pointer access (only!)
+
+ std::condition_variable conditionSleepInterruption;
+ std::mutex lockSleep;
+};
+
+
+namespace impl
+{
+inline
+InterruptionStatus*& refThreadLocalInterruptionStatus()
+{
+ static ZEN_THREAD_LOCAL_SPECIFIER InterruptionStatus* threadLocalInterruptionStatus = nullptr;
+ return threadLocalInterruptionStatus;
+}
+}
+
+//context of worker thread:
+inline
+void interruptionPoint() //throw ThreadInterruption
+{
+ assert(impl::refThreadLocalInterruptionStatus());
+ if (impl::refThreadLocalInterruptionStatus())
+ impl::refThreadLocalInterruptionStatus()->checkInterruption(); //throw ThreadInterruption
+}
+
+
+//context of worker thread:
+template<class Predicate> inline
+void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption
+{
+ assert(impl::refThreadLocalInterruptionStatus());
+ if (impl::refThreadLocalInterruptionStatus())
+ impl::refThreadLocalInterruptionStatus()->interruptibleWait(cv, lock, pred);
+ else
+ cv.wait(lock, pred);
+}
+
+//context of worker thread:
+template <class Rep, class Period> inline
+void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption
+{
+ assert(impl::refThreadLocalInterruptionStatus());
+ if (impl::refThreadLocalInterruptionStatus())
+ impl::refThreadLocalInterruptionStatus()->interruptibleSleep(relTime);
+ else
+ std::this_thread::sleep_for(relTime);
+}
+
+
+template <class Function> inline
+InterruptibleThread::InterruptibleThread(Function&& f) : intStatus_(std::make_shared<InterruptionStatus>())
+{
+ std::promise<void> pFinished;
+ threadCompleted = pFinished.get_future();
+
+ stdThread = std::thread([f = std::forward<Function>(f),
+ intStatus = this->intStatus_,
+ pFinished = std::move(pFinished)]() mutable
+ {
+ assert(!impl::refThreadLocalInterruptionStatus());
+ impl::refThreadLocalInterruptionStatus() = intStatus.get();
+ ZEN_ON_SCOPE_EXIT(impl::refThreadLocalInterruptionStatus() = nullptr);
+ ZEN_ON_SCOPE_EXIT(pFinished.set_value());
+
+ try
+ {
+ f(); //throw ThreadInterruption
+ }
+ catch (ThreadInterruption&) {}
+ });
+}
+
+
+inline
+void InterruptibleThread::interrupt() { intStatus_->interrupt(); }
+
+
+
+
+inline
+uint64_t getThreadId()
+{
+ //obviously "gettid()" is not available on Ubuntu/Debian/Suse => use the OpenSSL approach:
+ static_assert(sizeof(uint64_t) >= sizeof(void*), "");
+ return reinterpret_cast<uint64_t>(static_cast<void*>(&errno));
+
+}
+}
+
+#endif //THREAD_H_7896323423432235246427
diff --git a/zen/time.h b/zen/time.h
index a4613408..1b6a3c92 100644..100755
--- a/zen/time.h
+++ b/zen/time.h
@@ -1,357 +1,368 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef TIME_H_8457092814324342453627
-#define TIME_H_8457092814324342453627
-
-#include <ctime>
-#include "string_tools.h"
-
-
-namespace zen
-{
-struct TimeComp //replaces std::tm and SYSTEMTIME
-{
- int year = 0; // -
- int month = 0; //1-12
- int day = 0; //1-31
- int hour = 0; //0-23
- int minute = 0; //0-59
- int second = 0; //0-60 (including leap second)
-};
-
-TimeComp localTime (time_t utc = std::time(nullptr)); //convert time_t (UTC) to local time components
-time_t localToTimeT(const TimeComp& comp); //convert local time components to time_t (UTC), returns -1 on error
-
-//----------------------------------------------------------------------------------------------------------------------------------
-
-/*
-format (current) date and time; example:
- formatTime<std::wstring>(L"%Y|%m|%d"); -> "2011|10|29"
- formatTime<std::wstring>(FORMAT_DATE); -> "2011-10-29"
- formatTime<std::wstring>(FORMAT_TIME); -> "17:55:34"
-*/
-template <class String, class String2>
-String formatTime(const String2& format, const TimeComp& comp = localTime()); //format as specified by "std::strftime", returns empty string on failure
-
-//the "format" parameter of formatTime() is partially specialized with the following type tags:
-const struct FormatDateTag {} FORMAT_DATE = {}; //%x - locale dependent date representation: e.g. 08/23/01
-const struct FormatTimeTag {} FORMAT_TIME = {}; //%X - locale dependent time representation: e.g. 14:55:02
-const struct FormatDateTimeTag {} FORMAT_DATE_TIME = {}; //%c - locale dependent date and time: e.g. Thu Aug 23 14:55:02 2001
-
-const struct FormatIsoDateTag {} FORMAT_ISO_DATE = {}; //%Y-%m-%d - e.g. 2001-08-23
-const struct FormatIsoTimeTag {} FORMAT_ISO_TIME = {}; //%H:%M:%S - e.g. 14:55:02
-const struct FormatIsoDateTimeTag {} FORMAT_ISO_DATE_TIME = {}; //%Y-%m-%d %H:%M:%S - e.g. 2001-08-23 14:55:02
-
-//----------------------------------------------------------------------------------------------------------------------------------
-
-template <class String, class String2>
-bool parseTime(const String& format, const String2& str, TimeComp& comp); //similar to ::strptime(), return true on success
-
-//----------------------------------------------------------------------------------------------------------------------------------
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//############################ implementation ##############################
-namespace implementation
-{
-inline
-std::tm toClibTimeComponents(const TimeComp& comp)
-{
- assert(1 <= comp.month && comp.month <= 12 &&
- 1 <= comp.day && comp.day <= 31 &&
- 0 <= comp.hour && comp.hour <= 23 &&
- 0 <= comp.minute && comp.minute <= 59 &&
- 0 <= comp.second && comp.second <= 61);
-
- std::tm ctc = {};
- ctc.tm_year = comp.year - 1900; //years since 1900
- ctc.tm_mon = comp.month - 1; //0-11
- ctc.tm_mday = comp.day; //1-31
- ctc.tm_hour = comp.hour; //0-23
- ctc.tm_min = comp.minute; //0-59
- ctc.tm_sec = comp.second; //0-60 (including leap second)
- ctc.tm_isdst = -1; //> 0 if DST is active, == 0 if DST is not active, < 0 if the information is not available
- //ctc.tm_wday
- //ctc.tm_yday
- return ctc;
-}
-
-inline
-TimeComp toZenTimeComponents(const std::tm& ctc)
-{
- TimeComp comp;
- comp.year = ctc.tm_year + 1900;
- comp.month = ctc.tm_mon + 1;
- comp.day = ctc.tm_mday;
- comp.hour = ctc.tm_hour;
- comp.minute = ctc.tm_min;
- comp.second = ctc.tm_sec;
- return comp;
-}
-
-
-template <class T>
-struct GetFormat; //get default time formats as char* or wchar_t*
-
-template <>
-struct GetFormat<FormatDateTag> //%x - locale dependent date representation: e.g. 08/23/01
-{
- const char* format(char) const { return "%x"; }
- const wchar_t* format(wchar_t) const { return L"%x"; }
-};
-
-template <>
-struct GetFormat<FormatTimeTag> //%X - locale dependent time representation: e.g. 14:55:02
-{
- const char* format(char) const { return "%X"; }
- const wchar_t* format(wchar_t) const { return L"%X"; }
-};
-
-template <>
-struct GetFormat<FormatDateTimeTag> //%c - locale dependent date and time: e.g. Thu Aug 23 14:55:02 2001
-{
- const char* format(char) const { return "%c"; }
- const wchar_t* format(wchar_t) const { return L"%c"; }
-};
-
-template <>
-struct GetFormat<FormatIsoDateTag> //%Y-%m-%d - e.g. 2001-08-23
-{
- const char* format(char) const { return "%Y-%m-%d"; }
- const wchar_t* format(wchar_t) const { return L"%Y-%m-%d"; }
-};
-
-template <>
-struct GetFormat<FormatIsoTimeTag> //%H:%M:%S - e.g. 14:55:02
-{
- const char* format(char) const { return "%H:%M:%S"; }
- const wchar_t* format(wchar_t) const { return L"%H:%M:%S"; }
-};
-
-template <>
-struct GetFormat<FormatIsoDateTimeTag> //%Y-%m-%d %H:%M:%S - e.g. 2001-08-23 14:55:02
-{
- const char* format(char) const { return "%Y-%m-%d %H:%M:%S"; }
- const wchar_t* format(wchar_t) const { return L"%Y-%m-%d %H:%M:%S"; }
-};
-
-
-//strftime() craziness on invalid input:
-// VS 2010: CRASH unless "_invalid_parameter_handler" is set: https://msdn.microsoft.com/en-us/library/ksazx244.aspx
-// GCC: returns 0, apparently no crash. Still, considering some clib maintainer's comments, we should expect the worst!
-inline
-size_t strftimeWrap_impl(char* buffer, size_t bufferSize, const char* format, const std::tm* timeptr)
-{
- return std::strftime(buffer, bufferSize, format, timeptr);
-}
-
-
-inline
-size_t strftimeWrap_impl(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const std::tm* timeptr)
-{
- return std::wcsftime(buffer, bufferSize, format, timeptr);
-}
-
-/*
-inline
-bool isValid(const std::tm& t)
-{
- -> not enough! MSCRT has different limits than the C standard which even seem to change with different versions:
- _VALIDATE_RETURN((( timeptr->tm_sec >=0 ) && ( timeptr->tm_sec <= 59 ) ), EINVAL, FALSE)
- _VALIDATE_RETURN(( timeptr->tm_year >= -1900 ) && ( timeptr->tm_year <= 8099 ), EINVAL, FALSE)
- -> also std::mktime does *not* help here at all!
-
- auto inRange = [](int value, int minVal, int maxVal) { return minVal <= value && value <= maxVal; };
-
- //http://www.cplusplus.com/reference/clibrary/ctime/tm/
- return inRange(t.tm_sec , 0, 61) &&
- inRange(t.tm_min , 0, 59) &&
- inRange(t.tm_hour, 0, 23) &&
- inRange(t.tm_mday, 1, 31) &&
- inRange(t.tm_mon , 0, 11) &&
- //tm_year
- inRange(t.tm_wday, 0, 6) &&
- inRange(t.tm_yday, 0, 365);
- //tm_isdst
-};
-*/
-
-template <class CharType> inline
-size_t strftimeWrap(CharType* buffer, size_t bufferSize, const CharType* format, const std::tm* timeptr)
-{
-#if defined _MSC_VER && !defined NDEBUG
- //there's no way around: application init must register an invalid parameter handler that does nothing !!!
- //=> strftime will abort with 0 and set errno to EINVAL instead of CRASHING THE APPLICATION!
- _invalid_parameter_handler oldHandler = _set_invalid_parameter_handler(nullptr);
- assert(oldHandler);
- _set_invalid_parameter_handler(oldHandler);
-#endif
-
- return strftimeWrap_impl(buffer, bufferSize, format, timeptr);
-}
-
-
-struct UserDefinedFormatTag {};
-struct PredefinedFormatTag {};
-
-template <class String, class String2> inline
-String formatTime(const String2& format, const TimeComp& comp, UserDefinedFormatTag) //format as specified by "std::strftime", returns empty string on failure
-{
- using CharType = typename GetCharType<String>::Type;
- std::tm ctc = toClibTimeComponents(comp);
- std::mktime(&ctc); // unfortunately std::strftime() needs all elements of "struct tm" filled, e.g. tm_wday, tm_yday
- //note: although std::mktime() explicitly expects "local time", calculating weekday and day of year *should* be time-zone and DST independent
-
- CharType buffer[256] = {};
- const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc);
- return String(buffer, charsWritten);
-}
-
-template <class String, class FormatType> inline
-String formatTime(FormatType, const TimeComp& comp, PredefinedFormatTag)
-{
- using CharType = typename GetCharType<String>::Type;
- return formatTime<String>(GetFormat<FormatType>().format(CharType()), comp, UserDefinedFormatTag());
-}
-}
-
-
-inline
-TimeComp localTime(time_t utc)
-{
- std::tm lt = {};
-
-#ifdef ZEN_WIN //use thread-safe variants of std::localtime()!
- if (::localtime_s(&lt, &utc) != 0)
-#else
- if (::localtime_r(&utc, &lt) == nullptr)
-#endif
- return TimeComp();
-
- return implementation::toZenTimeComponents(lt);
-}
-
-
-inline
-time_t localToTimeT(const TimeComp& comp) //returns -1 on error
-{
- std::tm ctc = implementation::toClibTimeComponents(comp);
- return std::mktime(&ctc);
-}
-
-
-template <class String, class String2> inline
-String formatTime(const String2& format, const TimeComp& comp)
-{
- using FormatTag = typename SelectIf<
- IsSameType<String2, FormatDateTag >::value ||
- IsSameType<String2, FormatTimeTag >::value ||
- IsSameType<String2, FormatDateTimeTag >::value ||
- IsSameType<String2, FormatIsoDateTag >::value ||
- IsSameType<String2, FormatIsoTimeTag >::value ||
- IsSameType<String2, FormatIsoDateTimeTag>::value, implementation::PredefinedFormatTag, implementation::UserDefinedFormatTag>::Type;
-
- return implementation::formatTime<String>(format, comp, FormatTag());
-}
-
-
-template <class String, class String2>
-bool parseTime(const String& format, const String2& str, TimeComp& comp) //return true on success
-{
- using CharType = typename GetCharType<String>::Type;
- static_assert(IsSameType<CharType, typename GetCharType<String2>::Type>::value, "");
-
- const CharType* itFmt = strBegin(format);
- const CharType* const fmtLast = itFmt + strLength(format);
-
- const CharType* itStr = strBegin(str);
- const CharType* const strLast = itStr + strLength(str);
-
- auto extractNumber = [&](int& result, size_t digitCount) -> bool
- {
- if (strLast - itStr < makeSigned(digitCount))
- return false;
-
- if (std::any_of(itStr, itStr + digitCount, [](CharType c) { return !isDigit(c); }))
- return false;
-
- result = zen::stringTo<int>(StringRef<const CharType>(itStr, itStr + digitCount));
- itStr += digitCount;
- return true;
- };
-
- for (; itFmt != fmtLast; ++itFmt)
- {
- const CharType fmt = *itFmt;
-
- if (fmt == '%')
- {
- ++itFmt;
- if (itFmt == fmtLast)
- return false;
-
- switch (*itFmt)
- {
- case 'Y':
- if (!extractNumber(comp.year, 4))
- return false;
- break;
- case 'm':
- if (!extractNumber(comp.month, 2))
- return false;
- break;
- case 'd':
- if (!extractNumber(comp.day, 2))
- return false;
- break;
- case 'H':
- if (!extractNumber(comp.hour, 2))
- return false;
- break;
- case 'M':
- if (!extractNumber(comp.minute, 2))
- return false;
- break;
- case 'S':
- if (!extractNumber(comp.second, 2))
- return false;
- break;
- default:
- return false;
- }
- }
- else if (isWhiteSpace(fmt)) //single whitespace in format => skip 0..n whitespace chars
- {
- while (itStr != strLast && isWhiteSpace(*itStr))
- ++itStr;
- }
- else
- {
- if (itStr == strLast || *itStr != fmt)
- return false;
- ++itStr;
- }
- }
-
- return itStr == strLast;
-}
-}
-
-#endif //TIME_H_8457092814324342453627
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef TIME_H_8457092814324342453627
+#define TIME_H_8457092814324342453627
+
+#include <ctime>
+#include "string_tools.h"
+
+
+namespace zen
+{
+struct TimeComp //replaces std::tm and SYSTEMTIME
+{
+ int year = 0; // -
+ int month = 0; //1-12
+ int day = 0; //1-31
+ int hour = 0; //0-23
+ int minute = 0; //0-59
+ int second = 0; //0-60 (including leap second)
+};
+
+TimeComp getLocalTime(time_t utc = std::time(nullptr)); //convert time_t (UTC) to local time components
+time_t localToTimeT(const TimeComp& comp); //convert local time components to time_t (UTC), returns -1 on error
+
+TimeComp getUtcTime(time_t utc = std::time(nullptr)); //convert time_t (UTC) to UTC time components
+time_t utcToTimeT(const TimeComp& comp); //convert UTC time components to time_t (UTC), returns -1 on error
+
+//----------------------------------------------------------------------------------------------------------------------------------
+
+/*
+format (current) date and time; example:
+ formatTime<std::wstring>(L"%Y|%m|%d"); -> "2011|10|29"
+ formatTime<std::wstring>(FORMAT_DATE); -> "2011-10-29"
+ formatTime<std::wstring>(FORMAT_TIME); -> "17:55:34"
+*/
+template <class String, class String2>
+String formatTime(const String2& format, const TimeComp& comp = getLocalTime()); //format as specified by "std::strftime", returns empty string on failure
+
+//the "format" parameter of formatTime() is partially specialized with the following type tags:
+const struct FormatDateTag {} FORMAT_DATE = {}; //%x - locale dependent date representation: e.g. 08/23/01
+const struct FormatTimeTag {} FORMAT_TIME = {}; //%X - locale dependent time representation: e.g. 14:55:02
+const struct FormatDateTimeTag {} FORMAT_DATE_TIME = {}; //%c - locale dependent date and time: e.g. Thu Aug 23 14:55:02 2001
+
+const struct FormatIsoDateTag {} FORMAT_ISO_DATE = {}; //%Y-%m-%d - e.g. 2001-08-23
+const struct FormatIsoTimeTag {} FORMAT_ISO_TIME = {}; //%H:%M:%S - e.g. 14:55:02
+const struct FormatIsoDateTimeTag {} FORMAT_ISO_DATE_TIME = {}; //%Y-%m-%d %H:%M:%S - e.g. 2001-08-23 14:55:02
+
+//----------------------------------------------------------------------------------------------------------------------------------
+
+template <class String, class String2>
+bool parseTime(const String& format, const String2& str, TimeComp& comp); //similar to ::strptime(), return true on success
+
+//----------------------------------------------------------------------------------------------------------------------------------
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//############################ implementation ##############################
+namespace implementation
+{
+inline
+std::tm toClibTimeComponents(const TimeComp& comp)
+{
+ assert(1 <= comp.month && comp.month <= 12 &&
+ 1 <= comp.day && comp.day <= 31 &&
+ 0 <= comp.hour && comp.hour <= 23 &&
+ 0 <= comp.minute && comp.minute <= 59 &&
+ 0 <= comp.second && comp.second <= 61);
+
+ std::tm ctc = {};
+ ctc.tm_year = comp.year - 1900; //years since 1900
+ ctc.tm_mon = comp.month - 1; //0-11
+ ctc.tm_mday = comp.day; //1-31
+ ctc.tm_hour = comp.hour; //0-23
+ ctc.tm_min = comp.minute; //0-59
+ ctc.tm_sec = comp.second; //0-60 (including leap second)
+ ctc.tm_isdst = -1; //> 0 if DST is active, == 0 if DST is not active, < 0 if the information is not available
+ //ctc.tm_wday
+ //ctc.tm_yday
+ return ctc;
+}
+
+inline
+TimeComp toZenTimeComponents(const std::tm& ctc)
+{
+ TimeComp comp;
+ comp.year = ctc.tm_year + 1900;
+ comp.month = ctc.tm_mon + 1;
+ comp.day = ctc.tm_mday;
+ comp.hour = ctc.tm_hour;
+ comp.minute = ctc.tm_min;
+ comp.second = ctc.tm_sec;
+ return comp;
+}
+
+
+template <class T>
+struct GetFormat; //get default time formats as char* or wchar_t*
+
+template <>
+struct GetFormat<FormatDateTag> //%x - locale dependent date representation: e.g. 08/23/01
+{
+ const char* format(char) const { return "%x"; }
+ const wchar_t* format(wchar_t) const { return L"%x"; }
+};
+
+template <>
+struct GetFormat<FormatTimeTag> //%X - locale dependent time representation: e.g. 14:55:02
+{
+ const char* format(char) const { return "%X"; }
+ const wchar_t* format(wchar_t) const { return L"%X"; }
+};
+
+template <>
+struct GetFormat<FormatDateTimeTag> //%c - locale dependent date and time: e.g. Thu Aug 23 14:55:02 2001
+{
+ const char* format(char) const { return "%c"; }
+ const wchar_t* format(wchar_t) const { return L"%c"; }
+};
+
+template <>
+struct GetFormat<FormatIsoDateTag> //%Y-%m-%d - e.g. 2001-08-23
+{
+ const char* format(char) const { return "%Y-%m-%d"; }
+ const wchar_t* format(wchar_t) const { return L"%Y-%m-%d"; }
+};
+
+template <>
+struct GetFormat<FormatIsoTimeTag> //%H:%M:%S - e.g. 14:55:02
+{
+ const char* format(char) const { return "%H:%M:%S"; }
+ const wchar_t* format(wchar_t) const { return L"%H:%M:%S"; }
+};
+
+template <>
+struct GetFormat<FormatIsoDateTimeTag> //%Y-%m-%d %H:%M:%S - e.g. 2001-08-23 14:55:02
+{
+ const char* format(char) const { return "%Y-%m-%d %H:%M:%S"; }
+ const wchar_t* format(wchar_t) const { return L"%Y-%m-%d %H:%M:%S"; }
+};
+
+
+//strftime() craziness on invalid input:
+// VS 2010: CRASH unless "_invalid_parameter_handler" is set: https://msdn.microsoft.com/en-us/library/ksazx244.aspx
+// GCC: returns 0, apparently no crash. Still, considering some clib maintainer's comments, we should expect the worst!
+inline
+size_t strftimeWrap_impl(char* buffer, size_t bufferSize, const char* format, const std::tm* timeptr)
+{
+ return std::strftime(buffer, bufferSize, format, timeptr);
+}
+
+
+inline
+size_t strftimeWrap_impl(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const std::tm* timeptr)
+{
+ return std::wcsftime(buffer, bufferSize, format, timeptr);
+}
+
+/*
+inline
+bool isValid(const std::tm& t)
+{
+ -> not enough! MSCRT has different limits than the C standard which even seem to change with different versions:
+ _VALIDATE_RETURN((( timeptr->tm_sec >=0 ) && ( timeptr->tm_sec <= 59 ) ), EINVAL, FALSE)
+ _VALIDATE_RETURN(( timeptr->tm_year >= -1900 ) && ( timeptr->tm_year <= 8099 ), EINVAL, FALSE)
+ -> also std::mktime does *not* help here at all!
+
+ auto inRange = [](int value, int minVal, int maxVal) { return minVal <= value && value <= maxVal; };
+
+ //http://www.cplusplus.com/reference/clibrary/ctime/tm/
+ return inRange(t.tm_sec , 0, 61) &&
+ inRange(t.tm_min , 0, 59) &&
+ inRange(t.tm_hour, 0, 23) &&
+ inRange(t.tm_mday, 1, 31) &&
+ inRange(t.tm_mon , 0, 11) &&
+ //tm_year
+ inRange(t.tm_wday, 0, 6) &&
+ inRange(t.tm_yday, 0, 365);
+ //tm_isdst
+};
+*/
+
+template <class CharType> inline
+size_t strftimeWrap(CharType* buffer, size_t bufferSize, const CharType* format, const std::tm* timeptr)
+{
+
+ return strftimeWrap_impl(buffer, bufferSize, format, timeptr);
+}
+
+
+struct UserDefinedFormatTag {};
+struct PredefinedFormatTag {};
+
+template <class String, class String2> inline
+String formatTime(const String2& format, const TimeComp& comp, UserDefinedFormatTag) //format as specified by "std::strftime", returns empty string on failure
+{
+ using CharType = typename GetCharType<String>::Type;
+ std::tm ctc = toClibTimeComponents(comp);
+ std::mktime(&ctc); // unfortunately std::strftime() needs all elements of "struct tm" filled, e.g. tm_wday, tm_yday
+ //note: although std::mktime() explicitly expects "local time", calculating weekday and day of year *should* be time-zone and DST independent
+
+ CharType buffer[256] = {};
+ const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc);
+ return String(buffer, charsWritten);
+}
+
+template <class String, class FormatType> inline
+String formatTime(FormatType, const TimeComp& comp, PredefinedFormatTag)
+{
+ using CharType = typename GetCharType<String>::Type;
+ return formatTime<String>(GetFormat<FormatType>().format(CharType()), comp, UserDefinedFormatTag());
+}
+}
+
+
+inline
+TimeComp getLocalTime(time_t utc)
+{
+ std::tm comp = {};
+ if (::localtime_r(&utc, &comp) == nullptr)
+ return TimeComp();
+
+ return implementation::toZenTimeComponents(comp);
+}
+
+
+inline
+TimeComp getUtcTime(time_t utc)
+{
+ std::tm comp = {};
+ if (::gmtime_r(&utc, &comp) == nullptr)
+ return TimeComp();
+
+ return implementation::toZenTimeComponents(comp);
+}
+
+
+inline
+time_t localToTimeT(const TimeComp& comp) //returns -1 on error
+{
+ std::tm ctc = implementation::toClibTimeComponents(comp);
+ return std::mktime(&ctc);
+}
+
+
+inline
+time_t utcToTimeT(const TimeComp& comp) //returns -1 on error
+{
+ std::tm ctc = implementation::toClibTimeComponents(comp);
+ ctc.tm_isdst = 0; //"Zero (0) to indicate that standard time is in effect" => unused by _mkgmtime, but take no chances
+ return ::timegm(&ctc);
+}
+
+
+template <class String, class String2> inline
+String formatTime(const String2& format, const TimeComp& comp)
+{
+ using FormatTag = typename SelectIf<
+ IsSameType<String2, FormatDateTag >::value ||
+ IsSameType<String2, FormatTimeTag >::value ||
+ IsSameType<String2, FormatDateTimeTag >::value ||
+ IsSameType<String2, FormatIsoDateTag >::value ||
+ IsSameType<String2, FormatIsoTimeTag >::value ||
+ IsSameType<String2, FormatIsoDateTimeTag>::value, implementation::PredefinedFormatTag, implementation::UserDefinedFormatTag>::Type;
+
+ return implementation::formatTime<String>(format, comp, FormatTag());
+}
+
+
+template <class String, class String2>
+bool parseTime(const String& format, const String2& str, TimeComp& comp) //return true on success
+{
+ using CharType = typename GetCharType<String>::Type;
+ static_assert(IsSameType<CharType, typename GetCharType<String2>::Type>::value, "");
+
+ const CharType* itFmt = strBegin(format);
+ const CharType* const fmtLast = itFmt + strLength(format);
+
+ const CharType* itStr = strBegin(str);
+ const CharType* const strLast = itStr + strLength(str);
+
+ auto extractNumber = [&](int& result, size_t digitCount) -> bool
+ {
+ if (strLast - itStr < makeSigned(digitCount))
+ return false;
+
+ if (std::any_of(itStr, itStr + digitCount, [](CharType c) { return !isDigit(c); }))
+ return false;
+
+ result = zen::stringTo<int>(StringRef<const CharType>(itStr, itStr + digitCount));
+ itStr += digitCount;
+ return true;
+ };
+
+ for (; itFmt != fmtLast; ++itFmt)
+ {
+ const CharType fmt = *itFmt;
+
+ if (fmt == '%')
+ {
+ ++itFmt;
+ if (itFmt == fmtLast)
+ return false;
+
+ switch (*itFmt)
+ {
+ case 'Y':
+ if (!extractNumber(comp.year, 4))
+ return false;
+ break;
+ case 'm':
+ if (!extractNumber(comp.month, 2))
+ return false;
+ break;
+ case 'd':
+ if (!extractNumber(comp.day, 2))
+ return false;
+ break;
+ case 'H':
+ if (!extractNumber(comp.hour, 2))
+ return false;
+ break;
+ case 'M':
+ if (!extractNumber(comp.minute, 2))
+ return false;
+ break;
+ case 'S':
+ if (!extractNumber(comp.second, 2))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ }
+ else if (isWhiteSpace(fmt)) //single whitespace in format => skip 0..n whitespace chars
+ {
+ while (itStr != strLast && isWhiteSpace(*itStr))
+ ++itStr;
+ }
+ else
+ {
+ if (itStr == strLast || *itStr != fmt)
+ return false;
+ ++itStr;
+ }
+ }
+
+ return itStr == strLast;
+}
+}
+
+#endif //TIME_H_8457092814324342453627
diff --git a/zen/type_tools.h b/zen/type_tools.h
index d0a62ea2..082530ec 100644..100755
--- a/zen/type_tools.h
+++ b/zen/type_tools.h
@@ -1,103 +1,103 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef TYPE_TOOLS_H_45237590734254545
-#define TYPE_TOOLS_H_45237590734254545
-
-#include "type_traits.h"
-
-
-namespace zen
-{
-//########## Strawman Classes ##########################
-struct NullType {}; //:= no type here
-
-//########## Type Mapping ##############################
-template <int n>
-struct Int2Type {};
-//------------------------------------------------------
-template <class T>
-struct Type2Type {};
-
-//########## Control Structures ########################
-template <bool flag, class T, class U>
-struct SelectIf : ResultType<T> {};
-
-template <class T, class U>
-struct SelectIf<false, T, U> : ResultType<U> {};
-//------------------------------------------------------
-template <class T, class U>
-struct IsSameType : FalseType {};
-
-template <class T>
-struct IsSameType<T, T> : TrueType {};
-
-//------------------------------------------------------
-template <bool, class T = void>
-struct EnableIf {};
-
-template <class T>
-struct EnableIf<true, T> : ResultType<T> {};
-//########## Type Cleanup ##############################
-template <class T>
-struct RemoveRef : ResultType<T> {};
-
-template <class T>
-struct RemoveRef<T&> : ResultType<T> {};
-
-template <class T>
-struct RemoveRef<T&&> : ResultType<T> {};
-//------------------------------------------------------
-template <class T>
-struct RemoveConst : ResultType<T> {};
-
-template <class T>
-struct RemoveConst<const T> : ResultType<T> {};
-//------------------------------------------------------
-template <class T>
-struct RemovePointer : ResultType<T> {};
-
-template <class T>
-struct RemovePointer<T*> : ResultType<T> {};
-//------------------------------------------------------
-template <class T>
-struct RemoveArray : ResultType<T> {};
-
-template <class T, int N>
-struct RemoveArray<T[N]> : ResultType<T> {};
-
-//########## Sorting ##############################
-/*
-Generate a descending binary predicate at compile time!
-
-Usage:
- static const bool ascending = ...
- makeSortDirection(old binary predicate, Int2Type<ascending>()) -> new binary predicate
-
-or directly;
- makeDescending(old binary predicate) -> new binary predicate
-*/
-
-template <class Predicate>
-struct LessDescending
-{
- LessDescending(Predicate lessThan) : lessThan_(lessThan) {}
- template <class T> bool operator()(const T& lhs, const T& rhs) const { return lessThan_(rhs, lhs); }
-private:
- Predicate lessThan_;
-};
-
-template <class Predicate> inline
-/**/ Predicate makeSortDirection(Predicate pred, Int2Type<true>) { return pred; }
-
-template <class Predicate> inline
-LessDescending<Predicate> makeSortDirection(Predicate pred, Int2Type<false>) { return pred; }
-
-template <class Predicate> inline
-LessDescending<Predicate> makeDescending(Predicate pred) { return pred; }
-}
-
-#endif //TYPE_TOOLS_H_45237590734254545
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef TYPE_TOOLS_H_45237590734254545
+#define TYPE_TOOLS_H_45237590734254545
+
+#include "type_traits.h"
+
+
+namespace zen
+{
+//########## Strawman Classes ##########################
+struct NullType {}; //:= no type here
+
+//########## Type Mapping ##############################
+template <int n>
+struct Int2Type {};
+//------------------------------------------------------
+template <class T>
+struct Type2Type {};
+
+//########## Control Structures ########################
+template <bool flag, class T, class U>
+struct SelectIf : ResultType<T> {};
+
+template <class T, class U>
+struct SelectIf<false, T, U> : ResultType<U> {};
+//------------------------------------------------------
+template <class T, class U>
+struct IsSameType : FalseType {};
+
+template <class T>
+struct IsSameType<T, T> : TrueType {};
+
+//------------------------------------------------------
+template <bool, class T = void>
+struct EnableIf {};
+
+template <class T>
+struct EnableIf<true, T> : ResultType<T> {};
+//########## Type Cleanup ##############################
+template <class T>
+struct RemoveRef : ResultType<T> {};
+
+template <class T>
+struct RemoveRef<T&> : ResultType<T> {};
+
+template <class T>
+struct RemoveRef<T&&> : ResultType<T> {};
+//------------------------------------------------------
+template <class T>
+struct RemoveConst : ResultType<T> {};
+
+template <class T>
+struct RemoveConst<const T> : ResultType<T> {};
+//------------------------------------------------------
+template <class T>
+struct RemovePointer : ResultType<T> {};
+
+template <class T>
+struct RemovePointer<T*> : ResultType<T> {};
+//------------------------------------------------------
+template <class T>
+struct RemoveArray : ResultType<T> {};
+
+template <class T, int N>
+struct RemoveArray<T[N]> : ResultType<T> {};
+
+//########## Sorting ##############################
+/*
+Generate a descending binary predicate at compile time!
+
+Usage:
+ static const bool ascending = ...
+ makeSortDirection(old binary predicate, Int2Type<ascending>()) -> new binary predicate
+
+or directly;
+ makeDescending(old binary predicate) -> new binary predicate
+*/
+
+template <class Predicate>
+struct LessDescending
+{
+ LessDescending(Predicate lessThan) : lessThan_(lessThan) {}
+ template <class T> bool operator()(const T& lhs, const T& rhs) const { return lessThan_(rhs, lhs); }
+private:
+ Predicate lessThan_;
+};
+
+template <class Predicate> inline
+/**/ Predicate makeSortDirection(Predicate pred, Int2Type<true>) { return pred; }
+
+template <class Predicate> inline
+LessDescending<Predicate> makeSortDirection(Predicate pred, Int2Type<false>) { return pred; }
+
+template <class Predicate> inline
+LessDescending<Predicate> makeDescending(Predicate pred) { return pred; }
+}
+
+#endif //TYPE_TOOLS_H_45237590734254545
diff --git a/zen/type_traits.h b/zen/type_traits.h
index 917b3258..f4e5cebd 100644..100755
--- a/zen/type_traits.h
+++ b/zen/type_traits.h
@@ -1,195 +1,195 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef TYPE_TRAITS_H_3425628658765467
-#define TYPE_TRAITS_H_3425628658765467
-
-#include <type_traits> //all we need is std::is_class!!
-
-
-namespace zen
-{
-//################# TMP compile time return values: "inherit to return compile-time result" ##############
-template <int i>
-struct StaticInt
-{
- enum { value = i };
-};
-
-template <bool b>
-struct StaticBool : StaticInt<b> {};
-
-using TrueType = StaticBool<true>;
-using FalseType = StaticBool<false>;
-
-template <class EnumType, EnumType val>
-struct StaticEnum
-{
- static const EnumType value = val;
-};
-//---------------------------------------------------------
-template <class T>
-struct ResultType
-{
- using Type = T;
-};
-
-//Herb Sutter's signedness conversion helpers: http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/
-template<class T> inline auto makeSigned (T t) { return static_cast<std::make_signed_t <T>>(t); }
-template<class T> inline auto makeUnsigned(T t) { return static_cast<std::make_unsigned_t<T>>(t); }
-
-//################# Built-in Types ########################
-//Example: "IsSignedInt<int>::value" 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> : TrueType {};
-
-//################# Class Members ########################
-
-/* Detect data or function members of a class by name: ZEN_INIT_DETECT_MEMBER + HasMember_
- Example: 1. ZEN_INIT_DETECT_MEMBER(c_str);
- 2. HasMember_c_str<T>::value -> use boolean
-*/
-
-/* Detect data or function members of a class by name *and* type: ZEN_INIT_DETECT_MEMBER2 + HasMember_
-
- Example: 1. ZEN_INIT_DETECT_MEMBER2(size, size_t (T::*)() const);
- 2. HasMember_size<T>::value -> use as boolean
-*/
-
-/* Detect member type of a class: ZEN_INIT_DETECT_MEMBER_TYPE + HasMemberType_
-
- Example: 1. ZEN_INIT_DETECT_MEMBER_TYPE(value_type);
- 2. HasMemberType_value_type<T>::value -> use as boolean
-*/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//################ implementation ######################
-#define ZEN_SPECIALIZE_TRAIT(X, Y) template <> struct X<Y> : TrueType {};
-
-template <class T>
-struct IsUnsignedInt : FalseType {};
-
-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 : FalseType {};
-
-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 : FalseType {};
-
-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 : StaticBool<IsUnsignedInt<T>::value || IsSignedInt<T>::value> {};
-
-template <class T>
-struct IsArithmetic : StaticBool<IsInteger<T>::value || IsFloat<T>::value> {};
-//####################################################################
-
-#define ZEN_INIT_DETECT_MEMBER(NAME) \
- \
- template<bool isClass, class T> \
- struct HasMemberImpl_##NAME \
- { \
- private: \
- using Yes = char[1]; \
- using No = char[2]; \
- \
- template <typename U, U t> \
- class Helper {}; \
- struct Fallback { int NAME; }; \
- \
- template <class U> \
- struct Helper2 : public U, public Fallback {}; /*this works only for class types!!!*/ \
- \
- template <class U> static No& hasMember(Helper<int Fallback::*, &Helper2<U>::NAME>*); \
- template <class U> static Yes& hasMember(...); \
- public: \
- enum { value = sizeof(hasMember<T>(nullptr)) == sizeof(Yes) }; \
- }; \
- \
- template<class T> \
- struct HasMemberImpl_##NAME<false, T> : FalseType {}; \
- \
- template<typename T> \
- struct HasMember_##NAME : StaticBool<HasMemberImpl_##NAME<std::is_class<T>::value, T>::value> {};
-
-//####################################################################
-
-#define ZEN_INIT_DETECT_MEMBER2(NAME, TYPE) \
- \
- template<typename U> \
- class HasMember_##NAME \
- { \
- using Yes = char[1]; \
- using No = char[2]; \
- \
- template <typename T, T t> class Helper {}; \
- \
- template <class T> static Yes& hasMember(Helper<TYPE, &T::NAME>*); \
- template <class T> static No& hasMember(...); \
- public: \
- enum { value = sizeof(hasMember<U>(nullptr)) == sizeof(Yes) }; \
- };
-//####################################################################
-
-#define ZEN_INIT_DETECT_MEMBER_TYPE(TYPENAME) \
- \
- template<typename T> \
- class HasMemberType_##TYPENAME \
- { \
- using Yes = char[1]; \
- using No = char[2]; \
- \
- template <typename U> class Helper {}; \
- \
- template <class U> static Yes& hasMemberType(Helper<typename U::TYPENAME>*); \
- template <class U> static No& hasMemberType(...); \
- public: \
- enum { value = sizeof(hasMemberType<T>(nullptr)) == sizeof(Yes) }; \
- };
-}
-
-#endif //TYPE_TRAITS_H_3425628658765467
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef TYPE_TRAITS_H_3425628658765467
+#define TYPE_TRAITS_H_3425628658765467
+
+#include <type_traits> //all we need is std::is_class!!
+
+
+namespace zen
+{
+//################# TMP compile time return values: "inherit to return compile-time result" ##############
+template <int i>
+struct StaticInt
+{
+ enum { value = i };
+};
+
+template <bool b>
+struct StaticBool : StaticInt<b> {};
+
+using TrueType = StaticBool<true>;
+using FalseType = StaticBool<false>;
+
+template <class EnumType, EnumType val>
+struct StaticEnum
+{
+ static const EnumType value = val;
+};
+//---------------------------------------------------------
+template <class T>
+struct ResultType
+{
+ using Type = T;
+};
+
+//Herb Sutter's signedness conversion helpers: http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/
+template<class T> inline auto makeSigned (T t) { return static_cast<std::make_signed_t <T>>(t); }
+template<class T> inline auto makeUnsigned(T t) { return static_cast<std::make_unsigned_t<T>>(t); }
+
+//################# Built-in Types ########################
+//Example: "IsSignedInt<int>::value" 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> : TrueType {};
+
+//################# Class Members ########################
+
+/* Detect data or function members of a class by name: ZEN_INIT_DETECT_MEMBER + HasMember_
+ Example: 1. ZEN_INIT_DETECT_MEMBER(c_str);
+ 2. HasMember_c_str<T>::value -> use boolean
+*/
+
+/* Detect data or function members of a class by name *and* type: ZEN_INIT_DETECT_MEMBER2 + HasMember_
+
+ Example: 1. ZEN_INIT_DETECT_MEMBER2(size, size_t (T::*)() const);
+ 2. HasMember_size<T>::value -> use as boolean
+*/
+
+/* Detect member type of a class: ZEN_INIT_DETECT_MEMBER_TYPE + HasMemberType_
+
+ Example: 1. ZEN_INIT_DETECT_MEMBER_TYPE(value_type);
+ 2. HasMemberType_value_type<T>::value -> use as boolean
+*/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################ implementation ######################
+#define ZEN_SPECIALIZE_TRAIT(X, Y) template <> struct X<Y> : TrueType {};
+
+template <class T>
+struct IsUnsignedInt : FalseType {};
+
+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 : FalseType {};
+
+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 : FalseType {};
+
+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 : StaticBool<IsUnsignedInt<T>::value || IsSignedInt<T>::value> {};
+
+template <class T>
+struct IsArithmetic : StaticBool<IsInteger<T>::value || IsFloat<T>::value> {};
+//####################################################################
+
+#define ZEN_INIT_DETECT_MEMBER(NAME) \
+ \
+ template<bool isClass, class T> \
+ struct HasMemberImpl_##NAME \
+ { \
+ private: \
+ using Yes = char[1]; \
+ using No = char[2]; \
+ \
+ template <typename U, U t> \
+ class Helper {}; \
+ struct Fallback { int NAME; }; \
+ \
+ template <class U> \
+ struct Helper2 : public U, public Fallback {}; /*this works only for class types!!!*/ \
+ \
+ template <class U> static No& hasMember(Helper<int Fallback::*, &Helper2<U>::NAME>*); \
+ template <class U> static Yes& hasMember(...); \
+ public: \
+ enum { value = sizeof(hasMember<T>(nullptr)) == sizeof(Yes) }; \
+ }; \
+ \
+ template<class T> \
+ struct HasMemberImpl_##NAME<false, T> : FalseType {}; \
+ \
+ template<typename T> \
+ struct HasMember_##NAME : StaticBool<HasMemberImpl_##NAME<std::is_class<T>::value, T>::value> {};
+
+//####################################################################
+
+#define ZEN_INIT_DETECT_MEMBER2(NAME, TYPE) \
+ \
+ template<typename U> \
+ class HasMember_##NAME \
+ { \
+ using Yes = char[1]; \
+ using No = char[2]; \
+ \
+ template <typename T, T t> class Helper {}; \
+ \
+ template <class T> static Yes& hasMember(Helper<TYPE, &T::NAME>*); \
+ template <class T> static No& hasMember(...); \
+ public: \
+ enum { value = sizeof(hasMember<U>(nullptr)) == sizeof(Yes) }; \
+ };
+//####################################################################
+
+#define ZEN_INIT_DETECT_MEMBER_TYPE(TYPENAME) \
+ \
+ template<typename T> \
+ class HasMemberType_##TYPENAME \
+ { \
+ using Yes = char[1]; \
+ using No = char[2]; \
+ \
+ template <typename U> class Helper {}; \
+ \
+ template <class U> static Yes& hasMemberType(Helper<typename U::TYPENAME>*); \
+ template <class U> static No& hasMemberType(...); \
+ public: \
+ enum { value = sizeof(hasMemberType<T>(nullptr)) == sizeof(Yes) }; \
+ };
+}
+
+#endif //TYPE_TRAITS_H_3425628658765467
diff --git a/zen/utf.h b/zen/utf.h
index 1544c9ab..41fdf58c 100644..100755
--- a/zen/utf.h
+++ b/zen/utf.h
@@ -1,476 +1,476 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef UTF_H_01832479146991573473545
-#define UTF_H_01832479146991573473545
-
-#include <cstdint>
-#include <iterator>
-#include "string_tools.h" //copyStringTo
-
-namespace zen
-{
-//convert all(!) char- and wchar_t-based "string-like" objects applying a UTF8 conversions (but only if necessary!)
-template <class TargetString, class SourceString>
-TargetString utfCvrtTo(const SourceString& str);
-
-const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF";
-
-template <class CharString>
-bool isValidUtf8(const CharString& str); //check for UTF-8 encoding errors
-
-//---- explicit conversion: wide <-> utf8 ----
-template <class CharString, class WideString>
-CharString wideToUtf8(const WideString& str); //example: std::string tmp = wideToUtf8<std::string>(L"abc");
-
-template <class WideString, class CharString>
-WideString utf8ToWide(const CharString& str); //std::wstring tmp = utf8ToWide<std::wstring>("abc");
-
-//access unicode characters in UTF-encoded string (char- or wchar_t-based)
-template <class UtfString>
-size_t unicodeLength(const UtfString& str); //return number of code points for UTF-encoded string
-
-template <class UtfString>
-size_t findUnicodePos(const UtfString& str, size_t unicodePos); //return position of unicode char in UTF-encoded string
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//----------------------- implementation ----------------------------------
-namespace implementation
-{
-using CodePoint = std::uint32_t;
-using Char16 = std::uint16_t;
-using Char8 = unsigned char;
-
-const CodePoint LEAD_SURROGATE = 0xd800;
-const CodePoint TRAIL_SURROGATE = 0xdc00; //== LEAD_SURROGATE_MAX + 1
-const CodePoint TRAIL_SURROGATE_MAX = 0xdfff;
-
-const CodePoint REPLACEMENT_CHAR = 0xfffd;
-const CodePoint CODE_POINT_MAX = 0x10ffff;
-
-
-template <class Function> inline
-void codePointToUtf16(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a Char16
-{
- //http://en.wikipedia.org/wiki/UTF-16
-
- if (cp < LEAD_SURROGATE)
- writeOutput(static_cast<Char16>(cp));
- else if (cp <= TRAIL_SURROGATE_MAX) //invalid code point
- codePointToUtf16(REPLACEMENT_CHAR, writeOutput); //resolves to 1-character utf16
- else if (cp < 0x10000)
- writeOutput(static_cast<Char16>(cp));
- else if (cp <= CODE_POINT_MAX)
- {
- cp -= 0x10000;
- writeOutput(LEAD_SURROGATE + static_cast<Char16>(cp >> 10));
- writeOutput(TRAIL_SURROGATE + static_cast<Char16>(cp & 0x3ff));
- }
- else //invalid code point
- codePointToUtf16(REPLACEMENT_CHAR, writeOutput); //resolves to 1-character utf16
-}
-
-
-inline
-size_t getUtf16Len(Char16 ch) //ch must be first code unit! returns 0 on error!
-{
- if (ch < LEAD_SURROGATE)
- return 1;
- else if (ch < TRAIL_SURROGATE)
- return 2;
- else if (ch <= TRAIL_SURROGATE_MAX)
- return 0; //unexpected trail surrogate!
- else
- return 1;
-}
-
-
-template <class CharIterator, class Function> inline
-void utf16ToCodePoint(CharIterator first, CharIterator last, Function writeOutput) //"writeOutput" is a unary function taking a CodePoint
-{
- static_assert(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 2, "");
-
- for ( ; first != last; ++first)
- {
- CodePoint cp = static_cast<Char16>(*first);
- switch (getUtf16Len(static_cast<Char16>(cp)))
- {
- case 0: //invalid utf16 character
- cp = REPLACEMENT_CHAR;
- break;
- case 1:
- break;
- case 2:
- if (++first != last) //trail surrogate expected!
- {
- const Char16 ch = static_cast<Char16>(*first);
- if (TRAIL_SURROGATE <= ch && ch <= TRAIL_SURROGATE_MAX) //trail surrogate expected!
- {
- cp = ((cp - LEAD_SURROGATE) << 10) + (ch - TRAIL_SURROGATE) + 0x10000;
- break;
- }
- }
- --first;
- cp = REPLACEMENT_CHAR;
- break;
- }
- writeOutput(cp);
- }
-}
-
-
-template <class Function> inline
-void codePointToUtf8(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a Char8
-{
- //http://en.wikipedia.org/wiki/UTF-8
- //assert(cp < LEAD_SURROGATE || TRAIL_SURROGATE_MAX < cp); //code points [0xd800, 0xdfff] are reserved for UTF-16 and *should* not be encoded in UTF-8
-
- if (cp < 0x80)
- writeOutput(static_cast<Char8>(cp));
- else if (cp < 0x800)
- {
- writeOutput(static_cast<Char8>((cp >> 6 ) | 0xc0));
- writeOutput(static_cast<Char8>((cp & 0x3f) | 0x80));
- }
- else if (cp < 0x10000)
- {
- writeOutput(static_cast<Char8>( (cp >> 12 ) | 0xe0));
- writeOutput(static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80));
- writeOutput(static_cast<Char8>( (cp & 0x3f ) | 0x80));
- }
- else if (cp <= CODE_POINT_MAX)
- {
- writeOutput(static_cast<Char8>( (cp >> 18 ) | 0xf0));
- writeOutput(static_cast<Char8>(((cp >> 12) & 0x3f) | 0x80));
- writeOutput(static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80));
- writeOutput(static_cast<Char8>( (cp & 0x3f ) | 0x80));
- }
- else //invalid code point
- codePointToUtf8(REPLACEMENT_CHAR, writeOutput); //resolves to 3-byte utf8
-}
-
-
-inline
-size_t getUtf8Len(unsigned char ch) //ch must be first code unit! returns 0 on error!
-{
- if (ch < 0x80)
- return 1;
- if (ch >> 5 == 0x6)
- return 2;
- if (ch >> 4 == 0xe)
- return 3;
- if (ch >> 3 == 0x1e)
- return 4;
- return 0; //innvalid begin of UTF8 encoding
-}
-
-
-template <class CharIterator> inline
-bool decodeTrail(CharIterator& first, CharIterator last, CodePoint& cp) //decode trailing surrogate byte
-{
- if (++first != last) //trail surrogate expected!
- {
- const Char8 ch = static_cast<Char8>(*first);
- if (ch >> 6 == 0x2) //trail surrogate expected!
- {
- cp = (cp << 6) + (ch & 0x3f);
- return true;
- }
- }
- --first;
- cp = REPLACEMENT_CHAR;
- return false;
-}
-
-template <class CharIterator, class Function> inline
-void utf8ToCodePoint(CharIterator first, CharIterator last, Function writeOutput) //"writeOutput" is a unary function taking a CodePoint
-{
- static_assert(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 1, "");
-
- for ( ; first != last; ++first)
- {
- CodePoint cp = static_cast<Char8>(*first);
- switch (getUtf8Len(static_cast<Char8>(cp)))
- {
- case 0: //invalid utf8 character
- cp = REPLACEMENT_CHAR;
- break;
- case 1:
- break;
- case 2:
- cp &= 0x1f;
- decodeTrail(first, last, cp);
- break;
- case 3:
- cp &= 0xf;
- if (decodeTrail(first, last, cp))
- decodeTrail(first, last, cp);
- break;
- case 4:
- cp &= 0x7;
- if (decodeTrail(first, last, cp))
- if (decodeTrail(first, last, cp))
- decodeTrail(first, last, cp);
- if (cp > CODE_POINT_MAX) cp = REPLACEMENT_CHAR;
- break;
- }
- writeOutput(cp);
- }
-}
-
-
-template <class CharString> inline
-size_t unicodeLength(const CharString& str, char) //utf8
-{
- using CharType = typename GetCharType<CharString>::Type;
-
- const CharType* strFirst = strBegin(str);
- const CharType* const strLast = strFirst + strLength(str);
-
- size_t len = 0;
- while (strFirst < strLast) //[!]
- {
- ++len;
- size_t utf8len = getUtf8Len(*strFirst);
- if (utf8len == 0) ++utf8len; //invalid utf8 character
- strFirst += utf8len;
- }
- return len;
-}
-
-
-template <class WideString> inline
-size_t unicodeLengthWide(const WideString& str, Int2Type<2>) //windows: utf16-wchar_t
-{
- using CharType = typename GetCharType<WideString>::Type;
-
- const CharType* strFirst = strBegin(str);
- const CharType* const strLast = strFirst + strLength(str);
-
- size_t len = 0;
- while (strFirst < strLast) //[!]
- {
- ++len;
- size_t utf16len = getUtf16Len(*strFirst);
- if (utf16len == 0) ++utf16len; //invalid utf16 character
- strFirst += utf16len;
- }
- return len;
-}
-
-
-template <class WideString> inline
-size_t unicodeLengthWide(const WideString& str, Int2Type<4>) //other OS: utf32-wchar_t
-{
- return strLength(str);
-}
-
-
-template <class WideString> inline
-size_t unicodeLength(const WideString& str, wchar_t)
-{
- return unicodeLengthWide(str, Int2Type<sizeof(wchar_t)>());
-}
-}
-
-
-template <class UtfString> inline
-size_t unicodeLength(const UtfString& str) //return number of code points
-{
- return implementation::unicodeLength(str, typename GetCharType<UtfString>::Type());
-}
-
-
-namespace implementation
-{
-template <class CharString> inline
-size_t findUnicodePos(const CharString& str, size_t unicodePos, char) //utf8-char
-{
- using CharType = typename GetCharType<CharString>::Type;
-
- const CharType* strFirst = strBegin(str);
- const size_t strLen = strLength(str);
-
- size_t utfPos = 0;
- while (unicodePos-- > 0)
- {
- if (utfPos >= strLen)
- return strLen;
-
- size_t utf8len = getUtf8Len(strFirst[utfPos]);
- if (utf8len == 0) ++utf8len; //invalid utf8 character
- utfPos += utf8len;
- }
- if (utfPos >= strLen)
- return strLen;
- return utfPos;
-}
-
-
-template <class WideString> inline
-size_t findUnicodePosWide(const WideString& str, size_t unicodePos, Int2Type<2>) //windows: utf16-wchar_t
-{
- using CharType = typename GetCharType<WideString>::Type;
-
- const CharType* strFirst = strBegin(str);
- const size_t strLen = strLength(str);
-
- size_t utfPos = 0;
- while (unicodePos-- > 0)
- {
- if (utfPos >= strLen)
- return strLen;
-
- size_t utf16len = getUtf16Len(strFirst[utfPos]);
- if (utf16len == 0) ++utf16len; //invalid utf16 character
- utfPos += utf16len;
- }
- if (utfPos >= strLen)
- return strLen;
- return utfPos;
-}
-
-
-template <class WideString> inline
-size_t findUnicodePosWide(const WideString& str, size_t unicodePos, Int2Type<4>) //other OS: utf32-wchar_t
-{
- return std::min(strLength(str), unicodePos);
-}
-
-
-template <class UtfString> inline
-size_t findUnicodePos(const UtfString& str, size_t unicodePos, wchar_t)
-{
- return findUnicodePosWide(str, unicodePos, Int2Type<sizeof(wchar_t)>());
-}
-}
-
-
-template <class UtfString> inline
-size_t findUnicodePos(const UtfString& str, size_t unicodePos) //return position of unicode char in UTF-encoded string
-{
- return implementation::findUnicodePos(str, unicodePos, typename GetCharType<UtfString>::Type());
-}
-
-//-------------------------------------------------------------------------------------------
-
-namespace implementation
-{
-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, [&](Char16 c) { output += static_cast<wchar_t>(c); }); });
- 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 += static_cast<wchar_t>(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, [&](char c) { output += c; }); });
- 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, [&](char c) { output += c; }); });
- return output;
-}
-}
-
-
-template <class CharString> inline
-bool isValidUtf8(const CharString& str)
-{
- using namespace implementation;
- bool valid = true;
- utf8ToCodePoint(strBegin(str), strBegin(str) + strLength(str),
- [&](CodePoint cp)
- {
- if (cp == REPLACEMENT_CHAR)
- valid = false; //perf: should we use an (expensive) exception for iteration break?
- });
- return valid;
-}
-
-
-template <class WideString, class CharString> inline
-WideString utf8ToWide(const CharString& str)
-{
- static_assert(IsSameType<typename GetCharType<CharString>::Type, char >::value, "");
- static_assert(IsSameType<typename GetCharType<WideString>::Type, wchar_t>::value, "");
-
- return implementation::utf8ToWide<WideString>(str, Int2Type<sizeof(wchar_t)>());
-}
-
-
-template <class CharString, class WideString> inline
-CharString wideToUtf8(const WideString& str)
-{
- static_assert(IsSameType<typename GetCharType<CharString>::Type, char >::value, "");
- static_assert(IsSameType<typename GetCharType<WideString>::Type, wchar_t>::value, "");
-
- return implementation::wideToUtf8<CharString>(str, Int2Type<sizeof(wchar_t)>());
-}
-
-//-------------------------------------------------------------------------------------------
-
-template <class TargetString, class SourceString> inline
-TargetString utfCvrtTo(const SourceString& str, char, wchar_t) { return utf8ToWide<TargetString>(str); }
-
-template <class TargetString, class SourceString> inline
-TargetString utfCvrtTo(const SourceString& str, wchar_t, char) { return wideToUtf8<TargetString>(str); }
-
-template <class TargetString, class SourceString> inline
-TargetString utfCvrtTo(const SourceString& str, char, char) { return copyStringTo<TargetString>(str); }
-
-template <class TargetString, class SourceString> inline
-TargetString utfCvrtTo(const SourceString& str, wchar_t, wchar_t) { return copyStringTo<TargetString>(str); }
-
-template <class TargetString, class SourceString> inline
-TargetString utfCvrtTo(const SourceString& str)
-{
- return utfCvrtTo<TargetString>(str,
- typename GetCharType<SourceString>::Type(),
- typename GetCharType<TargetString>::Type());
-}
-}
-
-#endif //UTF_H_01832479146991573473545
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef UTF_H_01832479146991573473545
+#define UTF_H_01832479146991573473545
+
+#include <cstdint>
+#include <iterator>
+#include "string_tools.h" //copyStringTo
+
+namespace zen
+{
+//convert all(!) char- and wchar_t-based "string-like" objects applying a UTF8 conversions (but only if necessary!)
+template <class TargetString, class SourceString>
+TargetString utfCvrtTo(const SourceString& str);
+
+const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF";
+
+template <class CharString>
+bool isValidUtf8(const CharString& str); //check for UTF-8 encoding errors
+
+//---- explicit conversion: wide <-> utf8 ----
+template <class CharString, class WideString>
+CharString wideToUtf8(const WideString& str); //example: std::string tmp = wideToUtf8<std::string>(L"abc");
+
+template <class WideString, class CharString>
+WideString utf8ToWide(const CharString& str); //std::wstring tmp = utf8ToWide<std::wstring>("abc");
+
+//access unicode characters in UTF-encoded string (char- or wchar_t-based)
+template <class UtfString>
+size_t unicodeLength(const UtfString& str); //return number of code points for UTF-encoded string
+
+template <class UtfString>
+size_t findUnicodePos(const UtfString& str, size_t unicodePos); //return position of unicode char in UTF-encoded string
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//----------------------- implementation ----------------------------------
+namespace implementation
+{
+using CodePoint = uint32_t;
+using Char16 = uint16_t;
+using Char8 = unsigned char;
+
+const CodePoint LEAD_SURROGATE = 0xd800;
+const CodePoint TRAIL_SURROGATE = 0xdc00; //== LEAD_SURROGATE_MAX + 1
+const CodePoint TRAIL_SURROGATE_MAX = 0xdfff;
+
+const CodePoint REPLACEMENT_CHAR = 0xfffd;
+const CodePoint CODE_POINT_MAX = 0x10ffff;
+
+
+template <class Function> inline
+void codePointToUtf16(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a Char16
+{
+ //http://en.wikipedia.org/wiki/UTF-16
+
+ if (cp < LEAD_SURROGATE)
+ writeOutput(static_cast<Char16>(cp));
+ else if (cp <= TRAIL_SURROGATE_MAX) //invalid code point
+ codePointToUtf16(REPLACEMENT_CHAR, writeOutput); //resolves to 1-character utf16
+ else if (cp < 0x10000)
+ writeOutput(static_cast<Char16>(cp));
+ else if (cp <= CODE_POINT_MAX)
+ {
+ cp -= 0x10000;
+ writeOutput(LEAD_SURROGATE + static_cast<Char16>(cp >> 10));
+ writeOutput(TRAIL_SURROGATE + static_cast<Char16>(cp & 0x3ff));
+ }
+ else //invalid code point
+ codePointToUtf16(REPLACEMENT_CHAR, writeOutput); //resolves to 1-character utf16
+}
+
+
+inline
+size_t getUtf16Len(Char16 ch) //ch must be first code unit! returns 0 on error!
+{
+ if (ch < LEAD_SURROGATE)
+ return 1;
+ else if (ch < TRAIL_SURROGATE)
+ return 2;
+ else if (ch <= TRAIL_SURROGATE_MAX)
+ return 0; //unexpected trail surrogate!
+ else
+ return 1;
+}
+
+
+template <class CharIterator, class Function> inline
+void utf16ToCodePoint(CharIterator first, CharIterator last, Function writeOutput) //"writeOutput" is a unary function taking a CodePoint
+{
+ static_assert(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 2, "");
+
+ for ( ; first != last; ++first)
+ {
+ CodePoint cp = static_cast<Char16>(*first);
+ switch (getUtf16Len(static_cast<Char16>(cp)))
+ {
+ case 0: //invalid utf16 character
+ cp = REPLACEMENT_CHAR;
+ break;
+ case 1:
+ break;
+ case 2:
+ if (++first != last) //trail surrogate expected!
+ {
+ const Char16 ch = static_cast<Char16>(*first);
+ if (TRAIL_SURROGATE <= ch && ch <= TRAIL_SURROGATE_MAX) //trail surrogate expected!
+ {
+ cp = ((cp - LEAD_SURROGATE) << 10) + (ch - TRAIL_SURROGATE) + 0x10000;
+ break;
+ }
+ }
+ --first;
+ cp = REPLACEMENT_CHAR;
+ break;
+ }
+ writeOutput(cp);
+ }
+}
+
+
+template <class Function> inline
+void codePointToUtf8(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a Char8
+{
+ //http://en.wikipedia.org/wiki/UTF-8
+ //assert(cp < LEAD_SURROGATE || TRAIL_SURROGATE_MAX < cp); //code points [0xd800, 0xdfff] are reserved for UTF-16 and *should* not be encoded in UTF-8
+
+ if (cp < 0x80)
+ writeOutput(static_cast<Char8>(cp));
+ else if (cp < 0x800)
+ {
+ writeOutput(static_cast<Char8>((cp >> 6 ) | 0xc0));
+ writeOutput(static_cast<Char8>((cp & 0x3f) | 0x80));
+ }
+ else if (cp < 0x10000)
+ {
+ writeOutput(static_cast<Char8>( (cp >> 12 ) | 0xe0));
+ writeOutput(static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80));
+ writeOutput(static_cast<Char8>( (cp & 0x3f ) | 0x80));
+ }
+ else if (cp <= CODE_POINT_MAX)
+ {
+ writeOutput(static_cast<Char8>( (cp >> 18 ) | 0xf0));
+ writeOutput(static_cast<Char8>(((cp >> 12) & 0x3f) | 0x80));
+ writeOutput(static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80));
+ writeOutput(static_cast<Char8>( (cp & 0x3f ) | 0x80));
+ }
+ else //invalid code point
+ codePointToUtf8(REPLACEMENT_CHAR, writeOutput); //resolves to 3-byte utf8
+}
+
+
+inline
+size_t getUtf8Len(unsigned char ch) //ch must be first code unit! returns 0 on error!
+{
+ if (ch < 0x80)
+ return 1;
+ if (ch >> 5 == 0x6)
+ return 2;
+ if (ch >> 4 == 0xe)
+ return 3;
+ if (ch >> 3 == 0x1e)
+ return 4;
+ return 0; //innvalid begin of UTF8 encoding
+}
+
+
+template <class CharIterator> inline
+bool decodeTrail(CharIterator& first, CharIterator last, CodePoint& cp) //decode trailing surrogate byte
+{
+ if (++first != last) //trail surrogate expected!
+ {
+ const Char8 ch = static_cast<Char8>(*first);
+ if (ch >> 6 == 0x2) //trail surrogate expected!
+ {
+ cp = (cp << 6) + (ch & 0x3f);
+ return true;
+ }
+ }
+ --first;
+ cp = REPLACEMENT_CHAR;
+ return false;
+}
+
+template <class CharIterator, class Function> inline
+void utf8ToCodePoint(CharIterator first, CharIterator last, Function writeOutput) //"writeOutput" is a unary function taking a CodePoint
+{
+ static_assert(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 1, "");
+
+ for ( ; first != last; ++first)
+ {
+ CodePoint cp = static_cast<Char8>(*first);
+ switch (getUtf8Len(static_cast<Char8>(cp)))
+ {
+ case 0: //invalid utf8 character
+ cp = REPLACEMENT_CHAR;
+ break;
+ case 1:
+ break;
+ case 2:
+ cp &= 0x1f;
+ decodeTrail(first, last, cp);
+ break;
+ case 3:
+ cp &= 0xf;
+ if (decodeTrail(first, last, cp))
+ decodeTrail(first, last, cp);
+ break;
+ case 4:
+ cp &= 0x7;
+ if (decodeTrail(first, last, cp))
+ if (decodeTrail(first, last, cp))
+ decodeTrail(first, last, cp);
+ if (cp > CODE_POINT_MAX) cp = REPLACEMENT_CHAR;
+ break;
+ }
+ writeOutput(cp);
+ }
+}
+
+
+template <class CharString> inline
+size_t unicodeLength(const CharString& str, char) //utf8
+{
+ using CharType = typename GetCharType<CharString>::Type;
+
+ const CharType* strFirst = strBegin(str);
+ const CharType* const strLast = strFirst + strLength(str);
+
+ size_t len = 0;
+ while (strFirst < strLast) //[!]
+ {
+ ++len;
+ size_t utf8len = getUtf8Len(*strFirst);
+ if (utf8len == 0) ++utf8len; //invalid utf8 character
+ strFirst += utf8len;
+ }
+ return len;
+}
+
+
+template <class WideString> inline
+size_t unicodeLengthWide(const WideString& str, Int2Type<2>) //windows: utf16-wchar_t
+{
+ using CharType = typename GetCharType<WideString>::Type;
+
+ const CharType* strFirst = strBegin(str);
+ const CharType* const strLast = strFirst + strLength(str);
+
+ size_t len = 0;
+ while (strFirst < strLast) //[!]
+ {
+ ++len;
+ size_t utf16len = getUtf16Len(*strFirst);
+ if (utf16len == 0) ++utf16len; //invalid utf16 character
+ strFirst += utf16len;
+ }
+ return len;
+}
+
+
+template <class WideString> inline
+size_t unicodeLengthWide(const WideString& str, Int2Type<4>) //other OS: utf32-wchar_t
+{
+ return strLength(str);
+}
+
+
+template <class WideString> inline
+size_t unicodeLength(const WideString& str, wchar_t)
+{
+ return unicodeLengthWide(str, Int2Type<sizeof(wchar_t)>());
+}
+}
+
+
+template <class UtfString> inline
+size_t unicodeLength(const UtfString& str) //return number of code points
+{
+ return implementation::unicodeLength(str, typename GetCharType<UtfString>::Type());
+}
+
+
+namespace implementation
+{
+template <class CharString> inline
+size_t findUnicodePos(const CharString& str, size_t unicodePos, char) //utf8-char
+{
+ using CharType = typename GetCharType<CharString>::Type;
+
+ const CharType* strFirst = strBegin(str);
+ const size_t strLen = strLength(str);
+
+ size_t utfPos = 0;
+ while (unicodePos-- > 0)
+ {
+ if (utfPos >= strLen)
+ return strLen;
+
+ size_t utf8len = getUtf8Len(strFirst[utfPos]);
+ if (utf8len == 0) ++utf8len; //invalid utf8 character
+ utfPos += utf8len;
+ }
+ if (utfPos >= strLen)
+ return strLen;
+ return utfPos;
+}
+
+
+template <class WideString> inline
+size_t findUnicodePosWide(const WideString& str, size_t unicodePos, Int2Type<2>) //windows: utf16-wchar_t
+{
+ using CharType = typename GetCharType<WideString>::Type;
+
+ const CharType* strFirst = strBegin(str);
+ const size_t strLen = strLength(str);
+
+ size_t utfPos = 0;
+ while (unicodePos-- > 0)
+ {
+ if (utfPos >= strLen)
+ return strLen;
+
+ size_t utf16len = getUtf16Len(strFirst[utfPos]);
+ if (utf16len == 0) ++utf16len; //invalid utf16 character
+ utfPos += utf16len;
+ }
+ if (utfPos >= strLen)
+ return strLen;
+ return utfPos;
+}
+
+
+template <class WideString> inline
+size_t findUnicodePosWide(const WideString& str, size_t unicodePos, Int2Type<4>) //other OS: utf32-wchar_t
+{
+ return std::min(strLength(str), unicodePos);
+}
+
+
+template <class UtfString> inline
+size_t findUnicodePos(const UtfString& str, size_t unicodePos, wchar_t)
+{
+ return findUnicodePosWide(str, unicodePos, Int2Type<sizeof(wchar_t)>());
+}
+}
+
+
+template <class UtfString> inline
+size_t findUnicodePos(const UtfString& str, size_t unicodePos) //return position of unicode char in UTF-encoded string
+{
+ return implementation::findUnicodePos(str, unicodePos, typename GetCharType<UtfString>::Type());
+}
+
+//-------------------------------------------------------------------------------------------
+
+namespace implementation
+{
+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, [&](Char16 c) { output += static_cast<wchar_t>(c); }); });
+ 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 += static_cast<wchar_t>(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, [&](char c) { output += c; }); });
+ 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, [&](char c) { output += c; }); });
+ return output;
+}
+}
+
+
+template <class CharString> inline
+bool isValidUtf8(const CharString& str)
+{
+ using namespace implementation;
+ bool valid = true;
+ utf8ToCodePoint(strBegin(str), strBegin(str) + strLength(str),
+ [&](CodePoint cp)
+ {
+ if (cp == REPLACEMENT_CHAR)
+ valid = false; //perf: should we use an (expensive) exception for iteration break?
+ });
+ return valid;
+}
+
+
+template <class WideString, class CharString> inline
+WideString utf8ToWide(const CharString& str)
+{
+ static_assert(IsSameType<typename GetCharType<CharString>::Type, char >::value, "");
+ static_assert(IsSameType<typename GetCharType<WideString>::Type, wchar_t>::value, "");
+
+ return implementation::utf8ToWide<WideString>(str, Int2Type<sizeof(wchar_t)>());
+}
+
+
+template <class CharString, class WideString> inline
+CharString wideToUtf8(const WideString& str)
+{
+ static_assert(IsSameType<typename GetCharType<CharString>::Type, char >::value, "");
+ static_assert(IsSameType<typename GetCharType<WideString>::Type, wchar_t>::value, "");
+
+ return implementation::wideToUtf8<CharString>(str, Int2Type<sizeof(wchar_t)>());
+}
+
+//-------------------------------------------------------------------------------------------
+
+template <class TargetString, class SourceString> inline
+TargetString utfCvrtTo(const SourceString& str, char, wchar_t) { return utf8ToWide<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utfCvrtTo(const SourceString& str, wchar_t, char) { return wideToUtf8<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utfCvrtTo(const SourceString& str, char, char) { return copyStringTo<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utfCvrtTo(const SourceString& str, wchar_t, wchar_t) { return copyStringTo<TargetString>(str); }
+
+template <class TargetString, class SourceString> inline
+TargetString utfCvrtTo(const SourceString& str)
+{
+ return utfCvrtTo<TargetString>(str,
+ typename GetCharType<SourceString>::Type(),
+ typename GetCharType<TargetString>::Type());
+}
+}
+
+#endif //UTF_H_01832479146991573473545
diff --git a/zen/warn_static.h b/zen/warn_static.h
new file mode 100755
index 00000000..74dbd627
--- /dev/null
+++ b/zen/warn_static.h
@@ -0,0 +1,24 @@
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef WARN_STATIC_H_08724567834560832745
+#define WARN_STATIC_H_08724567834560832745
+
+/*
+Portable Compile-Time Warning
+-----------------------------
+Usage:
+ warn_static("my message")
+*/
+
+#define STATIC_WARNING_CONCAT_SUB(X, Y) X ## Y
+#define STATIC_WARNING_CONCAT(X, Y) STATIC_WARNING_CONCAT_SUB(X, Y)
+
+#define warn_static(TXT) \
+ typedef int STATIC_WARNING_87903124 __attribute__ ((deprecated)); \
+ enum { STATIC_WARNING_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING_87903124) };
+
+#endif //WARN_STATIC_H_08724567834560832745
diff --git a/zen/xml_io.cpp b/zen/xml_io.cpp
index 4b77c851..d3d59200 100644..100755
--- a/zen/xml_io.cpp
+++ b/zen/xml_io.cpp
@@ -1,85 +1,83 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "xml_io.h"
-#include "file_access.h"
-#include "file_io.h"
-
-using namespace zen;
-
-
-XmlDoc zen::loadXmlDocument(const Zstring& filepath) //throw FileError
-{
- //can't simply use zen::unbufferedLoad) due to the short-circuit xml-validation below!
-
- FileInput fileIn(filepath); //throw FileError, ErrorFileLocked
- const size_t blockSize = fileIn.getBlockSize();
- const std::string xmlPrefix = "<?xml version=";
- bool xmlPrefixChecked = false;
-
- std::string buffer;
- for (;;)
- {
- buffer.resize(buffer.size() + blockSize);
- const size_t bytesRead = fileIn.tryRead(&*(buffer.end() - blockSize), blockSize); //throw X; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
- buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
-
- //quick test whether input is an XML: avoid loading large binary files up front!
- if (!xmlPrefixChecked && buffer.size() >= xmlPrefix.size() + strLength(BYTE_ORDER_MARK_UTF8))
- {
- xmlPrefixChecked = true;
- if (!startsWith(buffer, xmlPrefix) &&
- !startsWith(buffer, BYTE_ORDER_MARK_UTF8 + xmlPrefix)) //allow BOM!
- throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filepath)));
- }
-
- if (bytesRead == 0) //end of file
- break;
- }
-
- try
- {
- return parse(buffer); //throw XmlParsingError
- }
- catch (const XmlParsingError& e)
- {
- throw FileError(
- replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."),
- L"%x", fmtPath(filepath)),
- L"%y", numberTo<std::wstring>(e.row + 1)),
- L"%z", numberTo<std::wstring>(e.col + 1)));
- }
-}
-
-
-void zen::saveXmlDocument(const XmlDoc& doc, const Zstring& filepath) //throw FileError
-{
- const std::string stream = serialize(doc); //noexcept
-
- //only update xml file if there are real changes
- try
- {
- if (getFilesize(filepath) == stream.size()) //throw FileError
- if (loadBinContainer<std::string>(filepath, nullptr) == stream) //throw FileError
- return;
- }
- catch (FileError&) {}
-
- saveBinContainer(filepath, stream, nullptr); //throw FileError
-}
-
-
-void zen::checkForMappingErrors(const XmlIn& xmlInput, const Zstring& filepath) //throw FileError
-{
- if (xmlInput.errorsOccured())
- {
- std::wstring msg = _("The following XML elements could not be read:") + L"\n";
- for (const std::wstring& elem : xmlInput.getErrorsAs<std::wstring>())
- msg += L"\n" + elem;
-
- throw FileError(replaceCpy(_("Configuration file %x is incomplete. The missing elements will be set to their default values."), L"%x", fmtPath(filepath)) + L"\n\n" + msg);
- }
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "xml_io.h"
+#include "file_access.h"
+#include "file_io.h"
+
+using namespace zen;
+
+
+XmlDoc zen::loadXmlDocument(const Zstring& filePath) //throw FileError
+{
+ FileInput fileIn(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError, ErrorFileLocked
+ const size_t blockSize = fileIn.getBlockSize();
+ const std::string xmlPrefix = "<?xml version=";
+ bool xmlPrefixChecked = false;
+
+ std::string buffer;
+ for (;;)
+ {
+ buffer.resize(buffer.size() + blockSize);
+ const size_t bytesRead = fileIn.read(&*(buffer.end() - blockSize), blockSize); //throw FileError, (X); return "bytesToRead" bytes unless end of stream!
+ buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
+
+ //quick test whether input is an XML: avoid loading large binary files up front!
+ if (!xmlPrefixChecked && buffer.size() >= xmlPrefix.size() + strLength(BYTE_ORDER_MARK_UTF8))
+ {
+ xmlPrefixChecked = true;
+ if (!startsWith(buffer, xmlPrefix) &&
+ !startsWith(buffer, BYTE_ORDER_MARK_UTF8 + xmlPrefix)) //allow BOM!
+ throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath)));
+ }
+
+ if (bytesRead < blockSize) //end of file
+ break;
+ }
+
+ try
+ {
+ return parse(buffer); //throw XmlParsingError
+ }
+ catch (const XmlParsingError& e)
+ {
+ throw FileError(
+ replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."),
+ L"%x", fmtPath(filePath)),
+ L"%y", numberTo<std::wstring>(e.row + 1)),
+ L"%z", numberTo<std::wstring>(e.col + 1)));
+ }
+}
+
+
+void zen::saveXmlDocument(const XmlDoc& doc, const Zstring& filePath) //throw FileError
+{
+ const std::string stream = serialize(doc); //noexcept
+
+ //only update xml file if there are real changes
+ try
+ {
+ if (getFileSize(filePath) == stream.size()) //throw FileError
+ if (loadBinContainer<std::string>(filePath, nullptr /*notifyUnbufferedIO*/) == stream) //throw FileError
+ return;
+ }
+ catch (FileError&) {}
+
+ saveBinContainer(filePath, stream, nullptr /*notifyUnbufferedIO*/); //throw FileError
+}
+
+
+void zen::checkForMappingErrors(const XmlIn& xmlInput, const Zstring& filePath) //throw FileError
+{
+ if (xmlInput.errorsOccured())
+ {
+ std::wstring msg = _("The following XML elements could not be read:") + L"\n";
+ for (const std::wstring& elem : xmlInput.getErrorsAs<std::wstring>())
+ msg += L"\n" + elem;
+
+ throw FileError(replaceCpy(_("Configuration file %x is incomplete. The missing elements will be set to their default values."), L"%x", fmtPath(filePath)) + L"\n\n" + msg);
+ }
+}
diff --git a/zen/xml_io.h b/zen/xml_io.h
index d0492be1..ae735ae6 100644..100755
--- a/zen/xml_io.h
+++ b/zen/xml_io.h
@@ -1,26 +1,26 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef XML_IO_H_8914759321263879
-#define XML_IO_H_8914759321263879
-
-#include <zenxml/xml.h>
-#include "file_error.h"
-
-//combine zen::Xml and zen file i/o
-//-> loadXmlDocument vs loadStream:
-//1. better error reporting
-//2. quick exit if (potentially large) input file is not an XML
-
-namespace zen
-{
-XmlDoc loadXmlDocument(const Zstring& filepath); //throw FileError
-void checkForMappingErrors(const XmlIn& xmlInput, const Zstring& filepath); //throw FileError
-
-void saveXmlDocument(const XmlDoc& doc, const Zstring& filepath); //throw FileError
-}
-
-#endif //XML_IO_H_8914759321263879
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef XML_IO_H_8914759321263879
+#define XML_IO_H_8914759321263879
+
+#include <zenxml/xml.h>
+#include "file_error.h"
+
+//combine zen::Xml and zen file i/o
+//-> loadXmlDocument vs loadStream:
+//1. better error reporting
+//2. quick exit if (potentially large) input file is not an XML
+
+namespace zen
+{
+XmlDoc loadXmlDocument(const Zstring& filePath); //throw FileError
+void checkForMappingErrors(const XmlIn& xmlInput, const Zstring& filePath); //throw FileError
+
+void saveXmlDocument(const XmlDoc& doc, const Zstring& filePath); //throw FileError
+}
+
+#endif //XML_IO_H_8914759321263879
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
index f5732c3f..5f5b1ec8 100644..100755
--- a/zen/zstring.cpp
+++ b/zen/zstring.cpp
@@ -1,117 +1,34 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "zstring.h"
-#include <stdexcept>
-
-#ifdef ZEN_WIN
- #include "win.h"
-#endif
-
-using namespace zen;
-
-/*
-Perf test: compare strings 10 mio times; 64 bit build
------------------------------------------------------
- string a = "Fjk84$%kgfj$%T\\\\Gffg\\gsdgf\\fgsx----------d-"
- string b = "fjK84$%kgfj$%T\\\\gfFg\\gsdgf\\fgSy----------dfdf"
-
-Windows (UTF16 wchar_t)
- 4 ns | wcscmp
- 67 ns | CompareStringOrdinalFunc+ + bIgnoreCase
-314 ns | LCMapString + wmemcmp
-
-OS X (UTF8 char)
- 6 ns | strcmp
- 98 ns | strcasecmp
- 120 ns | strncasecmp + std::min(sizeLhs, sizeRhs);
- 856 ns | CFStringCreateWithCString + CFStringCompare(kCFCompareCaseInsensitive)
-1110 ns | CFStringCreateWithCStringNoCopy + CFStringCompare(kCFCompareCaseInsensitive)
-________________________
-time per call | function
-*/
-
-
-#ifdef ZEN_WIN
-int cmpStringNoCase(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen)
-{
- assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
- assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
-
-#ifdef ZEN_WIN_VISTA_AND_LATER
- //"CompareStringOrdinal" (available since Windows Vista) is by a factor ~3 faster than old string comparison using "LCMapString"
- const int rv = ::CompareStringOrdinal(lhs, //__in LPCWSTR lpString1,
- static_cast<int>(lhsLen), //__in int cchCount1,
- rhs, //__in LPCWSTR lpString2,
- static_cast<int>(rhsLen), //__in int cchCount2,
- true); //__in BOOL bIgnoreCase
- if (rv <= 0)
- throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
- else
- return rv - 2; //convert to C-style string compare result
-#else
- //do NOT use "CompareString"; this function is NOT meant for file name comparisons (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!!
- //the only reliable way to compare filepaths (with XP) is to call "CharUpper" or "LCMapString":
-
- const auto minSize = std::min(lhsLen, rhsLen);
-
- if (minSize == 0) //LCMapString does not allow input sizes of 0!
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-
- auto copyToUpperCase = [minSize](const wchar_t* strIn, wchar_t* strOut)
- {
- //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString()
- if (::LCMapString(LOCALE_INVARIANT, //__in LCID Locale,
- LCMAP_UPPERCASE, //__in DWORD dwMapFlags,
- strIn, //__in LPCTSTR lpSrcStr,
- static_cast<int>(minSize), //__in int cchSrc,
- strOut, //__out LPTSTR lpDestStr,
- static_cast<int>(minSize)) == 0) //__in int cchDest
- throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
- };
-
- auto eval = [&](wchar_t* bufL, wchar_t* bufR)
- {
- copyToUpperCase(lhs, bufL);
- copyToUpperCase(rhs, bufR);
-
- const int rv = ::wcsncmp(bufL, bufR, minSize);
- if (rv != 0)
- return rv;
-
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
- };
-
- if (minSize <= MAX_PATH) //performance optimization: stack
- {
- wchar_t bufferL[MAX_PATH] = {};
- wchar_t bufferR[MAX_PATH] = {};
- return eval(bufferL, bufferR);
- }
- else //use freestore
- {
- std::vector<wchar_t> buffer(2 * minSize);
- return eval(&buffer[0], &buffer[minSize]);
- }
-#endif
-}
-
-
-void makeUpperInPlace(wchar_t* str, size_t strLen)
-{
- //- use Windows' upper case conversion: faster than ::CharUpper()
- //- LOCALE_INVARIANT is NOT available with Windows 2000 -> ok
- //- MSDN: "The destination string can be the same as the source string only if LCMAP_UPPERCASE or LCMAP_LOWERCASE is set"
- if (strLen != 0) //LCMapString does not allow input sizes of 0!
- if (::LCMapString(LOCALE_INVARIANT, //__in LCID Locale,
- LCMAP_UPPERCASE, //__in DWORD dwMapFlags,
- str, //__in LPCTSTR lpSrcStr,
- static_cast<int>(strLen), //__in int cchSrc,
- str, //__out LPTSTR lpDestStr,
- static_cast<int>(strLen)) == 0) //__in int cchDest
- throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
-}
-#endif
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "zstring.h"
+#include <stdexcept>
+
+
+using namespace zen;
+
+/*
+Perf test: compare strings 10 mio times; 64 bit build
+-----------------------------------------------------
+ string a = "Fjk84$%kgfj$%T\\\\Gffg\\gsdgf\\fgsx----------d-"
+ string b = "fjK84$%kgfj$%T\\\\gfFg\\gsdgf\\fgSy----------dfdf"
+
+Windows (UTF16 wchar_t)
+ 4 ns | wcscmp
+ 67 ns | CompareStringOrdinalFunc+ + bIgnoreCase
+314 ns | LCMapString + wmemcmp
+
+OS X (UTF8 char)
+ 6 ns | strcmp
+ 98 ns | strcasecmp
+ 120 ns | strncasecmp + std::min(sizeLhs, sizeRhs);
+ 856 ns | CFStringCreateWithCString + CFStringCompare(kCFCompareCaseInsensitive)
+1110 ns | CFStringCreateWithCStringNoCopy + CFStringCompare(kCFCompareCaseInsensitive)
+________________________
+time per call | function
+*/
+
+
diff --git a/zen/zstring.h b/zen/zstring.h
index 902da80e..12bda29f 100644..100755
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -1,269 +1,222 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#ifndef ZSTRING_H_73425873425789
-#define ZSTRING_H_73425873425789
-
-#include "string_base.h"
-
-
-#ifdef ZEN_WIN //Windows encodes Unicode as UTF-16 wchar_t
- using Zchar = wchar_t;
- #define Zstr(x) L ## x
- const Zchar FILE_NAME_SEPARATOR = L'\\';
-
-#elif defined ZEN_LINUX || defined ZEN_MAC //Linux uses UTF-8
- using Zchar = char;
- #define Zstr(x) x
- const Zchar FILE_NAME_SEPARATOR = '/';
-#endif
-
-//"The reason for all the fuss above" - Loki/SmartPtr
-//a high-performance string for interfacing with native OS APIs in multithreaded contexts
-using Zstring = zen::Zbase<Zchar>;
-
-
-int cmpStringNoCase(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen);
-#if defined ZEN_LINUX || defined ZEN_MAC
- int cmpStringNoCase(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen);
-#endif
-
-template <class S, class T> inline
-bool equalNoCase(const S& lhs, const T& rhs) { using namespace zen; return cmpStringNoCase(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0; }
-
-template <class S>
-S makeUpperCopy(S str);
-
-
-//Compare filepaths: Windows/OS X does NOT distinguish between upper/lower-case, while Linux DOES
-int cmpFilePath(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen);
-#if defined ZEN_LINUX || defined ZEN_MAC
- int cmpFilePath(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen);
-#endif
-
-template <class S, class T> inline
-bool equalFilePath(const S& lhs, const T& rhs) { using namespace zen; return cmpFilePath(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0; }
-
-struct LessFilePath
-{
- template <class S, class T>
- bool operator()(const S& lhs, const T& rhs) const { using namespace zen; return cmpFilePath(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
-};
-
-
-
-inline
-Zstring appendSeparator(Zstring path) //support rvalue references!
-{
- return zen::endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR); //returning a by-value parameter implicitly converts to r-value!
-}
-
-
-inline
-Zstring getFileExtension(const Zstring& filePath)
-{
- const Zstring shortName = afterLast(filePath, FILE_NAME_SEPARATOR, zen::IF_MISSING_RETURN_ALL);
- return afterLast(shortName, Zchar('.'), zen::IF_MISSING_RETURN_NONE);
-}
-
-
-template <class S, class T> inline
-bool pathStartsWith(const S& str, const T& prefix)
-{
- using namespace zen;
- const size_t pfLen = strLength(prefix);
- if (strLength(str) < pfLen)
- return false;
-
- return cmpFilePath(strBegin(str), pfLen, strBegin(prefix), pfLen) == 0;
-}
-
-
-template <class S, class T> inline
-bool pathEndsWith(const S& str, const T& postfix)
-{
- using namespace zen;
- const size_t strLen = strLength(str);
- const size_t pfLen = strLength(postfix);
- if (strLen < pfLen)
- return false;
-
- return cmpFilePath(strBegin(str) + strLen - pfLen, pfLen, strBegin(postfix), pfLen) == 0;
-}
-
-
-template <class S, class T, class U>
-S pathReplaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true);
-
-
-
-
-//################################# inline implementation ########################################
-#ifdef ZEN_WIN
-void makeUpperInPlace(wchar_t* str, size_t strLen);
-
-#elif defined ZEN_LINUX || defined ZEN_MAC
-inline
-void makeUpperInPlace(wchar_t* str, size_t strLen)
-{
- std::for_each(str, str + strLen, [](wchar_t& c) { c = std::towupper(c); }); //locale-dependent!
-}
-
-
-inline
-void makeUpperInPlace(char* str, size_t strLen)
-{
- std::for_each(str, str + strLen, [](char& c) { c = std::toupper(static_cast<unsigned char>(c)); }); //locale-dependent!
- //result of toupper() is an unsigned char mapped to int range, so the char representation is in the last 8 bits and we need not care about signedness!
- //this should work for UTF-8, too: all chars >= 128 are mapped upon themselves!
-}
-
-
-inline
-int cmpStringNoCase(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen)
-{
- assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
- assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
-
- const int rv = ::wcsncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent!
- if (rv != 0)
- return rv;
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-}
-
-
-inline
-int cmpStringNoCase(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen)
-{
- assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
- assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
-
- const int rv = ::strncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent!
- if (rv != 0)
- return rv;
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-}
-#endif
-
-
-template <class S> inline
-S makeUpperCopy(S str)
-{
- const size_t len = str.length(); //we assert S is a string type!
- if (len > 0)
- makeUpperInPlace(&*str.begin(), len);
-
- return std::move(str); //"str" is an l-value parameter => no copy elision!
-}
-
-
-inline
-int cmpFilePath(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen)
-{
-#if defined ZEN_WIN || defined ZEN_MAC
- return cmpStringNoCase(lhs, lhsLen, rhs, rhsLen);
-
-#elif defined ZEN_LINUX
- assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
- assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
-
- const int rv = std::wcsncmp(lhs, rhs, std::min(lhsLen, rhsLen));
- if (rv != 0)
- return rv;
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-#endif
-}
-
-
-#if defined ZEN_LINUX || defined ZEN_MAC
-inline
-int cmpFilePath(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen)
-{
-#if defined ZEN_MAC
- return cmpStringNoCase(lhs, lhsLen, rhs, rhsLen);
-
-#elif defined ZEN_LINUX
- assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
- assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
-
- const int rv = std::strncmp(lhs, rhs, std::min(lhsLen, rhsLen));
- if (rv != 0)
- return rv;
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-#endif
-}
-#endif
-
-
-template <class S, class T, class U> inline
-S pathReplaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll)
-{
- assert(!contains(str, Zchar('\0')));
-
-#if defined ZEN_WIN || defined ZEN_MAC
- using namespace zen;
-
- S strU = makeUpperCopy(str); //S required to be a string class
- S oldTermU = makeUpperCopy<S>(oldTerm); //[!] T not required to be a string class
- assert(strLength(strU ) == strLength(str ));
- assert(strLength(oldTermU) == strLength(oldTerm));
-
- replace(strU, oldTermU, Zchar('\0'), replaceAll);
-
- S output;
-
- size_t i = 0;
- for (auto c : strU)
- if (c == Zchar('\0'))
- {
- output += newTerm;
- i += oldTermU.size();
- }
- else
- output += str[i++];
-
- return output;
-
-#elif defined ZEN_LINUX
- return replaceCpy(str, oldTerm, newTerm, replaceAll);
-#endif
-}
-
-
-//---------------------------------------------------------------------------
-//ZEN macro consistency checks:
-#ifdef ZEN_WIN
- #if defined ZEN_LINUX || defined ZEN_MAC
- #error more than one target platform defined
- #endif
-
- #ifdef ZEN_WIN_VISTA_AND_LATER
- #ifdef ZEN_WIN_PRE_VISTA
- #error choose only one of the two variants
- #endif
- #elif defined ZEN_WIN_PRE_VISTA
- #ifdef ZEN_WIN_VISTA_AND_LATER
- #error choose only one of the two variants
- #endif
- #else
- #error choose one of the two variants
- #endif
-
-#elif defined ZEN_LINUX
- #if defined ZEN_WIN || defined ZEN_MAC
- #error more than one target platform defined
- #endif
-
-#elif defined ZEN_MAC
- #if defined ZEN_WIN || defined ZEN_LINUX
- #error more than one target platform defined
- #endif
-
-#else
- #error no target platform defined
-#endif
-
-#endif //ZSTRING_H_73425873425789
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef ZSTRING_H_73425873425789
+#define ZSTRING_H_73425873425789
+
+#include "string_base.h"
+
+
+ using Zchar = char;
+ #define Zstr(x) x
+ const Zchar FILE_NAME_SEPARATOR = '/';
+
+//"The reason for all the fuss above" - Loki/SmartPtr
+//a high-performance string for interfacing with native OS APIs in multithreaded contexts
+using Zstring = zen::Zbase<Zchar>;
+
+
+int cmpStringNoCase(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen);
+ int cmpStringNoCase(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen);
+
+template <class S>
+S makeUpperCopy(S str);
+
+
+//Compare filepaths: Windows/OS X does NOT distinguish between upper/lower-case, while Linux DOES
+int cmpFilePath(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen);
+ int cmpFilePath(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen);
+
+
+template <class S, class T> inline
+bool equalFilePath(const S& lhs, const T& rhs) { using namespace zen; return cmpFilePath(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0; }
+
+struct LessFilePath
+{
+ template <class S, class T>
+ bool operator()(const S& lhs, const T& rhs) const { using namespace zen; return cmpFilePath(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
+};
+
+
+struct LessNoCase
+{
+ template <class S, class T>
+ bool operator()(const S& lhs, const T& rhs) const { using namespace zen; return cmpStringNoCase(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
+};
+
+
+inline
+Zstring appendSeparator(Zstring path) //support rvalue references!
+{
+ return zen::endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR); //returning a by-value parameter implicitly converts to r-value!
+}
+
+
+inline
+Zstring getFileExtension(const Zstring& filePath)
+{
+ const Zstring shortName = afterLast(filePath, FILE_NAME_SEPARATOR, zen::IF_MISSING_RETURN_ALL);
+ return afterLast(shortName, Zchar('.'), zen::IF_MISSING_RETURN_NONE);
+}
+
+
+template <class S, class T> inline
+bool ciEqual(const S& lhs, const T& rhs) { using namespace zen; return cmpStringNoCase(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0; }
+
+
+template <class S, class T> inline
+bool ciStartsWith(const S& str, const T& prefix)
+{
+ using namespace zen;
+ const size_t pfLen = strLength(prefix);
+ if (strLength(str) < pfLen)
+ return false;
+
+ return cmpStringNoCase(strBegin(str), pfLen, strBegin(prefix), pfLen) == 0;
+}
+
+
+template <class S, class T> inline
+bool ciEndsWith(const S& str, const T& postfix)
+{
+ using namespace zen;
+ const size_t strLen = strLength(str);
+ const size_t pfLen = strLength(postfix);
+ if (strLen < pfLen)
+ return false;
+
+ return cmpStringNoCase(strBegin(str) + strLen - pfLen, pfLen, strBegin(postfix), pfLen) == 0;
+}
+
+
+template <class S, class T, class U>
+S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm);
+
+
+
+
+//################################# inline implementation ########################################
+inline
+void makeUpperInPlace(wchar_t* str, size_t strLen)
+{
+ std::for_each(str, str + strLen, [](wchar_t& c) { c = std::towupper(c); }); //locale-dependent!
+}
+
+
+inline
+void makeUpperInPlace(char* str, size_t strLen)
+{
+ std::for_each(str, str + strLen, [](char& c) { c = std::toupper(static_cast<unsigned char>(c)); }); //locale-dependent!
+ //result of toupper() is an unsigned char mapped to int range, so the char representation is in the last 8 bits and we need not care about signedness!
+ //this should work for UTF-8, too: all chars >= 128 are mapped upon themselves!
+}
+
+
+inline
+int cmpStringNoCase(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen)
+{
+ assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
+ assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
+
+ const int rv = ::wcsncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent!
+ if (rv != 0)
+ return rv;
+ return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
+}
+
+
+inline
+int cmpStringNoCase(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen)
+{
+ assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
+ assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
+
+ const int rv = ::strncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent!
+ if (rv != 0)
+ return rv;
+ return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
+}
+
+
+template <class S> inline
+S makeUpperCopy(S str)
+{
+ const size_t len = str.length(); //we assert S is a string type!
+ if (len > 0)
+ makeUpperInPlace(&*str.begin(), len);
+
+ return std::move(str); //"str" is an l-value parameter => no copy elision!
+}
+
+
+inline
+int cmpFilePath(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen)
+{
+ assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
+ assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
+
+ const int rv = std::wcsncmp(lhs, rhs, std::min(lhsLen, rhsLen));
+ if (rv != 0)
+ return rv;
+ return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
+}
+
+
+inline
+int cmpFilePath(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen)
+{
+ assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
+ assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //
+
+ const int rv = std::strncmp(lhs, rhs, std::min(lhsLen, rhsLen));
+ if (rv != 0)
+ return rv;
+ return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
+}
+
+
+template <class S, class T, class U> inline
+S ciReplaceCpy(const S& str, const T& oldTerm, const U& newTerm)
+{
+ using namespace zen;
+ static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
+ static_assert(IsSameType<typename GetCharType<T>::Type, typename GetCharType<U>::Type>::value, "");
+ const size_t oldLen = strLength(oldTerm);
+ if (oldLen == 0)
+ return str;
+
+ const S strU = makeUpperCopy(str); //S required to be a string class
+ const S oldU = makeUpperCopy<S>(oldTerm); //[!] T not required to be a string class
+ assert(strLength(strU) == strLength(str ));
+ assert(strLength(oldU) == strLength(oldTerm));
+
+ const auto* const newBegin = strBegin(newTerm);
+ const auto* const newEnd = newBegin + strLength(newTerm);
+
+ S output;
+
+ for (size_t pos = 0;;)
+ {
+ const auto itFound = std::search(strU.begin() + pos, strU.end(),
+ oldU.begin(), oldU.end());
+ if (itFound == strU.end() && pos == 0)
+ return str; //optimize "oldTerm not found": return ref-counted copy
+
+ impl::stringAppend(output, str.begin() + pos, str.begin() + (itFound - strU.begin()));
+ if (itFound == strU.end())
+ return output;
+
+ impl::stringAppend(output, newBegin, newEnd);
+ pos = (itFound - strU.begin()) + oldLen;
+ }
+}
+
+
+//---------------------------------------------------------------------------
+//ZEN macro consistency checks:
+
+
+#endif //ZSTRING_H_73425873425789
bgstack15