summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorB Stack <bgstack15@gmail.com>2020-09-01 00:24:17 +0000
committerB Stack <bgstack15@gmail.com>2020-09-01 00:24:17 +0000
commit5a3f52b016581a6a0cb4513614b6c620d365dde2 (patch)
treeacfdfb3e1046db87040477033fda0df76d92916a /zen
parentMerge branch '11.0' into 'master' (diff)
parentadd upstream 11.1 (diff)
downloadFreeFileSync-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')
-rw-r--r--zen/basic_math.h10
-rw-r--r--zen/dir_watcher.cpp14
-rw-r--r--zen/error_log.h2
-rw-r--r--zen/file_access.cpp27
-rw-r--r--zen/file_id_def.h3
-rw-r--r--zen/file_io.cpp95
-rw-r--r--zen/file_io.h80
-rw-r--r--zen/file_traverser.cpp1
-rw-r--r--zen/format_unit.cpp51
-rw-r--r--zen/globals.h85
-rw-r--r--zen/guid.h6
-rw-r--r--zen/http.cpp40
-rw-r--r--zen/i18n.h13
-rw-r--r--zen/json.h110
-rw-r--r--zen/legacy_compiler.cpp2
-rw-r--r--zen/legacy_compiler.h11
-rw-r--r--zen/open_ssl.cpp24
-rw-r--r--zen/perf.h2
-rw-r--r--zen/recycler.cpp18
-rw-r--r--zen/recycler.h27
-rw-r--r--zen/ring_buffer.h5
-rw-r--r--zen/scope_guard.h21
-rw-r--r--zen/shell_execute.cpp13
-rw-r--r--zen/string_base.h64
-rw-r--r--zen/string_tools.h49
-rw-r--r--zen/string_traits.h38
-rw-r--r--zen/sys_error.cpp122
-rw-r--r--zen/sys_error.h20
-rw-r--r--zen/sys_info.cpp (renamed from zen/system.cpp)55
-rw-r--r--zen/sys_info.h (renamed from zen/system.h)3
-rw-r--r--zen/sys_version.cpp91
-rw-r--r--zen/sys_version.h37
-rw-r--r--zen/thread.cpp36
-rw-r--r--zen/thread.h223
-rw-r--r--zen/time.h9
-rw-r--r--zen/utf.h9
-rw-r--r--zen/warn_static.h6
-rw-r--r--zen/zlib_wrap.cpp28
-rw-r--r--zen/zstring.cpp12
-rw-r--r--zen/zstring.h18
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
}
diff --git a/zen/guid.h b/zen/guid.h
index f4af4880..8d0553ec 100644
--- a/zen/guid.h
+++ b/zen/guid.h
@@ -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;
diff --git a/zen/i18n.h b/zen/i18n.h
index a70649fe..160b9625 100644
--- a/zen/i18n.h
+++ b/zen/i18n.h
@@ -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));
}
diff --git a/zen/json.h b/zen/json.h
index 0d23719c..82e5e271 100644
--- a/zen/json.h
+++ b/zen/json.h
@@ -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)");
diff --git a/zen/perf.h b/zen/perf.h
index 2598ea76..6bc328bb 100644
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -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
diff --git a/zen/time.h b/zen/time.h
index d3aef36f..aaf36983 100644
--- a/zen/time.h
+++ b/zen/time.h
@@ -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)
{
diff --git a/zen/utf.h b/zen/utf.h
index 75bf7424..ced185fd 100644
--- a/zen/utf.h
+++ b/zen/utf.h
@@ -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';
bgstack15