diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2017-02-13 21:25:04 -0700 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2017-02-13 21:25:04 -0700 |
commit | 9d071d2a2cec9a7662a02669488569a017f0ea35 (patch) | |
tree | c83a623fbdff098339b66d21ea2e81f3f67344ae /zen | |
parent | 8.8 (diff) | |
download | FreeFileSync-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.h | 766 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/build_info.h | 67 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/crc.h | 115 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/deprecate.h | 35 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/dir_watcher.cpp | 784 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/dir_watcher.h | 150 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/error_log.h | 270 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_access.cpp | 3265 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_access.h | 197 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_error.h | 123 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_id_def.h | 118 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_io.cpp | 663 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_io.h | 240 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_traverser.cpp | 299 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/file_traverser.h | 94 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/fixed_list.h | 478 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/format_unit.cpp | 614 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/format_unit.h | 111 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/globals.h | 125 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/guid.h | 64 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/i18n.h | 243 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/optional.h | 190 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/perf.h | 177 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/process_priority.cpp | 144 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/process_priority.h | 76 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/recycler.cpp | 395 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/recycler.h | 94 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/scope_guard.h | 251 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/serialize.h | 607 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/shell_execute.h | 188 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/stl_tools.h | 491 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/string_base.h | 1230 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/string_tools.h | 1420 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/string_traits.h | 432 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/symlink_target.h | 315 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/sys_error.h | 266 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/thread.h | 892 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/time.h | 725 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/type_tools.h | 206 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/type_traits.h | 390 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/utf.h | 952 | ||||
-rwxr-xr-x | zen/warn_static.h | 24 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/xml_io.cpp | 168 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/xml_io.h | 52 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/zstring.cpp | 151 | ||||
-rwxr-xr-x[-rw-r--r--] | zen/zstring.h | 491 |
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(<, &utc) != 0) -#else - if (::localtime_r(&utc, <) == 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
|