summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2023-04-03 09:56:38 -0400
committerB. Stack <bgstack15@gmail.com>2023-04-03 09:56:38 -0400
commit765e1bb3bdeb5c49f08543c20206e55c772e6b80 (patch)
tree920f288910890016e540213fe65f2d6f38aa82be
parentadd upstream 12.1 (diff)
downloadFreeFileSync-12.2.tar.gz
FreeFileSync-12.2.tar.bz2
FreeFileSync-12.2.zip
add upstream 12.212.2
-rw-r--r--Changelog.txt12
-rw-r--r--FreeFileSync/Build/Resources/Languages.zipbin509888 -> 528431 bytes
-rw-r--r--FreeFileSync/Source/RealTimeSync/config.cpp5
-rw-r--r--FreeFileSync/Source/RealTimeSync/tray_menu.cpp1
-rw-r--r--FreeFileSync/Source/afs/abstract.cpp125
-rw-r--r--FreeFileSync/Source/afs/abstract.h36
-rw-r--r--FreeFileSync/Source/afs/ftp.cpp151
-rw-r--r--FreeFileSync/Source/afs/gdrive.cpp25
-rw-r--r--FreeFileSync/Source/afs/native.cpp31
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp35
-rw-r--r--FreeFileSync/Source/application.cpp2
-rw-r--r--FreeFileSync/Source/base/dir_lock.cpp17
-rw-r--r--FreeFileSync/Source/base/synchronization.cpp38
-rw-r--r--FreeFileSync/Source/base/versioning.cpp6
-rw-r--r--FreeFileSync/Source/config.cpp11
-rw-r--r--FreeFileSync/Source/localization.cpp204
-rw-r--r--FreeFileSync/Source/localization.h2
-rw-r--r--FreeFileSync/Source/ui/gui_generated.cpp28
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp12
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp6
-rw-r--r--FreeFileSync/Source/ui/sync_cfg.cpp119
-rw-r--r--FreeFileSync/Source/ui/version_check.cpp14
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--wx+/tooltip.cpp11
-rw-r--r--zen/argon2.cpp2
-rw-r--r--zen/file_access.cpp205
-rw-r--r--zen/file_access.h8
-rw-r--r--zen/file_path.cpp19
-rw-r--r--zen/open_ssl.cpp2
-rw-r--r--zen/resolve_path.cpp2
-rw-r--r--zen/shutdown.cpp2
-rw-r--r--zen/socket.h38
-rw-r--r--zen/string_tools.h34
-rw-r--r--zen/symlink_target.h40
-rw-r--r--zen/sys_info.cpp12
-rw-r--r--zen/sys_version.cpp4
36 files changed, 687 insertions, 574 deletions
diff --git a/Changelog.txt b/Changelog.txt
index 9235d34a..8621e5ac 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,15 @@
+FreeFileSync 12.2 [2023-04-02]
+------------------------------
+Fixed temporary access error when creating multiple folders in parallel
+Log failure to copy folder attributes as warning only
+Enable UTF-8, even if FTP server does not advertize in FEAT (vsftpd)
+Fixed drag and drop for non-ASCII folders (macOS)
+Explicitly detect MTP path without existence check
+Fixed crash when parsing SFTP package from stream
+Revert back to GTK2 build due to GTK3 hangs on KDE (Linux)
+Fixed missing COM initialization for MTP path parsing
+
+
FreeFileSync 12.1 [2023-02-20]
------------------------------
First official build based on GTK3 (Linux)
diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip
index a6e9f1e8..6bcd626d 100644
--- a/FreeFileSync/Build/Resources/Languages.zip
+++ b/FreeFileSync/Build/Resources/Languages.zip
Binary files differ
diff --git a/FreeFileSync/Source/RealTimeSync/config.cpp b/FreeFileSync/Source/RealTimeSync/config.cpp
index 75b819ce..951aabd2 100644
--- a/FreeFileSync/Source/RealTimeSync/config.cpp
+++ b/FreeFileSync/Source/RealTimeSync/config.cpp
@@ -8,7 +8,8 @@
#include <zen/file_access.h>
#include <zen/process_exec.h>
#include <zenxml/xml.h>
-#include <wx/intl.h>
+//#include <wx/intl.h>
+#include <wx/uilocale.h>
#include "../ffs_paths.h"
#include "../localization.h"
@@ -25,7 +26,7 @@ namespace zen
template <> inline
bool readText(const std::string& input, wxLanguage& value)
{
- if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo<wxString>(input)))
+ if (const wxLanguageInfo* lngInfo = wxUILocale::FindLanguageInfo(utfTo<wxString>(input)))
{
value = static_cast<wxLanguage>(lngInfo->Language);
return true;
diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
index 0caffe70..f20580e9 100644
--- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
+++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
@@ -254,6 +254,7 @@ rts::AbortReason rts::runFolderMonitor(const XmlRealConfig& config, const wxStri
warn_static("maybe not a good idea!? job for execve? https://rachelbythebay.com/w/2017/01/30/env/")
::wxSetEnv(L"change_path", utfTo<wxString>(changedItemPath)); //crude way to report changed file
::wxSetEnv(L"change_action", actionName); //
+ warn_static("caveat: %change_path% is not subsituted 'thanks' to our *static* env variables! luckily there's a workaround") //https://freefilesync.org/forum/viewtopic.php?t=10160
auto cmdLineExp = expandMacros(cmdLine);
try
diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp
index 17dd4f4c..c7f20a92 100644
--- a/FreeFileSync/Source/afs/abstract.cpp
+++ b/FreeFileSync/Source/afs/abstract.cpp
@@ -8,6 +8,7 @@
#include <zen/serialize.h>
#include <zen/guid.h>
#include <zen/crc.h>
+#include <zen/ring_buffer.h>
#include <typeindex>
using namespace zen;
@@ -19,7 +20,7 @@ AfsPath fff::sanitizeDeviceRelativePath(Zstring relPath)
{
if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(relPath, Zstr('/'), FILE_NAME_SEPARATOR);
if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(relPath, Zstr('\\'), FILE_NAME_SEPARATOR);
- trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; });
+ trim(relPath, TrimSide::both, [](Zchar c) { return c == FILE_NAME_SEPARATOR; });
return AfsPath(relPath);
}
@@ -248,47 +249,62 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& sourcePath, c
void AFS::createFolderIfMissingRecursion(const AbstractPath& folderPath) //throw FileError
{
- //expect that path already exists (see: versioning, base folder, log file path) => check first
- const std::variant<ItemType, AfsPath /*last existing parent path*/> typeOrPath = getItemTypeIfExists(folderPath); //throw FileError
-
- try
+ auto getItemType2 = [&](const AbstractPath& itemPath) //throw FileError
{
- if (const ItemType* type = std::get_if<ItemType>(&typeOrPath))
+ try
+ { return getItemType(itemPath); } //throw FileError
+ catch (const FileError& e) //need to add context!
{
- if (*type == ItemType::file /*obscure, but possible*/)
- throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(folderPath))));
+ throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(getDisplayPath(folderPath))),
+ replaceCpy(e.toString(), L"\n\n", L'\n'));
}
- else
- {
- const AfsPath existingFolderPath = std::get<AfsPath>(typeOrPath);
- assert(startsWith(folderPath.afsPath.value, existingFolderPath.value));
+ };
+
+ try
+ {
+ //- path most likely already exists (see: versioning, base folder, log file path) => check first
+ //- do NOT use getItemTypeIfExists()! race condition when multiple threads are calling createDirectoryIfMissingRecursion(): https://freefilesync.org/forum/viewtopic.php?t=10137#p38062
+ //- find first existing + accessible parent folder (backwards iteration):
+ AbstractPath folderPathEx = folderPath;
+ RingBuffer<Zstring> folderNames; //caveat: 1. might have been created in the meantime 2. getItemType2() may have failed with access error
+ for (;;)
+ try
+ {
+ if (getItemType2(folderPathEx) == ItemType::file /*obscure, but possible*/) //throw FileError
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(folderPathEx))));
+ break;
+ }
+ catch (FileError&) //not yet existing or access error
+ {
+ const std::optional<AbstractPath> parentPath = getParentPath(folderPathEx);
+ if (!parentPath)//device root => quick access test
+ throw;
+ folderNames.push_front(getItemName(folderPathEx));
+ folderPathEx = *parentPath;
+ }
+ //-----------------------------------------------------------
- const ZstringView relPathTmp = makeStringView(folderPath.afsPath.value.begin() + existingFolderPath.value.size(), folderPath.afsPath.value.end());
- const std::vector<ZstringView> relPathComp = splitCpy(relPathTmp, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); //sanitize! relPathTmp starts with FILE_NAME_SEPARATOR unless existingFolderPath.empty()
- assert(!relPathComp.empty());
+ AbstractPath folderPathNew = folderPathEx;
+ for (const Zstring& folderName : folderNames)
+ try
+ {
+ folderPathNew = appendRelPath(folderPathNew, folderName);
- AbstractPath folderPathNew{folderPath.afsDevice, existingFolderPath};
- for (const ZstringView folderName : relPathComp)
+ createFolderPlain(folderPathNew); //throw FileError
+ }
+ catch (FileError&)
+ {
try
{
- folderPathNew = appendRelPath(folderPathNew, Zstring(folderName));
-
- createFolderPlain(folderPathNew); //throw FileError
+ if (getItemType2(folderPathNew) == ItemType::file /*obscure, but possible*/) //throw FileError
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(folderPathNew))));
+ else
+ continue; //already existing => possible, if createDirectoryIfMissingRecursion() is run in parallel
}
- catch (FileError&)
- {
- try
- {
- if (getItemType(folderPathNew) == ItemType::file /*obscure, but possible*/) //throw FileError
- throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(folderPathNew))));
- else
- continue; //already existing => possible, if createFolderIfMissingRecursion() is run in parallel
- }
- catch (FileError&) {} //not yet existing or access error
+ catch (FileError&) {} //not yet existing or access error
- throw;
- }
- }
+ throw;
+ }
}
catch (const SysError& e)
{
@@ -297,13 +313,6 @@ void AFS::createFolderIfMissingRecursion(const AbstractPath& folderPath) //throw
}
-bool AFS::itemExists(const AfsPath& itemPath) const //throw FileError
-{
- const std::variant<ItemType, AfsPath /*last existing parent path*/> typeOrPath = getItemTypeIfExists(itemPath); //throw FileError
- return std::get_if<ItemType>(&typeOrPath);
-}
-
-
//default implementation: folder traversal
void AFS::removeFolderIfExistsRecursion(const AfsPath& folderPath, //throw FileError
const std::function<void(const std::wstring& displayPath)>& onBeforeFileDeletion /*throw X*/, //
@@ -317,11 +326,18 @@ void AFS::removeFolderIfExistsRecursion(const AfsPath& folderPath, //throw FileE
{
std::vector<Zstring> fileNames;
std::vector<Zstring> symlinkNames;
-
- traverseFolder(folderPath2, //throw FileError
- [&](const FileInfo& fi) { fileNames.push_back(fi.itemName); },
- [&](const FolderInfo& fi) { folderNames.push_back(fi.itemName); },
- [&](const SymlinkInfo& si) { symlinkNames.push_back(si.itemName); });
+ try
+ {
+ traverseFolder(folderPath2, //throw FileError
+ [&](const FileInfo& fi) { fileNames.push_back(fi.itemName); },
+ [&](const FolderInfo& fi) { folderNames.push_back(fi.itemName); },
+ [&](const SymlinkInfo& si) { symlinkNames.push_back(si.itemName); });
+ }
+ catch (const FileError& e) //add context
+ {
+ throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(getDisplayPath(folderPath2))),
+ replaceCpy(e.toString(), L"\n\n", L'\n'));
+ }
for (const Zstring& fileName : fileNames)
{
@@ -352,9 +368,20 @@ void AFS::removeFolderIfExistsRecursion(const AfsPath& folderPath, //throw FileE
};
//--------------------------------------------------------------------------------------------------------------
- //no error situation if directory is not existing! manual deletion relies on it!
- const std::variant<ItemType, AfsPath /*last existing parent path*/> typeOrPath = getItemTypeIfExists(folderPath); //throw FileError
- if (const ItemType* type = std::get_if<ItemType>(&typeOrPath))
+ const std::optional<ItemType> type = [&]
+ {
+ try
+ {
+ return getItemTypeIfExists(folderPath); //throw FileError
+ }
+ catch (const FileError& e) //add context
+ {
+ throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(getDisplayPath(folderPath))),
+ replaceCpy(e.toString(), L"\n\n", L'\n'));
+ }
+ }();
+
+ if (type)
{
assert(*type != ItemType::symlink);
@@ -368,7 +395,7 @@ void AFS::removeFolderIfExistsRecursion(const AfsPath& folderPath, //throw FileE
else
removeFolderRecursionImpl(folderPath); //throw FileError
}
- else //even if the folder does not exist anymore, significant I/O work was done => report
+ else //no error situation if directory is not existing! manual deletion relies on it! significant I/O work was done => report:
if (onBeforeFolderDeletion) onBeforeFolderDeletion(getDisplayPath(folderPath)); //throw X
}
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index c5e8305f..72161695 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -9,7 +9,7 @@
#include <functional>
#include <chrono>
-#include <variant>
+//#include <variant>
#include <zen/file_error.h>
#include <zen/file_path.h>
#include <zen/serialize.h> //InputStream/OutputStream support buffered stream concept
@@ -97,10 +97,10 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
// - all child item path parts must correspond to folder traversal
// => conclude whether an item is *not* existing anymore by doing a *case-sensitive* name search => potentially SLOW!
// - root path? => do access test
- static std::variant<ItemType, AfsPath /*last existing parent path*/> getItemTypeIfExists(const AbstractPath& itemPath)
+ static std::optional<ItemType> getItemTypeIfExists(const AbstractPath& itemPath)
{ return itemPath.afsDevice.ref().getItemTypeIfExists(itemPath.afsPath); } //throw FileError
- static bool itemExists(const AbstractPath& itemPath) { return itemPath.afsDevice.ref().itemExists(itemPath.afsPath); } //throw FileError
+ static bool itemExists(const AbstractPath& itemPath) { return static_cast<bool>(getItemTypeIfExists(itemPath)); } //throw FileError
//----------------------------------------------------------------------------------------------------------------
//already existing: fail
@@ -288,9 +288,13 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
//accummulated delta != file size! consider ADS, sparse, compressed files
const zen::IoCallback& notifyUnbufferedIO /*throw X*/);
+ struct FolderCopyResult
+ {
+ std::optional<zen::FileError> errorAttribs;
+ };
//already existing: fail
//symlink handling: follow
- static void copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions); //throw FileError
+ static FolderCopyResult copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions); //throw FileError
//already existing: fail
static void copySymlink(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions); //throw FileError
@@ -332,8 +336,6 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
protected:
- bool itemExists(const AfsPath& itemPath) const; //throw FileError
-
//default implementation: folder traversal
virtual void removeFolderIfExistsRecursion(const AfsPath& folderPath, //throw FileError
const std::function<void(const std::wstring& displayPath)>& onBeforeFileDeletion,
@@ -365,7 +367,7 @@ private:
//----------------------------------------------------------------------------------------------------------------
virtual ItemType getItemType(const AfsPath& itemPath) const = 0; //throw FileError
- virtual std::variant<ItemType, AfsPath /*last existing parent path*/> getItemTypeIfExists(const AfsPath& itemPath) const = 0; //throw FileError
+ virtual std::optional<ItemType> getItemTypeIfExists(const AfsPath& itemPath) const = 0; //throw FileError
//already existing: fail
virtual void createFolderPlain(const AfsPath& folderPath) const = 0; //throw FileError
@@ -406,7 +408,7 @@ private:
//symlink handling: follow
//already existing: fail
- virtual void copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const = 0; //throw FileError
+ virtual FolderCopyResult copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const = 0; //throw FileError
//already existing: fail
virtual void copySymlinkForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const = 0; //throw FileError
@@ -541,22 +543,24 @@ void AbstractFileSystem::moveAndRenameItem(const AbstractPath& pathFrom, const A
inline
-void AbstractFileSystem::copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) //throw FileError
+AbstractFileSystem::FolderCopyResult AbstractFileSystem::copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) //throw FileError
{
using namespace zen;
- if (typeid(sourcePath.afsDevice.ref()) != typeid(targetPath.afsDevice.ref()))
+ if (typeid(sourcePath.afsDevice.ref()) != typeid(targetPath.afsDevice.ref())) //fall back:
{
- //fall back:
- if (copyFilePermissions)
- throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(getDisplayPath(targetPath))),
- _("Operation not supported between different devices."));
-
//already existing: fail
createFolderPlain(targetPath); //throw FileError
+
+ FolderCopyResult result;
+ if (copyFilePermissions)
+ result.errorAttribs = FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(getDisplayPath(targetPath))),
+ _("Operation not supported between different devices."));
+
+ return result;
}
else
- sourcePath.afsDevice.ref().copyNewFolderForSameAfsType(sourcePath.afsPath, targetPath, copyFilePermissions); //throw FileError
+ return sourcePath.afsDevice.ref().copyNewFolderForSameAfsType(sourcePath.afsPath, targetPath, copyFilePermissions); //throw FileError
}
diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp
index 0bb9ee99..e8225790 100644
--- a/FreeFileSync/Source/afs/ftp.cpp
+++ b/FreeFileSync/Source/afs/ftp.cpp
@@ -317,10 +317,10 @@ public:
//returns server response (header data)
std::string perform(const AfsPath& itemPath, bool isDir, curl_ftpmethod pathMethod,
- const std::vector<CurlOption>& extraOptions, bool requiresUtf8) //throw SysError, SysErrorPassword, SysErrorFtpProtocol
+ const std::vector<CurlOption>& extraOptions, bool requestUtf8) //throw SysError, SysErrorPassword, SysErrorFtpProtocol
{
- if (requiresUtf8) //avoid endless recursion
- requestUtf8(); //throw SysError, SysErrorFtpProtocol
+ if (requestUtf8) //avoid endless recursion
+ initUtf8(); //throw SysError, SysErrorFtpProtocol
if (!easyHandle_)
{
@@ -509,8 +509,8 @@ public:
//=======================================================================================================
const CURLcode rcPerf = ::curl_easy_perform(easyHandle_);
- //WTF: curl_easy_perform() considers FTP response codes 4XX, 5XX as failure, but for HTTP response codes 4XX are considered success!! CONSISTENCY, people!!!
- //note: CURLOPT_FAILONERROR(default:off) is only available for HTTP => BUT at least we can prefix FTP commands with * for same effect
+ //WTF: curl_easy_perform() considers FTP response codes >= 400 as failure, but for HTTP response codes 4XX are considered success!! CONSISTENCY, people!!!
+ //note: CURLOPT_FAILONERROR(default:off) is only available for HTTP => BUT at least we can prefix FTP commands with * for same effect: https://curl.se/libcurl/c/CURLOPT_QUOTE.html
if (socketException)
throw* socketException; //throw SysError
@@ -536,9 +536,11 @@ public:
if (rcPerf == CURLE_LOGIN_DENIED)
throw SysErrorPassword(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
- long ftpStatusCode = CURLE_OK; //optional
+ long ftpStatusCode = 0; //optional
/*const CURLcode rc =*/ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &ftpStatusCode);
- if (ftpStatusCode != CURLE_OK)
+ //https://en.wikipedia.org/wiki/List_of_FTP_server_return_codes
+ assert(ftpStatusCode == 0 || 400 <= ftpStatusCode && ftpStatusCode < 600);
+ if (ftpStatusCode != 0)
throw SysErrorFtpProtocol(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg), ftpStatusCode);
throw SysError(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
@@ -549,7 +551,7 @@ public:
}
//returns server response (header data)
- std::string runSingleFtpCommand(const std::string& ftpCmd, bool requiresUtf8) //throw SysError, SysErrorFtpProtocol
+ std::string runSingleFtpCommand(const std::string& ftpCmd, bool requestUtf8) //throw SysError, SysErrorFtpProtocol
{
curl_slist* quote = nullptr;
ZEN_ON_SCOPE_EXIT(::curl_slist_free_all(quote));
@@ -559,7 +561,7 @@ public:
{
{CURLOPT_NOBODY, 1L},
{CURLOPT_QUOTE, quote},
- }, requiresUtf8); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
+ }, requestUtf8); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
}
void testConnection() //throw SysError
@@ -574,11 +576,10 @@ public:
... and it tells! FUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU https://freefilesync.org/forum/viewtopic.php?t=8041 */
//=> '*' to the rescue: as long as we get an FTP response - *any* FTP response (including 550) - the connection itself is fine!
- const std::string& featBuf = runSingleFtpCommand("*FEAT", false /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ const std::string& featBuf = runSingleFtpCommand("*FEAT", false /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
for (const std::string_view& line : splitFtpResponse(featBuf))
- if (startsWith(line, "211-") ||
- startsWith(line, "211 ") ||
+ if (startsWith(line, "211 ") ||
startsWith(line, "500 ") ||
startsWith(line, "550 "))
return;
@@ -608,7 +609,7 @@ public:
easyHandle_ = nullptr;
}
- const std::string& pwdBuf = runSingleFtpCommand("PWD", true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ const std::string& pwdBuf = runSingleFtpCommand("PWD", true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
for (const std::string_view& line : splitFtpResponse(pwdBuf))
if (startsWith(line, "257 "))
@@ -644,7 +645,7 @@ public:
if (*currentSocket == binaryEnabledSocket_)
return;
- runSingleFtpCommand("TYPE I", false /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ runSingleFtpCommand("TYPE I", false /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
//make sure our binary-enabled session is still there (== libcurl behaves as we expect)
std::optional<curl_socket_t> currentSocket = getActiveSocket(); //throw SysError
@@ -661,7 +662,14 @@ public:
bool supportsMlsd() { return getFeatureSupport(&Features::mlsd); } //
bool supportsMfmt() { return getFeatureSupport(&Features::mfmt); } //throw SysError
bool supportsClnt() { return getFeatureSupport(&Features::clnt); } //
- bool supportsUtf8() { return getFeatureSupport(&Features::utf8); } //
+ bool supportsUtf8()
+ {
+ if (getFeatureSupport(&Features::utf8))
+ return true;
+
+ initUtf8(); //vsFTPd (ftp.sunet.se): supports UTF8 via "OPTS UTF8 ON", even if "UTF8" is missing from "FEAT"
+ return socketUsesUtf8_;
+ }
bool isHealthy() const
{
@@ -697,7 +705,7 @@ public:
case ServerEncoding::utf8:
if (!isValidUtf(str))
- throw SysError(_("Invalid character encoding:") + L" [UTF-8] " + utfTo<std::wstring>(str));
+ throw SysError(_("Invalid character encoding:") + L' ' + utfTo<std::wstring>(str) + L' ' + _("Expected:") + L" [UTF-8]");
return utfTo<Zstring>(str);
@@ -724,7 +732,7 @@ public:
case ServerEncoding::utf8:
//validate! we consider REPLACEMENT_CHAR as indication for server using ANSI encoding in serverToUtfEncoding()
if (!isValidUtf(str))
- throw SysError(_("Invalid character encoding:") + (sizeof(str[0]) == 1 ? L" [UTF-8] " : L" [UTF-16] ") + utfTo<std::wstring>(str));
+ throw SysError(_("Invalid character encoding:") + L' ' + utfTo<std::wstring>(str) + L' ' + _("Expected:") + (sizeof(str[0]) == 1 ? L" [UTF-8]" : L" [UTF-16]"));
static_assert(sizeof(str[0]) == 1 || sizeof(str[0]) == 2);
return utfTo<std::string>(str);
@@ -775,29 +783,42 @@ private:
return path;
}
- void requestUtf8() //throw SysError, SysErrorFtpProtocol
+ void initUtf8() //throw SysError, SysErrorFtpProtocol
{
- //Some RFC-2640-non-compliant servers require UTF8 to be explicitly enabled: https://wiki.filezilla-project.org/Character_Encoding#Conflicting_specification
- //e.g. this one (Microsoft FTP Service): https://freefilesync.org/forum/viewtopic.php?t=4303
+ /* 1. Some RFC-2640-non-compliant servers require UTF8 to be explicitly enabled: https://wiki.filezilla-project.org/Character_Encoding#Conflicting_specification
+ - e.g. Microsoft FTP Service: https://freefilesync.org/forum/viewtopic.php?t=4303
- //check supportsUtf8()? no, let's *always* request UTF8, even if server does not set UTF8 in FEAT response! e.g. like https://freefilesync.org/forum/viewtopic.php?t=9564
- //=> should not hurt + we rely on auto-encoding detection anyway; see serverToUtfEncoding()
+ 2. Others do not advertize "UTF8" in "FEAT", but *still* allow enabling it via "OPTS UTF8 ON":
+ - https://freefilesync.org/forum/viewtopic.php?t=9564
+ - vsFTPd: ftp.sunet.se https://security.appspot.com/vsftpd.html#download
- //"OPTS UTF8 ON" needs to be activated each time libcurl internally creates a new session
- //hopyfully libcurl will offer a better solution: https://github.com/curl/curl/issues/1457
+ "OPTS UTF8 ON" needs to be activated each time libcurl internally creates a new session
+ hopyfully libcurl will offer a better solution: https://github.com/curl/curl/issues/1457 */
if (std::optional<curl_socket_t> currentSocket = getActiveSocket()) //throw SysError
if (*currentSocket == utf8RequestedSocket_) //caveat: a non-UTF8-enabled session might already exist, e.g. from a previous call to supportsMlsd()
return;
- //some servers even require "CLNT" before accepting "OPTS UTF8 ON": https://social.msdn.microsoft.com/Forums/en-US/d602574f-8a69-4d69-b337-52b6081902cf/problem-with-ftpwebrequestopts-utf8-on-501-please-clnt-first
+ //some (broken!?) servers require "CLNT" before accepting "OPTS UTF8 ON": https://social.msdn.microsoft.com/Forums/en-US/d602574f-8a69-4d69-b337-52b6081902cf/problem-with-ftpwebrequestopts-utf8-on-501-please-clnt-first
if (supportsClnt()) //throw SysError
- runSingleFtpCommand("CLNT FreeFileSync", false /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ runSingleFtpCommand("CLNT FreeFileSync", false /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
//"prefix the command with an asterisk to make libcurl continue even if the command fails"
- //-> ignore if server does not know this legacy command (but report all *other* issues; else getActiveSocket() below won't return value and hide real error!)
- //"If an RFC 2640 compliant client sends OPTS UTF-8 ON, it has to use UTF-8 regardless whether OPTS UTF-8 ON succeeds or not. "
- runSingleFtpCommand("*OPTS UTF8 ON", false /*requiresUtf8*/); //throw SysError, (SysErrorFtpProtocol)
+ //-> ignore if server does not know this legacy command (but report all *other* issues; else getActiveSocket() below won't have a socket and we've hidden the real error!)
+ const std::string& optsBuf = runSingleFtpCommand("*OPTS UTF8 ON", false /*requestUtf8*/); //throw SysError, (SysErrorFtpProtocol)
+
+ //get *last* FTP status code (can there be more than one!?)
+ int ftpStatusCode = 0;
+ for (const std::string_view& line : splitFtpResponse(optsBuf))
+ if (line.size() >= 4 &&
+ isDigit(line[0]) &&
+ isDigit(line[1]) &&
+ isDigit(line[2]) &&
+ line[3] == ' ')
+ ftpStatusCode = stringTo<int>(line);
+
+ socketUsesUtf8_ = ftpStatusCode == 200 || //"200 Always in UTF8 mode." "200 UTF8 set to on"
+ ftpStatusCode == 202; //"202 UTF8 mode is always enabled."
//make sure our Unicode-enabled session is still there (== libcurl behaves as we expect)
std::optional<curl_socket_t> currentSocket = getActiveSocket(); //throw SysError
@@ -852,8 +873,8 @@ private:
if (!featureCache_)
{
//*: ignore error if server does not support/allow FEAT
- featureCache_ = parseFeatResponse(runSingleFtpCommand("*FEAT", false /*requiresUtf8*/)); //throw SysError, (SysErrorFtpProtocol)
- //used by requestUtf8()! => requiresUtf8 = false!!!
+ featureCache_ = parseFeatResponse(runSingleFtpCommand("*FEAT", false /*requestUtf8*/)); //throw SysError, (SysErrorFtpProtocol)
+ //used by initUtf8()! => requestUtf8 = false!!!
sf->access([&](FeatureList& feat) { feat.emplace(sessionCfg_.deviceId.server, *featureCache_); });
}
@@ -884,7 +905,7 @@ private:
//https://tools.ietf.org/html/rfc3659#section-7.8
//"a server-FTP process that supports MLST, and MLSD [...] MUST indicate that this support exists"
//"there is no distinct FEAT output for MLSD. The presence of the MLST feature indicates that both MLST and MLSD are supported"
- if (equalAsciiNoCase (line, " MLST") ||
+ if (equalAsciiNoCase (line, " MLST") ||
startsWithAsciiNoCase(line, " MLST ") || //SP "MLST" [SP factlist] CRLF
//so much the theory. In practice FTP server implementers can't read (specs): https://freefilesync.org/forum/viewtopic.php?t=6752
equalAsciiNoCase(line, " MLSD"))
@@ -913,6 +934,8 @@ private:
curl_socket_t utf8RequestedSocket_ = 0;
curl_socket_t binaryEnabledSocket_ = 0;
+ bool socketUsesUtf8_ = false;
+
ServerEncoding encoding_ = ServerEncoding::unknown;
std::optional<Features> featureCache_;
@@ -1143,7 +1166,7 @@ FtpItem getFtpSymlinkInfo(const FtpLogin& login, const AfsPath& linkPath) //thro
session.ensureBinaryMode(); //throw SysError
//...or some server return ASCII size or fail with '550 SIZE not allowed in ASCII mode: https://freefilesync.org/forum/viewtopic.php?t=7669&start=30#p27742
const std::string sizeBuf = session.runSingleFtpCommand("*SIZE " + session.getServerPathInternal(linkPath),
- true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
//alternative: use libcurl + CURLINFO_CONTENT_LENGTH_DOWNLOAD_T? => nah, suprise (motherfucker)! libcurl adds needless "REST 0" command!
for (const std::string_view& line : splitFtpResponse(sizeBuf))
if (startsWith(line, "213 ")) // 213<space>[rubbish]<file size> according to libcurl
@@ -1154,7 +1177,7 @@ FtpItem getFtpSymlinkInfo(const FtpLogin& login, const AfsPath& linkPath) //thro
output.fileSize = stringTo<uint64_t>(makeStringView(it.base(), line.end()));
mdtmBuf = session.runSingleFtpCommand("MDTM " + session.getServerPathInternal(linkPath),
- true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
return;
}
break;
@@ -1245,7 +1268,7 @@ public:
//else: use "LIST" + CURLFTPMETHOD_SINGLECWD
//caveat: let's better not use LIST parameters: https://cr.yp.to/ftp/list.html
- session.perform(dirPath, true /*isDir*/, pathMethod, options, true /*requiresUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
+ session.perform(dirPath, true /*isDir*/, pathMethod, options, true /*requestUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
if (session.supportsMlsd()) //throw SysError
output = parseMlsd(rawListing, session); //throw SysError
@@ -1837,7 +1860,7 @@ void ftpFileDownload(const FtpLogin& login, const AfsPath& afsFilePath, //throw
//{CURLOPT_BUFFERSIZE, 256 * 1024} -> defaults is 16 kB which seems to correspond to SSL packet size
//=> setting larget buffers size does nothing (recv still returns only 16 kB)
- }, true /*requiresUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
+ }, true /*requestUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
});
}
catch (const SysError& e)
@@ -1908,7 +1931,7 @@ void ftpFileUpload(const FtpLogin& login, const AfsPath& afsFilePath,
//{CURLOPT_PREQUOTE, quote},
//{CURLOPT_POSTQUOTE, quote},
- }, true /*requiresUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
+ }, true /*requestUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
});
}
catch (const SysError& e)
@@ -2090,7 +2113,7 @@ private:
throw SysError(L"Server does not support the MFMT command.");
session.runSingleFtpCommand("MFMT " + isoTime + ' ' + session.getServerPathInternal(filePath_),
- true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
//not relevant for OutputStreamFtp, but: does MFMT follow symlinks? for Linux FTP server (using utime) it does
});
}
@@ -2159,7 +2182,7 @@ private:
return FtpDirectoryReader::execute(login_, *parentPath); //throw SysError, SysErrorFtpProtocol
}
catch (const SysError& e) //add context: error might be folder-specific
- { throw SysError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(getItemName(*parentPath))) + L'\n' + e.toString()); }
+ { throw SysError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(parentPath->value.empty() ? Zstr("/") : getItemName(*parentPath))) + L'\n' + e.toString()); }
}();
const Zstring itemName = getItemName(itemPath);
@@ -2178,7 +2201,7 @@ private:
}
}
- std::variant<ItemType, AfsPath /*last existing parent path*/> getItemTypeIfExistsImpl(const AfsPath& itemPath) const //throw SysError
+ std::optional<ItemType> getItemTypeIfExistsImpl(const AfsPath& itemPath) const //throw SysError
{
const std::optional<AfsPath> parentPath = getParentPath(itemPath);
if (!parentPath) //device root => quick access test
@@ -2201,7 +2224,7 @@ private:
if (item.itemName == itemName) //case-sensitive comparison! itemPath must be normalized!
return item.type;
- return *parentPath;
+ return std::nullopt;
}
catch (const SysErrorFtpProtocol& e)
{
@@ -2216,12 +2239,10 @@ private:
}
}
catch (const SysError& e) //add context: error might be folder-specific
- { throw SysError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(getItemName(*parentPath))) + L'\n' + e.toString()); }
+ { throw SysError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(parentPath->value.empty() ? Zstr("/") : getItemName(*parentPath))) + L'\n' + e.toString()); }
//----------------------------------------------------------------
- const std::variant<ItemType, AfsPath /*last existing parent path*/> parentTypeOrPath = getItemTypeIfExistsImpl(*parentPath); //throw SysError
-
- if (const ItemType* parentType = std::get_if<ItemType>(&parentTypeOrPath))
+ if (const std::optional<ItemType> parentType = getItemTypeIfExistsImpl(*parentPath)) //throw SysError
{
if (*parentType == ItemType::file /*obscure, but possible*/)
throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(*parentPath))));
@@ -2229,10 +2250,10 @@ private:
throw* lastFtpError; //throw SysError; parent path existing, so traversal should not have failed!
}
else
- return parentTypeOrPath;
+ return std::nullopt;
}
- std::variant<ItemType, AfsPath /*last existing parent path*/> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
+ std::optional<ItemType> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
{
try
{
@@ -2256,7 +2277,7 @@ private:
{
accessFtpSession(login_, [&](FtpSession& session) //throw SysError
{
- session.runSingleFtpCommand("MKD " + session.getServerPathInternal(folderPath), true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ session.runSingleFtpCommand("MKD " + session.getServerPathInternal(folderPath), true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
});
}
catch (const SysError& e)
@@ -2271,7 +2292,7 @@ private:
{
accessFtpSession(login_, [&](FtpSession& session) //throw SysError
{
- session.runSingleFtpCommand("DELE " + session.getServerPathInternal(filePath), true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ session.runSingleFtpCommand("DELE " + session.getServerPathInternal(filePath), true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
});
}
catch (const SysError& e)
@@ -2288,7 +2309,7 @@ private:
{
//works fine for Linux hosts, but what about Windows-hosted FTP??? Distinguish DELE/RMD?
//Windows test, FileZilla Server and Windows IIS FTP: all symlinks are reported as regular folders
- session.runSingleFtpCommand("DELE " + session.getServerPathInternal(linkPath), true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ session.runSingleFtpCommand("DELE " + session.getServerPathInternal(linkPath), true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
});
}
catch (const SysError& e)
@@ -2305,7 +2326,7 @@ private:
{
//Windows server: FileZilla Server and Windows IIS FTP: all symlinks are reported as regular folders
//Linux server (freefilesync.org): RMD will fail for symlinks!
- session.runSingleFtpCommand("RMD " + session.getServerPathInternal(folderPath), true /*requiresUtf8*/); //throw SysError, SysErrorFtpProtocol
+ session.runSingleFtpCommand("RMD " + session.getServerPathInternal(folderPath), true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
});
}
catch (const SysError& e)
@@ -2377,13 +2398,19 @@ private:
//symlink handling: follow
//already existing: fail
- void copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
+ FolderCopyResult copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
{
- if (copyFilePermissions)
- throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
-
//already existing: fail
AFS::createFolderPlain(targetPath); //throw FileError
+
+ FolderCopyResult result;
+ try
+ {
+ if (copyFilePermissions)
+ throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
+ }
+ catch (const FileError& e) { result.errorAttribs = e; }
+ return result;
}
//already existing: fail
@@ -2422,7 +2449,7 @@ private:
{
{CURLOPT_NOBODY, 1L},
{CURLOPT_QUOTE, quote},
- }, true /*requiresUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
+ }, true /*requestUtf8*/); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
});
}
catch (const SysError& e)
@@ -2445,7 +2472,7 @@ private:
accessFtpSession(login_, [](FtpSession& session) //connect with FTP server, *unless* already connected (in which case *nothing* is sent)
{
session.perform(AfsPath(), true /*isDir*/, CURLFTPMETHOD_NOCWD,
- {{CURLOPT_NOBODY, 1L}, {CURLOPT_SERVER_RESPONSE_TIMEOUT, 0}}, false /*requiresUtf8*/);
+ {{CURLOPT_NOBODY, 1L}, {CURLOPT_SERVER_RESPONSE_TIMEOUT, 0}}, false /*requestUtf8*/);
//caveat: connection phase only, so disable CURLOPT_SERVER_RESPONSE_TIMEOUT, or next access may fail with CURLE_OPERATION_TIMEDOUT!
}); //throw SysError, SysErrorPassword, SysErrorFtpProtocol
};
@@ -2501,14 +2528,12 @@ private:
std::unique_ptr<RecycleSession> createRecyclerSession(const AfsPath& folderPath) const override //throw FileError, RecycleBinUnavailable
{
- throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(folderPath))),
- _("Operation not supported by device."));
+ throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(folderPath))));
}
void moveToRecycleBin(const AfsPath& itemPath) const override //throw FileError, RecycleBinUnavailable
{
- throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(itemPath))),
- _("Operation not supported by device."));
+ throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(itemPath))));
}
const FtpLogin login_;
@@ -2596,7 +2621,7 @@ AfsDevice fff::condenseToFtpDevice(const FtpLogin& login) //noexcept
startsWithAsciiNoCase(loginTmp.server, "ftps:" ) ||
startsWithAsciiNoCase(loginTmp.server, "sftp:" ))
loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IfNotFoundReturn::none);
- trim(loginTmp.server, true, true, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+ trim(loginTmp.server, TrimSide::both, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
return makeSharedRef<FtpFileSystem>(loginTmp);
}
@@ -2631,7 +2656,7 @@ AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept
if (startsWithAsciiNoCase(pathPhrase, ftpPrefix))
pathPhrase = pathPhrase.c_str() + strLength(ftpPrefix);
- trim(pathPhrase, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+ trim(pathPhrase, TrimSide::left, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
const ZstringView credentials = beforeFirst<ZstringView>(pathPhrase, Zstr('@'), IfNotFoundReturn::none);
const ZstringView fullPathOpt = afterFirst<ZstringView>(pathPhrase, Zstr('@'), IfNotFoundReturn::all);
diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp
index e82273cc..668a45db 100644
--- a/FreeFileSync/Source/afs/gdrive.cpp
+++ b/FreeFileSync/Source/afs/gdrive.cpp
@@ -475,7 +475,7 @@ GdriveAccessInfo gdriveAuthorizeAccess(const std::string& gdriveLoginHint, const
&hints, //_In_opt_ const ADDRINFOA* pHints
&servinfo); //_Outptr_ PADDRINFOA* ppResult
if (rcGai != 0)
- throw SysError(formatSystemError("getaddrinfo", replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
+ THROW_LAST_SYS_ERROR_GAI(rcGai);
if (!servinfo)
throw SysError(L"getaddrinfo: empty server info");
@@ -3332,11 +3332,10 @@ public:
if (ps.existingType != GdriveItemType::folder)
throw SysError(replaceCpy<std::wstring>(L"%x is not a folder.", L"%x", fmtPath(getItemName(folderPath))));
- warn_static("weird in combination with 'file attributes'")
return Zstr("https://drive.google.com/drive/folders/") + utfTo<Zstring>(ps.existingItemId);
}
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getDisplayPath(folderPath))), e.toString()); }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(getDisplayPath(folderPath))), e.toString()); }
}
private:
@@ -3401,7 +3400,7 @@ private:
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getDisplayPath(itemPath))), e.toString()); }
}
- std::variant<ItemType, AfsPath /*last existing parent path*/> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
+ std::optional<ItemType> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
{
try
{
@@ -3419,7 +3418,7 @@ private:
case GdriveItemType::shortcut: return ItemType::symlink;
//*INDENT-ON*
}
- return ps.existingPath;
+ return std::nullopt;
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getDisplayPath(itemPath))), e.toString()); }
}
@@ -3707,13 +3706,19 @@ private:
//symlink handling: follow
//already existing: fail
- void copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
+ FolderCopyResult copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
{
- if (copyFilePermissions)
- throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
-
//already existing: 1. fails or 2. creates duplicate (unlikely)
AFS::createFolderPlain(targetPath); //throw FileError
+
+ FolderCopyResult result;
+ try
+ {
+ if (copyFilePermissions)
+ throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
+ }
+ catch (const FileError& e) { result.errorAttribs = e; }
+ return result;
}
//already existing: fail
@@ -4066,7 +4071,7 @@ AbstractPath fff::createItemPathGdrive(const Zstring& itemPathPhrase) //noexcept
if (startsWithAsciiNoCase(pathPhrase, gdrivePrefix))
pathPhrase = pathPhrase.c_str() + strLength(gdrivePrefix);
- trim(pathPhrase, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+ trim(pathPhrase, TrimSide::left, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
const ZstringView fullPath = beforeFirst<ZstringView>(pathPhrase, Zstr('|'), IfNotFoundReturn::all);
const ZstringView options = afterFirst<ZstringView>(pathPhrase, Zstr('|'), IfNotFoundReturn::none);
diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp
index 48fdcac2..1c23d688 100644
--- a/FreeFileSync/Source/afs/native.cpp
+++ b/FreeFileSync/Source/afs/native.cpp
@@ -448,18 +448,12 @@ private:
return zenToAfsItemType(zen::getItemType(getNativePath(itemPath))); //throw FileError
}
- std::variant<ItemType, AfsPath /*last existing parent path*/> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
+ std::optional<ItemType> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
{
initComForThread(); //throw FileError
- std::variant<zen::ItemType, Zstring /*last existing parent path*/> typeOrPath = zen::getItemTypeIfExists(getNativePath(itemPath)); //throw FileError
-
- if (const zen::ItemType* type = std::get_if<zen::ItemType>(&typeOrPath))
+ if (const std::optional<zen::ItemType> type = zen::getItemTypeIfExists(getNativePath(itemPath))) //throw FileError
return zenToAfsItemType(*type);
-
- const Zstring existingFolderPath = std::get<Zstring>(typeOrPath);
-
- assert(startsWith(existingFolderPath, rootPath_)); //"reverse" getNativePath() => safer than parsePathComponents()->relPath!?
- return sanitizeDeviceRelativePath({existingFolderPath.begin() + rootPath_.size(), existingFolderPath.end()});
+ return std::nullopt;
}
//----------------------------------------------------------------------------------------------------------------
@@ -590,7 +584,7 @@ private:
//symlink handling: follow
//already existing: fail
- void copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
+ FolderCopyResult copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
{
initComForThread(); //throw FileError
@@ -599,15 +593,16 @@ private:
zen::createDirectory(targetPathNative); //throw FileError, ErrorTargetExisting
- ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPathNative); }
- catch (FileError&) {});
- warn_static("log it!")
-
- warn_static("implement properly + FileError should lead to a warning only:")
- tryCopyDirectoryAttributes(sourcePathNative, targetPathNative); //throw FileError
+ FolderCopyResult result;
+ try
+ {
+ copyDirectoryAttributes(sourcePathNative, targetPathNative); //throw FileError
- if (copyFilePermissions)
- copyItemPermissions(sourcePathNative, targetPathNative, ProcSymlink::follow); //throw FileError
+ if (copyFilePermissions)
+ copyItemPermissions(sourcePathNative, targetPathNative, ProcSymlink::follow); //throw FileError
+ }
+ catch (const FileError& e) { result.errorAttribs = e; }
+ return result;
}
//already existing: fail
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index b284345d..1221db24 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -1602,7 +1602,7 @@ private:
}
}
- std::variant<ItemType, AfsPath /*last existing parent path*/> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
+ std::optional<ItemType> getItemTypeIfExists(const AfsPath& itemPath) const override //throw FileError
{
try
{
@@ -1621,9 +1621,7 @@ private:
if (e.sftpErrorCode == LIBSSH2_FX_NO_SUCH_FILE ||
e.sftpErrorCode == LIBSSH2_FX_NO_SUCH_PATH) //-> not seen yet, but sounds reasonable
{
- const std::variant<ItemType, AfsPath /*last existing parent path*/> parentTypeOrPath = getItemTypeIfExists(*parentPath); //throw FileError
-
- if (const ItemType* parentType = std::get_if<ItemType>(&parentTypeOrPath))
+ if (const std::optional<ItemType> parentType = getItemTypeIfExists(*parentPath)) //throw FileError
{
if (*parentType == ItemType::file /*obscure, but possible*/)
throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(*parentPath))));
@@ -1637,11 +1635,8 @@ private:
[&](const SymlinkInfo& si) { if (si.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); });
//- case-sensitive comparison! itemPath must be normalized!
//- finding the item after getItemType() previously failed is exceptional
-
- return *parentPath;
}
- else
- return parentTypeOrPath;
+ return std::nullopt;
}
else
throw;
@@ -1810,13 +1805,19 @@ private:
//symlink handling: follow
//already existing: fail
- void copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
+ FolderCopyResult copyNewFolderForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
{
- if (copyFilePermissions)
- throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
-
//already existing: fail
AFS::createFolderPlain(targetPath); //throw FileError
+
+ FolderCopyResult result;
+ try
+ {
+ if (copyFilePermissions)
+ throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
+ }
+ catch (const FileError& e) { result.errorAttribs = e; }
+ return result;
}
//already existing: fail
@@ -1950,14 +1951,12 @@ private:
std::unique_ptr<RecycleSession> createRecyclerSession(const AfsPath& folderPath) const override //throw FileError, RecycleBinUnavailable
{
- throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(folderPath))),
- _("Operation not supported by device."));
+ throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(folderPath))));
}
void moveToRecycleBin(const AfsPath& itemPath) const override //throw FileError, RecycleBinUnavailable
{
- throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(itemPath))),
- _("Operation not supported by device."));
+ throw RecycleBinUnavailable(replaceCpy(_("The recycle bin is not available for %x."), L"%x", fmtPath(getDisplayPath(itemPath))));
}
const SftpLogin login_;
@@ -2059,7 +2058,7 @@ AfsDevice fff::condenseToSftpDevice(const SftpLogin& login) //noexcept
startsWithAsciiNoCase(loginTmp.server, "ftps:" ) ||
startsWithAsciiNoCase(loginTmp.server, "sftp:" ))
loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IfNotFoundReturn::none);
- trim(loginTmp.server, true, true, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+ trim(loginTmp.server, TrimSide::both, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
return makeSharedRef<SftpFileSystem>(loginTmp);
}
@@ -2125,7 +2124,7 @@ AbstractPath fff::createItemPathSftp(const Zstring& itemPathPhrase) //noexcept
if (startsWithAsciiNoCase(pathPhrase, sftpPrefix))
pathPhrase = pathPhrase.c_str() + strLength(sftpPrefix);
- trim(pathPhrase, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+ trim(pathPhrase, TrimSide::left, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
const ZstringView credentials = beforeFirst<ZstringView>(pathPhrase, Zstr('@'), IfNotFoundReturn::none);
const ZstringView fullPathOpt = afterFirst<ZstringView>(pathPhrase, Zstr('@'), IfNotFoundReturn::all);
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index 7a2d23fb..cb4c556b 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -41,6 +41,8 @@ using namespace fff;
"I'd really like to know if there is some deep technical reason for it or
if this is really as bloody stupid as it seems?" - vadz https://github.com/wxWidgets/wxWidgets/issues/18733#issuecomment-1011235902
+ Show all available GTK backends: run FreeFileSync with env variable: GDK_BACKEND=help
+
=> workaround: https://docs.gtk.org/gdk3/func.set_allowed_backends.html */
GLOBAL_RUN_ONCE(::gdk_set_allowed_backends("x11,*")); //call *before* gtk_init()
#endif
diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp
index 543313bb..5c6450ef 100644
--- a/FreeFileSync/Source/base/dir_lock.cpp
+++ b/FreeFileSync/Source/base/dir_lock.cpp
@@ -415,15 +415,7 @@ void waitOnDirLock(const Zstring& lockFilePath, const DirLockCallback& notifySta
}
-void releaseLock(const Zstring& lockFilePath) //noexcept
-{
- try
- {
- removeFilePlain(lockFilePath); //throw FileError
- }
- catch (FileError&) {}
- warn_static("log!!! at the very least") //https://freefilesync.org/forum/viewtopic.php?t=7655
-}
+void releaseLock(const Zstring& lockFilePath) { removeFilePlain(lockFilePath); } //throw FileError
bool tryLock(const Zstring& lockFilePath) //throw FileError
@@ -484,7 +476,12 @@ public:
lifeSignthread_.requestStop(); //thread lifetime is subset of this instances's life
lifeSignthread_.join();
- ::releaseLock(lockFilePath_); //noexcept
+ try
+ {
+ ::releaseLock(lockFilePath_); //throw FileError
+ }
+ catch (FileError&) {}
+ warn_static("log!!! at the very least") //https://freefilesync.org/forum/viewtopic.php?t=7655
}
private:
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index 105ac8bc..f87903ca 100644
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -745,8 +745,8 @@ void copySymlink(const AbstractPath& sourcePath, const AbstractPath& targetPath,
{ parallelScope([sourcePath, targetPath, copyFilePermissions] { AFS::copySymlink(sourcePath, targetPath, copyFilePermissions); /*throw FileError*/ }, singleThread); }
inline
-void copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions, std::mutex& singleThread) //throw FileError
-{ parallelScope([sourcePath, targetPath, copyFilePermissions] { AFS::copyNewFolder(sourcePath, targetPath, copyFilePermissions); /*throw FileError*/ }, singleThread); }
+AFS::FolderCopyResult copyNewFolder(const AbstractPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions, std::mutex& singleThread) //throw FileError
+{ return parallelScope([sourcePath, targetPath, copyFilePermissions] { return AFS::copyNewFolder(sourcePath, targetPath, copyFilePermissions); /*throw FileError*/ }, singleThread); }
inline
void removeFilePlain(const AbstractPath& filePath, std::mutex& singleThread) //throw FileError
@@ -834,11 +834,11 @@ private:
DeletionHandler (const DeletionHandler&) = delete;
DeletionHandler& operator=(const DeletionHandler&) = delete;
- //might not be needed => create lazily:
- AFS::RecycleSession& getOrCreateRecyclerSession() //throw FileError, RecycleBinUnavailable
+ void moveToRecycleBinIfExists(const AbstractPath& itemPath, const Zstring& relPath, std::mutex& singleThread) //throw FileError, RecycleBinUnavailable
{
assert(deletionVariant_ == DeletionVariant::recycler);
+ //might not be needed => create lazily:
if (!recyclerSession_ && !recyclerUnavailableExcept_)
try
{
@@ -848,10 +848,16 @@ private:
}
catch (const RecycleBinUnavailable& e) { recyclerUnavailableExcept_ = e; }
- if (recyclerUnavailableExcept_)
- throw* recyclerUnavailableExcept_; //throw RecycleBinUnavailable
- else
- return *recyclerSession_;
+ if (recyclerUnavailableExcept_) //add context, or user might think we're removing baseFolderPath_!
+ throw RecycleBinUnavailable(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(AFS::getDisplayPath(itemPath))),
+ replaceCpy(recyclerUnavailableExcept_->toString(), L"\n\n", L'\n'));
+ /* "Unable to move "Z:\folder\file.txt" to the recycle bin.
+
+ The recycle bin is not available for "Z:\".
+
+ Ignore and delete permanently each time recycle bin is unavailable?" */
+
+ parallel::moveToRecycleBinIfExists(*recyclerSession_, itemPath, relPath, singleThread); //throw FileError, RecycleBinUnavailable
}
//might not be needed => create lazily:
@@ -967,7 +973,7 @@ void DeletionHandler::removeFileWithCallback(const FileDescriptor& fileDescr, co
if (!beforeOverwrite) reportInfo(replaceCpy(txtDelFileRecycler_, L"%x", fmtPath(AFS::getDisplayPath(fileDescr.path))), statReporter); //throw ThreadStopRequest
try
{
- parallel::moveToRecycleBinIfExists(getOrCreateRecyclerSession(), fileDescr.path, relPath, singleThread); //throw FileError, RecycleBinUnavailable
+ moveToRecycleBinIfExists(fileDescr.path, relPath, singleThread); //throw FileError, RecycleBinUnavailable
}
catch (const RecycleBinUnavailable& e)
{
@@ -1025,7 +1031,7 @@ void DeletionHandler::removeLinkWithCallback(const AbstractPath& linkPath, const
if (!beforeOverwrite) reportInfo(replaceCpy(txtDelSymlinkRecycler_, L"%x", fmtPath(AFS::getDisplayPath(linkPath))), statReporter); //throw ThreadStopRequest
try
{
- parallel::moveToRecycleBinIfExists(getOrCreateRecyclerSession(), linkPath, relPath, singleThread); //throw FileError, RecycleBinUnavailable
+ moveToRecycleBinIfExists(linkPath, relPath, singleThread); //throw FileError, RecycleBinUnavailable
}
catch (const RecycleBinUnavailable& e)
{
@@ -1084,7 +1090,7 @@ void DeletionHandler::removeDirWithCallback(const AbstractPath& folderPath, cons
reportInfo(replaceCpy(txtDelFolderRecycler_, L"%x", fmtPath(AFS::getDisplayPath(folderPath))), statReporter); //throw ThreadStopRequest
try
{
- parallel::moveToRecycleBinIfExists(getOrCreateRecyclerSession(), folderPath, relPath, singleThread); //throw FileError, RecycleBinUnavailable
+ moveToRecycleBinIfExists(folderPath, relPath, singleThread); //throw FileError, RecycleBinUnavailable
statReporter.reportDelta(1, 0); //moving to recycler is ONE logical operation, irrespective of the number of child elements!
}
catch (const RecycleBinUnavailable& e)
@@ -2196,7 +2202,10 @@ void FolderPairSyncer::synchronizeFolderInt(FolderPair& folder, SyncOperation sy
try
{
//already existing: fail
- parallel::copyNewFolder(folder.getAbstractPath<sideSrc>(), targetPath, copyFilePermissions_, singleThread_); //throw FileError
+ AFS::FolderCopyResult result = parallel::copyNewFolder(folder.getAbstractPath<sideSrc>(), targetPath, copyFilePermissions_, singleThread_); //throw FileError
+
+ if (result.errorAttribs) //log only; no popup
+ acb_.logMessage(result.errorAttribs->toString(), PhaseCallback::MsgType::warning); //throw ThreadStopRequest
}
catch (FileError&)
{
@@ -2426,7 +2435,10 @@ bool createBaseFolder(BaseFolderPair& baseFolder, bool copyFilePermissions, Phas
if (const std::optional<AbstractPath> parentPath = AFS::getParentPath(folderPath))
AFS::createFolderIfMissingRecursion(*parentPath); //throw FileError
- AFS::copyNewFolder(baseFolder.getAbstractPath<sideSrc>(), folderPath, copyFilePermissions); //throw FileError
+ AFS::FolderCopyResult result = AFS::copyNewFolder(baseFolder.getAbstractPath<sideSrc>(), folderPath, copyFilePermissions); //throw FileError
+
+ if (result.errorAttribs) //log only; no popup
+ callback.logMessage(result.errorAttribs->toString(), PhaseCallback::MsgType::warning); //throw X
}
else
AFS::createFolderIfMissingRecursion(folderPath); //throw FileError
diff --git a/FreeFileSync/Source/base/versioning.cpp b/FreeFileSync/Source/base/versioning.cpp
index 3efe0788..c3822d65 100644
--- a/FreeFileSync/Source/base/versioning.cpp
+++ b/FreeFileSync/Source/base/versioning.cpp
@@ -183,8 +183,7 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract
void FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& relativePath, const IoCallback& notifyUnbufferedIO /*throw X*/) const //throw FileError, X
{
- const std::variant<AFS::ItemType, AfsPath /*last existing parent path*/> typeOrPath = AFS::getItemTypeIfExists(fileDescr.path); //throw FileError
- if (const AFS::ItemType* type = std::get_if<AFS::ItemType>(&typeOrPath))
+ if (const std::optional<AFS::ItemType> type = AFS::getItemTypeIfExists(fileDescr.path)) //throw FileError
{
assert(*type != AFS::ItemType::symlink);
@@ -250,8 +249,7 @@ void FileVersioner::revisionFolder(const AbstractPath& folderPath, const Zstring
const IoCallback& notifyUnbufferedIO /*throw X*/) const
{
//no error situation if directory is not existing! manual deletion relies on it!
- const std::variant<AFS::ItemType, AfsPath /*last existing parent path*/> typeOrPath = AFS::getItemTypeIfExists(folderPath); //throw FileError
- if (const AFS::ItemType* type = std::get_if<AFS::ItemType>(&typeOrPath))
+ if (const std::optional<AFS::ItemType> type = AFS::getItemTypeIfExists(folderPath)) //throw FileError
{
assert(*type != AFS::ItemType::symlink);
diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp
index 98ebfe4a..49f988e9 100644
--- a/FreeFileSync/Source/config.cpp
+++ b/FreeFileSync/Source/config.cpp
@@ -10,7 +10,8 @@
#include <zen/file_io.h>
#include <zen/time.h>
#include <zen/process_exec.h>
-#include <wx/intl.h>
+//#include <wx/intl.h>
+#include <wx/uilocale.h>
#include "ffs_paths.h"
#include "base_tools.h"
#include "afs/native.h"
@@ -101,18 +102,18 @@ void writeText(const wxLanguage& value, std::string& output)
//use description as unique wxLanguage identifier, see localization.cpp
//=> handle changes to wxLanguage enum between wxWidgets versions
- const wxString& canonicalName = wxLocale::GetLanguageCanonicalName(value);
+ const wxString& canonicalName = wxUILocale::GetLanguageCanonicalName(value);
assert(!canonicalName.empty());
if (!canonicalName.empty())
output = utfTo<std::string>(canonicalName);
else
- output = utfTo<std::string>(wxLocale::GetLanguageCanonicalName(wxLANGUAGE_ENGLISH_US));
+ output = utfTo<std::string>(wxUILocale::GetLanguageCanonicalName(wxLANGUAGE_ENGLISH_US));
}
template <> inline
bool readText(const std::string& input, wxLanguage& value)
{
- if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo<wxString>(input)))
+ if (const wxLanguageInfo* lngInfo = wxUILocale::FindLanguageInfo(utfTo<wxString>(input)))
{
value = static_cast<wxLanguage>(lngInfo->Language);
return true;
@@ -1455,7 +1456,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
cfg.programLanguage = wxLANGUAGE_NORWEGIAN;
else if (lngName == "Portuguese (Brazilian)")
cfg.programLanguage = wxLANGUAGE_PORTUGUESE_BRAZILIAN;
- else if (const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo<wxString>(lngName)))
+ else if (const wxLanguageInfo* lngInfo = wxUILocale::FindLanguageInfo(utfTo<wxString>(lngName)))
cfg.programLanguage = static_cast<wxLanguage>(lngInfo->Language);
}
else
diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp
index d1de6201..6bb2f376 100644
--- a/FreeFileSync/Source/localization.cpp
+++ b/FreeFileSync/Source/localization.cpp
@@ -5,27 +5,15 @@
// *****************************************************************************
#include "localization.h"
-#include <unordered_map>
#include <clocale> //setlocale
-#include <map>
-#include <list>
-#include <iterator>
-#include <zen/string_tools.h>
#include <zen/file_traverser.h>
#include <zen/file_io.h>
-#include <zen/i18n.h>
-#include <zen/format_unit.h>
-#include <zen/perf.h>
#include <wx/zipstrm.h>
#include <wx/mstream.h>
-#include <wx/intl.h>
-#include <wx/log.h>
+#include <wx/uilocale.h>
#include "parse_plural.h"
#include "parse_lng.h"
- #include <wchar.h> //wcscasecmp
-
-
using namespace zen;
using namespace fff;
@@ -154,7 +142,7 @@ std::vector<TranslationInfo> loadTranslations(const Zstring& zipPath) //throw Fi
assert(!lngHeader.locale .empty());
assert(!lngHeader.flagFile .empty());
- const wxLanguageInfo* lngInfo = wxLocale::FindLanguageInfo(utfTo<wxString>(lngHeader.locale));
+ const wxLanguageInfo* lngInfo = wxUILocale::FindLanguageInfo(utfTo<wxString>(lngHeader.locale));
assert(lngInfo && lngInfo->CanonicalName == utfTo<wxString>(lngHeader.locale));
if (lngInfo)
translations.push_back(
@@ -180,23 +168,23 @@ std::vector<TranslationInfo> loadTranslations(const Zstring& zipPath) //throw Fi
}
-/* Some ISO codes are used by multiple wxLanguage IDs which can lead to incorrect mapping by wxLocale::FindLanguageInfo()!!!
+/* Some ISO codes are used by multiple wxLanguage IDs which can lead to incorrect mapping by wxUILocale::FindLanguageInfo()!!!
=> Identify by description, e.g. "Chinese (Traditional)". The following IDs are affected:
- zh_TW: wxLANGUAGE_CHINESE_TAIWAN, wxLANGUAGE_CHINESE, wxLANGUAGE_CHINESE_TRADITIONAL_EXPLICIT
- en_GB: wxLANGUAGE_ENGLISH_UK, wxLANGUAGE_ENGLISH
- es_ES: wxLANGUAGE_SPANISH, wxLANGUAGE_SPANISH_SPAIN */
wxLanguage mapLanguageDialect(wxLanguage lng)
{
- if (const wxString& canonicalName = wxLocale::GetLanguageCanonicalName(lng);
+ if (const wxString& canonicalName = wxUILocale::GetLanguageCanonicalName(lng);
!canonicalName.empty())
{
assert(!contains(canonicalName, L'-'));
- const std::string locale = beforeFirst(utfTo<std::string>(canonicalName), '@', IfNotFoundReturn::all); //e.g. "sr_RS@latin"; see wxLocale::InitLanguagesDB()
+ const std::string locale = beforeFirst(utfTo<std::string>(canonicalName), '@', IfNotFoundReturn::all); //e.g. "sr_RS@latin"; see wxUILocale::InitLanguagesDB()
const std::string lngCode = beforeFirst(locale, '_', IfNotFoundReturn::all);
if (lngCode == "zh")
{
- if (lng == wxLANGUAGE_CHINESE) //wxWidgets assigns this to "zh" or "zh_TW" for some reason
+ if (lng == wxLANGUAGE_CHINESE) //wxWidgets assigns this to "zh_TW" for some reason
return wxLANGUAGE_CHINESE_CHINA;
for (const char* l : {"zh_HK", "zh_MO", "zh_TW"})
@@ -208,7 +196,7 @@ wxLanguage mapLanguageDialect(wxLanguage lng)
if (lngCode == "en")
{
- if (lng == wxLANGUAGE_ENGLISH || //wxWidgets assigns this to "en" or "en_GB" for some reason
+ if (lng == wxLANGUAGE_ENGLISH || //wxWidgets assigns this to "en_GB" for some reason
lng == wxLANGUAGE_ENGLISH_WORLD)
return wxLANGUAGE_ENGLISH_US;
@@ -227,7 +215,7 @@ wxLanguage mapLanguageDialect(wxLanguage lng)
//all other cases: map to primary language code
if (contains(locale, '_'))
- if (const wxLanguageInfo* lngInfo2 = wxLocale::FindLanguageInfo(utfTo<wxString>(lngCode)))
+ if (const wxLanguageInfo* lngInfo2 = wxUILocale::FindLanguageInfo(utfTo<wxString>(lngCode)))
return static_cast<wxLanguage>(lngInfo2->Language);
}
return lng; //including wxLANGUAGE_DEFAULT, wxLANGUAGE_UNKNOWN
@@ -240,7 +228,7 @@ class MemoryTranslationLoader : public wxTranslationsLoader
{
public:
MemoryTranslationLoader(wxLanguage langId, std::map<std::string, std::wstring>&& transMapping) :
- canonicalName_(wxLocale::GetLanguageCanonicalName(langId))
+ canonicalName_(wxUILocale::GetLanguageCanonicalName(langId))
{
assert(!canonicalName_.empty());
static_assert(std::is_same_v<std::remove_cvref_t<decltype(transMapping)>, std::map<std::string, std::wstring>>); //translations *must* be sorted in MO file!
@@ -280,15 +268,13 @@ public:
wxMsgCatalog* LoadCatalog(const wxString& domain, const wxString& lang) override
{
- //"lang" is NOT (exactly) what we return from GetAvailableTranslations(), but has a little "extra", e.g.: de_DE.WINDOWS-1252 or ar.WINDOWS-1252
- auto extractIsoLangCode = [](wxString langCode)
- {
- langCode = beforeLast(langCode, L".", IfNotFoundReturn::all);
- return beforeLast(langCode, L"_", IfNotFoundReturn::all);
- };
+ //"lang" is NOT (exactly) what we return from GetAvailableTranslations(), but has a little "extra"
+ //e.g.: de_DE.WINDOWS-1252 ar.WINDOWS-1252 zh_TW.MacRoman
+ auto extractIsoLangCode = [](wxString langCode) { return beforeLast(langCode, L".", IfNotFoundReturn::all); };
if (equalAsciiNoCase(extractIsoLangCode(lang), extractIsoLangCode(canonicalName_)))
return wxMsgCatalog::CreateFromData(wxScopedCharBuffer::CreateNonOwned(moBuf_.ref().c_str(), moBuf_.ref().size()), domain);
+
assert(false);
return nullptr;
}
@@ -306,108 +292,65 @@ private:
};
-//global wxWidgets localization: sets up C locale as well!
-class ZenLocale
-{
-public:
- static ZenLocale& getInstance()
- {
- static ZenLocale inst;
- return inst;
- }
-
- void init(wxLanguage lng)
- {
- lng_ = lng;
-
- if (const wxLanguageInfo* selLngInfo = wxLocale::GetLanguageInfo(lng))
- layoutDir_ = selLngInfo->LayoutDirection;
- else
- layoutDir_ = wxLayout_LeftToRight;
-
- /* use wxLANGUAGE_DEFAULT to preserve sub-language-specific rules (e.g. number and date format)
- - beneficial even for Arabic locale: use user-specific date settings instead of Hijri calendar year 1441 (= Gregorian 2019)
- - note when testing: format_unit.cpp::formatNumber() always uses LOCALE_NAME_USER_DEFAULT => test date format instead */
- if (!locale_)
- {
- //wxWidgets shows a modal dialog on error during wxLocale::Init() -> at least we can shut it up!
- wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership!
- ZEN_ON_SCOPE_EXIT(delete wxLog::SetActiveTarget(oldLogTarget));
-
- //locale_.reset(); //avoid global locale lifetime overlap! wxWidgets cannot handle this and will crash!
- locale_ = std::make_unique<wxLocale>(wxLANGUAGE_DEFAULT, wxLOCALE_DONT_LOAD_DEFAULT /*we're not using wxwin.mo*/);
- //wxLANGUAGE_DEFAULT => internally calls std::setlocale(LC_ALL, "" /*== user-preferred locale*/) on Windows/Linux (but not macOS)
- /* => exactly what's needed on Windows/Linux
-
- but not needed on macOS; even detrimental:
- - breaks wxWidgets file drag and drop! https://freefilesync.org/forum/viewtopic.php?t=8215
- - even wxWidgets knows: "under macOS C locale must not be changed, as doing this exposes bugs in the system"
- https://docs.wxwidgets.org/trunk/classwx_u_i_locale.html
-
- reproduce: - std::setlocale(LC_ALL, "");
- - double-click the app (*)
- - drag and drop folder named "アアアア"
- - wxFileDropTarget::OnDropFiles() called with empty file array!
-
- *) CAVEAT: context matters! this yields a different user-preferred locale than running Contents/MacOS/FreeFileSync_main!!!
- e.g. 1. locale after wxLocale creation is "en_US"
- 2. call std::setlocale(LC_ALL, ""):
- a) app was double-clicked: locale is "C" => drag/drop FAILS!
- b) run Contents/MacOS/FreeFileSync_main: locale is "en_US.UTF-8" => drag/drop works! */
- assert(locale_->IsOk());
-
- //const char* currentLocale = std::setlocale(LC_ALL, nullptr);
- }
- }
+std::vector<TranslationInfo> globalTranslations;
+wxLanguage globalLang = wxLANGUAGE_UNKNOWN;
+wxLayoutDirection globalLayoutDir = wxLayout_Default;
+}
- void tearDown() { locale_.reset(); lng_ = wxLANGUAGE_UNKNOWN; layoutDir_ = wxLayout_Default; }
- wxLanguage getLanguage() const { return lng_; }
- wxLayoutDirection getLayoutDirection() const { return layoutDir_; }
+void fff::localizationInit(const Zstring& zipPath) //throw FileError
+{
+ assert(globalTranslations.empty());
+ globalTranslations = loadTranslations(zipPath); //throw FileError
-private:
- ZenLocale() {}
- ~ZenLocale() { assert(!locale_); }
-
- wxLanguage lng_ = wxLANGUAGE_UNKNOWN;
- wxLayoutDirection layoutDir_ = wxLayout_Default;
- std::unique_ptr<wxLocale> locale_;
- //use wxWidgets 3.1.6 wxUILocale? wxLocale already does *exactly* what we need, while wxLocale does a half-arsed job (as expected):
- //setlocale() is only called on wxGTK, but not on Windows + wxTranslations is not initialized.
-};
+ /* wxLocale vs wxUILocale (since wxWidgets 3.1.6)
+ ------------------------------------------|--------------------
+ calls setlocale() Windows, Linux, maCOS | Linux only
+ wxTranslations initialized | not initialized
+ caveat: setlocale() calls on macOS lead to bugs:
+ - breaks wxWidgets file drag and drop! https://freefilesync.org/forum/viewtopic.php?t=8215
+ - "under macOS C locale must not be changed, as doing this exposes bugs in the system": https://docs.wxwidgets.org/trunk/classwx_u_i_locale.html
-std::vector<TranslationInfo> globalTranslations;
-}
+ reproduce: - std::setlocale(LC_ALL, "");
+ - double-click the app (*)
+ - drag and drop folder named "アアアア"
+ - wxFileDropTarget::OnDropFiles() called with empty file array!
+ *) CAVEAT: context matters! this yields a different user-preferred locale than running Contents/MacOS/FreeFileSync_main!!!
+ e.g. 1. locale after wxLocale creation is "en_US"
+ 2. call std::setlocale(LC_ALL, ""):
+ a) app was double-clicked: locale is "C" => drag/drop FAILS!
+ b) run Contents/MacOS/FreeFileSync_main: locale is "en_US.UTF-8" => drag/drop works! */
+ [[maybe_unused]] const bool rv = wxUILocale::UseDefault();
+ assert(rv);
-const std::vector<TranslationInfo>& fff::getAvailableTranslations()
-{
- assert(!globalTranslations.empty()); //localizationInit() not called, or failed!?
- return globalTranslations;
-}
+ //const char* currentLocale = std::setlocale(LC_ALL, nullptr);
+ assert(!wxTranslations::Get());
+ wxTranslations::Set(new wxTranslations() /*pass ownership*/); //implicitly done by wxLocale, but *not* wxUILocale
-void fff::localizationInit(const Zstring& zipPath) //throw FileError
-{
- assert(globalTranslations.empty());
- globalTranslations = loadTranslations(zipPath); //throw FileError
setLanguage(getDefaultLanguage()); //throw FileError
}
void fff::localizationCleanup()
{
+#if 0 //good place for clean up rather than some time during static destruction: is this an actual benefit???
+ globalLang = wxLANGUAGE_UNKNOWN;
+ globalLayoutDir = wxLayout_Default;
+
+ setTranslator(nullptr);
+
assert(!globalTranslations.empty());
- ZenLocale::getInstance().tearDown();
- setTranslator(nullptr); //good place for clean up rather than some time during static destruction: is this an actual benefit???
globalTranslations.clear();
+#endif
}
void fff::setLanguage(wxLanguage lng) //throw FileError
{
- if (getLanguage() == lng)
+ if (globalLang == lng)
return; //support polling
//(try to) retrieve language file
@@ -445,41 +388,44 @@ void fff::setLanguage(wxLanguage lng) //throw FileError
{
throw FileError(L"Invalid plural form definition: " + fmtPath(lngFileName)); //user should never see this!
}
+ //------------------------------------------------------------
+
+ globalLang = lng;
+
+ if (const wxLanguageInfo* selLngInfo = wxUILocale::GetLanguageInfo(lng))
+ globalLayoutDir = selLngInfo->LayoutDirection;
+ else
+ globalLayoutDir = wxLayout_LeftToRight;
- //handle RTL swapping: we need wxWidgets to do this
- ZenLocale::getInstance().init(lng);
//add translation for wxWidgets-internal strings:
- assert(wxTranslations::Get()); //already initialized by wxLocale
- if (wxTranslations* wxtrans = wxTranslations::Get())
+ std::map<std::string, std::wstring> transMapping =
{
- std::map<std::string, std::wstring> transMapping =
- {
- };
- wxtrans->SetLanguage(lng); //!= wxLocale's language, which could be wxLANGUAGE_DEFAULT (see ZenLocale)
- wxtrans->SetLoader(new MemoryTranslationLoader(lng, std::move(transMapping)));
- [[maybe_unused]] const bool catalogAdded = wxtrans->AddCatalog(wxString());
- assert(catalogAdded || lng == wxLANGUAGE_ENGLISH_US);
- }
+ };
+ wxTranslations& wxtrans = *wxTranslations::Get(); //*assert* creation by localizationInit()!
+ wxtrans.SetLanguage(lng); //!= wxLocale's language, which could be wxLANGUAGE_DEFAULT (see ZenLocale)
+ wxtrans.SetLoader(new MemoryTranslationLoader(lng, std::move(transMapping)));
+ [[maybe_unused]] const bool catalogAdded = wxtrans.AddCatalog(wxString());
+ assert(catalogAdded || lng == wxLANGUAGE_ENGLISH_US);
+}
+
+
+const std::vector<TranslationInfo>& fff::getAvailableTranslations()
+{
+ assert(!globalTranslations.empty()); //localizationInit() not called, or failed!?
+ return globalTranslations;
}
wxLanguage fff::getDefaultLanguage()
{
- static const wxLanguage defaultLng = mapLanguageDialect(static_cast<wxLanguage>(wxLocale::GetSystemLanguage()));
+ static const wxLanguage defaultLng = mapLanguageDialect(static_cast<wxLanguage>(wxUILocale::GetSystemLanguage()));
//uses GetUserPreferredUILanguages() since wxWidgets 1.3.6, not GetUserDefaultUILanguage() anymore:
// https://github.com/wxWidgets/wxWidgets/blob/master/src/common/intl.cpp
return defaultLng;
}
-wxLanguage fff::getLanguage()
-{
- return ZenLocale::getInstance().getLanguage();
-}
-
+wxLanguage fff::getLanguage() { return globalLang; }
-wxLayoutDirection fff::getLayoutDirection()
-{
- return ZenLocale::getInstance().getLayoutDirection();
-}
+wxLayoutDirection fff::getLayoutDirection() { return globalLayoutDir; }
diff --git a/FreeFileSync/Source/localization.h b/FreeFileSync/Source/localization.h
index 3672e6f5..f52db495 100644
--- a/FreeFileSync/Source/localization.h
+++ b/FreeFileSync/Source/localization.h
@@ -9,7 +9,7 @@
#include <vector>
#include <zen/file_error.h>
-#include <wx/intl.h>
+#include <wx/intl.h> //wxLayoutDirection
#include <wx/language.h>
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index d0fd57ee..68967d1c 100644
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -620,11 +620,11 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_staticTextProcessed = new wxStaticText( m_panelLog, wxID_ANY, _("Processed:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextProcessed->Wrap( -1 );
- ffgSizer11->Add( m_staticTextProcessed, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxRIGHT, 5 );
+ ffgSizer11->Add( m_staticTextProcessed, 0, wxALIGN_RIGHT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 );
m_staticTextRemaining = new wxStaticText( m_panelLog, wxID_ANY, _("Remaining:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextRemaining->Wrap( -1 );
- ffgSizer11->Add( m_staticTextRemaining, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
+ ffgSizer11->Add( m_staticTextRemaining, 0, wxALIGN_RIGHT|wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 );
bSizer42->Add( ffgSizer11, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM, 10 );
@@ -643,7 +643,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
bSizer293 = new wxBoxSizer( wxHORIZONTAL );
m_bitmapItemStat = new wxStaticBitmap( m_panelItemStats, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
- bSizer293->Add( m_bitmapItemStat, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
+ bSizer293->Add( m_bitmapItemStat, 0, wxRIGHT|wxALIGN_CENTER_VERTICAL, 5 );
bSizer293->Add( 0, 0, 1, 0, 5 );
@@ -652,24 +652,24 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_staticTextItemsProcessed->Wrap( -1 );
m_staticTextItemsProcessed->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- bSizer293->Add( m_staticTextItemsProcessed, 0, wxALIGN_CENTER_VERTICAL, 5 );
+ bSizer293->Add( m_staticTextItemsProcessed, 0, wxALIGN_BOTTOM, 5 );
ffgSizer111->Add( bSizer293, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
m_staticTextBytesProcessed = new wxStaticText( m_panelItemStats, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextBytesProcessed->Wrap( -1 );
- ffgSizer111->Add( m_staticTextBytesProcessed, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 );
+ ffgSizer111->Add( m_staticTextBytesProcessed, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM, 5 );
m_staticTextItemsRemaining = new wxStaticText( m_panelItemStats, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
m_staticTextItemsRemaining->Wrap( -1 );
m_staticTextItemsRemaining->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- ffgSizer111->Add( m_staticTextItemsRemaining, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 );
+ ffgSizer111->Add( m_staticTextItemsRemaining, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM, 5 );
m_staticTextBytesRemaining = new wxStaticText( m_panelItemStats, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextBytesRemaining->Wrap( -1 );
- ffgSizer111->Add( m_staticTextBytesRemaining, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 );
+ ffgSizer111->Add( m_staticTextBytesRemaining, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM, 5 );
bSizer291->Add( ffgSizer111, 0, wxALL, 5 );
@@ -703,7 +703,7 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
m_staticTextTimeElapsed->Wrap( -1 );
m_staticTextTimeElapsed->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- bSizer294->Add( m_staticTextTimeElapsed, 0, wxALIGN_CENTER_VERTICAL, 5 );
+ bSizer294->Add( m_staticTextTimeElapsed, 0, wxALIGN_BOTTOM, 5 );
ffgSizer112->Add( bSizer294, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
@@ -3474,24 +3474,24 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi
m_staticTextItemsProcessed->Wrap( -1 );
m_staticTextItemsProcessed->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- bSizer293->Add( m_staticTextItemsProcessed, 0, wxALIGN_CENTER_VERTICAL, 5 );
+ bSizer293->Add( m_staticTextItemsProcessed, 0, wxALIGN_BOTTOM, 5 );
ffgSizer111->Add( bSizer293, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
m_staticTextBytesProcessed = new wxStaticText( m_panelItemStats, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextBytesProcessed->Wrap( -1 );
- ffgSizer111->Add( m_staticTextBytesProcessed, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 );
+ ffgSizer111->Add( m_staticTextBytesProcessed, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM, 5 );
m_staticTextItemsRemaining = new wxStaticText( m_panelItemStats, wxID_ANY, _("dummy"), wxDefaultPosition, wxSize( -1, -1 ), 0 );
m_staticTextItemsRemaining->Wrap( -1 );
m_staticTextItemsRemaining->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- ffgSizer111->Add( m_staticTextItemsRemaining, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 );
+ ffgSizer111->Add( m_staticTextItemsRemaining, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM, 5 );
m_staticTextBytesRemaining = new wxStaticText( m_panelItemStats, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextBytesRemaining->Wrap( -1 );
- ffgSizer111->Add( m_staticTextBytesRemaining, 0, wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5 );
+ ffgSizer111->Add( m_staticTextBytesRemaining, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM, 5 );
bSizer291->Add( ffgSizer111, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
@@ -3525,7 +3525,7 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi
m_staticTextTimeElapsed->Wrap( -1 );
m_staticTextTimeElapsed->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- bSizer294->Add( m_staticTextTimeElapsed, 0, wxALIGN_CENTER_VERTICAL, 5 );
+ bSizer294->Add( m_staticTextTimeElapsed, 0, wxALIGN_BOTTOM, 5 );
ffgSizer112->Add( bSizer294, 0, wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
@@ -3534,7 +3534,7 @@ CompareProgressDlgGenerated::CompareProgressDlgGenerated( wxWindow* parent, wxWi
m_staticTextTimeRemaining->Wrap( -1 );
m_staticTextTimeRemaining->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
- ffgSizer112->Add( m_staticTextTimeRemaining, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT, 5 );
+ ffgSizer112->Add( m_staticTextTimeRemaining, 0, wxALIGN_RIGHT|wxALIGN_BOTTOM, 5 );
bSizer292->Add( ffgSizer112, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 3ebebeba..a5eab887 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -2628,14 +2628,14 @@ void MainDialog::addFilterPhrase(const Zstring& phrase, bool include, bool requi
if (requireNewLine)
{
- trim(filterString, false, true, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n') || c == Zstr(' '); });
+ trim(filterString, TrimSide::right, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n') || c == Zstr(' '); });
if (!filterString.empty())
filterString += Zstr('\n');
filterString += phrase;
}
else
{
- trim(filterString, false, true, [](Zchar c) { return c == Zstr('\n') || c == Zstr(' '); });
+ trim(filterString, TrimSide::right, [](Zchar c) { return c == Zstr('\n') || c == Zstr(' '); });
if (contains(afterLast(filterString, Zstr('\n'), IfNotFoundReturn::all), FILTER_ITEM_SEPARATOR))
{
@@ -4295,9 +4295,8 @@ void MainDialog::onCompare(wxCommandEvent& event)
//mark selected cfg files as "in sync" when there is nothing to do: https://freefilesync.org/forum/viewtopic.php?t=4991
if (r.summary.syncResult == SyncResult::finishedSuccess)
- {
- const SyncStatistics st(folderCmp_);
- if (st.createCount() +
+ if (const SyncStatistics st(folderCmp_);
+ st.createCount() +
st.updateCount() +
st.deleteCount() == 0)
{
@@ -4306,7 +4305,6 @@ void MainDialog::onCompare(wxCommandEvent& event)
updateConfigLastRunStats(std::chrono::system_clock::to_time_t(r.summary.startTime), r.summary.syncResult, getNullPath() /*logFilePath*/);
}
- }
//reset icon cache (IconBuffer) after *each* comparison!
filegrid::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg_.mainDlg.showIcons, convert(globalCfg_.mainDlg.iconSize));
@@ -4780,6 +4778,8 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s
return loadImage("msg_error", getDefaultMenuIconSize());
if (logCount.warning > 0)
return loadImage("msg_warning", getDefaultMenuIconSize());
+
+ //return loadImage("msg_success", getDefaultMenuIconSize()); -> too noisy?
}
return wxNullImage;
}();
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index 6be25206..47e3ed99 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -58,7 +58,7 @@ private:
void onOkay (wxCommandEvent& event) override { EndModal(static_cast<int>(ConfirmationButton::accept)); }
void onClose (wxCloseEvent& event) override { EndModal(static_cast<int>(ConfirmationButton::cancel)); }
void onOpenForum(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/forum"); }
- void onSendEmail(wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"mailto:zenju@" L"freefilesync.org"); }
+ void onSendEmail(wxCommandEvent& event) override { wxLaunchDefaultBrowser(wxString() + L"mailto:zenju@" + /*don't leave full email in either source or binary*/ L"freefilesync.org"); }
void onDonate (wxCommandEvent& event) override { wxLaunchDefaultBrowser(L"https://freefilesync.org/donate"); }
void onLocalKeyEvent(wxKeyEvent& event);
@@ -75,8 +75,8 @@ AboutDlg::AboutDlg(wxWindow* parent) : AboutDlgGenerated(parent)
setImage(*m_bitmapLogoLeft, loadImage("logo-left"));
setBitmapTextLabel(*m_bpButtonForum, loadImage("ffs_forum"), L"FreeFileSync Forum");
- setBitmapTextLabel(*m_bpButtonEmail, loadImage("ffs_email"), L"zenju@" L"freefilesync.org");
- m_bpButtonEmail->SetToolTip(L"mailto:zenju@" L"freefilesync.org");
+ setBitmapTextLabel(*m_bpButtonEmail, loadImage("ffs_email"), wxString() + L"zenju@" + /*don't leave full email in either source or binary*/ L"freefilesync.org");
+ m_bpButtonEmail->SetToolTip( wxString() + L"mailto:zenju@" + /*don't leave full email in either source or binary*/ L"freefilesync.org");
wxString build = utfTo<wxString>(ffsVersion);
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index 65b1b645..ec79a65d 100644
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -127,73 +127,76 @@ bool sanitizeFilter(FilterConfig& filterCfg, const std::vector<AbstractPath>& ba
removeDuplicates(folderPathsPf);
}
- if (!folderPathsPf.empty())
+
+ std::vector<std::pair<Zstring /*from*/, Zstring /*to*/>> replacements;
+
+ auto replaceFullPaths = [&](Zstring& filterPhrase)
{
- std::vector<std::pair<Zstring /*from*/, Zstring /*to*/>> replacements;
+ Zstring filterPhraseNew;
+ const Zchar* itFilterOrig = filterPhrase.begin();
- auto replaceFullPaths = [&](Zstring& filterPhrase)
+ split2(filterPhrase, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n'); }, //delimiters
+ [&](const ZstringView phrase)
{
- Zstring filterPhraseNew;
- const Zchar* itFilterOrig = filterPhrase.begin();
-
- split2(filterPhrase, [](Zchar c) { return c == FILTER_ITEM_SEPARATOR || c == Zstr('\n'); }, //delimiters
- [&](ZstringView phrase)
+ const ZstringView phraseTrm = trimCpy(phrase);
+ if (!phraseTrm.empty())
{
- phrase = trimCpy(phrase);
- if (!phrase.empty())
- {
- const Zstring phraseNorm = normalizeForSearch(Zstring{phrase});
-
- for (const Zstring& pathNormPf : folderPathsPf)
- if (startsWith(phraseNorm, pathNormPf))
- {
- //emulate a "normalized afterFirst()":
- ptrdiff_t sepCount = std::count(pathNormPf.begin(), pathNormPf.end(), FILE_NAME_SEPARATOR);
- assert(sepCount > 0);
-
- for (auto it = phrase.begin(); it != phrase.end(); ++it)
- if (*it == Zstr('/') ||
- *it == Zstr('\\'))
- if (--sepCount == 0)
- {
- const Zstring relPath(it, phrase.end()); //include first path separator
-
- filterPhraseNew.append(itFilterOrig, phrase.data());
- filterPhraseNew += relPath;
- itFilterOrig = phrase.data() + phrase.size();
-
- replacements.emplace_back(phrase, relPath);
- return; //... to next block
- }
- throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
- }
- }
- });
- filterPhraseNew.append(itFilterOrig, filterPhrase.cend());
-
- filterPhrase = filterPhraseNew;
- };
- replaceFullPaths(filterCfg.includeFilter);
- replaceFullPaths(filterCfg.excludeFilter);
+ const Zstring phraseNorm = normalizeForSearch(Zstring{phraseTrm});
+
+ for (const Zstring& pathNormPf : folderPathsPf)
+ if (startsWith(phraseNorm, pathNormPf))
+ {
+ //emulate a "normalized afterFirst()":
+ ptrdiff_t sepCount = std::count(pathNormPf.begin(), pathNormPf.end(), FILE_NAME_SEPARATOR);
+ assert(sepCount > 0);
+
+ for (auto it = phraseTrm.begin(); it != phraseTrm.end(); ++it)
+ if (*it == Zstr('/') ||
+ *it == Zstr('\\'))
+ if (--sepCount == 0)
+ {
+ const Zstring relPath(it, phraseTrm.end()); //include first path separator
+
+ filterPhraseNew.append(itFilterOrig, phraseTrm.data());
+ filterPhraseNew += relPath;
+ itFilterOrig = phraseTrm.data() + phraseTrm.size();
+
+ replacements.emplace_back(phraseTrm, relPath);
+ return; //... to next block
+ }
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
+ }
+ }
+ });
- assert(!replacements.empty());
- if (!replacements.empty())
+ if (itFilterOrig != filterPhrase.begin()) //perf!?
{
- std::wstring detailsMsg;
- for (const auto& [from, to] : replacements)
+ filterPhraseNew.append(itFilterOrig, filterPhrase.cend());
+ filterPhrase = std::move(filterPhraseNew);
+ }
+ };
+ replaceFullPaths(filterCfg.includeFilter);
+ replaceFullPaths(filterCfg.excludeFilter);
+
+ if (!replacements.empty())
+ {
+ std::wstring detailsMsg;
+ for (const auto& [from, to] : replacements)
+ if (to.empty())
+ detailsMsg += _("Remove:") + L' ' + utfTo<std::wstring>(from) + L'\n';
+ else
detailsMsg += utfTo<std::wstring>(from) + L' ' + arrowRight + L' ' + utfTo<std::wstring>(to) + L'\n';
- detailsMsg.pop_back();
+ detailsMsg.pop_back();
- switch (showConfirmationDialog(parent, DialogInfoType::info, PopupDialogCfg().
- setMainInstructions(_("Each filter item must be a path relative to the selected folder pairs. The following changes are suggested:")).
- setDetailInstructions(detailsMsg), _("&Change")))
- {
- case ConfirmationButton::accept: //change
- break;
+ switch (showConfirmationDialog(parent, DialogInfoType::info, PopupDialogCfg().
+ setMainInstructions(_("Each filter item must be a path relative to the selected folder pairs. The following changes are suggested:")).
+ setDetailInstructions(detailsMsg), _("&Change")))
+ {
+ case ConfirmationButton::accept: //change
+ break;
- case ConfirmationButton::cancel:
- return false;
- }
+ case ConfirmationButton::cancel:
+ return false;
}
}
return true;
diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp
index 29b5ed2f..9af55432 100644
--- a/FreeFileSync/Source/ui/version_check.cpp
+++ b/FreeFileSync/Source/ui/version_check.cpp
@@ -26,6 +26,8 @@
#include "../version/version.h"
#include "small_dlgs.h"
+ #include <gtk/gtk.h>
+ #include <wx/uilocale.h>
#include <zen/symlink_target.h>
@@ -91,8 +93,8 @@ std::wstring getIso639Language()
{
assert(runningOnMainThread()); //this function is not thread-safe: consider wxWidgets usage
- std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()));
- localeName = beforeFirst(localeName, L'@', IfNotFoundReturn::all); //the locale may contain an @, e.g. "sr_RS@latin"; see wxLocale::InitLanguagesDB()
+ std::wstring localeName(wxUILocale::GetLanguageCanonicalName(wxUILocale::GetSystemLanguage()));
+ localeName = beforeFirst(localeName, L'@', IfNotFoundReturn::all); //the locale may contain an @, e.g. "sr_RS@latin"; see wxUILocale::InitLanguagesDB()
if (!localeName.empty())
{
@@ -111,8 +113,8 @@ std::wstring getIso3166Country()
{
assert(runningOnMainThread()); //this function is not thread-safe, consider wxWidgets usage
- std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()));
- localeName = beforeFirst(localeName, L'@', IfNotFoundReturn::all); //the locale may contain an @, e.g. "sr_RS@latin"; see wxLocale::InitLanguagesDB()
+ std::wstring localeName(wxUILocale::GetLanguageCanonicalName(wxUILocale::GetSystemLanguage()));
+ localeName = beforeFirst(localeName, L'@', IfNotFoundReturn::all); //the locale may contain an @, e.g. "sr_RS@latin"; see wxUILocale::InitLanguagesDB()
if (contains(localeName, L'_'))
{
@@ -142,9 +144,9 @@ std::vector<std::pair<std::string, std::string>> geHttpPostParameters(wxWindow&
const char* osArch = cpuArchName;
params.emplace_back("os_arch", osArch);
-#ifdef __WXGTK2__
+#if GTK_MAJOR_VERSION == 2
//wxWindow::GetContentScaleFactor() requires GTK3 or later
-#elif defined __WXGTK3__
+#elif GTK_MAJOR_VERSION == 3
params.emplace_back("dip_scale", numberTo<std::string>(parent.GetContentScaleFactor()));
#else
#error unknown GTK version!
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 87e6db77..b4002e5c 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "12.1"; //internal linkage!
+const char ffsVersion[] = "12.2"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp
index 5ad5da31..f8d23c5d 100644
--- a/wx+/tooltip.cpp
+++ b/wx+/tooltip.cpp
@@ -13,6 +13,7 @@
#include "image_tools.h"
#include "bitmap_button.h"
#include "dc.h"
+#include <gtk/gtk.h>
using namespace zen;
@@ -27,7 +28,8 @@ class Tooltip::TooltipDlgGenerated : public wxDialog
{
public:
TooltipDlgGenerated(wxWindow* parent) : //Suse Linux/X11: needs parent window, else there are z-order issues
- wxDialog(parent, wxID_ANY, L"" /*title*/, wxDefaultPosition, wxDefaultSize, 0 /*style*/)
+ wxDialog(parent, wxID_ANY, L"" /*title*/, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER /*style*/)
+ //wxSIMPLE_BORDER side effect: removes title bar on KDE
{
SetSizeHints(wxDefaultSize, wxDefaultSize);
SetExtraStyle(this->GetExtraStyle() | wxWS_EX_TRANSIENT);
@@ -101,13 +103,16 @@ void Tooltip::hide()
{
if (tipWindow_)
{
-#ifdef __WXGTK2__ //the tooltip sometimes turns blank or is not shown again after it was hidden: e.g. drag-selection on middle grid
+#if GTK_MAJOR_VERSION == 2 //the tooltip sometimes turns blank or is not shown again after it was hidden: e.g. drag-selection on middle grid
//=> no such issues on GTK3!
tipWindow_->Destroy(); //apply brute force:
tipWindow_ = nullptr; //
lastUsedImg_ = wxNullImage;
-#else
+
+#elif GTK_MAJOR_VERSION == 3
tipWindow_->Hide();
+#else
+#error unknown GTK version!
#endif
}
}
diff --git a/zen/argon2.cpp b/zen/argon2.cpp
index f48abe5e..1250e545 100644
--- a/zen/argon2.cpp
+++ b/zen/argon2.cpp
@@ -59,7 +59,7 @@
/* ----------------------------------------------------------------------
-/*
+ *
* A sort of 'abstract base class' or 'interface' or 'trait' which is
* the common feature of all types that want to accept data formatted
* using the SSH binary conventions of uint32, string, mpint etc.
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index ef6cdc80..c5cbf095 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -8,12 +8,14 @@
#include <map>
#include <algorithm>
#include <chrono>
+#include <variant>
#include "file_traverser.h"
#include "scope_guard.h"
#include "symlink_target.h"
#include "file_io.h"
#include "crc.h"
#include "guid.h"
+#include "ring_buffer.h"
#include <sys/vfs.h> //statfs
#ifdef HAVE_SELINUX
@@ -51,72 +53,79 @@ ItemType getItemTypeImpl(const Zstring& itemPath) //throw SysErrorCode
return ItemType::folder;
return ItemType::file; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK
}
-}
-ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
+std::variant<ItemType, Zstring /*last existing parent path*/> getItemTypeIfExistsImpl(const Zstring& itemPath) //throw SysError
{
try
{
+ //fast check: 1. perf 2. expected by getFolderStatusNonBlocking()
return getItemTypeImpl(itemPath); //throw SysErrorCode
}
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); }
-}
-
-
-std::variant<ItemType, Zstring /*last existing parent path*/> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
-{
- try
+ catch (const SysErrorCode& e) //let's dig deeper, but *only* if error code sounds like "not existing"
{
- try
- {
- //fast check: 1. perf 2. expected by getFolderStatusNonBlocking()
- return getItemTypeImpl(itemPath); //throw SysErrorCode
- }
- catch (const SysErrorCode& e) //let's dig deeper, but *only* if error code sounds like "not existing"
+ const std::optional<Zstring> parentPath = getParentFolderPath(itemPath);
+ if (!parentPath) //device root => quick access test
+ throw;
+ if (e.errorCode == ENOENT)
{
- const std::optional<Zstring> parentPath = getParentFolderPath(itemPath);
- if (!parentPath) //device root => quick access test
- throw;
- if (e.errorCode == ENOENT)
- {
- const std::variant<ItemType, Zstring /*last existing parent path*/> parentTypeOrPath = getItemTypeIfExists(*parentPath); //throw FileError
+ const std::variant<ItemType, Zstring /*last existing parent path*/> parentTypeOrPath = getItemTypeIfExistsImpl(*parentPath); //throw SysError
- if (const ItemType* parentType = std::get_if<ItemType>(&parentTypeOrPath))
- {
- if (*parentType == ItemType::file /*obscure, but possible*/)
- throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(*parentPath))));
+ if (const ItemType* parentType = std::get_if<ItemType>(&parentTypeOrPath))
+ {
+ if (*parentType == ItemType::file /*obscure, but possible*/)
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(*parentPath))));
- const Zstring itemName = getItemName(itemPath);
- assert(!itemName.empty());
+ const Zstring itemName = getItemName(itemPath);
+ assert(!itemName.empty());
+ try
+ {
traverseFolder(*parentPath, //throw FileError
[&](const FileInfo& fi) { if (fi.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); },
[&](const FolderInfo& fi) { if (fi.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); },
[&](const SymlinkInfo& si) { if (si.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); });
//- case-sensitive comparison! itemPath must be normalized!
//- finding the item after getItemType() previously failed is exceptional
-
- return *parentPath;
}
- else
- return parentTypeOrPath;
+ catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); }
+
+ return *parentPath;
}
else
- throw;
+ return parentTypeOrPath;
}
+ else
+ throw;
}
- catch (const SysError& e)
+}
+}
+
+
+ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
+{
+ try
{
- throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString());
+ return getItemTypeImpl(itemPath); //throw SysErrorCode
}
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); }
}
-bool zen::itemExists(const Zstring& itemPath) //throw FileError
+std::optional<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
{
- const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(itemPath); //throw FileError
- return std::get_if<ItemType>(&typeOrPath);
+ try
+ {
+ const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExistsImpl(itemPath); //throw SysError
+ if (const ItemType* type = std::get_if<ItemType>(&typeOrPath))
+ return *type;
+ else
+ return std::nullopt;
+ }
+ catch (const SysError& e)
+ {
+ throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString());
+ }
}
@@ -130,16 +139,16 @@ namespace
//- folderPath does not need to exist (yet)
int64_t zen::getFreeDiskSpace(const Zstring& folderPath) //throw FileError
{
- const Zstring existingPath = [&]
- {
- const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(folderPath); //throw FileError
- if (std::get_if<ItemType>(&typeOrPath))
- return folderPath;
- else
- return std::get<Zstring>(typeOrPath);
- }();
try
{
+ const Zstring existingPath = [&]
+ {
+ const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExistsImpl(folderPath); //throw SysError
+ if (std::get_if<ItemType>(&typeOrPath))
+ return folderPath;
+ else
+ return std::get<Zstring>(typeOrPath);
+ }();
struct statfs info = {};
if (::statfs(existingPath.c_str(), &info) != 0) //follows symlinks!
THROW_LAST_SYS_ERROR("statfs");
@@ -253,10 +262,14 @@ void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError
{
- if (getItemType(dirPath) == ItemType::symlink) //throw FileError
- removeSymlinkPlain(dirPath); //throw FileError
- else
- removeDirectoryImpl(dirPath); //throw FileError
+ try
+ {
+ if (getItemTypeImpl(dirPath) == ItemType::symlink) //throw SysErrorCode
+ removeSymlinkPlain(dirPath); //throw FileError
+ else
+ removeDirectoryImpl(dirPath); //throw FileError
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), e.toString()); }
}
@@ -354,16 +367,15 @@ void setWriteTimeNative(const Zstring& itemPath, const timespec& modTime, ProcSy
//https://freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works (but not for gvfs SFTP)
if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, procSl == ProcSymlink::asLink ? AT_SYMLINK_NOFOLLOW : 0) == 0)
return;
+ const ErrorCode ecUtimensat = errno;
try
{
if (procSl == ProcSymlink::asLink)
- try
- {
- if (getItemType(itemPath) == ItemType::symlink) //throw FileError
- THROW_LAST_SYS_ERROR("utimensat(AT_SYMLINK_NOFOLLOW)"); //use lutimes()? just a wrapper around utimensat()!
- //else: fall back
- }
- catch (const FileError& e) { throw SysError(e.toString()); }
+ {
+ if (getItemTypeImpl(itemPath) == ItemType::symlink) //throw SysErrorCode
+ throw SysError(formatSystemError("utimensat(AT_SYMLINK_NOFOLLOW)", ecUtimensat)); //use lutimes()? just a wrapper around utimensat()!
+ //else: fall back
+ }
//in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: https://freefilesync.org/forum/viewtopic.php?t=387
//2017-07-04: O_WRONLY | O_APPEND seems to avoid EOPNOTSUPP on gvfs SFTP!
@@ -475,10 +487,14 @@ void zen::copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPa
if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "lchown");
- const bool isSymlinkTarget = getItemType(targetPath) == ItemType::symlink; //throw FileError
- if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
- ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "chmod");
+ try
+ {
+ if (getItemTypeImpl(targetPath) != ItemType::symlink && //throw SysErrorCode
+ //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
+ THROW_LAST_SYS_ERROR("chmod");
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), e.toString()); }
}
}
@@ -517,29 +533,47 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget
void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError
{
- //expect that path already exists (see: versioning, base folder, log file path) => check first
- const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(dirPath); //throw FileError
-
- if (const ItemType* type = std::get_if<ItemType>(&typeOrPath))
+ auto getItemType2 = [&](const Zstring& itemPath) //throw FileError
{
- if (*type == ItemType::file /*obscure, but possible*/)
+ try
+ { return getItemTypeImpl(itemPath); } //throw SysErrorCode
+ catch (const SysErrorCode& e) //need to add context!
+ {
throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)),
- replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(dirPath))));
- }
- else
- {
- const Zstring existingDirPath = std::get<Zstring>(typeOrPath);
- assert(startsWith(dirPath, existingDirPath));
+ replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getParentFolderPath(itemPath) ? getItemName(itemPath) : itemPath)) + L'\n' +
+ e.toString());
+ }
+ };
- const ZstringView relPath = makeStringView(dirPath.begin() + existingDirPath.size(), dirPath.end());
- const std::vector<ZstringView> namesMissing = splitCpy(relPath, FILE_NAME_SEPARATOR, SplitOnEmpty::skip);
- assert(!namesMissing.empty());
+ try
+ {
+ //- path most likely already exists (see: versioning, base folder, log file path) => check first
+ //- do NOT use getItemTypeIfExists()! race condition when multiple threads are calling createDirectoryIfMissingRecursion(): https://freefilesync.org/forum/viewtopic.php?t=10137#p38062
+ //- find first existing + accessible parent folder (backwards iteration):
+ Zstring dirPathEx = dirPath;
+ RingBuffer<Zstring> dirNames; //caveat: 1. might have been created in the meantime 2. getItemType2() may have failed with access error
+ for (;;)
+ try
+ {
+ if (getItemType2(dirPathEx) == ItemType::file /*obscure, but possible*/) //throw FileError
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(dirPathEx))));
+ break;
+ }
+ catch (FileError&) //not yet existing or access error
+ {
+ const std::optional<Zstring> parentPath = getParentFolderPath(dirPathEx);
+ if (!parentPath)//device root => quick access test
+ throw;
+ dirNames.push_front(getItemName(dirPathEx));
+ dirPathEx = *parentPath;
+ }
+ //-----------------------------------------------------------
- Zstring dirPathNew = existingDirPath;
- for (const ZstringView folderName : namesMissing)
+ Zstring dirPathNew = dirPathEx;
+ for (const Zstring& dirName : dirNames)
try
{
- dirPathNew = appendPath(dirPathNew, Zstring(folderName));
+ dirPathNew = appendPath(dirPathNew, dirName);
createDirectory(dirPathNew); //throw FileError
}
@@ -547,18 +581,24 @@ void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw File
{
try
{
- if (getItemType(dirPathNew) != ItemType::file /*obscure, but possible*/) //throw FileError
- continue; //already existing => possible, if createFolderIfMissingRecursion() is run in parallel
+ if (getItemType2(dirPathNew) == ItemType::file /*obscure, but possible*/) //throw FileError
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(dirPathNew))));
+ else
+ continue; //already existing => possible, if createDirectoryIfMissingRecursion() is run in parallel
}
catch (FileError&) {} //not yet existing or access error
throw;
}
}
+ catch (const SysError& e)
+ {
+ throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)), e.toString());
+ }
}
-void zen::tryCopyDirectoryAttributes(const Zstring& sourcePath, const Zstring& targetPath) //throw FileError
+void zen::copyDirectoryAttributes(const Zstring& sourcePath, const Zstring& targetPath) //throw FileError
{
//do NOT copy attributes for volume root paths which return as: FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY
//https://freefilesync.org/forum/viewtopic.php?t=5550
@@ -570,10 +610,11 @@ void zen::tryCopyDirectoryAttributes(const Zstring& sourcePath, const Zstring& t
void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath) //throw FileError
{
- const SymlinkRawContent linkContent = getSymlinkRawContent(sourcePath); //throw FileError; accept broken symlinks
-
+ SymlinkRawContent linkContent{};
try //harmonize with NativeFileSystem::equalSymlinkContentForSameAfsType()
{
+ linkContent = getSymlinkRawContent_impl(sourcePath); //throw SysError; accept broken symlinks
+
if (::symlink(linkContent.targetPath.c_str(), targetPath.c_str()) != 0)
THROW_LAST_SYS_ERROR("symlink");
}
diff --git a/zen/file_access.h b/zen/file_access.h
index 5af8b879..f6ac3740 100644
--- a/zen/file_access.h
+++ b/zen/file_access.h
@@ -8,7 +8,6 @@
#define FILE_ACCESS_H_8017341345614857
#include <functional>
-#include <variant>
#include "file_path.h"
#include "file_error.h"
#include "serialize.h" //IoCallback
@@ -39,9 +38,9 @@ ItemType getItemType(const Zstring& itemPath); //throw FileError
//execute potentially SLOW folder traversal but distinguish error/not existing:
// - all child item path parts must correspond to folder traversal
// => we can conclude whether an item is *not* existing anymore by doing a *case-sensitive* name search => potentially SLOW!
-std::variant<ItemType, Zstring /*last existing parent path*/> getItemTypeIfExists(const Zstring& itemPath); //throw FileError
+std::optional<ItemType> getItemTypeIfExists(const Zstring& itemPath); //throw FileError
-bool itemExists(const Zstring& itemPath); //throw FileError
+inline bool itemExists(const Zstring& itemPath) { return static_cast<bool>(getItemTypeIfExists(itemPath)); } //throw FileError
enum class ProcSymlink
{
@@ -80,8 +79,7 @@ void createDirectoryIfMissingRecursion(const Zstring& dirPath); //throw FileErro
//symlink handling: follow
//expects existing source/target directories
-//reports "note-worthy" errors only
-void tryCopyDirectoryAttributes(const Zstring& sourcePath, const Zstring& targetPath); //throw FileError
+void copyDirectoryAttributes(const Zstring& sourcePath, const Zstring& targetPath); //throw FileError
void copySymlink(const Zstring& sourcePath, const Zstring& targetPath); //throw FileError
diff --git a/zen/file_path.cpp b/zen/file_path.cpp
index 7ef78569..c9d6f236 100644
--- a/zen/file_path.cpp
+++ b/zen/file_path.cpp
@@ -23,7 +23,7 @@ std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath)
Zstring rootPath(itemPathPf.begin(), rootWithSep ? it + 1 : it);
Zstring relPath(it + 1, itemPathPf.end());
- trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; });
+ trim(relPath, TrimSide::both, [](Zchar c) { return c == FILE_NAME_SEPARATOR; });
return PathComponents{std::move(rootPath), std::move(relPath)};
}
@@ -203,24 +203,11 @@ std::optional<Zstring> zen::getEnvironmentVar(const ZstringView name)
envVars = globalEnvVars.get();
}
- auto it = envVars->find(name);
+ const auto it = envVars->find(name);
if (it == envVars->end())
return {};
- Zstring value = it->second;
-
- //some postprocessing (good idea!? Is this even needed!?
- warn_static("let's find out!")
-#if 0
- trim(value); //remove leading, trailing blanks
-
- //remove leading, trailing double-quotes
- if (startsWith(value, Zstr('"')) &&
- endsWith (value, Zstr('"')) &&
- value.length() >= 2)
- value = Zstring(value.c_str() + 1, value.length() - 2);
-#endif
- return value;
+ return it->second;
}
diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp
index af4306b2..5cfd7f12 100644
--- a/zen/open_ssl.cpp
+++ b/zen/open_ssl.cpp
@@ -387,7 +387,7 @@ void zen::verifySignature(const std::string_view message, const std::string_view
bool zen::isPuttyKeyStream(const std::string_view keyStream)
{
- return startsWith(trimCpy(keyStream, true, false), "PuTTY-User-Key-File-");
+ return startsWith(trimCpy(keyStream, TrimSide::left), "PuTTY-User-Key-File-");
}
diff --git a/zen/resolve_path.cpp b/zen/resolve_path.cpp
index daaf91ff..7bf50b12 100644
--- a/zen/resolve_path.cpp
+++ b/zen/resolve_path.cpp
@@ -149,7 +149,7 @@ namespace
Zstring tryExpandVolumeName(Zstring pathPhrase) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder
{
//we only expect the [.*] pattern at the beginning => do not touch dir names like "C:\somedir\[stuff]"
- trim(pathPhrase, true, false);
+ trim(pathPhrase, TrimSide::left);
if (startsWith(pathPhrase, Zstr('[')))
{
diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp
index e64e1e70..ee68b467 100644
--- a/zen/shutdown.cpp
+++ b/zen/shutdown.cpp
@@ -55,7 +55,7 @@ void zen::terminateProcess(int exitCode)
for (;;) //why still here?? => crash deliberately!
- *reinterpret_cast<volatile int*>(0) = 0; //crude but at least we'll get crash dumps if it ever happens
+ *reinterpret_cast<volatile int*>(0) = 0; //crude but at least we'll get crash dumps *if* it ever happens
}
diff --git a/zen/socket.h b/zen/socket.h
index 4ccde190..f706daab 100644
--- a/zen/socket.h
+++ b/zen/socket.h
@@ -19,6 +19,42 @@ namespace zen
do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false)
+#define THROW_LAST_SYS_ERROR_GAI(rcGai) \
+ do { \
+ if (rcGai == EAI_SYSTEM) /*"check errno for details"*/ \
+ THROW_LAST_SYS_ERROR("getaddrinfo"); \
+ \
+ throw SysError(formatSystemError("getaddrinfo", formatGaiErrorCode(rcGai), utfTo<std::wstring>(::gai_strerror(rcGai)))); \
+ } while (false)
+
+inline
+std::wstring formatGaiErrorCode(int ec)
+{
+ switch (ec) //codes used on both Linux and macOS
+ {
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_ADDRFAMILY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_AGAIN);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_BADFLAGS);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_FAIL);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_FAMILY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_MEMORY);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_NODATA);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_NONAME);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_SERVICE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_SOCKTYPE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_SYSTEM);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_OVERFLOW);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_INPROGRESS);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_CANCELED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_NOTCANCELED);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_ALLDONE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_INTR);
+ ZEN_CHECK_CASE_FOR_CONSTANT(EAI_IDN_ENCODE);
+ default:
+ return replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(ec));
+ }
+}
+
//patch up socket portability:
using SocketType = int;
const SocketType invalidSocket = -1;
@@ -53,7 +89,7 @@ public:
const int rcGai = ::getaddrinfo(server.c_str(), serviceName.c_str(), &hints, &servinfo);
if (rcGai != 0)
- throw SysError(formatSystemError("getaddrinfo", replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
+ THROW_LAST_SYS_ERROR_GAI(rcGai);
if (!servinfo)
throw SysError(formatSystemError("getaddrinfo", L"", L"Empty server info."));
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 03563d41..1cd8ef0d 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -73,9 +73,15 @@ template <class S, class Char, class Function> void split(const S& str, Char del
template <class S, class Function1, class Function2> void split2(const S& str, Function1 isDelimiter, Function2 onStringPart);
template <class S, class Char> [[nodiscard]] std::vector<S> splitCpy(const S& str, Char delimiter, SplitOnEmpty soe);
-template <class S> [[nodiscard]] S trimCpy(const S& str, bool fromLeft = true, bool fromRight = true);
-template <class S> void trim (S& str, bool fromLeft = true, bool fromRight = true);
-template <class S, class Function> void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar);
+enum class TrimSide
+{
+ both,
+ left,
+ right,
+};
+template <class S> [[nodiscard]] S trimCpy(const S& str, TrimSide side = TrimSide::both);
+template <class S> void trim(S& str, TrimSide side = TrimSide::both);
+template <class S, class Function> void trim(S& str, TrimSide side, Function trimThisChar);
template <class S, class T, class U> [[nodiscard]] S replaceCpy(S str, const T& oldTerm, const U& newTerm);
@@ -540,15 +546,13 @@ S replaceCpyAsciiNoCase(S str, const T& oldTerm, const U& newTerm)
template <class Char, class Function>
[[nodiscard]] inline
-std::pair<Char*, Char*> trimCpy2(Char* first, Char* last, bool fromLeft, bool fromRight, Function trimThisChar)
+std::pair<Char*, Char*> trimCpy2(Char* first, Char* last, TrimSide side, Function trimThisChar)
{
- assert(fromLeft || fromRight);
-
- if (fromRight)
+ if (side == TrimSide::right || side == TrimSide::both)
while (first != last && trimThisChar(last[-1]))
--last;
- if (fromLeft)
+ if (side == TrimSide::left || side == TrimSide::both)
while (first != last && trimThisChar(*first))
++first;
@@ -557,12 +561,10 @@ std::pair<Char*, Char*> trimCpy2(Char* first, Char* last, bool fromLeft, bool fr
template <class S, class Function> inline
-void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar)
+void trim(S& str, TrimSide side, Function trimThisChar)
{
- assert(fromLeft || fromRight);
-
const auto* const oldBegin = strBegin(str);
- const auto& [newBegin, newEnd] = trimCpy2(oldBegin, oldBegin + strLength(str), fromLeft, fromRight, trimThisChar);
+ const auto [newBegin, newEnd] = trimCpy2(oldBegin, oldBegin + strLength(str), side, trimThisChar);
if (newBegin != oldBegin)
str = S(newBegin, newEnd); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only
@@ -572,21 +574,21 @@ void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar)
template <class S> inline
-void trim(S& str, bool fromLeft, bool fromRight)
+void trim(S& str, TrimSide side)
{
using CharType = GetCharTypeT<S>;
- trim(str, fromLeft, fromRight, [](CharType c) { return isWhiteSpace(c); });
+ trim(str, side, [](CharType c) { return isWhiteSpace(c); });
}
template <class S> inline
-S trimCpy(const S& str, bool fromLeft, bool fromRight)
+S trimCpy(const S& str, TrimSide side)
{
using CharType = GetCharTypeT<S>;
const auto* const oldBegin = strBegin(str);
const auto* const oldEnd = oldBegin + strLength(str);
- const auto& [newBegin, newEnd] = trimCpy2(oldBegin, oldEnd, fromLeft, fromRight, [](CharType c) { return isWhiteSpace(c); });
+ const auto [newBegin, newEnd] = trimCpy2(oldBegin, oldEnd, side, [](CharType c) { return isWhiteSpace(c); });
if (newBegin == oldBegin && newEnd == oldEnd)
return str;
diff --git a/zen/symlink_target.h b/zen/symlink_target.h
index 89c00571..083360af 100644
--- a/zen/symlink_target.h
+++ b/zen/symlink_target.h
@@ -38,7 +38,7 @@ Zstring getSymlinkResolvedPath(const Zstring& linkPath); //throw FileError
namespace
{
//retrieve raw target data of symlink or junction
-zen::SymlinkRawContent getSymlinkRawContent_impl(const Zstring& linkPath) //throw FileError
+zen::SymlinkRawContent getSymlinkRawContent_impl(const Zstring& linkPath) //throw SysError
{
using namespace zen;
const size_t bufSize = 10000;
@@ -46,26 +46,22 @@ zen::SymlinkRawContent getSymlinkRawContent_impl(const Zstring& linkPath) //thro
const ssize_t bytesWritten = ::readlink(linkPath.c_str(), buf.data(), bufSize);
if (bytesWritten < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), "readlink");
+ THROW_LAST_SYS_ERROR("readlink");
if (makeUnsigned(bytesWritten) >= bufSize) //detect truncation; not an error for readlink!
- throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), formatSystemError("readlink", L"", L"Buffer truncated."));
+ throw SysError(formatSystemError("readlink", L"", L"Buffer truncated."));
return {.targetPath = Zstring(buf.data(), bytesWritten)}; //readlink does not append 0-termination!
}
-Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError
+Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw SysError
{
using namespace zen;
- try
- {
- char* targetPath = ::realpath(linkPath.c_str(), nullptr);
- if (!targetPath)
- THROW_LAST_SYS_ERROR("realpath");
- ZEN_ON_SCOPE_EXIT(::free(targetPath));
- return targetPath;
- }
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), e.toString()); }
+ char* targetPath = ::realpath(linkPath.c_str(), nullptr);
+ if (!targetPath)
+ THROW_LAST_SYS_ERROR("realpath");
+ ZEN_ON_SCOPE_EXIT(::free(targetPath));
+ return targetPath;
}
}
@@ -73,11 +69,25 @@ Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError
namespace zen
{
inline
-SymlinkRawContent getSymlinkRawContent(const Zstring& linkPath) { return getSymlinkRawContent_impl(linkPath); } //throw FileError
+SymlinkRawContent getSymlinkRawContent(const Zstring& linkPath)
+{
+ try
+ {
+ return getSymlinkRawContent_impl(linkPath); //throw SysError
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), e.toString()); }
+}
inline
-Zstring getSymlinkResolvedPath(const Zstring& linkPath) { return getResolvedSymlinkPath_impl(linkPath); } //throw FileError
+Zstring getSymlinkResolvedPath(const Zstring& linkPath)
+{
+ try
+ {
+ return getResolvedSymlinkPath_impl(linkPath); //throw SysError
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), e.toString()); }
+}
}
diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp
index 55465711..2a32d247 100644
--- a/zen/sys_info.cpp
+++ b/zen/sys_info.cpp
@@ -131,8 +131,8 @@ ComputerModel zen::getComputerModel() //throw FileError
cm.model = beforeFirst(cm.model, L'\u00ff', IfNotFoundReturn::all); //fix broken BIOS entries:
cm.vendor = beforeFirst(cm.vendor, L'\u00ff', IfNotFoundReturn::all); //0xff can be considered 0
- trim(cm.model, false, true, [](wchar_t c) { return c == L'_'; }); //e.g. "CBX3___" or just "_"
- trim(cm.vendor, false, true, [](wchar_t c) { return c == L'_'; }); //e.g. "DELL__" or just "_"
+ trim(cm.model, TrimSide::right, [](wchar_t c) { return c == L'_'; }); //e.g. "CBX3___" or just "_"
+ trim(cm.vendor, TrimSide::right, [](wchar_t c) { return c == L'_'; }); //e.g. "DELL__" or just "_"
for (const char* dummyModel :
{
@@ -209,9 +209,13 @@ std::wstring zen::getOsDescription() //throw FileError
Zstring zen::getRealProcessPath() //throw FileError
{
- return getSymlinkRawContent("/proc/self/exe").targetPath; //throw FileError
- //path does not contain symlinks => no need for ::realpath()
+ try
+ {
+ return getSymlinkRawContent_impl("/proc/self/exe").targetPath; //throw SysError
+ //path does not contain symlinks => no need for ::realpath()
+ }
+ catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); }
}
diff --git a/zen/sys_version.cpp b/zen/sys_version.cpp
index b123ba07..e57c9b69 100644
--- a/zen/sys_version.cpp
+++ b/zen/sys_version.cpp
@@ -55,8 +55,8 @@ OsVersionDetail zen::getOsVersionDetail() //throw SysError
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'\''; });
+ trim(osName, TrimSide::both, [](char c) { return c == L'"' || c == L'\''; });
+ trim(osVersion, TrimSide::both, [](char c) { return c == L'"' || c == L'\''; });
}
if (osName.empty())
bgstack15