diff options
author | B Stack <bgstack15@gmail.com> | 2020-09-01 00:24:17 +0000 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2020-09-01 00:24:17 +0000 |
commit | 5a3f52b016581a6a0cb4513614b6c620d365dde2 (patch) | |
tree | acfdfb3e1046db87040477033fda0df76d92916a /zen | |
parent | Merge branch '11.0' into 'master' (diff) | |
parent | add upstream 11.1 (diff) | |
download | FreeFileSync-11.1.tar.gz FreeFileSync-11.1.tar.bz2 FreeFileSync-11.1.zip |
Merge branch '11.1' into 'master'11.1
add upstream 11.1
See merge request opensource-tracking/FreeFileSync!25
Diffstat (limited to 'zen')
40 files changed, 857 insertions, 623 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index 0a226555..0e30c276 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -7,14 +7,11 @@ #ifndef BASIC_MATH_H_3472639843265675 #define BASIC_MATH_H_3472639843265675 +#include <cassert> #include <algorithm> -#include <iterator> -#include <limits> #include <cmath> -#include <functional> -#include <cassert> + #include <numbers> #include "type_traits.h" -#include "legacy_compiler.h" namespace numeric @@ -22,7 +19,7 @@ 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> bool isNull(T value); +template <class T> bool isNull(T value); //...definitively fishy... template <class T, class InputIterator> //precondition: range must be sorted! auto nearMatch(const T& val, InputIterator first, InputIterator last); @@ -168,6 +165,7 @@ int64_t round(double d) { assert(d - 0.5 >= std::numeric_limits<int64_t>::min() && //if double is larger than what int can represent: d + 0.5 <= std::numeric_limits<int64_t>::max()); //=> undefined behavior! + return static_cast<int64_t>(d < 0 ? d - 0.5 : d + 0.5); } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index d02e229e..62493084 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -57,14 +57,12 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError 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) == 0; - } - if (!initSuccess) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl"); + const int flags = ::fcntl(pimpl_->notifDescr, F_GETFL); + if (flags == -1) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl(F_GETFL)"); + + if (::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != 0) + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl(F_SETFL, O_NONBLOCK)"); //add watches for (const Zstring& subDirPath : fullFolderList) diff --git a/zen/error_log.h b/zen/error_log.h index 8604f127..6d9f80ae 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -90,7 +90,7 @@ ErrorLog::Stats ErrorLog::getStats() const ++count.error; break; } - assert(static_cast<int>(entries_.size()) == count.info + count.warning + count.error); + assert(std::ssize(entries_) == count.info + count.warning + count.error); return count; } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 10713ce5..7d3fbfc5 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -84,7 +84,7 @@ std::optional<Zstring> zen::getParentFolderPath(const Zstring& itemPath) if (comp->relPath.empty()) return {}; - const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); if (parentRelPath.empty()) return comp->rootPath; return appendSeparator(comp->rootPath) + parentRelPath; @@ -123,7 +123,7 @@ std::optional<ItemType> zen::itemStillExists(const Zstring& itemPath) //throw Fi // 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); + const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); assert(!itemName.empty()); const std::optional<ItemType> parentType = itemStillExists(*parentPath); //throw FileError @@ -316,8 +316,11 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r if (ec == EXDEV) throw ErrorMoveUnsupported(errorMsg, errorDescr); - if (ec == EEXIST) + + assert(!replaceExisting || ec != EEXIST); + if (!replaceExisting && ec == EEXIST) throw ErrorTargetExisting(errorMsg, errorDescr); + throw FileError(errorMsg, errorDescr); }; @@ -518,16 +521,16 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget auto getErrorMsg = [&] { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)); }; //don't allow creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/ - const Zstring dirName = afterLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + const Zstring dirName = afterLast(dirPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); if (std::all_of(dirName.begin(), dirName.end(), [](Zchar c) { return c == Zstr('.'); })) /**/throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Invalid folder name %x.", L"%x", fmtPath(dirName))); - #if 0 //not appreciated: https://freefilesync.org/forum/viewtopic.php?t=7509 +#if 0 //not appreciated: https://freefilesync.org/forum/viewtopic.php?t=7509 //not critical, but will visually confuse user sooner or later: if (startsWith(dirName, Zstr(' ')) || endsWith (dirName, Zstr(' '))) throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Folder name %x starts/ends with space character.", L"%x", fmtPath(dirName))); - #endif +#endif const mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories @@ -638,14 +641,10 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target 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 - //fileOut.preAllocateSpaceBestEffort(sourceInfo.st_size); //throw FileError - //=> perf: seems like no real benefit... + //preallocate disk space + reduce fragmentation (perf: no real benefit) + fileOut.reserveSpace(sourceInfo.st_size); //throw FileError bufferedStreamCopy(fileIn, fileOut); //throw FileError, (ErrorFileLocked), X @@ -659,6 +658,10 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target //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 + //========================================================================================================== + //take fileOut ownership => from this point on, WE are responsible for calling removeFilePlain() on failure!! + //=========================================================================================================== + std::optional<FileError> errorModTime; try { diff --git a/zen/file_id_def.h b/zen/file_id_def.h index 55ee77f5..d2d104d5 100644 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -31,8 +31,9 @@ struct FileId //always available on Linux, and *generally* available on Windows) } VolumeId volumeId = 0; FileIndex fileIndex = 0; + + bool operator==(const FileId&) const = default; }; -inline bool operator==(const FileId& lhs, const FileId& rhs) { return lhs.volumeId == rhs.volumeId && lhs.fileIndex == rhs.fileIndex; } inline diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 80a83724..4c6602cc 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -5,7 +5,6 @@ // ***************************************************************************** #include "file_io.h" -#include "file_access.h" #include <sys/stat.h> #include <fcntl.h> //open @@ -14,12 +13,9 @@ using namespace zen; - const FileBase::FileHandle FileBase::invalidHandleValue_ = -1; - - FileBase::~FileBase() { - if (fileHandle_ != invalidHandleValue_) + if (hFile_ != invalidFileHandle_) try { close(); //throw FileError @@ -30,13 +26,11 @@ FileBase::~FileBase() void FileBase::close() //throw FileError { - if (fileHandle_ == invalidHandleValue_) + if (hFile_ == invalidFileHandle_) 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! + ZEN_ON_SCOPE_EXIT(hFile_ = invalidFileHandle_); - if (::close(fileHandle_) != 0) + if (::close(hFile_) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "close"); } @@ -72,10 +66,10 @@ FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileErro //else: let ::open() fail for errors like "not existing" //don't use O_DIRECT: https://yarchive.net/comp/linux/o_direct.html - const FileBase::FileHandle fileHandle = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC); - if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle + const int fdFile = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC); + if (fdFile == -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)), "open"); - return fileHandle; //pass ownership + return fdFile; //pass ownership } } @@ -90,7 +84,7 @@ FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbuffered { //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)), "posix_fadvise"); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), "posix_fadvise(POSIX_FADV_SEQUENTIAL)"); } @@ -168,13 +162,13 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, Erro namespace { -FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::AccessFlag access) //throw FileError, ErrorTargetExisting +FileBase::FileHandle openHandleForWrite(const Zstring& filePath) //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 | O_CLOEXEC | (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 fdFile = ::open(filePath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | /*access == FileOutput::ACC_OVERWRITE ? O_TRUNC : */ O_EXCL, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 + if (fdFile == -1) { const int ec = errno; //copy before making other system calls! const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)); @@ -186,27 +180,30 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::Acc throw FileError(errorMsg, errorDescr); } - return fileHandle; //pass ownership + return fdFile; //pass ownership } } FileOutput::FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : - FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} + FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) +{ +} -FileOutput::FileOutput(AccessFlag access, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : - FileBase(openHandleForWrite(filePath, access), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting +FileOutput::FileOutput(const Zstring& filePath, const IOCallback& notifyUnbufferedIO) : + FileBase(openHandleForWrite(filePath), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting FileOutput::~FileOutput() { - notifyUnbufferedIO_ = nullptr; //no call-backs during destruction!!! - try + + if (getHandle() != invalidFileHandle_) //not finalized => clean up garbage { - flushBuffers(); //throw FileError, (X) + //"deleting while handle is open" == FILE_FLAG_DELETE_ON_CLOSE + if (::unlink(getFilePath().c_str()) != 0) + assert(false); } - catch (...) { assert(false); } } @@ -288,20 +285,44 @@ void FileOutput::flushBuffers() //throw FileError, 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 + close(); //throw FileError + //~FileBase() calls this one, too, but we want to propagate errors if any } -void FileOutput::preAllocateSpaceBestEffort(uint64_t expectedSize) //throw FileError +void FileOutput::reserveSpace(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 + //NTFS: "If you set the file allocation info [...] the file contents will be forced into nonresident data, even if it would have fit inside the MFT." + if (expectedSize < 1024) //https://www.sciencedirect.com/topics/computer-science/master-file-table + return; + + //don't use ::posix_fallocate! horribly inefficient if FS doesn't support it + changes file size + //FALLOC_FL_KEEP_SIZE => allocate only, file size is NOT changed! + if (::fallocate(getHandle(), //int fd, + FALLOC_FL_KEEP_SIZE, //int mode, + 0, //off_t offset + expectedSize) != 0) //off_t len + if (errno != EOPNOTSUPP) //possible, unlike with posix_fallocate() + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "fallocate"); + +} + +std::string zen::getFileContent(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +{ + FileInput streamIn(filePath, notifyUnbufferedIO); //throw FileError, ErrorFileLocked + return bufferedLoad<std::string>(streamIn); //throw FileError, X +} + + +void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +{ + TempFileOutput fileOut(filePath, notifyUnbufferedIO); //throw FileError + if (!byteStream.empty()) + { + //preallocate disk space + reduce fragmentation + fileOut.reserveSpace(byteStream.size()); //throw FileError + fileOut.write(&byteStream[0], byteStream.size()); //throw FileError, X + } + fileOut.commit(); //throw FileError, X } diff --git a/zen/file_io.h b/zen/file_io.h index b47c6077..81e1e7cc 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -8,46 +8,47 @@ #define FILE_IO_H_89578342758342572345 #include "file_error.h" +#include "file_access.h" #include "serialize.h" +#include "crc.h" +#include "guid.h" namespace zen { const char LINE_BREAK[] = "\n"; //since OS X Apple uses newline, too -/* -OS-buffered file IO optimized for +/* OS-buffered file IO optimized for - sequential read/write accesses - better error reporting - long path support - - follows symlinks - */ + - follows symlinks */ class FileBase { public: - const Zstring& getFilePath() const { return filePath_; } - using FileHandle = int; + static const int invalidFileHandle_ = -1; - FileHandle getHandle() { return fileHandle_; } + FileHandle getHandle() { return hFile_; } //Windows: use 64kB ?? https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-2000-server/cc938632%28v=technet.10%29 //Linux: use st_blksize? //macOS: use f_iosize? static size_t getBlockSize() { return 128 * 1024; }; + const Zstring& getFilePath() const { return filePath_; } + protected: - FileBase(FileHandle handle, const Zstring& filePath) : fileHandle_(handle), filePath_(filePath) {} + FileBase(FileHandle handle, const Zstring& filePath) : hFile_(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_; + FileHandle hFile_ = invalidFileHandle_; const Zstring filePath_; }; @@ -75,54 +76,65 @@ private: class FileOutput : public FileBase { public: - enum AccessFlag - { - ACC_OVERWRITE, - ACC_CREATE_NEW - }; - FileOutput(AccessFlag access, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorTargetExisting + FileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, ErrorTargetExisting FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //takes ownership! ~FileOutput(); - void preAllocateSpaceBestEffort(uint64_t expectedSize); //throw FileError + void reserveSpace(uint64_t expectedSize); //throw FileError void write(const void* buffer, size_t bytesToWrite); //throw FileError, X void flushBuffers(); //throw FileError, X + //caveat: does NOT flush OS or hard disk buffers like e.g. FlushFileBuffers()! + 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 IOCallback notifyUnbufferedIO_; //throw X - std::vector<std::byte> memBuf_ = std::vector<std::byte>(getBlockSize()); size_t bufPos_ = 0; size_t bufPosEnd_ = 0; }; - //----------------------------------------------------------------------------------------------- - //native stream I/O convenience functions: -template <class BinContainer> inline -BinContainer loadBinContainer(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +class TempFileOutput { - FileInput streamIn(filePath, notifyUnbufferedIO); //throw FileError, ErrorFileLocked - return bufferedLoad<BinContainer>(streamIn); //throw FileError, X -} +public: + TempFileOutput( const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/) : //throw FileError + filePath_(filePath), + tmpFile_(tmpFilePath_, notifyUnbufferedIO) {} //throw FileError, (ErrorTargetExisting) + void reserveSpace(uint64_t expectedSize) { tmpFile_.reserveSpace(expectedSize); } //throw FileError -template <class BinContainer> inline -void saveBinContainer(const Zstring& filePath, const BinContainer& buffer, const IOCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X -{ - FileOutput fileOut(FileOutput::ACC_OVERWRITE, filePath, notifyUnbufferedIO); //throw FileError, (ErrorTargetExisting) - if (!buffer.empty()) + void write(const void* buffer, size_t bytesToWrite) { tmpFile_.write(buffer, bytesToWrite); } //throw FileError, X + + void commit() //throw FileError, X { - /*snake oil?*/ fileOut.preAllocateSpaceBestEffort(buffer.size()); //throw FileError - fileOut.write(&buffer[0], buffer.size()); //throw FileError, X + tmpFile_.finalize(); //throw FileError, X + + //take ownership: + ZEN_ON_SCOPE_FAIL( try { removeFilePlain(tmpFilePath_); /*throw FileError*/ } + catch (FileError&) {}); + + //operation finished: move temp file transactionally + moveAndRenameItem(tmpFilePath_, filePath_, true /*replaceExisting*/); //throw FileError, (ErrorMoveUnsupported), (ErrorTargetExisting) } - fileOut.finalize(); //throw FileError, X -} + +private: + //generate (hopefully) unique file name to avoid clashing with unrelated tmp file + const Zstring filePath_; + const Zstring shortGuid_ = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID()))); + const Zstring tmpFilePath_ = filePath_ + Zstr('.') + shortGuid_ + Zstr(".tmp"); + FileOutput tmpFile_; +}; + + +[[nodiscard]] std::string getFileContent(const Zstring& filePath, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X + +//overwrites if existing + transactional! :) +void setFileContent(const Zstring& filePath, const std::string& bytes, const IOCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X } #endif //FILE_IO_H_89578342758342572345 diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 28e62236..0afc28ee 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -8,7 +8,6 @@ #include "file_error.h" - #include <cstddef> //offsetof #include <unistd.h> //::pathconf() #include <sys/stat.h> #include <dirent.h> diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index eeebda53..4984c1d7 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -73,12 +73,12 @@ std::wstring zen::formatFilesizeShort(int64_t size) namespace { -enum UnitRemTime +enum class UnitRemTime { - URT_SEC, - URT_MIN, - URT_HOUR, - URT_DAY + sec, + min, + hour, + day }; @@ -86,13 +86,13 @@ std::wstring formatUnitTime(int val, UnitRemTime unit) { switch (unit) { - case URT_SEC: + case UnitRemTime::sec: return _P("1 sec", "%x sec", val); - case URT_MIN: + case UnitRemTime::min: return _P("1 min", "%x min", val); - case URT_HOUR: + case UnitRemTime::hour: return _P("1 hour", "%x hours", val); - case URT_DAY: + case UnitRemTime::day: return _P("1 day", "%x days", val); } assert(false); @@ -131,18 +131,18 @@ std::wstring zen::formatRemainingTime(double timeInSec) //determine preferred unit double timeInUnit = timeInSec; if (timeInUnit <= 60) - return roundToBlock(timeInUnit, URT_SEC, steps60, 1, URT_SEC, steps60); + return roundToBlock(timeInUnit, UnitRemTime::sec, steps60, 1, UnitRemTime::sec, steps60); timeInUnit /= 60; if (timeInUnit <= 60) - return roundToBlock(timeInUnit, URT_MIN, steps60, 60, URT_SEC, steps60); + return roundToBlock(timeInUnit, UnitRemTime::min, steps60, 60, UnitRemTime::sec, steps60); timeInUnit /= 60; if (timeInUnit <= 24) - return roundToBlock(timeInUnit, URT_HOUR, steps24, 60, URT_MIN, steps60); + return roundToBlock(timeInUnit, UnitRemTime::hour, steps24, 60, UnitRemTime::min, steps60); timeInUnit /= 24; - return roundToBlock(timeInUnit, URT_DAY, steps10, 24, URT_HOUR, steps24); + return roundToBlock(timeInUnit, UnitRemTime::day, steps10, 24, UnitRemTime::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 } @@ -164,28 +164,9 @@ std::wstring zen::formatFraction(double fraction) std::wstring zen::formatNumber(int64_t n) { - //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 number = numberTo<std::wstring>(n); - - size_t i = number.size(); - for (;;) - { - if (i <= 3) - break; - i -= 3; - if (!isDigit(number[i - 1])) //stop on +, - signs - break; - number.insert(i, thousandSep); - } - return number; + //::setlocale (LC_ALL, ""); -> see localization.cpp::wxWidgetsLocale + static_assert(sizeof(long long int) == sizeof(n)); + return printNumber<std::wstring>(L"%'lld", n); //considers grouping (') } diff --git a/zen/globals.h b/zen/globals.h index 876d2598..635909f7 100644 --- a/zen/globals.h +++ b/zen/globals.h @@ -10,19 +10,18 @@ #include <atomic> #include <memory> #include "scope_guard.h" +#include "legacy_compiler.h" namespace zen { -/* -Solve static destruction order fiasco by providing shared ownership and serialized access to global variables +/* Solve static destruction order fiasco by providing shared ownership and serialized access to global variables -=> 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!!! - -ATTENTION: function-static globals have the compiler generate "magic statics" == compiler-genenerated locking code which will crash or leak memory when accessed after global is "dead" - => "solved" by FunStatGlobal, but we can't have "too many" of these... */ + => 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!!! + ATTENTION: function-static globals have the compiler generate "magic statics" == compiler-genenerated locking code which will crash or leak memory when accessed after global is "dead" + => "solved" by FunStatGlobal, but we can't have "too many" of these... */ class PodSpinMutex { public: @@ -32,7 +31,7 @@ public: bool isLocked(); private: - std::atomic_flag flag_; /* => avoid potential contention with worker thread during Global<> construction! + std::atomic_flag flag_{}; /* => avoid potential contention with worker thread during Global<> construction! - "For an atomic_flag with static storage duration, this guarantees static initialization:" => just what the doctor ordered! - "[default initialization] initializes std::atomic_flag to clear state" - since C++20 => - "std::atomic_flag is [...] guaranteed to be lock-free" @@ -40,21 +39,25 @@ private: }; +#define GLOBAL_RUN_ONCE(X) \ + struct ZEN_CONCAT(GlobalInitializer, __LINE__) \ + { \ + ZEN_CONCAT(GlobalInitializer, __LINE__)() { X; } \ + } ZEN_CONCAT(globalInitializer, __LINE__) + + template <class T> class Global //don't use for function-scope statics! { public: - Global() + consteval2 Global() {}; //demand static zero-initialization! + + ~Global() { static_assert(std::is_trivially_destructible_v<Pod>, "this memory needs to live forever"); - assert(!pod_.spinLock.isLocked()); //we depend on static zero-initialization! - assert(!pod_.inst); // + set(nullptr); } - 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!) { pod_.spinLock.lock(); @@ -83,8 +86,8 @@ private: struct Pod { PodSpinMutex spinLock; //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 - std::shared_ptr<T>* inst; //= nullptr; + //serialize access: can't use std::mutex: has non-trival destructor + std::shared_ptr<T>* inst = nullptr; } pod_; }; @@ -94,9 +97,9 @@ private: struct CleanUpEntry { using CleanUpFunction = void (*)(void* callbackData); - CleanUpFunction cleanUpFun; - void* callbackData; - CleanUpEntry* prev; + CleanUpFunction cleanUpFun = nullptr; + void* callbackData = nullptr; + CleanUpEntry* prev = nullptr; }; void registerGlobalForDestruction(CleanUpEntry& entry); @@ -105,15 +108,31 @@ template <class T> class FunStatGlobal { public: - //No FunStatGlobal() or ~FunStatGlobal()! + consteval2 FunStatGlobal() {}; //demand static zero-initialization! - std::shared_ptr<T> get() + //No ~FunStatGlobal()! + + void initOnce(std::unique_ptr<T> (*getInitialValue)()) { static_assert(std::is_trivially_destructible_v<FunStatGlobal>, "this class must not generate code for magic statics!"); pod_.spinLock.lock(); ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); + if (!pod_.cleanUpEntry.cleanUpFun) + { + assert(!pod_.inst); + if (std::unique_ptr<T> newInst = (*getInitialValue)()) + pod_.inst = new std::shared_ptr<T>(std::move(newInst)); + registerDestruction(); + } + } + + std::shared_ptr<T> get() + { + pod_.spinLock.lock(); + ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); + if (pod_.inst) return *pod_.inst; return nullptr; @@ -134,20 +153,6 @@ public: delete tmpInst; } - void initOnce(std::unique_ptr<T> (*getInitialValue)()) - { - pod_.spinLock.lock(); - ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); - - if (!pod_.cleanUpEntry.cleanUpFun) - { - assert(!pod_.inst); - if (std::unique_ptr<T> newInst = (*getInitialValue)()) - pod_.inst = new std::shared_ptr<T>(std::move(newInst)); - registerDestruction(); - } - } - private: //call while holding pod_.spinLock void registerDestruction() @@ -171,7 +176,7 @@ private: { PodSpinMutex spinLock; //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 - std::shared_ptr<T>* inst; // = nullptr; + std::shared_ptr<T>* inst = nullptr; CleanUpEntry cleanUpEntry; } pod_; }; @@ -206,7 +211,7 @@ void registerGlobalForDestruction(CleanUpEntry& entry) } //------------------------------------------------------------------------------------------ -#if __cpp_lib_atomic_wait +#ifdef __cpp_lib_atomic_wait #error implement + rewiew improvements #endif @@ -222,7 +227,7 @@ inline void PodSpinMutex::lock() { while (!tryLock()) -#if __cpp_lib_atomic_wait +#ifdef __cpp_lib_atomic_wait flag_.wait(true, std::memory_order_relaxed); #else ; @@ -234,7 +239,7 @@ inline void PodSpinMutex::unlock() { flag_.clear(std::memory_order_release); -#if __cpp_lib_atomic_wait +#ifdef __cpp_lib_atomic_wait flag_.notify_one(); #endif } @@ -20,11 +20,11 @@ std::string generateGUID() //creates a 16-byte GUID { std::string guid(16, '\0'); -#ifndef __GLIBC__ -#error Where is GLIB? +#ifndef __GLIBC_PREREQ +#error Where is Glibc? #endif -#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25) //getentropy() requires glibc 2.25 (ldd --version) PS: CentOS 7 is on 2.17 +#if __GLIBC_PREREQ(2, 25) //getentropy() requires Glibc 2.25 (ldd --version) PS: CentOS 7 is on 2.17 if (::getentropy(&guid[0], guid.size()) != 0) //"The maximum permitted value for the length argument is 256" throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" + utfTo<std::string>(formatSystemError("getentropy", errno))); diff --git a/zen/http.cpp b/zen/http.cpp index 57d61221..5d389719 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -31,9 +31,9 @@ public: //may be sending large POST: call back first if (notifyUnbufferedIO_) notifyUnbufferedIO_(0); //throw X - const Zstring urlFmt = afterFirst(url, Zstr("://"), IF_MISSING_RETURN_NONE); - const Zstring server = beforeFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_ALL); - const Zstring page = Zstr('/') + afterFirst(urlFmt, Zstr('/'), IF_MISSING_RETURN_NONE); + const Zstring urlFmt = afterFirst(url, Zstr("://"), IfNotFoundReturn::none); + const Zstring server = beforeFirst(urlFmt, Zstr('/'), IfNotFoundReturn::all); + const Zstring page = Zstr('/') + afterFirst(urlFmt, Zstr('/'), IfNotFoundReturn::none); const bool useTls = [&] { @@ -100,8 +100,8 @@ public: if (contains(buf, headerDelim)) { - headBuf = beforeFirst(buf, headerDelim, IF_MISSING_RETURN_NONE); - const std::string bodyBuf = afterFirst (buf, headerDelim, IF_MISSING_RETURN_NONE); + headBuf = beforeFirst(buf, headerDelim, IfNotFoundReturn::none); + const std::string bodyBuf = afterFirst (buf, headerDelim, IfNotFoundReturn::none); //put excess bytes into instance buffer for body retrieval assert(bufPos_ == 0 && bufPosEnd_ == 0); bufPosEnd_ = bodyBuf.size(); @@ -112,18 +112,18 @@ public: break; } //parse header - const std::string statusBuf = beforeFirst(headBuf, "\r\n", IF_MISSING_RETURN_ALL); - const std::string headersBuf = afterFirst (headBuf, "\r\n", IF_MISSING_RETURN_NONE); + const std::string statusBuf = beforeFirst(headBuf, "\r\n", IfNotFoundReturn::all); + const std::string headersBuf = afterFirst (headBuf, "\r\n", IfNotFoundReturn::none); - const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitType::ALLOW_EMPTY); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF + const std::vector<std::string> statusItems = split(statusBuf, ' ', SplitOnEmpty::allow); //HTTP-Version SP Status-Code SP Reason-Phrase CRLF if (statusItems.size() < 2 || !startsWith(statusItems[0], "HTTP/")) throw SysError(L"Invalid HTTP response: \"" + utfTo<std::wstring>(statusBuf) + L'"'); statusCode_ = stringTo<int>(statusItems[1]); - for (const std::string& line : split(headersBuf, "\r\n", SplitType::SKIP_EMPTY)) - responseHeaders_[trimCpy(beforeFirst(line, ':', IF_MISSING_RETURN_ALL))] = - /**/ trimCpy(afterFirst (line, ':', IF_MISSING_RETURN_NONE)); + for (const std::string& line : split(headersBuf, "\r\n", SplitOnEmpty::skip)) + responseHeaders_[trimCpy(beforeFirst(line, ':', IfNotFoundReturn::all))] = + /**/ trimCpy(afterFirst (line, ':', IfNotFoundReturn::none)); //try to get "Content-Length" header if available if (const std::string* value = getHeader("Content-Length")) @@ -332,9 +332,9 @@ std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const st { std::vector<std::pair<std::string, std::string>> output; - for (const std::string& nvPair : split(str, '&', SplitType::SKIP_EMPTY)) - output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)), - urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE))); + for (const std::string& nvPair : split(str, '&', SplitOnEmpty::skip)) + output.emplace_back(urldecode(beforeFirst(nvPair, '=', IfNotFoundReturn::all)), + urldecode(afterFirst (nvPair, '=', IfNotFoundReturn::none))); return output; } @@ -465,17 +465,17 @@ bool zen::isValidEmail(const std::string& email) //https://en.wikipedia.org/wiki/Email_address#Syntax //https://tools.ietf.org/html/rfc3696 => note errata! https://www.rfc-editor.org/errata_search.php?rfc=3696 //https://tools.ietf.org/html/rfc5321 - std::string local = beforeLast(email, '@', IF_MISSING_RETURN_NONE); - std::string domain = afterLast(email, '@', IF_MISSING_RETURN_NONE); + std::string local = beforeLast(email, '@', IfNotFoundReturn::none); + std::string domain = afterLast(email, '@', IfNotFoundReturn::none); //consider: "t@st"@email.com t\@st@email.com" auto stripComments = [](std::string& part) { if (startsWith(part, '(')) - part = afterFirst(part, ')', IF_MISSING_RETURN_NONE); + part = afterFirst(part, ')', IfNotFoundReturn::none); if (endsWith(part, ')')) - part = beforeLast(part, '(', IF_MISSING_RETURN_NONE); + part = beforeLast(part, '(', IfNotFoundReturn::none); }; stripComments(local); stripComments(domain); @@ -488,7 +488,7 @@ bool zen::isValidEmail(const std::string& email) const bool quoted = (startsWith(local, '"') && endsWith(local, '"')) || contains(local, '\\'); //e.g. "t\@st@email.com" if (!quoted) //I'm not going to parse and validate this! - for (const std::string& comp : split(local, '.', SplitType::ALLOW_EMPTY)) + for (const std::string& comp : split(local, '.', SplitOnEmpty::allow)) if (comp.empty() || !std::all_of(comp.begin(), comp.end(), [](char c) { const char printable[] = "!#$%&'*+-/=?^_`{|}~"; @@ -505,7 +505,7 @@ bool zen::isValidEmail(const std::string& email) if (!contains(domain, '.')) return false; - for (const std::string& comp : split(domain, '.', SplitType::ALLOW_EMPTY)) + for (const std::string& comp : split(domain, '.', SplitOnEmpty::allow)) if (comp.empty() || comp.size() > 63 || !std::all_of(comp.begin(), comp.end(), [](char c) { return isAsciiAlpha(c) ||isDigit(c) || !isAsciiChar(c) || c == '-'; })) return false; @@ -57,26 +57,21 @@ std::shared_ptr<const TranslationHandler> getTranslator(); //######################## implementation ############################## namespace impl { -inline -FunStatGlobal<const TranslationHandler>& refGlobalTranslationHandler() -{ - //getTranslator() may be called even after static objects of this translation unit are destroyed! - static FunStatGlobal<const TranslationHandler> inst; //external linkage even in header! - return inst; -} +//getTranslator() may be called even after static objects of this translation unit are destroyed! +inline constinit2 Global<const TranslationHandler> globalTranslationHandler; } inline std::shared_ptr<const TranslationHandler> getTranslator() { - return impl::refGlobalTranslationHandler().get(); + return impl::globalTranslationHandler.get(); } inline void setTranslator(std::unique_ptr<const TranslationHandler>&& newHandler) { - impl::refGlobalTranslationHandler().set(std::move(newHandler)); + impl::globalTranslationHandler.set(std::move(newHandler)); } @@ -26,14 +26,18 @@ struct JsonValue array, }; - explicit JsonValue() {} + /**/ JsonValue() {} explicit JsonValue(Type t) : type(t) {} explicit JsonValue(bool b) : type(Type::boolean), primVal(b ? "true" : "false") {} explicit JsonValue(int num) : type(Type::number), primVal(numberTo<std::string>(num)) {} explicit JsonValue(int64_t num) : type(Type::number), primVal(numberTo<std::string>(num)) {} explicit JsonValue(double num) : type(Type::number), primVal(numberTo<std::string>(num)) {} explicit JsonValue(std::string str) : type(Type::string), primVal(std::move(str)) {} //unifying assignment - explicit JsonValue(const void*) = delete; //catch usage errors e.g. const char* -> JsonValue(bool) + explicit JsonValue(const char* str) : type(Type::string), primVal(str) {} + explicit JsonValue(const void*) = delete; //catch usage errors e.g. const int* -> JsonValue(bool) + //explicit JsonValue(std::initializer_list<JsonValue> initList) : type(Type::array), arrayVal(initList) {} => empty list is ambiguous + explicit JsonValue(std::vector<JsonValue> initList) : type(Type::array), arrayVal(std::move(initList)) {} //unifying assignment + Type type = Type::null; std::string primVal; //for primitive types @@ -97,28 +101,28 @@ std::string jsonEscape(const std::string& str) for (const char c : str) switch (c) { - //*INDENT-OFF* - case '"': output += "\\\""; break; //escaping mandatory - case '\\': output += "\\\\"; break; // - - case '\b': output += "\\b"; break; // - case '\f': output += "\\f"; break; // - case '\n': output += "\\n"; break; //prefer compact escaping - case '\r': output += "\\r"; break; // - case '\t': output += "\\t"; break; // - - default: - if (static_cast<unsigned char>(c) < 32) - { - const auto [high, low] = hexify(c); - output += "\\u00"; - output += high; - output += low; - } - else - output += c; - break; - //*INDENT-ON* + //*INDENT-OFF* + case '"': output += "\\\""; break; //escaping mandatory + case '\\': output += "\\\\"; break; // + + case '\b': output += "\\b"; break; // + case '\f': output += "\\f"; break; // + case '\n': output += "\\n"; break; //prefer compact escaping + case '\r': output += "\\r"; break; // + case '\t': output += "\\t"; break; // + + default: + if (static_cast<unsigned char>(c) < 32) + { + const auto [high, low] = hexify(c); + output += "\\u00"; + output += high; + output += low; + } + else + output += c; + break; + //*INDENT-ON* } return output; } @@ -133,7 +137,7 @@ std::string jsonUnescape(const std::string& str) { if (!utf16Buf.empty()) { - impl::UtfDecoder<impl::Char16> decoder(utf16Buf.c_str(), utf16Buf.size()); + UtfDecoder<impl::Char16> decoder(utf16Buf.c_str(), utf16Buf.size()); while (std::optional<impl::CodePoint> cp = decoder.getNext()) impl::codePointToUtf<char>(*cp, [&](char c) { output += c; }); utf16Buf.clear(); @@ -161,34 +165,34 @@ std::string jsonUnescape(const std::string& str) const char c2 = *it; switch (c2) { - //*INDENT-OFF* - case '"': - case '\\': - case '/': writeOut(c2); break; - case 'b': writeOut('\b'); break; - case 'f': writeOut('\f'); break; - case 'n': writeOut('\n'); break; - case 'r': writeOut('\r'); break; - case 't': writeOut('\t'); break; - default: - if (c2 == 'u' && - str.end() - it >= 5 && - isHexDigit(it[1]) && - isHexDigit(it[2]) && - isHexDigit(it[3]) && - isHexDigit(it[4])) - { - utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 + - static_cast<unsigned char>(unhexify(it[3], it[4]))); - it += 4; - } - else //unknown escape sequence! - { - writeOut(c); - writeOut(c2); - } - break; - //*INDENT-ON* + //*INDENT-OFF* + case '"': + case '\\': + case '/': writeOut(c2); break; + case 'b': writeOut('\b'); break; + case 'f': writeOut('\f'); break; + case 'n': writeOut('\n'); break; + case 'r': writeOut('\r'); break; + case 't': writeOut('\t'); break; + default: + if (c2 == 'u' && + str.end() - it >= 5 && + isHexDigit(it[1]) && + isHexDigit(it[2]) && + isHexDigit(it[3]) && + isHexDigit(it[4])) + { + utf16Buf += static_cast<impl::Char16>(static_cast<unsigned char>(unhexify(it[1], it[2])) * 256 + + static_cast<unsigned char>(unhexify(it[3], it[4]))); + it += 4; + } + else //unknown escape sequence! + { + writeOut(c); + writeOut(c2); + } + break; + //*INDENT-ON* } } else diff --git a/zen/legacy_compiler.cpp b/zen/legacy_compiler.cpp index 416993ed..66125b0f 100644 --- a/zen/legacy_compiler.cpp +++ b/zen/legacy_compiler.cpp @@ -5,7 +5,7 @@ // ***************************************************************************** #include "legacy_compiler.h" -#if __cpp_lib_to_chars +#ifdef __cpp_lib_to_chars #error get rid of workarounds #endif diff --git a/zen/legacy_compiler.h b/zen/legacy_compiler.h index 16e22d03..82c404d8 100644 --- a/zen/legacy_compiler.h +++ b/zen/legacy_compiler.h @@ -7,9 +7,6 @@ #ifndef LEGACY_COMPILER_H_839567308565656789 #define LEGACY_COMPILER_H_839567308565656789 - #include <numbers> //C++20 - - //https://en.cppreference.com/w/cpp/feature_test @@ -18,12 +15,12 @@ //https://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html namespace std { - +} //--------------------------------------------------------------------------------- - - -} +//constinit, consteval + #define constinit2 constinit //GCC has it + #define consteval2 consteval // namespace zen diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp index f2b48fd9..1d0c4bf2 100644 --- a/zen/open_ssl.cpp +++ b/zen/open_ssl.cpp @@ -374,7 +374,7 @@ void zen::verifySignature(const std::string& message, const std::string& signatu namespace { -std::wstring formatSslErrorCode(int ec) +std::wstring getSslErrorLiteral(int ec) { switch (ec) { @@ -392,7 +392,7 @@ std::wstring formatSslErrorCode(int ec) ZEN_CHECK_CASE_FOR_CONSTANT(SSL_ERROR_WANT_CLIENT_HELLO_CB); default: - return replaceCpy<std::wstring>(L"SSL error %x", L"%x", numberTo<std::wstring>(ec)); + return L"SSL error " + numberTo<std::wstring>(ec); } } @@ -532,7 +532,7 @@ public: const int rv = ::SSL_connect(ssl_); //implicitly calls SSL_set_connect_state() if (rv != 1) - throw SysError(formatLastOpenSSLError("SSL_connect") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv))); + throw SysError(formatLastOpenSSLError("SSL_connect") + L' ' + getSslErrorLiteral(::SSL_get_error(ssl_, rv))); if (caCertFilePath) { @@ -579,7 +579,7 @@ public: if ((sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0)) //EOF: only expected for HTTP/1.0 return 0; #endif - throw SysError(formatLastOpenSSLError("SSL_read_ex") + L' ' + formatSslErrorCode(sslError)); + throw SysError(formatLastOpenSSLError("SSL_read_ex") + L' ' + getSslErrorLiteral(sslError)); } assert(bytesReceived > 0); //SSL_read_ex() considers EOF an error! if (bytesReceived > bytesToRead) //better safe than sorry @@ -596,7 +596,7 @@ public: size_t bytesWritten = 0; const int rv = ::SSL_write_ex(ssl_, buffer, bytesToWrite, &bytesWritten); if (rv != 1) - throw SysError(formatLastOpenSSLError("SSL_write_ex") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv))); + throw SysError(formatLastOpenSSLError("SSL_write_ex") + L' ' + getSslErrorLiteral(::SSL_get_error(ssl_, rv))); if (bytesWritten > bytesToWrite) throw SysError(formatSystemError("SSL_write_ex", L"", L"Buffer overflow.")); @@ -657,22 +657,22 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: auto itLine = lines.begin(); if (itLine == lines.end() || !startsWith(*itLine, "PuTTY-User-Key-File-2: ")) throw SysError(L"Unknown key file format"); - const std::string algorithm = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string algorithm = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Encryption: ")) throw SysError(L"Unknown key encryption"); - const std::string keyEncryption = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string keyEncryption = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Comment: ")) throw SysError(L"Invalid key comment"); - const std::string comment = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string comment = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; if (itLine == lines.end() || !startsWith(*itLine, "Public-Lines: ")) throw SysError(L"Invalid key: invalid public lines"); - size_t pubLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE)); + size_t pubLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IfNotFoundReturn::none)); ++itLine; std::string publicBlob64; @@ -684,7 +684,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (itLine == lines.end() || !startsWith(*itLine, "Private-Lines: ")) throw SysError(L"Invalid key: invalid private lines"); - size_t privLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE)); + size_t privLineCount = stringTo<size_t>(afterFirst(*itLine, ' ', IfNotFoundReturn::none)); ++itLine; std::string privateBlob64; @@ -696,7 +696,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: if (itLine == lines.end() || !startsWith(*itLine, "Private-MAC: ")) throw SysError(L"Invalid key: MAC missing"); - const std::string macHex = afterFirst(*itLine, ' ', IF_MISSING_RETURN_NONE); + const std::string macHex = afterFirst(*itLine, ' ', IfNotFoundReturn::none); ++itLine; //----------- unpack key file elements --------------------- @@ -945,7 +945,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std:: algorithm == "ecdsa-sha2-nistp384" || algorithm == "ecdsa-sha2-nistp521") { - const std::string algoShort = afterLast(algorithm, '-', IF_MISSING_RETURN_NONE); + const std::string algoShort = afterLast(algorithm, '-', IfNotFoundReturn::none); if (extractStringPub() != algoShort) throw SysError(L"Invalid public key stream (header)"); @@ -32,7 +32,7 @@ namespace zen // => wxStopWatch implementation uses QueryPerformanceCounter: https://github.com/wxWidgets/wxWidgets/blob/17d72a48ffd4d8ff42eed070ac48ee2de50ceabd/src/common/stopwatch.cpp // => whatever the problem was, it's almost certainly not caused by QueryPerformanceCounter(): // MSDN: "How often does QPC roll over? Not less than 100 years from the most recent system boot" -// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#How_often_does_QPC_roll_over +// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#general-faq-about-qpc-and-tsc // // => using the system clock is problematic: https://freefilesync.org/forum/viewtopic.php?t=5280 // diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 9c463546..b1f2c0fd 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -11,7 +11,6 @@ #include <gio/gio.h> #include "scope_guard.h" - using namespace zen; @@ -31,8 +30,15 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError if (!type) return false; - //implement same behavior as in Windows: if recycler is not existing, delete permanently - if (error && error->code == G_IO_ERROR_NOT_SUPPORTED) + /* g_file_trash() can fail with different error codes/messages when trash is unavailable: + Debian 8 (GLib 2.42): G_IO_ERROR_NOT_SUPPORTED: Unable to find or create trash directory + CentOS 7 (GLib 2.56): G_IO_ERROR_FAILED: Unable to find or create trash directory for file.txt + master (GLib 2.64): G_IO_ERROR_NOT_SUPPORTED: Trashing on system internal mounts is not supported + https://gitlab.gnome.org/GNOME/glib/blob/master/gio/glocalfile.c#L2042 */ + const bool trashUnavailable = error && + ((error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_SUPPORTED) || + startsWith(error->message, "Unable to find or create trash directory")); + if (trashUnavailable) //implement same behavior as on Windows: if recycler is not existing, delete permanently { if (*type == ItemType::folder) removeDirectoryPlainRecursion(itemPath); //throw FileError @@ -42,13 +48,9 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError } throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)), - formatSystemError("g_file_trash", - error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"", - error ? utfTo<std::wstring>(error->message) : L"Unknown error.")); - //g_quark_to_string(error->domain) + formatGlibError("g_file_trash", error)); } return true; - } diff --git a/zen/recycler.h b/zen/recycler.h index ad96aa53..deb03a1c 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -14,24 +14,21 @@ namespace zen { -/* --------------------- -|Recycle Bin Access| --------------------- +/* -------------------- + |Recycle Bin Access| + -------------------- -Windows -------- --> Recycler API (IFileOperation) always available --> COM needs to be initialized before calling any of these functions! CoInitializeEx/CoUninitialize + Windows + ------- + -> Recycler API (IFileOperation) always available + -> 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"! -*/ + 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 diff --git a/zen/ring_buffer.h b/zen/ring_buffer.h index a8d629c6..240262fa 100644 --- a/zen/ring_buffer.h +++ b/zen/ring_buffer.h @@ -28,6 +28,8 @@ public: } RingBuffer& operator=(RingBuffer&& tmp) noexcept { swap(tmp); return *this; } //noexcept *required* to support move for reallocations in std::vector and std::swap!!! + ~RingBuffer() { clear(); } + using value_type = T; using reference = T&; using const_reference = const T&; @@ -35,8 +37,6 @@ public: size_t size() const { return size_; } bool empty() const { return size_ == 0; } - ~RingBuffer() { clear(); } - reference front() { checkInvariants(); assert(!empty()); return getBufPtr()[bufStart_]; } const_reference front() const { checkInvariants(); assert(!empty()); return getBufPtr()[bufStart_]; } @@ -184,7 +184,6 @@ public: Iterator& operator++() { ++offset_; return *this; } Iterator& operator+=(ptrdiff_t offset) { offset_ += offset; return *this; } inline friend bool operator==(const Iterator& lhs, const Iterator& rhs) { assert(lhs.container_ == rhs.container_); return lhs.offset_ == rhs.offset_; } - inline friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { return !(lhs == rhs); } inline friend ptrdiff_t operator-(const Iterator& lhs, const Iterator& rhs) { return lhs.offset_ - rhs.offset_; } inline friend Iterator operator+(const Iterator& lhs, ptrdiff_t offset) { Iterator tmp(lhs); return tmp += offset; } Value& operator* () const { return (*container_)[offset_]; } diff --git a/zen/scope_guard.h b/zen/scope_guard.h index e97d3f0a..61422eb4 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -16,17 +16,16 @@ namespace zen { -//Scope Guard -/* - auto guardAio = zen::makeGuard<ScopeGuardRunMode::onExit>([&] { ::CloseHandle(hDir); }); - ... - guardAio.dismiss(); - -Scope Exit: - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); - ZEN_ON_SCOPE_FAIL(UndoPreviousWork()); - ZEN_ON_SCOPE_SUCCESS(NotifySuccess()); -*/ +/* Scope Guard + + auto guardAio = zen::makeGuard<ScopeGuardRunMode::onExit>([&] { ::CloseHandle(hDir); }); + ... + guardAio.dismiss(); + + Scope Exit: + ZEN_ON_SCOPE_EXIT (CleanUp()); + ZEN_ON_SCOPE_FAIL (UndoPreviousWork()); + ZEN_ON_SCOPE_SUCCESS(NotifySuccess()); */ enum class ScopeGuardRunMode { diff --git a/zen/shell_execute.cpp b/zen/shell_execute.cpp index 90ccfdf3..241b9786 100644 --- a/zen/shell_execute.cpp +++ b/zen/shell_execute.cpp @@ -89,7 +89,7 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm THROW_LAST_SYS_ERROR("open"); auto guardTmpFile = makeGuard<ScopeGuardRunMode::onExit>([&] { ::close(fdTempFile); }); - //"deleting while handles are open" == FILE_FLAG_DELETE_ON_CLOSE + //"deleting while handle is open" == FILE_FLAG_DELETE_ON_CLOSE if (::unlink(tempFilePath.c_str()) != 0) THROW_LAST_SYS_ERROR("unlink"); @@ -158,11 +158,16 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm guardFdLifeSignW.dismiss(); ::close(fdLifeSignW); //[!] make sure we get EOF when fd is closed by child! - if (::fcntl(fdLifeSignR, F_SETFL, O_NONBLOCK) != 0) - THROW_LAST_SYS_ERROR("fcntl(O_NONBLOCK)"); + const int flags = ::fcntl(fdLifeSignR, F_GETFL); + if (flags == -1) + THROW_LAST_SYS_ERROR("fcntl(F_GETFL)"); + + if (::fcntl(fdLifeSignR, F_SETFL, flags | O_NONBLOCK) == -1) + THROW_LAST_SYS_ERROR("fcntl(F_SETFL, O_NONBLOCK)"); + const auto endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(*timeoutMs); - for (;;) //EINTR handling? => allow interrupt!? + for (;;) //EINTR handling? => allow interruption!? { //read until EAGAIN char buf[16]; diff --git a/zen/string_base.h b/zen/string_base.h index 615c7d2c..1052de56 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -8,23 +8,19 @@ #define STRING_BASE_H_083217454562342526 #include <algorithm> -#include <cassert> -#include <cstdint> #include <atomic> - #include <compare> #include "string_tools.h" + //Zbase - a policy based string class optimizing performance and flexibility namespace zen { -/* -Allocator Policy: ------------------ +/* Allocator Policy: + ----------------- void* allocate(size_t size) //throw std::bad_alloc void deallocate(void* ptr) - size_t calcCapacity(size_t length) -*/ + size_t calcCapacity(size_t length) */ class AllocatorOptimalSpeed //exponential growth + min size { protected: @@ -45,20 +41,18 @@ protected: static size_t calcCapacity(size_t length) { return length; } }; -/* -Storage Policy: ---------------- -template <typename Char, //Character Type - class AP> //Allocator Policy +/* 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) -*/ + 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 @@ -135,6 +129,12 @@ protected: { assert(size <= minCapacity); + if (minCapacity == 0) //perf: avoid memory allocation for empty string + { + ++globalEmptyString.descr.refCount; + return &globalEmptyString.nullTerm; + } + const size_t newCapacity = AP::calcCapacity(minCapacity); assert(newCapacity >= minCapacity); @@ -186,20 +186,30 @@ protected: private: struct Descriptor { - Descriptor(size_t len, size_t cap) : + constexpr Descriptor(size_t len, size_t cap) : length (static_cast<uint32_t>(len)), capacity(static_cast<uint32_t>(cap)) { static_assert(decltype(refCount)::is_always_lock_free); } - std::atomic<uint32_t> refCount { 1 }; //std:atomic is uninitialized by default! + std::atomic<uint32_t> refCount{1}; //std:atomic is uninitialized by default! uint32_t length; const 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; } + + struct GlobalEmptyString + { + Descriptor descr{0 /*length*/, 0 /*capacity*/}; + Char nullTerm = 0; + }; + static_assert(offsetof(GlobalEmptyString, nullTerm) - offsetof(GlobalEmptyString, descr) == sizeof(Descriptor), "no gap!"); + static_assert(std::is_trivially_destructible_v<GlobalEmptyString>, "this memory needs to live forever"); + + inline static constinit2 GlobalEmptyString globalEmptyString; //constinit: dodge static initialization order fiasco! }; @@ -331,7 +341,6 @@ template <class Char, template <class> class SP> inline Zbase<Char, SP> operator template <class Char, template <class> class SP> inline Zbase<Char, SP>::Zbase() { - //resist the temptation to avoid this allocation by referencing a static global: NO performance advantage, MT issues! rawStr_ = this->create(0); rawStr_[0] = 0; } @@ -612,6 +621,7 @@ Zbase<Char, SP>& Zbase<Char, SP>::append(InputIterator first, InputIterator last } +//don't use unifying assignment but save one move-construction in the r-value case instead! template <class Char, template <class> class SP> inline Zbase<Char, SP>& Zbase<Char, SP>::operator=(const Zbase<Char, SP>& str) { @@ -623,10 +633,14 @@ Zbase<Char, SP>& Zbase<Char, SP>::operator=(const Zbase<Char, SP>& str) 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! + //don't use swap() but end rawStr_ life time immediately + this->destroy(rawStr_); + rawStr_ = tmp.rawStr_; + tmp.rawStr_ = nullptr; return *this; } + template <class Char, template <class> class SP> inline void Zbase<Char, SP>::pop_back() { diff --git a/zen/string_tools.h b/zen/string_tools.h index cfdb27bd..2c33a4f8 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -19,7 +19,7 @@ #include "legacy_compiler.h" //<charconv> (without compiler crashes) -//enhance arbitray string class with useful non-member functions: +//enhance *any* string class with useful non-member functions: namespace zen { template <class Char> bool isWhiteSpace(Char c); @@ -53,23 +53,22 @@ template <class S, class T, typename = std::enable_if_t<IsStringLikeV<S>>> bool }; -enum FailureReturnVal +enum class IfNotFoundReturn { - IF_MISSING_RETURN_ALL, - IF_MISSING_RETURN_NONE + all, + none }; +template <class S, class T> S afterLast (const S& str, const T& term, IfNotFoundReturn infr); +template <class S, class T> S beforeLast (const S& str, const T& term, IfNotFoundReturn infr); +template <class S, class T> S afterFirst (const S& str, const T& term, IfNotFoundReturn infr); +template <class S, class T> S beforeFirst(const S& str, const T& term, IfNotFoundReturn infr); -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 +enum class SplitOnEmpty { - ALLOW_EMPTY, - SKIP_EMPTY + allow, + skip }; -template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitType st); +template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitOnEmpty soe); 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); @@ -303,7 +302,7 @@ bool contains(const S& str, const T& term) template <class S, class T> inline -S afterLast(const S& str, const T& term, FailureReturnVal rv) +S afterLast(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -316,7 +315,7 @@ S afterLast(const S& str, const T& term, FailureReturnVal rv) const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::all ? str : S(); it += termLen; return S(it, strLast - it); @@ -324,7 +323,7 @@ S afterLast(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline -S beforeLast(const S& str, const T& term, FailureReturnVal rv) +S beforeLast(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -337,14 +336,14 @@ S beforeLast(const S& str, const T& term, FailureReturnVal rv) const auto* it = searchLast(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::all ? str : S(); return S(strFirst, it - strFirst); } template <class S, class T> inline -S afterFirst(const S& str, const T& term, FailureReturnVal rv) +S afterFirst(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -357,7 +356,7 @@ S afterFirst(const S& str, const T& term, FailureReturnVal rv) const auto* it = std::search(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::all ? str : S(); it += termLen; return S(it, strLast - it); @@ -365,7 +364,7 @@ S afterFirst(const S& str, const T& term, FailureReturnVal rv) template <class S, class T> inline -S beforeFirst(const S& str, const T& term, FailureReturnVal rv) +S beforeFirst(const S& str, const T& term, IfNotFoundReturn infr) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t termLen = strLength(term); @@ -378,21 +377,21 @@ S beforeFirst(const S& str, const T& term, FailureReturnVal rv) auto it = std::search(strFirst, strLast, termFirst, termFirst + termLen); if (it == strLast) - return rv == IF_MISSING_RETURN_ALL ? str : S(); + return infr == IfNotFoundReturn::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) +std::vector<S> split(const S& str, const T& delimiter, SplitOnEmpty soe) { static_assert(std::is_same_v<GetCharTypeT<S>, GetCharTypeT<T>>); const size_t delimLen = strLength(delimiter); assert(delimLen > 0); if (delimLen == 0) { - if (str.empty() && st == SplitType::SKIP_EMPTY) + if (str.empty() && soe == SplitOnEmpty::skip) return {}; return { str }; } @@ -408,7 +407,7 @@ std::vector<S> split(const S& str, const T& delimiter, SplitType st) { const auto* const blockEnd = std::search(blockStart, strLast, delimFirst, delimLast); - if (blockStart != blockEnd || st == SplitType::ALLOW_EMPTY) + if (blockStart != blockEnd || soe == SplitOnEmpty::allow) output.emplace_back(blockStart, blockEnd - blockStart); if (blockEnd == strLast) @@ -566,7 +565,7 @@ int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const template <class S, class T, class Num> inline S printNumber(const T& format, const Num& number) //format a single number using ::sprintf { -#if __cpp_lib_format +#ifdef __cpp_lib_format #error refactor #endif diff --git a/zen/string_traits.h b/zen/string_traits.h index 76d601b3..333c92c7 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -7,33 +7,31 @@ #ifndef STRING_TRAITS_H_813274321443234 #define STRING_TRAITS_H_813274321443234 -#include <string_view> #include <cstring> //strlen +#include <string_view> #include "type_traits.h" //uniform access to string-like types, both classes and character arrays namespace zen { -/* -IsStringLikeV<>: - IsStringLikeV<const wchar_t*> //equals "true" - IsStringLikeV<const int*> //equals "false" - -GetCharTypeT<>: - GetCharTypeT<std::wstring> //equals wchar_t - GetCharTypeT<wchar_t[5]> //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 -*/ +/* IsStringLikeV<>: + IsStringLikeV<const wchar_t*> //equals "true" + IsStringLikeV<const int*> //equals "false" + + GetCharTypeT<>: + GetCharTypeT<std::wstring> //equals wchar_t + GetCharTypeT<wchar_t[5]> //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 diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp index f9747d45..fa7352f0 100644 --- a/zen/sys_error.cpp +++ b/zen/sys_error.cpp @@ -5,24 +5,11 @@ // ***************************************************************************** #include "sys_error.h" - #include <cstring> + #include <gio/gio.h> using namespace zen; - - -std::wstring zen::getSystemErrorDescription(ErrorCode ec) //return empty string on error -{ - const ErrorCode currentError = getLastError(); //not necessarily == ec - ZEN_ON_SCOPE_EXIT(errno = currentError); - - std::wstring errorMsg; - errorMsg = utfTo<std::wstring>(::strerror(ec)); - return trimCpy(errorMsg); //Windows messages seem to end with a space... -} - - namespace { std::wstring formatSystemErrorCode(ErrorCode ec) @@ -168,6 +155,109 @@ std::wstring formatSystemErrorCode(ErrorCode ec) } +std::wstring zen::formatGlibError(const std::string& functionName, GError* error) +{ + if (!error) + return formatSystemError(functionName, L"", _("Error description not available.") + L" null GError"); + + if (error->domain == G_FILE_ERROR) //"values corresponding to errno codes" + return formatSystemError(functionName, error->code); + + std::wstring errorCode; + if (error->domain == G_IO_ERROR) + errorCode = [&]() -> std::wstring + { + switch (error->code) //GIOErrorEnum: https://gitlab.gnome.org/GNOME/glib/-/blob/master/gio/gioenums.h#L530 + { + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_FAILED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_FOUND); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_EXISTS); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_IS_DIRECTORY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_DIRECTORY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_EMPTY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_REGULAR_FILE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_SYMBOLIC_LINK); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_MOUNTABLE_FILE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_FILENAME_TOO_LONG); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_INVALID_FILENAME); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_TOO_MANY_LINKS); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NO_SPACE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_INVALID_ARGUMENT); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PERMISSION_DENIED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_SUPPORTED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_MOUNTED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_ALREADY_MOUNTED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CLOSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CANCELLED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PENDING); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_READ_ONLY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CANT_CREATE_BACKUP); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WRONG_ETAG); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_TIMED_OUT); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WOULD_RECURSE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_BUSY); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WOULD_BLOCK); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_HOST_NOT_FOUND); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_WOULD_MERGE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_FAILED_HANDLED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_TOO_MANY_OPEN_FILES); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_INITIALIZED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_ADDRESS_IN_USE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PARTIAL_INPUT); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_INVALID_DATA); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_DBUS_ERROR); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_HOST_UNREACHABLE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NETWORK_UNREACHABLE); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CONNECTION_REFUSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_FAILED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_AUTH_FAILED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_NEED_AUTH); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_PROXY_NOT_ALLOWED); +#ifndef GLIB_CHECK_VERSION //e.g Debian 8 (GLib 2.42) CentOS 7 (GLib 2.56) +#error Where is GLib? +#endif +#if GLIB_CHECK_VERSION(2, 44, 0) + static_assert(G_IO_ERROR_BROKEN_PIPE == G_IO_ERROR_CONNECTION_CLOSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_CONNECTION_CLOSED); + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_NOT_CONNECTED); +#endif +#if GLIB_CHECK_VERSION(2, 48, 0) + ZEN_CHECK_CASE_FOR_CONSTANT(G_IO_ERROR_MESSAGE_TOO_LARGE); +#endif + default: + return replaceCpy<std::wstring>(L"GIO error %x", L"%x", numberTo<std::wstring>(error->code)); + } + }(); + else + { + //g-file-error-quark => g-file-error + //g-io-error-quark => g-io-error + std::wstring domain = utfTo<std::wstring>(::g_quark_to_string(error->domain)); //e.g. "g-io-error-quark" + if (endsWith(domain, L"-quark")) + domain = beforeLast(domain, L"-", IfNotFoundReturn::none); + + errorCode = domain + L' ' + numberTo<std::wstring>(error->code); //e.g. "g-io-error 15" + } + + const std::wstring errorMsg = utfTo<std::wstring>(error->message); //e.g. "Unable to find or create trash directory for file.txt" + + return formatSystemError(functionName, errorCode, errorMsg); +} + + + +std::wstring zen::getSystemErrorDescription(ErrorCode ec) //return empty string on error +{ + const ErrorCode currentError = getLastError(); //not necessarily == ec + ZEN_ON_SCOPE_EXIT(errno = currentError); + + std::wstring errorMsg; + errorMsg = utfTo<std::wstring>(::g_strerror(ec)); //... vs strerror(): "marginally improves thread safety, and marginally improves consistency" + + return trimCpy(errorMsg); //Windows messages seem to end with a space... +} + + std::wstring zen::formatSystemError(const std::string& functionName, ErrorCode ec) { return formatSystemError(functionName, formatSystemErrorCode(ec), getSystemErrorDescription(ec)); @@ -176,10 +266,10 @@ std::wstring zen::formatSystemError(const std::string& functionName, ErrorCode e std::wstring zen::formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg) { - std::wstring output = errorCode; + std::wstring output = trimCpy(errorCode); const std::wstring errorMsgFmt = trimCpy(errorMsg); - if (!errorCode.empty() && !errorMsgFmt.empty()) + if (!output.empty() && !errorMsgFmt.empty()) output += L": "; output += errorMsgFmt; diff --git a/zen/sys_error.h b/zen/sys_error.h index 01df17ab..f4b867be 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -7,11 +7,11 @@ #ifndef SYS_ERROR_H_3284791347018951324534 #define SYS_ERROR_H_3284791347018951324534 -//#include <string> #include "scope_guard.h" // #include "utf.h" //not used by this header, but the "rest of the world" needs it! #include "i18n.h" // + #include <glib.h> #include <cerrno> @@ -24,6 +24,7 @@ ErrorCode getLastError(); std::wstring formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg); std::wstring formatSystemError(const std::string& functionName, ErrorCode ec); + std::wstring formatGlibError(const std::string& functionName, GError* error); //A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"! @@ -45,6 +46,12 @@ private: do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false) +/* Example: ASSERT_SYSERROR(expr); + + Equivalent to: + if (!expr) + throw zen::SysError(L"Assertion failed: \"expr\""); */ +#define ASSERT_SYSERROR(expr) ASSERT_SYSERROR_IMPL(expr, #expr) //throw SysError @@ -61,6 +68,17 @@ std::wstring getSystemErrorDescription(ErrorCode ec); //return empty string on e std::wstring getSystemErrorDescription(long long) = delete; + + +namespace impl +{ +inline bool validateBool(bool b) { return b; } +inline bool validateBool(void* b) { return b; } +bool validateBool(int) = delete; //catch unintended bool conversions, e.g. HRESULT +} +#define ASSERT_SYSERROR_IMPL(expr, exprStr) \ + { if (!impl::validateBool(expr)) \ + throw zen::SysError(L"Assertion failed: \"" L ## exprStr L"\""); } } #endif //SYS_ERROR_H_3284791347018951324534 diff --git a/zen/system.cpp b/zen/sys_info.cpp index 23e2c343..da5cc61f 100644 --- a/zen/system.cpp +++ b/zen/sys_info.cpp @@ -4,9 +4,10 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -#include "system.h" -#include "file_access.h" +#include "sys_info.h" #include "crc.h" +#include "file_access.h" +#include "sys_version.h" #include "symlink_target.h" #include "file_io.h" @@ -14,9 +15,9 @@ #include <net/if.h> //IFF_LOOPBACK #include <netpacket/packet.h> //sockaddr_ll + #include <unistd.h> //getuid() #include <pwd.h> //getpwuid_r() - #include "shell_execute.h" using namespace zen; @@ -53,7 +54,7 @@ ComputerModel zen::getComputerModel() //throw FileError return std::wstring(); try { - const std::string stream = loadBinContainer<std::string>(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError + const std::string stream = getFileContent(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError return utfTo<std::wstring>(trimCpy(stream)); } catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError @@ -96,36 +97,36 @@ std::wstring zen::getOsDescription() //throw FileError { try { - //"lsb_release" not available on some systems: https://freefilesync.org/forum/viewtopic.php?t=7191 - // => use /etc/os-release: https://www.freedesktop.org/software/systemd/man/os-release.html - std::string releaseInfo; - try - { - releaseInfo = loadBinContainer<std::string>("/etc/os-release", nullptr /*notifyUnbufferedIO*/); //throw FileError - } - catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError + const OsVersionDetail verDetail = getOsVersionDetail(); //throw SysError + return trimCpy(verDetail.osName + L' ' + verDetail.osVersionRaw); //e.g. "CentOS 7.8.2003" - std::string osName; - std::string osVersion; - for (const std::string& line : split(releaseInfo, '\n', SplitType::SKIP_EMPTY)) //throw FileError - if (startsWith(line, "NAME=")) - osName = afterFirst(line, '=', IF_MISSING_RETURN_NONE); - else if (startsWith(line, "VERSION_ID=")) - osVersion = afterFirst(line, '=', IF_MISSING_RETURN_NONE); + } + catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } +} - trim(osName, true, true, [](char c) { return c == '"' || c == '\''; }); - trim(osVersion, true, true, [](char c) { return c == '"' || c == '\''; }); - if (osName.empty()) throw SysError(formatSystemError("/etc/os-release", L"", L"NAME missing.")); - //VERSION_ID usually available, except for Arch Linux: https://freefilesync.org/forum/viewtopic.php?t=7276 - //PRETTY_NAME? too wordy! e.g. "Fedora 17 (Beefy Miracle)" - return utfTo<std::wstring>(trimCpy(osName + ' ' + osVersion)); //e.g. "CentOS Linux 7" +Zstring zen::getDesktopPath() //throw FileError +{ + try + { + const char* path = ::getenv("HOME"); //no extended error reporting + if (!path) + throw SysError(L"Cannot find HOME environment variable."); + + return appendSeparator(path) + "Desktop"; + } + catch (const SysError& e) + { + throw FileError(_("Cannot get process information."), e.toString() ); } - catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } -} +} +Zstring zen::getProcessPath() //throw FileError +{ + return getSymlinkRawContent("/proc/self/exe").targetPath; //throw FileError +} diff --git a/zen/system.h b/zen/sys_info.h index f10a6a40..9cd328bb 100644 --- a/zen/system.h +++ b/zen/sys_info.h @@ -28,6 +28,9 @@ ComputerModel getComputerModel(); //throw FileError std::wstring getOsDescription(); //throw FileError +Zstring getDesktopPath(); //throw FileError +Zstring getProcessPath(); //throw FileError + } #endif //SYSTEM_H_4189731847832147508915 diff --git a/zen/sys_version.cpp b/zen/sys_version.cpp new file mode 100644 index 00000000..46918315 --- /dev/null +++ b/zen/sys_version.cpp @@ -0,0 +1,91 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "sys_version.h" + #include <iostream> + #include "file_io.h" + #include "shell_execute.h" + +using namespace zen; + + +OsVersionDetail zen::getOsVersionDetail() //throw SysError +{ + /* prefer lsb_release: lsb_release Distributor ID: Debian + 1. terser OS name Release: 8.11 + 2. detailed version number + /etc/os-release NAME="Debian GNU/Linux" + VERSION_ID="8" */ + std::wstring osName; + std::wstring osVersion; + try + { + if (const auto [exitCode, output] = consoleExecute("lsb_release --id -s", std::nullopt); //throw SysError + exitCode != 0) + throw SysError(formatSystemError("lsb_release --id", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + else + osName = trimCpy(output); + + if (const auto [exitCode, output] = consoleExecute("lsb_release --release -s", std::nullopt); //throw SysError + exitCode != 0) + throw SysError(formatSystemError("lsb_release --release", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + else + osVersion = trimCpy(output); + } + //lsb_release not available on some systems: https://freefilesync.org/forum/viewtopic.php?t=7191 + catch (SysError&) // => fall back to /etc/os-release: https://www.freedesktop.org/software/systemd/man/os-release.html + { + std::string releaseInfo; + try + { + releaseInfo = getFileContent("/etc/os-release", nullptr /*notifyUnbufferedIO*/); //throw FileError + } + catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } //errors should be further enriched by context info => SysError + + for (const std::string& line : split(releaseInfo, '\n', SplitOnEmpty::skip)) + if (startsWith(line, "NAME=")) + osName = utfTo<std::wstring>(afterFirst(line, '=', IfNotFoundReturn::none)); + else if (startsWith(line, "VERSION_ID=")) + osVersion = utfTo<std::wstring>(afterFirst(line, '=', IfNotFoundReturn::none)); + //PRETTY_NAME? too wordy! e.g. "Fedora 17 (Beefy Miracle)" + + trim(osName, true, true, [](char c) { return c == L'"' || c == L'\''; }); + trim(osVersion, true, true, [](char c) { return c == L'"' || c == L'\''; }); + } + + if (osName.empty()) + throw SysError(L"Operating system release could not be determined."); //should never happen! + //osVersion is usually available, except for Arch Linux: https://freefilesync.org/forum/viewtopic.php?t=7276 + // lsb_release Release is "rolling" + // etc/os-release: VERSION_ID is missing + + std::vector<std::wstring> verDigits = split<std::wstring>(osVersion, L'.', SplitOnEmpty::allow); //e.g. "7.7.1908" + verDigits.resize(2); + + return OsVersionDetail + { + { + stringTo<int>(verDigits[0]), + stringTo<int>(verDigits[1]) + }, + osVersion, osName + }; +} + + +OsVersion zen::getOsVersion() +{ + try + { + static const OsVersionDetail verDetail = getOsVersionDetail(); //throw SysError + return verDetail.version; + } + catch (const SysError& e) + { + std::cerr << utfTo<std::string>(e.toString()) << '\n'; + return {}; //sigh, it's a jungle out there: https://freefilesync.org/forum/viewtopic.php?t=7276 + } +} diff --git a/zen/sys_version.h b/zen/sys_version.h new file mode 100644 index 00000000..4381ad67 --- /dev/null +++ b/zen/sys_version.h @@ -0,0 +1,37 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef WIN_VER_H_238470348254325 +#define WIN_VER_H_238470348254325 + +#include "file_error.h" + + +namespace zen +{ +struct OsVersion //keep it a POD, so that the global version constants can be used during static initialization +{ + int major = 0; + int minor = 0; + + std::strong_ordering operator<=>(const OsVersion&) const = default; +}; + + +struct OsVersionDetail +{ + OsVersion version; + std::wstring osVersionRaw; + std::wstring osName; +}; +OsVersionDetail getOsVersionDetail(); //throw SysError + +OsVersion getOsVersion(); + + +} + +#endif //WIN_VER_H_238470348254325 diff --git a/zen/thread.cpp b/zen/thread.cpp index 6b763f39..89fa0233 100644 --- a/zen/thread.cpp +++ b/zen/thread.cpp @@ -6,52 +6,32 @@ #include "thread.h" #include <sys/prctl.h> - #include <unistd.h> - #include <sys/syscall.h> using namespace zen; -void zen::setCurrentThreadName(const char* threadName) +void zen::setCurrentThreadName(const Zstring& threadName) { - ::prctl(PR_SET_NAME, threadName, 0, 0, 0); + ::prctl(PR_SET_NAME, threadName.c_str(), 0, 0, 0); } namespace { -uint64_t getThreadIdNative() -{ - const pid_t tid = ::syscall(SYS_gettid); //no-fail - //"Invalid thread and process IDs": https://devblogs.microsoft.com/oldnewthing/20040223-00/?p=40503 - //if (tid == 0) -> not sure this holds on Linux, too! - // throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to get thread ID."); - static_assert(sizeof(uint64_t) >= sizeof(tid)); - return tid; -} - - -const uint64_t globalMainThreadId = getThreadId(); //avoid code-gen for "magic static"! -} - - -uint64_t zen::getThreadId() -{ - thread_local const uint64_t tid = getThreadIdNative(); //buffer to get predictable perf characteristics - return tid; +//don't make this a function-scope static (avoid code-gen for "magic static") +const std::thread::id globalMainThreadId = std::this_thread::get_id(); } -uint64_t zen::getMainThreadId() +bool zen::runningOnMainThread() { - //don't make this a function-scope static (avoid code-gen for "magic static") - if (globalMainThreadId == 0) //might be called during static initialization - return getThreadId(); + if (globalMainThreadId == std::thread::id()) //called during static initialization! + return true; - return globalMainThreadId; + return std::this_thread::get_id() == globalMainThreadId; } diff --git a/zen/thread.h b/zen/thread.h index 99e61e1f..1bea95ea 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -12,81 +12,89 @@ #include "scope_guard.h" #include "ring_buffer.h" #include "string_tools.h" +#include "zstring.h" namespace zen { class InterruptionStatus; +//migrate towards https://en.cppreference.com/w/cpp/thread/jthread class InterruptibleThread { public: InterruptibleThread() {} - InterruptibleThread (InterruptibleThread&&) noexcept = default; - InterruptibleThread& operator=(InterruptibleThread&&) noexcept = default; + InterruptibleThread (InterruptibleThread&& ) noexcept = default; + InterruptibleThread& operator=(InterruptibleThread&& tmp) noexcept //don't use swap() but end stdThread_ life time immediately + { + if (joinable()) + { + requestStop(); + join(); + } + stdThread_ = std::move(tmp.stdThread_); + intStatus_ = std::move(tmp.intStatus_); + return *this; + } template <class Function> - InterruptibleThread(Function&& f); + explicit InterruptibleThread(Function&& f); + + ~InterruptibleThread() + { + if (joinable()) + { + requestStop(); + join(); + } + } bool joinable () const { return stdThread_.joinable(); } - void interrupt(); + void requestStop(); 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) - return false; - - stdThread_.join(); //runs thread-local destructors => this better be fast!!! - return true; - } - private: std::thread stdThread_; std::shared_ptr<InterruptionStatus> intStatus_ = std::make_shared<InterruptionStatus>(); - std::future<void> threadCompleted_; }; + +class ThreadStopRequest {}; + //context of worker thread: -void interruptionPoint(); //throw ThreadInterruption +void interruptionPoint(); //throw ThreadStopRequest template<class Predicate> -void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadInterruption +void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred); //throw ThreadStopRequest template <class Rep, class Period> -void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadInterruption - -void setCurrentThreadName(const char* threadName); +void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //throw ThreadStopRequest -uint64_t getThreadId(); //simple integer thread id, unlike boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 -uint64_t getMainThreadId(); +void setCurrentThreadName(const Zstring& threadName); -inline bool runningMainThread() { return getThreadId() == getMainThreadId(); } +bool runningOnMainThread(); //------------------------------------------------------------------------------------------ -/* -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 existing -*/ +/* 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 existing */ template <class Function> auto runAsync(Function&& fun); //wait for all with a time limit: return true if *all* results are available! //TODO: use std::when_all when available template<class InputIterator, class Duration> -bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); +bool waitForAllTimed(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; } +bool isReady(const std::future<T>& f) { assert(f.valid()); return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } //------------------------------------------------------------------------------------------ //wait until first job is successful or all failed @@ -115,13 +123,13 @@ private: //------------------------------------------------------------------------------------------ //value associated with mutex and guaranteed protected access: -//TODO: use std::synchronized_value when available +//TODO: use std::synchronized_value when available http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0290r2.html template <class T> class Protected { public: Protected() {} - Protected(T& value) : value_(value) {} + explicit Protected(T& value) : value_(value) {} //Protected(T&& tmp ) : value_(std::move(tmp)) {} <- wait until needed template <class Function> @@ -145,26 +153,24 @@ template <class Function> class ThreadGroup { public: - ThreadGroup(size_t threadCountMax, const std::string& groupName) : threadCountMax_(threadCountMax), groupName_(groupName) + ThreadGroup(size_t threadCountMax, const Zstring& groupName) : threadCountMax_(threadCountMax), groupName_(groupName) { if (threadCountMax == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__)); } + ThreadGroup (ThreadGroup&& tmp) noexcept = default; //noexcept *required* to support move for reallocations in std::vector and std::swap!!! + ThreadGroup& operator=(ThreadGroup&& tmp) noexcept = default; //don't use swap() but end worker_ life time immediately + ~ThreadGroup() { - for (InterruptibleThread& w : worker_) w.interrupt(); //interrupt all first, then join - for (InterruptibleThread& w : worker_) detach_ ? w.detach() : w.join(); - } + for (InterruptibleThread& w : worker_) + w.requestStop(); //stop *all* at the same time before join! - ThreadGroup(ThreadGroup&& tmp) noexcept : - worker_ (std::move(tmp.worker_)), - workLoad_ (std::move(tmp.workLoad_)), - detach_ (tmp.detach_), - threadCountMax_(tmp.threadCountMax_), - groupName_ (std::move(tmp.groupName_)) { tmp.worker_.clear(); /*just in case: make sure destructor is no-op!*/ } - - ThreadGroup& operator=(ThreadGroup&& tmp) noexcept { swap(tmp); return *this; } //noexcept *required* to support move for reallocations in std::vector and std::swap!!! + if (detach_) //detach() without requestStop() doesn't make sense + for (InterruptibleThread& w : worker_) + w.detach(); + } //context of controlling OR worker thread, non-blocking: - void run(Function&& wi /*should throw ThreadInterruption when needed*/, bool insertFront = false) + void run(Function&& wi /*should throw ThreadStopRequest when needed*/, bool insertFront = false) { { std::lock_guard dummy(workLoad_->lock); @@ -214,22 +220,22 @@ private: void addWorkerThread() { - std::string threadName = groupName_ + '[' + numberTo<std::string>(worker_.size() + 1) + '/' + numberTo<std::string>(threadCountMax_) + ']'; + Zstring threadName = groupName_ + Zstr('[') + numberTo<Zstring>(worker_.size() + 1) + Zstr('/') + numberTo<Zstring>(threadCountMax_) + Zstr(']'); - worker_.emplace_back([wl = workLoad_, threadName = std::move(threadName)] //don't capture "this"! consider detach() and swap() + worker_.emplace_back([wl = workLoad_, threadName = std::move(threadName)] //don't capture "this"! consider detach() and move operations { - setCurrentThreadName(threadName.c_str()); + setCurrentThreadName(threadName); std::unique_lock dummy(wl->lock); for (;;) { - interruptibleWait(wl->conditionNewTask, dummy, [&tasks = wl->tasks] { return !tasks.empty(); }); //throw ThreadInterruption + interruptibleWait(wl->conditionNewTask, dummy, [&tasks = wl->tasks] { return !tasks.empty(); }); //throw ThreadStopRequest Function task = std::move(wl->tasks. front()); //noexcept thanks to move /**/ wl->tasks.pop_front(); // dummy.unlock(); - task(); //throw ThreadInterruption? + task(); //throw ThreadStopRequest? dummy.lock(); if (--(wl->tasksPending) == 0) @@ -239,22 +245,14 @@ private: callbacks.swap(wl->onCompletionCallbacks); dummy.unlock(); - for (const auto& cb : callbacks) cb(); //noexcept! + for (const auto& cb : callbacks) + cb(); //noexcept! dummy.lock(); } } }); } - void swap(ThreadGroup& other) - { - std::swap(worker_, other.worker_); - std::swap(workLoad_, other.workLoad_); - std::swap(detach_, other.detach_); - std::swap(threadCountMax_, other.threadCountMax_); - std::swap(groupName_, other.groupName_); - } - struct WorkLoad { std::mutex lock; @@ -268,7 +266,7 @@ private: std::shared_ptr<WorkLoad> workLoad_ = std::make_shared<WorkLoad>(); bool detach_ = false; size_t threadCountMax_; - std::string groupName_; + Zstring groupName_; }; @@ -313,12 +311,12 @@ auto runAsync(Function&& fun) template<class InputIterator, class Duration> inline -bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& duration) +bool waitForAllTimed(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 + if (first->wait_until(stopTime) == std::future_status::timeout) + return false; return true; } @@ -360,7 +358,7 @@ private: std::mutex lockResult_; size_t jobsFinished_ = 0; // - std::optional<T> result_; //our condition is: "have result" or "jobsFinished_ == jobsTotal" + std::optional<T> result_; //our condition is: "have result" or "jobsFinished_ == jobsTotal" std::condition_variable conditionJobDone_; }; @@ -390,16 +388,13 @@ std::optional<T> AsyncFirstResult<T>::get() const { return asyncResult_->getResu //------------------------------------------------------------------------------------------ -class ThreadInterruption {}; - - class InterruptionStatus { public: //context of InterruptibleThread instance: - void interrupt() + void requestStop() { - interrupted_ = true; + stopRequested_ = true; { std::lock_guard dummy(lockSleep_); //needed! makes sure the following signal is not lost! @@ -414,34 +409,34 @@ public: } //context of worker thread: - void checkInterruption() //throw ThreadInterruption + void throwIfStopped() //throw ThreadStopRequest { - if (interrupted_) - throw ThreadInterruption(); + if (stopRequested_) + throw ThreadStopRequest(); } //context of worker thread: template<class Predicate> - void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption + void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadStopRequest { setConditionVar(&cv); ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr)); - //"interrupted_" is not protected by cv's mutex => signal may get lost!!! e.g. after condition was checked but before the wait begins + //"stopRequested_" is not protected by cv's mutex => signal may get lost!!! e.g. after condition was checked but before the wait begins //=> 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(); })) + while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->stopRequested_ || pred(); })) ; - checkInterruption(); //throw ThreadInterruption + throwIfStopped(); //throw ThreadStopRequest } //context of worker thread: template <class Rep, class Period> - void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption + void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadStopRequest { std::unique_lock lock(lockSleep_); - if (conditionSleepInterruption_.wait_for(lock, relTime, [this] { return static_cast<bool>(this->interrupted_); })) - throw ThreadInterruption(); + if (conditionSleepInterruption_.wait_for(lock, relTime, [this] { return static_cast<bool>(this->stopRequested_); })) + throw ThreadStopRequest(); } private: @@ -451,7 +446,7 @@ private: activeCondition_ = cv; } - std::atomic<bool> interrupted_{ false }; //std:atomic is uninitialized by default!!! + std::atomic<bool> stopRequested_{ 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; @@ -464,43 +459,40 @@ private: namespace impl { -inline -InterruptionStatus*& refThreadLocalInterruptionStatus() -{ - //thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: - thread_local InterruptionStatus* threadLocalInterruptionStatus = nullptr; - return threadLocalInterruptionStatus; -} +//thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: +inline thread_local InterruptionStatus* threadLocalInterruptionStatus = nullptr; } + //context of worker thread: inline -void interruptionPoint() //throw ThreadInterruption +void interruptionPoint() //throw ThreadStopRequest { - assert(impl::refThreadLocalInterruptionStatus()); - if (impl::refThreadLocalInterruptionStatus()) - impl::refThreadLocalInterruptionStatus()->checkInterruption(); //throw ThreadInterruption + assert(impl::threadLocalInterruptionStatus); + if (impl::threadLocalInterruptionStatus) + impl::threadLocalInterruptionStatus->throwIfStopped(); //throw ThreadStopRequest } //context of worker thread: template<class Predicate> inline -void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadInterruption +void interruptibleWait(std::condition_variable& cv, std::unique_lock<std::mutex>& lock, Predicate pred) //throw ThreadStopRequest { - assert(impl::refThreadLocalInterruptionStatus()); - if (impl::refThreadLocalInterruptionStatus()) - impl::refThreadLocalInterruptionStatus()->interruptibleWait(cv, lock, pred); + assert(impl::threadLocalInterruptionStatus); + if (impl::threadLocalInterruptionStatus) + impl::threadLocalInterruptionStatus->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 +void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadStopRequest { - assert(impl::refThreadLocalInterruptionStatus()); - if (impl::refThreadLocalInterruptionStatus()) - impl::refThreadLocalInterruptionStatus()->interruptibleSleep(relTime); + assert(impl::threadLocalInterruptionStatus); + if (impl::threadLocalInterruptionStatus) + impl::threadLocalInterruptionStatus->interruptibleSleep(relTime); else std::this_thread::sleep_for(relTime); } @@ -509,29 +501,24 @@ void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //thr template <class Function> inline InterruptibleThread::InterruptibleThread(Function&& f) { - std::promise<void> pFinished; - threadCompleted_ = pFinished.get_future(); - stdThread_ = std::thread([f = std::forward<Function>(f), - intStatus = this->intStatus_, - pFinished = std::move(pFinished)]() mutable + intStatus = this->intStatus_]() mutable { - assert(!impl::refThreadLocalInterruptionStatus()); - impl::refThreadLocalInterruptionStatus() = intStatus.get(); - ZEN_ON_SCOPE_EXIT(impl::refThreadLocalInterruptionStatus() = nullptr); - ZEN_ON_SCOPE_EXIT(pFinished.set_value()); + assert(!impl::threadLocalInterruptionStatus); + impl::threadLocalInterruptionStatus = intStatus.get(); + ZEN_ON_SCOPE_EXIT(impl::threadLocalInterruptionStatus = nullptr); try { - f(); //throw ThreadInterruption + f(); //throw ThreadStopRequest } - catch (ThreadInterruption&) {} + catch (ThreadStopRequest&) {} }); } inline -void InterruptibleThread::interrupt() { intStatus_->interrupt(); } +void InterruptibleThread::requestStop() { intStatus_->requestStop(); } } #endif //THREAD_H_7896323423432235246427 @@ -21,12 +21,9 @@ struct TimeComp //replaces std::tm and SYSTEMTIME int hour = 0; //0-23 int minute = 0; //0-59 int second = 0; //0-60 (including leap second) + + bool operator==(const TimeComp&) const = default; }; -inline bool operator==(const TimeComp& lhs, const TimeComp& rhs) -{ - return lhs.second == rhs.second && lhs.minute == rhs.minute && lhs.hour == rhs.hour && lhs.day == rhs.day && lhs.month == rhs.month && lhs.year == rhs.year; -} -inline bool operator!=(const TimeComp& lhs, const TimeComp& rhs) { return !(lhs == rhs); } TimeComp getLocalTime(time_t utc = std::time(nullptr)); //convert time_t (UTC) to local time components, returns TimeComp() on error time_t localToTimeT(const TimeComp& tc); //convert local time components to time_t (UTC), returns -1 on error @@ -199,6 +196,8 @@ TimeComp getCompileTime() } + + inline Zstring formatTime(const Zchar* format, const TimeComp& tc) { @@ -299,11 +299,10 @@ private: const CodePoint* it_; const CodePoint* last_; }; - +} template <class CharType> -using UtfDecoder = UtfDecoderImpl<CharType, sizeof(CharType)>; -} +using UtfDecoder = impl::UtfDecoderImpl<CharType, sizeof(CharType)>; //------------------------------------------------------------------------------------------- @@ -325,7 +324,7 @@ template <class UtfString> inline size_t unicodeLength(const UtfString& str) //return number of code points (+ correctly handle broken UTF encoding) { size_t uniLen = 0; - impl::UtfDecoder<GetCharTypeT<UtfString>> decoder(strBegin(str), strLength(str)); + UtfDecoder<GetCharTypeT<UtfString>> decoder(strBegin(str), strLength(str)); while (decoder.getNext()) ++uniLen; return uniLen; @@ -344,7 +343,7 @@ UtfString getUnicodeSubstring(const UtfString& str, size_t uniPosFirst, size_t u UtfDecoder<CharType> decoder(strBegin(str), strLength(str)); for (size_t uniPos = 0; std::optional<CodePoint> cp = decoder.getNext(); ++uniPos) //[!] declaration in condition part of the for-loop - if (uniPosFirst <= uniPos) + if (uniPos >= uniPosFirst) { if (uniPos >= uniPosLast) break; diff --git a/zen/warn_static.h b/zen/warn_static.h index d5f78b5d..86b4ec32 100644 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -7,12 +7,10 @@ #ifndef WARN_STATIC_H_08724567834560832745 #define WARN_STATIC_H_08724567834560832745 -/* - Portable Compile-Time Warning +/* Portable Compile-Time Warning ----------------------------- Usage: - warn_static("my message") -*/ + warn_static("my message") */ #define ZEN_STRINGIZE_STRING(NUM) #NUM #define ZEN_STRINGIZE_NUMBER(NUM) ZEN_STRINGIZE_STRING(NUM) diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index dba890ee..6f17dc08 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -16,7 +16,7 @@ using namespace zen; namespace { -std::wstring formatZlibStatusCode(int sc) +std::wstring getZlibErrorLiteral(int sc) { switch (sc) { @@ -31,7 +31,7 @@ std::wstring formatZlibStatusCode(int sc) ZEN_CHECK_CASE_FOR_CONSTANT(Z_VERSION_ERROR); default: - return replaceCpy<std::wstring>(L"zlib status %x", L"%x", numberTo<std::wstring>(sc)); + return replaceCpy<std::wstring>(L"zlib error %x", L"%x", numberTo<std::wstring>(sc)); } } } @@ -45,37 +45,37 @@ size_t zen::impl::zlib_compressBound(size_t len) size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_t trgLen, int level) //throw SysError { - uLongf bufferSize = static_cast<uLong>(trgLen); + uLongf bufSize = static_cast<uLong>(trgLen); const int rv = ::compress2(static_cast<Bytef*>(trg), //Bytef* dest, - &bufferSize, //uLongf* destLen, + &bufSize, //uLongf* destLen, static_cast<const Bytef*>(src), //const Bytef* source, static_cast<uLong>(srcLen), //uLong sourceLen, level); //int level // Z_OK: success // Z_MEM_ERROR: not enough memory // Z_BUF_ERROR: not enough room in the output buffer - if (rv != Z_OK || bufferSize > trgLen) - throw SysError(formatSystemError("zlib compress2", formatZlibStatusCode(rv), L"")); + if (rv != Z_OK || bufSize > trgLen) + throw SysError(formatSystemError("zlib compress2", getZlibErrorLiteral(rv), L"")); - return bufferSize; + return bufSize; } size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, size_t trgLen) //throw SysError { - uLongf bufferSize = static_cast<uLong>(trgLen); + uLongf bufSize = static_cast<uLong>(trgLen); const int rv = ::uncompress(static_cast<Bytef*>(trg), //Bytef* dest, - &bufferSize, //uLongf* destLen, + &bufSize, //uLongf* destLen, static_cast<const Bytef*>(src), //const Bytef* source, static_cast<uLong>(srcLen)); //uLong sourceLen // Z_OK: success // Z_MEM_ERROR: not enough memory // Z_BUF_ERROR: not enough room in the output buffer // Z_DATA_ERROR: input data was corrupted or incomplete - if (rv != Z_OK || bufferSize > trgLen) - throw SysError(formatSystemError("zlib uncompress", formatZlibStatusCode(rv), L"")); + if (rv != Z_OK || bufSize > trgLen) + throw SysError(formatSystemError("zlib uncompress", getZlibErrorLiteral(rv), L"")); - return bufferSize; + return bufSize; } @@ -98,7 +98,7 @@ public: memLevel, //int memLevel Z_DEFAULT_STRATEGY); //int strategy if (rv != Z_OK) - throw SysError(formatSystemError("zlib deflateInit2", formatZlibStatusCode(rv), L"")); + throw SysError(formatSystemError("zlib deflateInit2", getZlibErrorLiteral(rv), L"")); } ~Impl() @@ -133,7 +133,7 @@ public: if (rv == Z_STREAM_END) return bytesToRead - gzipStream_.avail_out; if (rv != Z_OK) - throw SysError(formatSystemError("zlib deflate", formatZlibStatusCode(rv), L"")); + throw SysError(formatSystemError("zlib deflate", getZlibErrorLiteral(rv), L"")); if (gzipStream_.avail_out == 0) return bytesToRead; diff --git a/zen/zstring.cpp b/zen/zstring.cpp index b78bc118..06c839e3 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -16,11 +16,14 @@ using namespace zen; Zstring getUpperCase(const Zstring& str) { + assert(str.find(Zchar('\0')) == Zstring::npos); //don't expect embedded nulls! + //fast pre-check: if (isAsciiString(str)) //perf: in the range of 3.5ns { Zstring output = str; - for (Zchar& c : output) c = asciiToUpper(c); + for (Zchar& c : output) + c = asciiToUpper(c); return output; } @@ -31,7 +34,7 @@ Zstring getUpperCase(const Zstring& str) Zstring output; output.reserve(strNorm.size()); - impl::UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size()); + UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size()); while (const std::optional<impl::CodePoint> cp = decoder.getNext()) impl::codePointToUtf<char>(::g_unichar_toupper(*cp), [&](char c) { output += c; }); //don't use std::towupper: *incomplete* and locale-dependent! @@ -77,6 +80,7 @@ Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const if (oldTerm.empty()) return str; + //assert(isAsciiString(oldTerm)); Zstring output; for (size_t pos = 0;;) @@ -139,8 +143,8 @@ int compareNoCaseUtf8(const char* lhs, size_t lhsLen, const char* rhs, size_t rh //- wcsncasecmp: https://opensource.apple.com/source/Libc/Libc-763.12/string/wcsncasecmp-fbsd.c // => re-implement comparison based on g_unichar_tolower() to avoid memory allocations - impl::UtfDecoder<char> decL(lhs, lhsLen); - impl::UtfDecoder<char> decR(rhs, rhsLen); + UtfDecoder<char> decL(lhs, lhsLen); + UtfDecoder<char> decR(rhs, rhsLen); for (;;) { const std::optional<impl::CodePoint> cpL = decL.getNext(); diff --git a/zen/zstring.h b/zen/zstring.h index adfd671b..607d3859 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -24,10 +24,10 @@ using Zstringc = zen::Zbase<char>; //using Zstringw = zen::Zbase<wchar_t>; -//Caveat: don't expect input/output string sizes to match: -// - different UTF-8 encoding length of upper-case chars -// - different number of upper case chars (e.g. "ߢ => "SS" on macOS) -// - output is Unicode-normalized +/* Caveat: don't expect input/output string sizes to match: + - different UTF-8 encoding length of upper-case chars + - different number of upper case chars (e.g. "ߢ => "SS" on macOS) + - output is Unicode-normalized */ Zstring getUpperCase(const Zstring& str); //Windows, Linux: precomposed @@ -50,10 +50,9 @@ struct ZstringNoCase //use as STL container key: avoid needless upper-case conve ZstringNoCase(const Zstring& str) : upperCase(getUpperCase(str)) {} Zstring upperCase; - std::strong_ordering operator<=>(const ZstringNoCase& other) const = default; + std::strong_ordering operator<=>(const ZstringNoCase&) const = default; }; - //------------------------------------------------------------------------------------------ //Compare *local* file paths: @@ -112,14 +111,15 @@ Zstring appendPaths(const Zstring& basePath, const Zstring& relPath, Zchar pathS return basePath + relPath; } + inline Zstring nativeAppendPaths(const Zstring& basePath, const Zstring& relPath) { return appendPaths(basePath, relPath, FILE_NAME_SEPARATOR); } inline Zstring getFileExtension(const Zstring& filePath) { - //const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, zen::IF_MISSING_RETURN_ALL); - //return afterLast(fileName, Zstr('.'), zen::IF_MISSING_RETURN_NONE); + //const Zstring fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + //return afterLast(fileName, Zstr('.'), zen::IfNotFoundReturn::none); auto it = zen::findLast(filePath.begin(), filePath.end(), FILE_NAME_SEPARATOR); if (it == filePath.end()) @@ -141,7 +141,7 @@ const wchar_t EN_DASH = L'\u2013'; const wchar_t* const SPACED_DASH = L" \u2013 "; //using 'EN DASH' const wchar_t LTR_MARK = L'\u200E'; //UTF-8: E2 80 8E const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F -const wchar_t ELLIPSIS = L'\u2026'; //"..." +const wchar_t* const ELLIPSIS = L"\u2026"; //"..." const wchar_t MULT_SIGN = L'\u00D7'; //fancy "x" //const wchar_t NOBREAK_SPACE = L'\u00A0'; |