diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2017-04-20 16:55:28 -0600 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2017-04-20 16:59:56 -0600 |
commit | 823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b (patch) | |
tree | 63222010af3b90a36f29c1c9c360116973cdff38 /zen | |
parent | add .gitattributes for less line ending hassles (diff) | |
download | FreeFileSync-823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b.tar.gz FreeFileSync-823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b.tar.bz2 FreeFileSync-823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b.zip |
normalize most lineendings
Diffstat (limited to 'zen')
46 files changed, 7745 insertions, 7745 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index 7836ae81..722722a5 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 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
+// ***************************************************************************** +// * 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 f3f7f5b1..8354f492 100755 --- a/zen/build_info.h +++ b/zen/build_info.h @@ -1,29 +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 __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
+// ***************************************************************************** +// * 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 @@ -1,54 +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
-
-#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
+// ***************************************************************************** +// * 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 94273ad8..c2a1ebfa 100755 --- a/zen/deprecate.h +++ b/zen/deprecate.h @@ -1,14 +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
- #define ZEN_DEPRECATE __attribute__ ((deprecated))
-
-
-#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 1b6f6f5c..4e759d55 100755 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -1,158 +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"
-
- #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;
-}
-
+// ***************************************************************************** +// * 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 b16cd417..5676555f 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 1a9d2679..90016666 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, 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
+// ***************************************************************************** +// * 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 71d00386..ed66aac4 100755 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -1,676 +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"
-
- #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::parsePathComponents(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 = parsePathComponents(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
-}
-
-
-PathStatus zen::getPathStatus(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());
-
- PathStatus pd = getPathStatus(*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 PathStatus pd = getPathStatus(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<PathStatus> pd;
- try { pd = getPathStatus(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;
-}
+// ***************************************************************************** +// * 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::parsePathComponents(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 = parsePathComponents(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 +} + + +PathStatus zen::getPathStatus(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()); + + PathStatus pd = getPathStatus(*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 PathStatus pd = getPathStatus(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<PathStatus> pd; + try { pd = getPathStatus(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 a6b221e5..ee33da93 100755 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -1,101 +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"
-#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> parsePathComponents(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 PathStatus
-{
- ItemType existingType;
- Zstring existingPath; //itemPath =: existingPath + relPath
- std::vector<Zstring> relPath; //
-};
-PathStatus getPathStatus(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
+// ***************************************************************************** +// * 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> parsePathComponents(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 PathStatus +{ + ItemType existingType; + Zstring existingPath; //itemPath =: existingPath + relPath + std::vector<Zstring> relPath; // +}; +PathStatus getPathStatus(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 949c644f..b318e708 100755 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -1,53 +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
-#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(utfTo<std::wstring>(displayPath)); }
-inline std::wstring fmtPath(const wchar_t* displayPath) { return fmtPath(std::wstring(displayPath)); } //resolve overload ambiguity
-}
-
-#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(utfTo<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 87a5b199..3dcd21b9 100755 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -1,41 +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>
-
- #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
+// ***************************************************************************** +// * 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 0c5ff490..d291e741 100755 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -1,274 +1,274 @@ -// *****************************************************************************
-// * 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 = std::min(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()
-{
- notifyUnbufferedIO_ = nullptr; //no call-backs during destruction!!!
- 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
-
-}
+// ***************************************************************************** +// * 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 = std::min(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() +{ + notifyUnbufferedIO_ = nullptr; //no call-backs during destruction!!! + 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 827abd9e..6b0665a1 100755 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -1,123 +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"
-
-
-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_;
- 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
+// ***************************************************************************** +// * 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_; + 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 d5e3912b..916b6e8d 100755 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -1,105 +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"
-
-
- #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());
- }
-}
+// ***************************************************************************** +// * 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 0eb3bbee..79bcae11 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;
- 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
+// ***************************************************************************** +// * 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 e143c7b7..27eb488c 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 a2208b3e..c0667fb1 100755 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -1,201 +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"
-
- #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 = utfTo<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();
-}
-
-
+// ***************************************************************************** +// * 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 = utfTo<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 fc79738a..336df74c 100755 --- a/zen/format_unit.h +++ b/zen/format_unit.h @@ -1,53 +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(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
+// ***************************************************************************** +// * 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 b6c5dd28..b85d5f77 100755 --- a/zen/globals.h +++ b/zen/globals.h @@ -1,65 +1,65 @@ -// *****************************************************************************
-// * 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");
- assert(!pod.inst && !pod.spinLock); //we depend on static zero-initialization!
- }
- 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;
- std::atomic<bool> spinLock; // { false }; rely entirely on static zero-initialization! => avoid potential contention with worker thread during Global<> construction!
- //serialize access; can't use std::mutex: has non-trival destructor
- } pod;
-};
-
-}
-
-#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"); + assert(!pod.inst && !pod.spinLock); //we depend on static zero-initialization! + } + 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; + std::atomic<bool> spinLock; // { false }; rely entirely on static zero-initialization! => avoid potential contention with worker thread during Global<> construction! + //serialize access; can't use std::mutex: has non-trival destructor + } pod; +}; + +} + +#endif //GLOBALS_H_8013740213748021573485 @@ -1,30 +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>
-
- #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
+// ***************************************************************************** +// * 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 @@ -1,119 +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!
-
-#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
+// ***************************************************************************** +// * 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 a4c67984..0efc34fe 100755 --- a/zen/optional.h +++ b/zen/optional.h @@ -1,100 +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( 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
+// ***************************************************************************** +// * 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 @@ -1,83 +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"
-
- #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
+// ***************************************************************************** +// * 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 50c975fe..5767e60c 100755 --- a/zen/process_priority.cpp +++ b/zen/process_priority.cpp @@ -1,54 +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"
-
-
-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;
-};
-*/
+// ***************************************************************************** +// * 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 b876dc92..07679b0c 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 0c71bf3b..c84ef8f3 100755 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -1,72 +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"
-
- #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, formatSystemError(L"g_file_trash", L"Glib Error Code " + numberTo<std::wstring>(error->code), utfTo<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.
-*/
+// ***************************************************************************** +// * 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, formatSystemError(L"g_file_trash", L"Glib Error Code " + numberTo<std::wstring>(error->code), utfTo<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 0f1b0023..54dd75ca 100755 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -1,43 +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
-
-
-}
-
-#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 62552f7b..b336ad53 100755 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -1,121 +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"
-
-
-//std::uncaught_exceptions() currently unsupported on GCC and Clang => clean up ASAP
- static_assert(__GNUC__ < 6 || (__GNUC__ == 6 && (__GNUC_MINOR__ < 3 || (__GNUC_MINOR__ == 3 && __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
+// ***************************************************************************** +// * 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__ < 3 || (__GNUC_MINOR__ == 3 && __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 c8dfb96d..81d2d1ef 100755 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -1,281 +1,281 @@ -// *****************************************************************************
-// * 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);
- assert(bytesRead <= len); //buffer overflow otherwise not always detected!
- 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
+// ***************************************************************************** +// * 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); + assert(bytesRead <= len); //buffer overflow otherwise not always detected! + 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 5e4ddf1a..7dcd6653 100755 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -1,52 +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"
-
- #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" + utfTo<std::wstring>(command));
- }
- else
- runAsync([=] { int rv = ::system(command.c_str()); (void)rv; });
-}
-}
-}
-
-#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" + utfTo<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 74a2b360..b03d7533 100755 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -1,244 +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);
-}
-
-
-
-
-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 b5e45c0e..2ec2e894 100755 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -1,613 +1,613 @@ -// *****************************************************************************
-// * 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=(Zbase&& tmp) noexcept;
- Zbase& operator=(const Zbase& str);
- 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);
- if (len > 0) //avoid making this string unshared for no reason
- {
- 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=(Zbase&& tmp) noexcept; + Zbase& operator=(const Zbase& str); + 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); + if (len > 0) //avoid making this string unshared for no reason + { + 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 236f8df6..bfa14257 100755 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -1,763 +1,763 @@ -// *****************************************************************************
-// * 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 isAsciiAlpha(Char ch);
-
-//case-sensitive comparison (compile-time correctness: use different number of arguments as STL comparison predicates!)
-struct CmpBinary { template <class Char> int operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const; };
-
-//basic case-insensitive comparison (considering A-Z only!)
-struct CmpAsciiNoCase { template <class Char> int operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const; };
-
-struct LessAsciiNoCase
-{
- template <class S> //don't support heterogenous input! => use as container predicate only!
- bool operator()(const S& lhs, const S& rhs) const { return CmpAsciiNoCase()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
-};
-
-//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);
-
-template <class S, class T> bool startsWith(const S& str, const T& prefix);
-template <class S, class T, class Function> bool startsWith(const S& str, const T& prefix, Function cmpStringFun);
-
-template <class S, class T> bool endsWith (const S& str, const T& postfix);
-template <class S, class T, class Function> bool endsWith (const S& str, const T& postfix, Function cmpStringFun);
-
-template <class S, class T> bool strEqual(const S& lhs, const T& rhs);
-template <class S, class T, class Function> bool strEqual(const S& lhs, const T& rhs, Function cmpStringFun);
-
-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);
-
-enum class SplitType
-{
- ALLOW_EMPTY,
- SKIP_EMPTY
-};
-template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitType st);
-
-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);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-//---------------------- 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 <class Char> inline
-bool isAsciiAlpha(Char c)
-{
- static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, "");
- return (static_cast<Char>('A') <= c && c <= static_cast<Char>('Z')) ||
- (static_cast<Char>('a') <= c && c <= static_cast<Char>('z'));
-}
-
-
-template <class S, class T, class Function> inline
-bool startsWith(const S& str, const T& prefix, Function cmpStringFun)
-{
- const size_t pfLen = strLength(prefix);
- if (strLength(str) < pfLen)
- return false;
-
- return cmpStringFun(strBegin(str), pfLen,
- strBegin(prefix), pfLen) == 0;
-}
-
-
-template <class S, class T, class Function> inline
-bool endsWith(const S& str, const T& postfix, Function cmpStringFun)
-{
- const size_t strLen = strLength(str);
- const size_t pfLen = strLength(postfix);
- if (strLen < pfLen)
- return false;
-
- return cmpStringFun(strBegin(str) + strLen - pfLen, pfLen,
- strBegin(postfix), pfLen) == 0;
-}
-
-
-template <class S, class T, class Function> inline
-bool strEqual(const S& lhs, const T& rhs, Function cmpStringFun)
-{
- return cmpStringFun(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0;
-}
-
-
-template <class S, class T> inline bool startsWith(const S& str, const T& prefix ) { return startsWith(str, prefix, CmpBinary()); }
-template <class S, class T> inline bool endsWith (const S& str, const T& postfix) { return endsWith (str, postfix, CmpBinary()); }
-template <class S, class T> inline bool strEqual (const S& lhs, const T& rhs ) { return strEqual (lhs, rhs, CmpBinary()); }
-
-
-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);
- assert(termLen > 0);
-
- 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 size_t termLen = strLength(term);
- assert(termLen > 0);
-
- 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();
-
- 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);
- assert(termLen > 0);
-
- 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 size_t termLen = strLength(term);
- assert(termLen > 0);
-
- 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 + termLen);
- 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, SplitType st)
-{
- static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, "");
- const size_t delimLen = strLength(delimiter);
- assert(delimLen > 0);
- if (delimLen == 0)
- {
- if (str.empty() && st == SplitType::SKIP_EMPTY)
- return {};
- return { str };
- }
-
- 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);
- if (blockStart != blockEnd || st == SplitType::ALLOW_EMPTY)
- output.emplace_back(blockStart, blockEnd - blockStart);
-
- if (blockEnd == strLast)
- 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); }
-};
-
-inline int strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std::memcmp (ptr1, ptr2, num); }
-inline int strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num); }
-}
-
-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 Char> inline
-int CmpBinary::operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const
-{
- //support embedded 0, unlike strncmp/wcsncmp!
- const int rv = impl::strcmpWithNulls(lhs, rhs, std::min(lhsLen, rhsLen));
- if (rv != 0)
- return rv;
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-}
-
-
-template <class Char> inline
-int CmpAsciiNoCase::operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const
-{
- auto asciiToLower = [](Char c) //ordering: lower-case chars have higher code points than uppper-case
- {
- if (static_cast<Char>('A') <= c && c <= static_cast<Char>('Z'))
- return static_cast<Char>(c - static_cast<Char>('A') + static_cast<Char>('a'));
- return c;
- };
-
- const auto* const lhsLast = lhs + std::min(lhsLen, rhsLen);
-
- while (lhs != lhsLast)
- {
- const Char charL = asciiToLower(*lhs++);
- const Char charR = asciiToLower(*rhs++);
- if (charL != charR)
- return static_cast<unsigned int>(charL) - static_cast<unsigned int>(charR); //unsigned char-comparison is the convention!
- //unsigned underflow is well-defined!
- }
- return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
-}
-
-
-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: returns number of chars written if successful, < 0 or >= bufferSize on failure
-}
-
-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: returns number of chars written if successful, < 0 on failure (including buffer too small)
-}
-}
-
-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 0 < charsWritten && charsWritten < BUFFER_SIZE ? 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!
- break; //assert(std::all_of(iter, last, &isWhiteSpace<CharType>)); -> this is NO assert situation
- }
- 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 isAsciiAlpha(Char ch); + +//case-sensitive comparison (compile-time correctness: use different number of arguments as STL comparison predicates!) +struct CmpBinary { template <class Char> int operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const; }; + +//basic case-insensitive comparison (considering A-Z only!) +struct CmpAsciiNoCase { template <class Char> int operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const; }; + +struct LessAsciiNoCase +{ + template <class S> //don't support heterogenous input! => use as container predicate only! + bool operator()(const S& lhs, const S& rhs) const { return CmpAsciiNoCase()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; } +}; + +//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); + +template <class S, class T> bool startsWith(const S& str, const T& prefix); +template <class S, class T, class Function> bool startsWith(const S& str, const T& prefix, Function cmpStringFun); + +template <class S, class T> bool endsWith (const S& str, const T& postfix); +template <class S, class T, class Function> bool endsWith (const S& str, const T& postfix, Function cmpStringFun); + +template <class S, class T> bool strEqual(const S& lhs, const T& rhs); +template <class S, class T, class Function> bool strEqual(const S& lhs, const T& rhs, Function cmpStringFun); + +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); + +enum class SplitType +{ + ALLOW_EMPTY, + SKIP_EMPTY +}; +template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitType st); + +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); + + + + + + + + + + + + + + + + +//---------------------- 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 <class Char> inline +bool isAsciiAlpha(Char c) +{ + static_assert(IsSameType<Char, char>::value || IsSameType<Char, wchar_t>::value, ""); + return (static_cast<Char>('A') <= c && c <= static_cast<Char>('Z')) || + (static_cast<Char>('a') <= c && c <= static_cast<Char>('z')); +} + + +template <class S, class T, class Function> inline +bool startsWith(const S& str, const T& prefix, Function cmpStringFun) +{ + const size_t pfLen = strLength(prefix); + if (strLength(str) < pfLen) + return false; + + return cmpStringFun(strBegin(str), pfLen, + strBegin(prefix), pfLen) == 0; +} + + +template <class S, class T, class Function> inline +bool endsWith(const S& str, const T& postfix, Function cmpStringFun) +{ + const size_t strLen = strLength(str); + const size_t pfLen = strLength(postfix); + if (strLen < pfLen) + return false; + + return cmpStringFun(strBegin(str) + strLen - pfLen, pfLen, + strBegin(postfix), pfLen) == 0; +} + + +template <class S, class T, class Function> inline +bool strEqual(const S& lhs, const T& rhs, Function cmpStringFun) +{ + return cmpStringFun(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0; +} + + +template <class S, class T> inline bool startsWith(const S& str, const T& prefix ) { return startsWith(str, prefix, CmpBinary()); } +template <class S, class T> inline bool endsWith (const S& str, const T& postfix) { return endsWith (str, postfix, CmpBinary()); } +template <class S, class T> inline bool strEqual (const S& lhs, const T& rhs ) { return strEqual (lhs, rhs, CmpBinary()); } + + +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); + assert(termLen > 0); + + 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 size_t termLen = strLength(term); + assert(termLen > 0); + + 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(); + + 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); + assert(termLen > 0); + + 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 size_t termLen = strLength(term); + assert(termLen > 0); + + 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 + termLen); + 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, SplitType st) +{ + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + const size_t delimLen = strLength(delimiter); + assert(delimLen > 0); + if (delimLen == 0) + { + if (str.empty() && st == SplitType::SKIP_EMPTY) + return {}; + return { str }; + } + + 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); + if (blockStart != blockEnd || st == SplitType::ALLOW_EMPTY) + output.emplace_back(blockStart, blockEnd - blockStart); + + if (blockEnd == strLast) + 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); } +}; + +inline int strcmpWithNulls(const char* ptr1, const char* ptr2, size_t num) { return std::memcmp (ptr1, ptr2, num); } +inline int strcmpWithNulls(const wchar_t* ptr1, const wchar_t* ptr2, size_t num) { return std::wmemcmp(ptr1, ptr2, num); } +} + +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 Char> inline +int CmpBinary::operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const +{ + //support embedded 0, unlike strncmp/wcsncmp! + const int rv = impl::strcmpWithNulls(lhs, rhs, std::min(lhsLen, rhsLen)); + if (rv != 0) + return rv; + return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); +} + + +template <class Char> inline +int CmpAsciiNoCase::operator()(const Char* lhs, size_t lhsLen, const Char* rhs, size_t rhsLen) const +{ + auto asciiToLower = [](Char c) //ordering: lower-case chars have higher code points than uppper-case + { + if (static_cast<Char>('A') <= c && c <= static_cast<Char>('Z')) + return static_cast<Char>(c - static_cast<Char>('A') + static_cast<Char>('a')); + return c; + }; + + const auto* const lhsLast = lhs + std::min(lhsLen, rhsLen); + + while (lhs != lhsLast) + { + const Char charL = asciiToLower(*lhs++); + const Char charR = asciiToLower(*rhs++); + if (charL != charR) + return static_cast<unsigned int>(charL) - static_cast<unsigned int>(charR); //unsigned char-comparison is the convention! + //unsigned underflow is well-defined! + } + return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); +} + + +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: returns number of chars written if successful, < 0 or >= bufferSize on failure +} + +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: returns number of chars written if successful, < 0 on failure (including buffer too small) +} +} + +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 0 < charsWritten && charsWritten < BUFFER_SIZE ? 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! + break; //assert(std::all_of(iter, last, &isWhiteSpace<CharType>)); -> this is NO assert situation + } + 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 5ae5733c..f17e5e0d 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 4620e7cc..84b8452a 100755 --- a/zen/symlink_target.h +++ b/zen/symlink_target.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 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
+// ***************************************************************************** +// * 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 f7c128ef..026a3be5 100755 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -1,105 +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"
-
- #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 = utfTo<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
+// ***************************************************************************** +// * 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 = utfTo<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 ae4c347e..76306a51 100755 --- a/zen/thread.h +++ b/zen/thread.h @@ -1,415 +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"
-
-
-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
+// ***************************************************************************** +// * 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 @@ -1,368 +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 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
+// ***************************************************************************** +// * 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 082530ec..d0a62ea2 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 f4e5cebd..917b3258 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 @@ -1,387 +1,387 @@ -// *****************************************************************************
-// * 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
-#include "optional.h"
-
-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 utfTo(const SourceString& str);
-
-const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF";
-
-template <class UtfString>
-bool isValidUtf(const UtfString& str); //check for UTF-8 encoding errors
-
-//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>
-UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t uniPosLast);
-
-
-
-
-
-
-
-
-
-//----------------------- implementation ----------------------------------
-namespace implementation
-{
-using CodePoint = uint32_t;
-using Char16 = uint16_t;
-using Char8 = uint8_t;
-
-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(static_cast<Char16>( LEAD_SURROGATE + (cp >> 10)));
- writeOutput(static_cast<Char16>(TRAIL_SURROGATE + (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;
-}
-
-
-class Utf16Decoder
-{
-public:
- Utf16Decoder(const Char16* str, size_t len) : it_(str), last_(str + len) {}
-
- Opt<CodePoint> getNext()
- {
- if (it_ == last_)
- return NoValue();
-
- const Char16 ch = *it_++;
- CodePoint cp = ch;
- switch (getUtf16Len(ch))
- {
- case 0: //invalid utf16 character
- cp = REPLACEMENT_CHAR;
- break;
- case 1:
- break;
- case 2:
- decodeTrail(cp);
- break;
- }
- return cp;
- }
-
-private:
- void decodeTrail(CodePoint& cp)
- {
- if (it_ != last_) //trail surrogate expected!
- {
- const Char16 ch = *it_;
- if (TRAIL_SURROGATE <= ch && ch <= TRAIL_SURROGATE_MAX) //trail surrogate expected!
- {
- cp = ((cp - LEAD_SURROGATE) << 10) + (ch - TRAIL_SURROGATE) + 0x10000;
- ++it_;
- return;
- }
- }
- cp = REPLACEMENT_CHAR;
- }
-
- const Char16* it_;
- const Char16* const last_;
-};
-
-//----------------------------------------------------------------------------------------------------------------
-
-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(Char8 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
-}
-
-
-class Utf8Decoder
-{
-public:
- Utf8Decoder(const Char8* str, size_t len) : it_(str), last_(str + len) {}
-
- Opt<CodePoint> getNext()
- {
- if (it_ == last_)
- return NoValue();
-
- const Char8 ch = *it_++;
- CodePoint cp = ch;
- switch (getUtf8Len(ch))
- {
- case 0: //invalid utf8 character
- cp = REPLACEMENT_CHAR;
- break;
- case 1:
- break;
- case 2:
- cp &= 0x1f;
- decodeTrail(cp);
- break;
- case 3:
- cp &= 0xf;
- if (decodeTrail(cp))
- decodeTrail(cp);
- break;
- case 4:
- cp &= 0x7;
- if (decodeTrail(cp))
- if (decodeTrail(cp))
- decodeTrail(cp);
- if (cp > CODE_POINT_MAX) cp = REPLACEMENT_CHAR;
- break;
- }
- return cp;
- }
-
-private:
- bool decodeTrail(CodePoint& cp)
- {
- if (it_ != last_) //trail surrogate expected!
- {
- const Char8 ch = *it_;
- if (ch >> 6 == 0x2) //trail surrogate expected!
- {
- cp = (cp << 6) + (ch & 0x3f);
- ++it_;
- return true;
- }
- }
- cp = REPLACEMENT_CHAR;
- return false;
- }
-
- const Char8* it_;
- const Char8* const last_;
-};
-
-//----------------------------------------------------------------------------------------------------------------
-
-template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<1>) { codePointToUtf8 (cp, writeOutput); } //UTF8-char
-template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<2>) { codePointToUtf16(cp, writeOutput); } //Windows: UTF16-wchar_t
-template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<4>) { writeOutput(cp); } //other OS: UTF32-wchar_t
-
-template <class CharType, class Function> inline
-void codePointToUtf(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a CharType
-{
- return codePointToUtf(cp, writeOutput, Int2Type<sizeof(CharType)>());
-}
-
-//----------------------------------------------------------------------------------------------------------------
-
-template <class CharType, int charSize>
-class UtfDecoderImpl;
-
-
-template <class CharType>
-class UtfDecoderImpl<CharType, 1> //UTF8-char
-{
-public:
- UtfDecoderImpl(const CharType* str, size_t len) : decoder_(reinterpret_cast<const Char8*>(str), len) {}
- Opt<CodePoint> getNext() { return decoder_.getNext(); }
-private:
- Utf8Decoder decoder_;
-};
-
-
-template <class CharType>
-class UtfDecoderImpl<CharType, 2> //Windows: UTF16-wchar_t
-{
-public:
- UtfDecoderImpl(const CharType* str, size_t len) : decoder_(reinterpret_cast<const Char16*>(str), len) {}
- Opt<CodePoint> getNext() { return decoder_.getNext(); }
-private:
- Utf16Decoder decoder_;
-};
-
-
-template <class CharType>
-class UtfDecoderImpl<CharType, 4> //other OS: UTF32-wchar_t
-{
-public:
- UtfDecoderImpl(const CharType* str, size_t len) : it_(reinterpret_cast<const CodePoint*>(str)), last_(it_ + len) {}
- Opt<CodePoint> getNext()
- {
- if (it_ == last_)
- return NoValue();
- return *it_++;
- }
-private:
- const CodePoint* it_;
- const CodePoint* last_;
-};
-
-
-template <class CharType>
-using UtfDecoder = UtfDecoderImpl<CharType, sizeof(CharType)>;
-}
-
-//-------------------------------------------------------------------------------------------
-
-template <class UtfString> inline
-bool isValidUtf(const UtfString& str)
-{
- using namespace implementation;
-
- UtfDecoder<typename GetCharType<UtfString>::Type> decoder(strBegin(str), strLength(str));
- while (Opt<CodePoint> cp = decoder.getNext())
- if (*cp == REPLACEMENT_CHAR)
- return false;
-
- return true;
-}
-
-
-template <class UtfString> inline
-size_t unicodeLength(const UtfString& str) //return number of code points (+ correctly handle broken UTF encoding)
-{
- size_t uniLen = 0;
- implementation::UtfDecoder<typename GetCharType<UtfString>::Type> decoder(strBegin(str), strLength(str));
- while (decoder.getNext())
- ++uniLen;
- return uniLen;
-}
-
-
-template <class UtfString> inline
-UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t uniPosLast) //return position of unicode char in UTF-encoded string
-{
- assert(uniPosFirst <= uniPosLast && uniPosLast <= unicodeLength(str));
- using namespace implementation;
- using CharType = typename GetCharType<UtfString>::Type;
- UtfString output;
- if (uniPosFirst >= uniPosLast) //optimize for empty range
- return output;
-
- UtfDecoder<CharType> decoder(strBegin(str), strLength(str));
- for (size_t uniPos = 0; Opt<CodePoint> cp = decoder.getNext(); ++uniPos) //[!] declaration in condition part of the for-loop
- if (uniPosFirst <= uniPos)
- {
- if (uniPos >= uniPosLast)
- break;
- codePointToUtf<CharType>(*cp, [&](CharType c) { output += c; });
- }
- return output;
-}
-
-//-------------------------------------------------------------------------------------------
-
-namespace implementation
-{
-template <class TargetString, class SourceString> inline
-TargetString utfTo(const SourceString& str, FalseType)
-{
- using CharSrc = typename GetCharType<SourceString>::Type;
- using CharTrg = typename GetCharType<TargetString>::Type;
- static_assert(sizeof(CharSrc) != sizeof(CharTrg), "no UTF-conversion needed");
-
- TargetString output;
-
- UtfDecoder<CharSrc> decoder(strBegin(str), strLength(str));
- while (Opt<CodePoint> cp = decoder.getNext())
- codePointToUtf<CharTrg>(*cp, [&](CharTrg c) { output += c; });
-
- return output;
-}
-
-
-template <class TargetString, class SourceString> inline
-TargetString utfTo(const SourceString& str, TrueType) { return copyStringTo<TargetString>(str); }
-}
-
-
-template <class TargetString, class SourceString> inline
-TargetString utfTo(const SourceString& str)
-{
- return implementation::utfTo<TargetString>(str, StaticBool<sizeof(typename GetCharType<SourceString>::Type) == sizeof(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 +#include "optional.h" + +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 utfTo(const SourceString& str); + +const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF"; + +template <class UtfString> +bool isValidUtf(const UtfString& str); //check for UTF-8 encoding errors + +//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> +UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t uniPosLast); + + + + + + + + + +//----------------------- implementation ---------------------------------- +namespace implementation +{ +using CodePoint = uint32_t; +using Char16 = uint16_t; +using Char8 = uint8_t; + +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(static_cast<Char16>( LEAD_SURROGATE + (cp >> 10))); + writeOutput(static_cast<Char16>(TRAIL_SURROGATE + (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; +} + + +class Utf16Decoder +{ +public: + Utf16Decoder(const Char16* str, size_t len) : it_(str), last_(str + len) {} + + Opt<CodePoint> getNext() + { + if (it_ == last_) + return NoValue(); + + const Char16 ch = *it_++; + CodePoint cp = ch; + switch (getUtf16Len(ch)) + { + case 0: //invalid utf16 character + cp = REPLACEMENT_CHAR; + break; + case 1: + break; + case 2: + decodeTrail(cp); + break; + } + return cp; + } + +private: + void decodeTrail(CodePoint& cp) + { + if (it_ != last_) //trail surrogate expected! + { + const Char16 ch = *it_; + if (TRAIL_SURROGATE <= ch && ch <= TRAIL_SURROGATE_MAX) //trail surrogate expected! + { + cp = ((cp - LEAD_SURROGATE) << 10) + (ch - TRAIL_SURROGATE) + 0x10000; + ++it_; + return; + } + } + cp = REPLACEMENT_CHAR; + } + + const Char16* it_; + const Char16* const last_; +}; + +//---------------------------------------------------------------------------------------------------------------- + +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(Char8 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 +} + + +class Utf8Decoder +{ +public: + Utf8Decoder(const Char8* str, size_t len) : it_(str), last_(str + len) {} + + Opt<CodePoint> getNext() + { + if (it_ == last_) + return NoValue(); + + const Char8 ch = *it_++; + CodePoint cp = ch; + switch (getUtf8Len(ch)) + { + case 0: //invalid utf8 character + cp = REPLACEMENT_CHAR; + break; + case 1: + break; + case 2: + cp &= 0x1f; + decodeTrail(cp); + break; + case 3: + cp &= 0xf; + if (decodeTrail(cp)) + decodeTrail(cp); + break; + case 4: + cp &= 0x7; + if (decodeTrail(cp)) + if (decodeTrail(cp)) + decodeTrail(cp); + if (cp > CODE_POINT_MAX) cp = REPLACEMENT_CHAR; + break; + } + return cp; + } + +private: + bool decodeTrail(CodePoint& cp) + { + if (it_ != last_) //trail surrogate expected! + { + const Char8 ch = *it_; + if (ch >> 6 == 0x2) //trail surrogate expected! + { + cp = (cp << 6) + (ch & 0x3f); + ++it_; + return true; + } + } + cp = REPLACEMENT_CHAR; + return false; + } + + const Char8* it_; + const Char8* const last_; +}; + +//---------------------------------------------------------------------------------------------------------------- + +template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<1>) { codePointToUtf8 (cp, writeOutput); } //UTF8-char +template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<2>) { codePointToUtf16(cp, writeOutput); } //Windows: UTF16-wchar_t +template <class Function> inline void codePointToUtf(CodePoint cp, Function writeOutput, Int2Type<4>) { writeOutput(cp); } //other OS: UTF32-wchar_t + +template <class CharType, class Function> inline +void codePointToUtf(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a CharType +{ + return codePointToUtf(cp, writeOutput, Int2Type<sizeof(CharType)>()); +} + +//---------------------------------------------------------------------------------------------------------------- + +template <class CharType, int charSize> +class UtfDecoderImpl; + + +template <class CharType> +class UtfDecoderImpl<CharType, 1> //UTF8-char +{ +public: + UtfDecoderImpl(const CharType* str, size_t len) : decoder_(reinterpret_cast<const Char8*>(str), len) {} + Opt<CodePoint> getNext() { return decoder_.getNext(); } +private: + Utf8Decoder decoder_; +}; + + +template <class CharType> +class UtfDecoderImpl<CharType, 2> //Windows: UTF16-wchar_t +{ +public: + UtfDecoderImpl(const CharType* str, size_t len) : decoder_(reinterpret_cast<const Char16*>(str), len) {} + Opt<CodePoint> getNext() { return decoder_.getNext(); } +private: + Utf16Decoder decoder_; +}; + + +template <class CharType> +class UtfDecoderImpl<CharType, 4> //other OS: UTF32-wchar_t +{ +public: + UtfDecoderImpl(const CharType* str, size_t len) : it_(reinterpret_cast<const CodePoint*>(str)), last_(it_ + len) {} + Opt<CodePoint> getNext() + { + if (it_ == last_) + return NoValue(); + return *it_++; + } +private: + const CodePoint* it_; + const CodePoint* last_; +}; + + +template <class CharType> +using UtfDecoder = UtfDecoderImpl<CharType, sizeof(CharType)>; +} + +//------------------------------------------------------------------------------------------- + +template <class UtfString> inline +bool isValidUtf(const UtfString& str) +{ + using namespace implementation; + + UtfDecoder<typename GetCharType<UtfString>::Type> decoder(strBegin(str), strLength(str)); + while (Opt<CodePoint> cp = decoder.getNext()) + if (*cp == REPLACEMENT_CHAR) + return false; + + return true; +} + + +template <class UtfString> inline +size_t unicodeLength(const UtfString& str) //return number of code points (+ correctly handle broken UTF encoding) +{ + size_t uniLen = 0; + implementation::UtfDecoder<typename GetCharType<UtfString>::Type> decoder(strBegin(str), strLength(str)); + while (decoder.getNext()) + ++uniLen; + return uniLen; +} + + +template <class UtfString> inline +UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t uniPosLast) //return position of unicode char in UTF-encoded string +{ + assert(uniPosFirst <= uniPosLast && uniPosLast <= unicodeLength(str)); + using namespace implementation; + using CharType = typename GetCharType<UtfString>::Type; + UtfString output; + if (uniPosFirst >= uniPosLast) //optimize for empty range + return output; + + UtfDecoder<CharType> decoder(strBegin(str), strLength(str)); + for (size_t uniPos = 0; Opt<CodePoint> cp = decoder.getNext(); ++uniPos) //[!] declaration in condition part of the for-loop + if (uniPosFirst <= uniPos) + { + if (uniPos >= uniPosLast) + break; + codePointToUtf<CharType>(*cp, [&](CharType c) { output += c; }); + } + return output; +} + +//------------------------------------------------------------------------------------------- + +namespace implementation +{ +template <class TargetString, class SourceString> inline +TargetString utfTo(const SourceString& str, FalseType) +{ + using CharSrc = typename GetCharType<SourceString>::Type; + using CharTrg = typename GetCharType<TargetString>::Type; + static_assert(sizeof(CharSrc) != sizeof(CharTrg), "no UTF-conversion needed"); + + TargetString output; + + UtfDecoder<CharSrc> decoder(strBegin(str), strLength(str)); + while (Opt<CodePoint> cp = decoder.getNext()) + codePointToUtf<CharTrg>(*cp, [&](CharTrg c) { output += c; }); + + return output; +} + + +template <class TargetString, class SourceString> inline +TargetString utfTo(const SourceString& str, TrueType) { return copyStringTo<TargetString>(str); } +} + + +template <class TargetString, class SourceString> inline +TargetString utfTo(const SourceString& str) +{ + return implementation::utfTo<TargetString>(str, StaticBool<sizeof(typename GetCharType<SourceString>::Type) == sizeof(typename GetCharType<TargetString>::Type)>()); +} +} + +#endif //UTF_H_01832479146991573473545 diff --git a/zen/warn_static.h b/zen/warn_static.h index 74dbd627..c2232f74 100755 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -1,24 +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
+// ***************************************************************************** +// * 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 d3d59200..0a1c8505 100755 --- a/zen/xml_io.cpp +++ b/zen/xml_io.cpp @@ -1,83 +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
-{
- 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);
- }
-}
+// ***************************************************************************** +// * 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 ae735ae6..6c2fc720 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 a936efb5..6b41af13 100755 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -1,151 +1,151 @@ -// *****************************************************************************
-// * 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>
-#include "utf.h"
-
-
-using namespace zen;
-
-/*
-MSDN "Handling Sorting in Your Applications": https://msdn.microsoft.com/en-us/library/windows/desktop/dd318144
-
-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
-*/
-
-
-
-
-namespace
-{
-int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen)
-{
- //- strncasecmp implements ASCII CI-comparsion only! => signature is broken for UTF8-input; toupper() similarly doesn't support Unicode
- //- wcsncasecmp: https://opensource.apple.com/source/Libc/Libc-763.12/string/wcsncasecmp-fbsd.c
- // => re-implement comparison based on towlower() to avoid memory allocations
- using namespace zen::implementation;
-
- UtfDecoder<char> decL(lhs, lhsLen);
- UtfDecoder<char> decR(rhs, rhsLen);
- for (;;)
- {
- const Opt<CodePoint> cpL = decL.getNext();
- const Opt<CodePoint> cpR = decR.getNext();
- if (!cpL || !cpR)
- return static_cast<int>(!cpR) - static_cast<int>(!cpL);
-
- static_assert(sizeof(wchar_t) == sizeof(CodePoint), "");
- const wchar_t charL = ::towlower(static_cast<wchar_t>(*cpL)); //ordering: towlower() converts to higher code points than towupper()
- const wchar_t charR = ::towlower(static_cast<wchar_t>(*cpR)); //uses LC_CTYPE category of current locale
- if (charL != charR)
- return static_cast<unsigned int>(charL) - static_cast<unsigned int>(charR); //unsigned char-comparison is the convention!
- //unsigned underflow is well-defined!
- }
-}
-}
-
-
-int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen)
-{
- const char* const lhsEnd = lhs + lhsLen;
- const char* const rhsEnd = rhs + rhsLen;
- /*
- - compare strings after conceptually creating blocks of whitespace/numbers/text
- - implement strict weak ordering!
- - don't follow broken "strnatcasecmp": https://github.com/php/php-src/blob/master/ext/standard/strnatcmp.c
- 1. incorrect non-ASCII CI-comparison 2. incorrect bounds checks
- 3. incorrect trimming of *all* whitespace 4. arbitrary handling of leading 0 only at string begin
- 5. incorrect handling of whitespace following a number 6. code is a mess
- */
- for (;;)
- {
- if (lhs == lhsEnd || rhs == rhsEnd)
- return static_cast<int>(lhs != lhsEnd) - static_cast<int>(rhs != rhsEnd); //"nothing" before "something"
- //note: "something" never would have been condensed to "nothing" further below => can finish evaluation here
-
- const bool wsL = isWhiteSpace(*lhs);
- const bool wsR = isWhiteSpace(*rhs);
- if (wsL != wsR)
- return static_cast<int>(!wsL) - static_cast<int>(!wsR); //whitespace before non-ws!
- if (wsL)
- {
- ++lhs, ++rhs;
- while (lhs != lhsEnd && isWhiteSpace(*lhs)) ++lhs;
- while (rhs != rhsEnd && isWhiteSpace(*rhs)) ++rhs;
- continue;
- }
-
- const bool digitL = isDigit(*lhs);
- const bool digitR = isDigit(*rhs);
- if (digitL != digitR)
- return static_cast<int>(!digitL) - static_cast<int>(!digitR); //number before chars!
- if (digitL)
- {
- while (lhs != lhsEnd && *lhs == '0') ++lhs;
- while (rhs != rhsEnd && *rhs == '0') ++rhs;
-
- int rv = 0;
- for (;; ++lhs, ++rhs)
- {
- const bool endL = lhs == lhsEnd || !isDigit(*lhs);
- const bool endR = rhs == rhsEnd || !isDigit(*rhs);
- if (endL != endR)
- return static_cast<int>(!endL) - static_cast<int>(!endR); //more digits means bigger number
- if (endL)
- break; //same number of digits
-
- if (rv == 0 && *lhs != *rhs)
- rv = *lhs - *rhs; //found first digit difference comparing from left
- }
- if (rv != 0)
- return rv;
- continue;
- }
-
- //compare full junks of text: consider unicode encoding!
- const char* textBeginL = lhs++;
- const char* textBeginR = rhs++; //current char is neither white space nor digit at this point!
- while (lhs != lhsEnd && !isWhiteSpace(*lhs) && !isDigit(*lhs)) ++lhs;
- while (rhs != rhsEnd && !isWhiteSpace(*rhs) && !isDigit(*rhs)) ++rhs;
-
- const int rv = compareNoCaseUtf8(textBeginL, lhs - textBeginL, textBeginR, rhs - textBeginR);
- if (rv != 0)
- return rv;
- }
-}
-
-
-namespace
-{
-}
-
-
-int CmpNaturalSort::operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const
-{
- //auto strL = utfTo<std::string>(Zstring(lhs, lhsLen));
- //auto strR = utfTo<std::string>(Zstring(rhs, rhsLen));
- //return cmpStringNaturalLinux(strL.c_str(), strL.size(), strR.c_str(), strR.size());
- return cmpStringNaturalLinux(lhs, lhsLen, rhs, rhsLen);
-
+// ***************************************************************************** +// * 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> +#include "utf.h" + + +using namespace zen; + +/* +MSDN "Handling Sorting in Your Applications": https://msdn.microsoft.com/en-us/library/windows/desktop/dd318144 + +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 +*/ + + + + +namespace +{ +int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) +{ + //- strncasecmp implements ASCII CI-comparsion only! => signature is broken for UTF8-input; toupper() similarly doesn't support Unicode + //- wcsncasecmp: https://opensource.apple.com/source/Libc/Libc-763.12/string/wcsncasecmp-fbsd.c + // => re-implement comparison based on towlower() to avoid memory allocations + using namespace zen::implementation; + + UtfDecoder<char> decL(lhs, lhsLen); + UtfDecoder<char> decR(rhs, rhsLen); + for (;;) + { + const Opt<CodePoint> cpL = decL.getNext(); + const Opt<CodePoint> cpR = decR.getNext(); + if (!cpL || !cpR) + return static_cast<int>(!cpR) - static_cast<int>(!cpL); + + static_assert(sizeof(wchar_t) == sizeof(CodePoint), ""); + const wchar_t charL = ::towlower(static_cast<wchar_t>(*cpL)); //ordering: towlower() converts to higher code points than towupper() + const wchar_t charR = ::towlower(static_cast<wchar_t>(*cpR)); //uses LC_CTYPE category of current locale + if (charL != charR) + return static_cast<unsigned int>(charL) - static_cast<unsigned int>(charR); //unsigned char-comparison is the convention! + //unsigned underflow is well-defined! + } +} +} + + +int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) +{ + const char* const lhsEnd = lhs + lhsLen; + const char* const rhsEnd = rhs + rhsLen; + /* + - compare strings after conceptually creating blocks of whitespace/numbers/text + - implement strict weak ordering! + - don't follow broken "strnatcasecmp": https://github.com/php/php-src/blob/master/ext/standard/strnatcmp.c + 1. incorrect non-ASCII CI-comparison 2. incorrect bounds checks + 3. incorrect trimming of *all* whitespace 4. arbitrary handling of leading 0 only at string begin + 5. incorrect handling of whitespace following a number 6. code is a mess + */ + for (;;) + { + if (lhs == lhsEnd || rhs == rhsEnd) + return static_cast<int>(lhs != lhsEnd) - static_cast<int>(rhs != rhsEnd); //"nothing" before "something" + //note: "something" never would have been condensed to "nothing" further below => can finish evaluation here + + const bool wsL = isWhiteSpace(*lhs); + const bool wsR = isWhiteSpace(*rhs); + if (wsL != wsR) + return static_cast<int>(!wsL) - static_cast<int>(!wsR); //whitespace before non-ws! + if (wsL) + { + ++lhs, ++rhs; + while (lhs != lhsEnd && isWhiteSpace(*lhs)) ++lhs; + while (rhs != rhsEnd && isWhiteSpace(*rhs)) ++rhs; + continue; + } + + const bool digitL = isDigit(*lhs); + const bool digitR = isDigit(*rhs); + if (digitL != digitR) + return static_cast<int>(!digitL) - static_cast<int>(!digitR); //number before chars! + if (digitL) + { + while (lhs != lhsEnd && *lhs == '0') ++lhs; + while (rhs != rhsEnd && *rhs == '0') ++rhs; + + int rv = 0; + for (;; ++lhs, ++rhs) + { + const bool endL = lhs == lhsEnd || !isDigit(*lhs); + const bool endR = rhs == rhsEnd || !isDigit(*rhs); + if (endL != endR) + return static_cast<int>(!endL) - static_cast<int>(!endR); //more digits means bigger number + if (endL) + break; //same number of digits + + if (rv == 0 && *lhs != *rhs) + rv = *lhs - *rhs; //found first digit difference comparing from left + } + if (rv != 0) + return rv; + continue; + } + + //compare full junks of text: consider unicode encoding! + const char* textBeginL = lhs++; + const char* textBeginR = rhs++; //current char is neither white space nor digit at this point! + while (lhs != lhsEnd && !isWhiteSpace(*lhs) && !isDigit(*lhs)) ++lhs; + while (rhs != rhsEnd && !isWhiteSpace(*rhs) && !isDigit(*rhs)) ++rhs; + + const int rv = compareNoCaseUtf8(textBeginL, lhs - textBeginL, textBeginR, rhs - textBeginR); + if (rv != 0) + return rv; + } +} + + +namespace +{ +} + + +int CmpNaturalSort::operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const +{ + //auto strL = utfTo<std::string>(Zstring(lhs, lhsLen)); + //auto strR = utfTo<std::string>(Zstring(rhs, rhsLen)); + //return cmpStringNaturalLinux(strL.c_str(), strL.size(), strR.c_str(), strR.size()); + return cmpStringNaturalLinux(lhs, lhsLen, rhs, rhsLen); + }
\ No newline at end of file diff --git a/zen/zstring.h b/zen/zstring.h index fdb71da0..273efd2f 100755 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -1,159 +1,159 @@ -// *****************************************************************************
-// * 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>;
-
-
-//Compare filepaths: Windows/OS X does NOT distinguish between upper/lower-case, while Linux DOES
-struct CmpFilePath
-{
- int operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const;
-};
-
-struct CmpNaturalSort
-{
- int operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const;
-};
-
-
-struct LessFilePath
-{
- template <class S> //don't support heterogenous input! => use as container predicate only!
- bool operator()(const S& lhs, const S& rhs) const { using namespace zen; return CmpFilePath()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
-};
-
-struct LessNaturalSort
-{
- template <class S> //don't support heterogenous input! => use as container predicate only!
- bool operator()(const S& lhs, const S& rhs) const { using namespace zen; return CmpNaturalSort()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; }
-};
-
-
-template <class S>
-S makeUpperCopy(S str);
-
-
-template <class S, class T> inline
-bool equalFilePath(const S& lhs, const T& rhs) { using namespace zen; return strEqual(lhs, rhs, CmpFilePath()); }
-
-
-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, 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: 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!
-}
-
-
-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::operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const
-{
- 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;
- }
-}
-
- int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen);
-
-//---------------------------------------------------------------------------
-//ZEN macro consistency checks:
-
-
-#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>; + + +//Compare filepaths: Windows/OS X does NOT distinguish between upper/lower-case, while Linux DOES +struct CmpFilePath +{ + int operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const; +}; + +struct CmpNaturalSort +{ + int operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const; +}; + + +struct LessFilePath +{ + template <class S> //don't support heterogenous input! => use as container predicate only! + bool operator()(const S& lhs, const S& rhs) const { using namespace zen; return CmpFilePath()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; } +}; + +struct LessNaturalSort +{ + template <class S> //don't support heterogenous input! => use as container predicate only! + bool operator()(const S& lhs, const S& rhs) const { using namespace zen; return CmpNaturalSort()(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; } +}; + + +template <class S> +S makeUpperCopy(S str); + + +template <class S, class T> inline +bool equalFilePath(const S& lhs, const T& rhs) { using namespace zen; return strEqual(lhs, rhs, CmpFilePath()); } + + +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, 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: 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! +} + + +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::operator()(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) const +{ + 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; + } +} + + int cmpStringNaturalLinux(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen); + +//--------------------------------------------------------------------------- +//ZEN macro consistency checks: + + +#endif //ZSTRING_H_73425873425789 |