summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Changelog.txt8
-rw-r--r--FreeFileSync/Source/afs/abstract.h2
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp56
-rw-r--r--FreeFileSync/Source/application.cpp41
-rw-r--r--FreeFileSync/Source/base/db_file.cpp2
-rw-r--r--FreeFileSync/Source/log_file.cpp27
-rw-r--r--FreeFileSync/Source/log_file.h2
-rw-r--r--FreeFileSync/Source/ui/batch_status_handler.cpp4
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.cpp8
-rw-r--r--FreeFileSync/Source/ui/log_panel.cpp14
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp175
-rw-r--r--FreeFileSync/Source/ui/main_dlg.h2
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.cpp2
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp2
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--libcurl/curl_wrap.cpp3
-rw-r--r--libssh2/libssh2_wrap.h14
-rw-r--r--wx+/grid.cpp2
-rw-r--r--wx+/window_tools.h3
-rw-r--r--zen/error_log.h14
-rw-r--r--zen/file_access.h2
-rw-r--r--zen/format_unit.h2
-rw-r--r--zen/socket.h29
-rw-r--r--zen/type_traits.h1
24 files changed, 232 insertions, 185 deletions
diff --git a/Changelog.txt b/Changelog.txt
index 1c5643f2..e0ddb683 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,11 @@
+FreeFileSync 13.7 [2024-06-20]
+------------------------------
+Support copying symlinks between SFTP devices
+Fixed input focus not being restored after comparison/sync
+Fixed log file pruning not considering selected configuration
+Show startup error details when running outside terminal (Linux)
+
+
FreeFileSync 13.6 [2024-05-10]
------------------------------
Compact parent path display for medium/large row sizes
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index 236a8d6a..834b834e 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -488,7 +488,7 @@ AbstractFileSystem::OutputStream::~OutputStream()
try { AbstractFileSystem::removeFilePlain(filePath_); /*throw FileError*/ }
catch (const zen::FileError& e) { zen::logExtraError(e.toString()); }
- //warn_static("we should not log if not existing anymore!?: ERROR_FILE_NOT_FOUND: ddddddddddd [DeleteFile]")
+ warn_static("we should not log if not existing anymore!?: ERROR_FILE_NOT_FOUND: ddddddddddd [DeleteFile]")
//solution: integrate cleanup into ~OutputStreamImpl() including appropriate loggin!
}
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index d1cce9a9..a451ac88 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -1474,10 +1474,10 @@ private:
{
if (!fileHandle_)
throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
+
+ ZEN_ON_SCOPE_EXIT(fileHandle_ = nullptr); //reset on error, too! there's no point in, calling libssh2_sftp_close() a second time in ~OutputStreamSftp()
try
{
- ZEN_ON_SCOPE_EXIT(fileHandle_ = nullptr); //reset on error, too! there's no point in, calling libssh2_sftp_close() a second time in ~OutputStreamSftp()
-
session_->executeBlocking("libssh2_sftp_close", //throw SysError, SysErrorSftpProtocol
[&](const SshSession::Details& sd) { return ::libssh2_sftp_close(fileHandle_); }); //noexcept!
}
@@ -1733,26 +1733,31 @@ private:
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(getDisplayPath(linkPath))), e.toString()); }
}
+ static std::string getSymlinkContentImpl(const SftpFileSystem& sftpFs, const AfsPath& linkPath) //throw SysError
+ {
+ std::string buf(10000, '\0');
+ int rc = 0;
+
+ runSftpCommand(sftpFs.login_, "libssh2_sftp_readlink", //throw SysError, SysErrorSftpProtocol
+ [&](const SshSession::Details& sd) { return rc = ::libssh2_sftp_readlink(sd.sftpChannel, getLibssh2Path(linkPath), buf.data(), buf.size()); }); //noexcept!
+
+ ASSERT_SYSERROR(makeUnsigned(rc) <= buf.size()); //better safe than sorry
+
+ buf.resize(rc);
+ return buf;
+ }
+
bool equalSymlinkContentForSameAfsType(const AfsPath& linkPathL, const AbstractPath& linkPathR) const override //throw FileError
{
- auto getTargetPath = [](const SftpFileSystem& sftpFs, const AfsPath& linkPath)
+ auto getLinkContent = [](const SftpFileSystem& sftpFs, const AfsPath& linkPath)
{
- std::string buf(10000, '\0');
- int rc = 0;
try
{
- runSftpCommand(sftpFs.login_, "libssh2_sftp_readlink", //throw SysError, SysErrorSftpProtocol
- [&](const SshSession::Details& sd) { return rc = ::libssh2_sftp_readlink(sd.sftpChannel, getLibssh2Path(linkPath), buf.data(), buf.size()); }); //noexcept!
-
- ASSERT_SYSERROR(makeUnsigned(rc) <= buf.size()); //better safe than sorry
+ return getSymlinkContentImpl(sftpFs, linkPath); //throw SysError
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(sftpFs.getDisplayPath(linkPath))), e.toString()); }
-
- buf.resize(rc);
- return buf;
};
-
- return getTargetPath(*this, linkPathL) == getTargetPath(static_cast<const SftpFileSystem&>(linkPathR.afsDevice.ref()), linkPathR.afsPath);
+ return getLinkContent(*this, linkPathL) == getLinkContent(static_cast<const SftpFileSystem&>(linkPathR.afsDevice.ref()), linkPathR.afsPath); //throw FileError
}
//----------------------------------------------------------------------------------------------------------------
@@ -1802,12 +1807,25 @@ private:
throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
}
- //already existing: fail
- void copySymlinkForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override
+ //already existing: fail (SSH_FX_FAILURE)
+ void copySymlinkForSameAfsType(const AfsPath& sourcePath, const AbstractPath& targetPath, bool copyFilePermissions) const override //throw FileError
{
- throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."),
- L"%x", L'\n' + fmtPath(getDisplayPath(sourcePath))),
- L"%y", L'\n' + fmtPath(AFS::getDisplayPath(targetPath))), _("Operation not supported by device."));
+ try
+ {
+ const std::string buf = getSymlinkContentImpl(*this, sourcePath); //throw SysError
+
+ runSftpCommand(static_cast<const SftpFileSystem&>(targetPath.afsDevice.ref()).login_, "libssh2_sftp_symlink", //throw SysError, SysErrorSftpProtocol
+ [&](const SshSession::Details& sd) //noexcept!
+ {
+ return ::libssh2_sftp_symlink(sd.sftpChannel, getLibssh2Path(targetPath.afsPath), buf);
+ });
+ }
+ catch (const SysError& e)
+ {
+ throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."),
+ L"%x", L'\n' + fmtPath(getDisplayPath(sourcePath))),
+ L"%y", L'\n' + fmtPath(AFS::getDisplayPath(targetPath))), e.toString());
+ }
}
//already existing: undefined behavior! (e.g. fail/overwrite)
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index 39d12d5a..fc9032f3 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -530,10 +530,6 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
-> WinInet not working when FFS is running as a service!!! https://support.microsoft.com/en-us/help/238425/info-wininet-not-supported-for-use-in-services */
- std::set<AbstractPath> logFilePathsToKeep;
- for (const ConfigFileItem& item : globalCfg.mainDlg.config.fileHistory)
- logFilePathsToKeep.insert(item.lastRunStats.logFilePath);
-
const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now();
const WindowLayout::Dimensions progressDim
@@ -666,26 +662,33 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
}
//--------------------- save log file ----------------------
+ std::set<AbstractPath> logsToKeepPaths;
+ for (const ConfigFileItem& cfi : globalCfg.mainDlg.config.fileHistory)
+ if (!equalNativePath(cfi.cfgFilePath, cfgFilePath)) //exception: don't keep old log for the selected cfg file!
+ logsToKeepPaths.insert(cfi.lastRunStats.logFilePath);
+
try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename
{
//do NOT use tryReportingError()! saving log files should not be cancellable!
- saveLogFile(logFilePath, r.summary, r.errorLog.ref(), globalCfg.logfilesMaxAgeDays, globalCfg.logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
+ saveLogFile(logFilePath, r.summary, r.errorLog.ref(), globalCfg.logfilesMaxAgeDays, globalCfg.logFormat, logsToKeepPaths, notifyStatusNoThrow); //throw FileError
}
catch (const FileError& e)
{
- logMsg(r.errorLog.ref(), e.toString(), MSG_TYPE_ERROR);
+ try //fallback: log file *must* be saved no matter what!
+ {
+ const AbstractPath logFileDefaultPath = AFS::appendRelPath(createAbstractPath(getLogFolderDefaultPath()), generateLogFileName(globalCfg.logFormat, r.summary));
+ if (logFilePath == logFileDefaultPath)
+ throw;
- const AbstractPath logFileDefaultPath = AFS::appendRelPath(createAbstractPath(getLogFolderDefaultPath()), generateLogFileName(globalCfg.logFormat, r.summary));
- if (logFilePath != logFileDefaultPath) //fallback: log file *must* be saved no matter what!
- try
- {
- logFilePath = logFileDefaultPath;
- saveLogFile(logFileDefaultPath, r.summary, r.errorLog.ref(), globalCfg.logfilesMaxAgeDays, globalCfg.logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
- }
- catch (const FileError& e2) { logMsg(r.errorLog.ref(), e2.toString(), MSG_TYPE_ERROR); assert(false); } //should never happen!!!
+ logMsg(r.errorLog.ref(), e.toString(), MSG_TYPE_ERROR);
+
+ logFilePath = logFileDefaultPath;
+ saveLogFile(logFileDefaultPath, r.summary, r.errorLog.ref(), globalCfg.logfilesMaxAgeDays, globalCfg.logFormat, logsToKeepPaths, notifyStatusNoThrow); //throw FileError
+ }
+ catch (const FileError& e2) { logMsg(r.errorLog.ref(), e2.toString(), MSG_TYPE_ERROR); logExtraError(e2.toString()); } //should never happen!!!
}
- //--------- update last sync stats for the selected cfg files ---------
+ //--------- update last sync stats for the selected cfg file ---------
const ErrorLogStats& logStats = getStats(r.errorLog.ref());
for (ConfigFileItem& cfi : globalCfg.mainDlg.config.fileHistory)
@@ -702,8 +705,8 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
r.summary.statsProcessed.items,
r.summary.statsProcessed.bytes,
r.summary.totalTime,
- logStats.error,
- logStats.warning,
+ logStats.errors,
+ logStats.warnings,
};
break;
}
@@ -726,9 +729,9 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
}
//email sending, or saving log file failed? at least this should affect the exit code:
- if (logStats.error > 0)
+ if (logStats.errors > 0)
raiseExitCode(exitCode_, FfsExitCode::error);
- else if (logStats.warning > 0)
+ else if (logStats.warnings > 0)
raiseExitCode(exitCode_, FfsExitCode::warning);
//---------------------------------------------------------------------------
diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp
index e70448f3..eb62ccb2 100644
--- a/FreeFileSync/Source/base/db_file.cpp
+++ b/FreeFileSync/Source/base/db_file.cpp
@@ -55,7 +55,7 @@ AbstractPath getDatabaseFilePath(const BaseFolderPair& baseFolder)
- precomposed/decomposed UTF: differences already ignored
- 32 vs 64-bit: already handled
- => give db files different names: */
+ => give DB files different names: */
const Zstring dbName = Zstr(".sync"); //files beginning with dots are usually hidden
return AFS::appendRelPath(baseFolder.getAbstractPath<side>(), dbName + SYNC_DB_FILE_ENDING);
}
diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp
index 830ce834..989579e5 100644
--- a/FreeFileSync/Source/log_file.cpp
+++ b/FreeFileSync/Source/log_file.cpp
@@ -50,8 +50,8 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i
const ErrorLogStats logCount = getStats(log);
- if (logCount.error > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Errors:") + L' ' + formatNumber(logCount.error)));
- if (logCount.warning > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Warnings:") + L' ' + formatNumber(logCount.warning)));
+ if (logCount.errors > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Errors:") + L' ' + formatNumber(logCount.errors)));
+ if (logCount.warnings > 0) summary.push_back(tabSpace + utfTo<std::string>(_("Warnings:") + L' ' + formatNumber(logCount.warnings)));
summary.push_back(tabSpace + utfTo<std::string>(_("Items processed:") + L' ' + formatNumber(s.statsProcessed.items) + //show always, even if 0!
L" (" + formatFilesizeShort(s.statsProcessed.bytes) + L')'));
@@ -79,7 +79,7 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i
output += '|' + std::string(sepLineLen, '_') + "\n\n";
//------------ warnings/errors preview ----------------
- const int logFailTotal = logCount.warning + logCount.error;
+ const int logFailTotal = logCount.warnings + logCount.errors;
if (logFailTotal > 0)
{
output += '\n' + utfTo<std::string>(_("Errors and warnings:")) + '\n';
@@ -251,20 +251,20 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log,
const ErrorLogStats logCount = getStats(log);
- if (logCount.error > 0)
+ if (logCount.errors > 0)
output += R"(
<tr>
<td>)" + htmlTxt(_("Errors:")) + R"(</td>
<td><img src="https://freefilesync.org/images/log/msg-error.png" width="24" height="24" alt=""></td>
- <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.error)) + R"(</span></td>
+ <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.errors)) + R"(</span></td>
</tr>)";
- if (logCount.warning > 0)
+ if (logCount.warnings > 0)
output += R"(
<tr>
<td>)" + htmlTxt(_("Warnings:")) + R"(</td>
<td><img src="https://freefilesync.org/images/log/msg-warning.png" width="24" height="24" alt=""></td>
- <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.warning)) + R"(</span></td>
+ <td><span style="font-weight:600;">)" + htmlTxt(formatNumber(logCount.warnings)) + R"(</span></td>
</tr>)";
output += R"(
@@ -299,7 +299,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log,
)";
//------------ warnings/errors preview ----------------
- const int logFailTotal = logCount.warning + logCount.error;
+ const int logFailTotal = logCount.warnings + logCount.errors;
if (logFailTotal > 0)
{
output += R"(
@@ -526,7 +526,7 @@ std::vector<LogFileInfo> getLogFiles(const AbstractPath& logFolderPath) //throw
void limitLogfileCount(const AbstractPath& logFolderPath, //throw FileError, X
int logfilesMaxAgeDays, //<= 0 := no limit
- const std::set<AbstractPath>& logFilePathsToKeep,
+ const std::set<AbstractPath>& logsToKeepPaths,
const std::function<void(std::wstring&& msg)>& notifyStatus /*throw X*/)
{
if (logfilesMaxAgeDays > 0)
@@ -551,7 +551,7 @@ void limitLogfileCount(const AbstractPath& logFolderPath, //throw FileError, X
for (const LogFileInfo& lfi : logFiles)
if (lfi.timeStamp < cutOffTime &&
- !logFilePathsToKeep.contains(lfi.filePath)) //don't trim latest log files corresponding to last used config files!
+ !logsToKeepPaths.contains(lfi.filePath)) //don't trim latest log files corresponding to last used config files!
//nitpicker's corner: what about path differences due to case? e.g. user-overriden log file path changed in case
{
if (notifyStatus) notifyStatus(statusPrefix + fmtPath(AFS::getDisplayPath(lfi.filePath))); //throw X
@@ -635,7 +635,7 @@ void fff::saveLogFile(const AbstractPath& logFilePath, //throw FileError, X
const ErrorLog& log,
int logfilesMaxAgeDays,
LogFileFormat logFormat,
- const std::set<AbstractPath>& logFilePathsToKeep,
+ const std::set<AbstractPath>& logsToKeepPaths,
const std::function<void(std::wstring&& msg)>& notifyStatus /*throw X*/)
{
std::exception_ptr firstError;
@@ -648,9 +648,8 @@ void fff::saveLogFile(const AbstractPath& logFilePath, //throw FileError, X
try
{
const std::optional<AbstractPath> logFolderPath = AFS::getParentPath(logFilePath);
- assert(logFolderPath);
- if (logFolderPath) //else: logFilePath == device root; not possible with generateLogFilePath()
- limitLogfileCount(*logFolderPath, logfilesMaxAgeDays, logFilePathsToKeep, notifyStatus); //throw FileError, X
+ assert(logFolderPath); //else: logFilePath == device root; not possible with generateLogFilePath()
+ limitLogfileCount(*logFolderPath, logfilesMaxAgeDays, logsToKeepPaths, notifyStatus); //throw FileError, X
}
catch (const FileError&) { if (!firstError) firstError = std::current_exception(); };
diff --git a/FreeFileSync/Source/log_file.h b/FreeFileSync/Source/log_file.h
index 8a7892bd..f2e056c9 100644
--- a/FreeFileSync/Source/log_file.h
+++ b/FreeFileSync/Source/log_file.h
@@ -29,7 +29,7 @@ void saveLogFile(const AbstractPath& logFilePath, //throw FileError, X
const zen::ErrorLog& log,
int logfilesMaxAgeDays,
LogFileFormat logFormat,
- const std::set<AbstractPath>& logFilePathsToKeep,
+ const std::set<AbstractPath>& logsToKeepPaths,
const std::function<void(std::wstring&& msg)>& notifyStatus /*throw X*/);
void sendLogAsEmail(const std::string& email, //throw FileError, X
diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp
index 4adcdf73..bbe67ade 100644
--- a/FreeFileSync/Source/ui/batch_status_handler.cpp
+++ b/FreeFileSync/Source/ui/batch_status_handler.cpp
@@ -85,9 +85,9 @@ BatchStatusHandler::Result BatchStatusHandler::prepareResult()
return TaskResult::cancelled;
}
const ErrorLogStats logCount = getStats(errorLog_.ref());
- if (logCount.error > 0)
+ if (logCount.errors > 0)
return TaskResult::error;
- else if (logCount.warning > 0)
+ else if (logCount.warnings > 0)
return TaskResult::warning;
if (getTotalStats() == ProgressStats())
diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp
index 0c64492e..a81e008e 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.cpp
+++ b/FreeFileSync/Source/ui/gui_status_handler.cpp
@@ -156,9 +156,9 @@ StatusHandlerTemporaryPanel::Result StatusHandlerTemporaryPanel::prepareResult()
}
const ErrorLogStats logCount = getStats(errorLog_);
- if (logCount.error > 0)
+ if (logCount.errors > 0)
return TaskResult::error;
- else if (logCount.warning > 0)
+ else if (logCount.warnings > 0)
return TaskResult::warning;
else
return TaskResult::success;
@@ -430,9 +430,9 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::prepareResult()
}
const ErrorLogStats logCount = getStats(errorLog_.ref());
- if (logCount.error > 0)
+ if (logCount.errors > 0)
return TaskResult::error;
- else if (logCount.warning > 0)
+ else if (logCount.warnings > 0)
return TaskResult::warning;
if (getTotalStats() == ProgressStats())
diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp
index 84a5263c..e69e19c7 100644
--- a/FreeFileSync/Source/ui/log_panel.cpp
+++ b/FreeFileSync/Source/ui/log_panel.cpp
@@ -354,17 +354,17 @@ void LogPanel::setLog(const std::shared_ptr<const ErrorLog>& log)
btn.SetToolTip(tooltip);
};
- initButton(*m_bpButtonErrors, "msg_error", _("Error" ) + L" (" + formatNumber(logCount.error) + L')');
- initButton(*m_bpButtonWarnings, "msg_warning", _("Warning") + L" (" + formatNumber(logCount.warning) + L')');
- initButton(*m_bpButtonInfo, "msg_info", _("Info" ) + L" (" + formatNumber(logCount.info) + L')');
+ initButton(*m_bpButtonErrors, "msg_error", _("Error" ) + L" (" + formatNumber(logCount.errors) + L')');
+ initButton(*m_bpButtonWarnings, "msg_warning", _("Warning") + L" (" + formatNumber(logCount.warnings) + L')');
+ initButton(*m_bpButtonInfo, "msg_info", _("Info" ) + L" (" + formatNumber(logCount.infos) + L')');
m_bpButtonErrors ->setActive(true);
m_bpButtonWarnings->setActive(true);
- m_bpButtonInfo ->setActive(logCount.warning + logCount.error == 0);
+ m_bpButtonInfo ->setActive(logCount.warnings + logCount.errors == 0);
- m_bpButtonErrors ->Show(logCount.error != 0);
- m_bpButtonWarnings->Show(logCount.warning != 0);
- m_bpButtonInfo ->Show(logCount.info != 0);
+ m_bpButtonErrors ->Show(logCount.errors != 0);
+ m_bpButtonWarnings->Show(logCount.warnings != 0);
+ m_bpButtonInfo ->Show(logCount.infos != 0);
m_gridMessages->setDataProvider(std::make_shared<GridDataMessages>(newLog));
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 3e176eb9..bcc4b20b 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -302,59 +302,38 @@ public:
//---------------------------------------------------------------------------------------------
-/* mitigate unwanted reentrancy caused by wxApp::Yield()
-
- CAVEAT: This doesn't block all theoretically possible Window events that were queued *before* disableGuiElementsImpl() takes effect,
- but at least the 90% case of (rare!) crashes caused by a duplicate click event on comparison or sync button. */
-class MainDialog::SingleOperationBlocker
+class MainDialog::UiInputDisabler
{
public:
- explicit SingleOperationBlocker(MainDialog& mainDlg) : mainDlg_(mainDlg) {}
-
- ~SingleOperationBlocker()
+ UiInputDisabler(MainDialog& mainDlg, bool enableAbort) : mainDlg_(mainDlg)
{
- if (opStarted_)
- {
- if (guiDisabled_)
- {
- wxTheApp->Yield(); //GUI update before enabling buttons again: prevent strange behaviour of delayed button clicks
- enableGuiElementsImpl();
- }
- assert(mainDlg_.operationInProgress_);
- mainDlg_.operationInProgress_ = false;
- }
- else assert(!guiDisabled_);
+ disableGuiElementsImpl(enableAbort);
}
- bool start() //disabling GUI elements is NOT enough! e.g. reentrancy when there's a second click event *already* in the Windows message queue
+ ~UiInputDisabler()
{
- if (mainDlg_.operationInProgress_)
- return false;
-
- return mainDlg_.operationInProgress_ = opStarted_ = true;
+ if (!dismissed_ )
+ {
+ wxTheApp->Yield(); //GUI update before enabling buttons again: prevent strange behaviour of delayed button clicks
+ enableGuiElementsImpl();
+ }
}
- void disableGui(bool enableAbort) //=> logically belongs into start()! But keep seperate: modal dialogs look better when GUI is not yet disabled
- {
- assert(opStarted_ && !guiDisabled_);
- guiDisabled_ = true;
- disableGuiElementsImpl(enableAbort);
- }
+ void dismiss() { dismissed_ = true; }
private:
- SingleOperationBlocker (const SingleOperationBlocker&) = delete;
- SingleOperationBlocker& operator=(const SingleOperationBlocker&) = delete;
+ UiInputDisabler (const UiInputDisabler&) = delete;
+ UiInputDisabler& operator=(const UiInputDisabler&) = delete;
void disableGuiElementsImpl(bool enableAbort); //dis-/enable all elements (except abort button) that might receive unwanted user input
void enableGuiElementsImpl(); //during long-running processes: comparison, deletion
MainDialog& mainDlg_;
- bool opStarted_ = false;
- bool guiDisabled_ = false;
+ bool dismissed_ = false;
};
-void MainDialog::SingleOperationBlocker::disableGuiElementsImpl(bool enableAbort)
+void MainDialog::UiInputDisabler::disableGuiElementsImpl(bool enableAbort)
{
//disables all elements (except abort button) that might receive user input during long-running processes:
//when changing consider: comparison, synchronization, manual deletion
@@ -405,7 +384,7 @@ void MainDialog::SingleOperationBlocker::disableGuiElementsImpl(bool enableAbort
}
-void MainDialog::SingleOperationBlocker::enableGuiElementsImpl()
+void MainDialog::UiInputDisabler::enableGuiElementsImpl()
{
//wxGTK, yet another QOI issue: some stupid bug keeps moving main dialog to top!!
mainDlg_.EnableCloseButton(true);
@@ -1149,7 +1128,7 @@ imgFileManagerSmall_([]
if (!extraLog.empty())
{
const ErrorLogStats logCount = getStats(extraLog);
- const TaskResult taskResult = logCount.error > 0 ? TaskResult::error : (logCount.warning > 0 ? TaskResult::warning : TaskResult::success);
+ const TaskResult taskResult = logCount.errors > 0 ? TaskResult::error : (logCount.warnings > 0 ? TaskResult::warning : TaskResult::success);
setLastOperationLog({.result = taskResult}, make_shared<const ErrorLog>(std::move(extraLog)));
showLogPanel(true);
}
@@ -1621,9 +1600,9 @@ std::vector<FileSystemObject*> MainDialog::getTreeSelection() const
void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& selectionL,
const std::vector<FileSystemObject*>& selectionR)
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
std::vector<const FileSystemObject*> copyLeft;
std::vector<const FileSystemObject*> copyRight;
@@ -1663,7 +1642,7 @@ void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& sel
const auto& guiCfg = getConfig();
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/,
false /*ignoreErrors*/,
@@ -1693,9 +1672,9 @@ void MainDialog::copyToAlternateFolder(const std::vector<FileSystemObject*>& sel
void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selectionL,
const std::vector<FileSystemObject*>& selectionR, bool moveToRecycler)
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
std::vector<FileSystemObject*> deleteLeft = selectionL;
std::vector<FileSystemObject*> deleteRight = selectionR;
@@ -1725,7 +1704,7 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec
//wxBusyCursor dummy; -> redundant: progress already shown in status bar!
const auto& guiCfg = getConfig();
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/,
false /*ignoreErrors*/,
@@ -1758,9 +1737,9 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec
void MainDialog::renameSelectedFiles(const std::vector<FileSystemObject*>& selectionL,
const std::vector<FileSystemObject*>& selectionR)
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
std::vector<FileSystemObject*> renameLeft = selectionL;
std::vector<FileSystemObject*> renameRight = selectionR;
@@ -1788,7 +1767,7 @@ void MainDialog::renameSelectedFiles(const std::vector<FileSystemObject*>& selec
//wxBusyCursor dummy; -> redundant: progress already shown in status bar!
const auto& guiCfg = getConfig();
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/,
false /*ignoreErrors*/,
@@ -1931,9 +1910,9 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool
const std::vector<FileSystemObject*>& selectionL,
const std::vector<FileSystemObject*>& selectionR)
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
try
{
@@ -2022,7 +2001,7 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool
FocusPreserver fp;
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/,
false /*ignoreErrors*/,
@@ -3281,6 +3260,8 @@ void MainDialog::onFolderSelected(wxCommandEvent& event)
void MainDialog::cfgHistoryRemoveObsolete(const std::vector<Zstring>& filePaths)
{
+ warn_static("shouldn't delete on access denied!?") //https://freefilesync.org/forum/viewtopic.php?t=11363
+
auto getUnavailableCfgFilesAsync = [filePaths] //don't use wxString: NOT thread-safe! (e.g. non-atomic ref-count)
{
std::vector<std::future<bool>> availableFiles; //check existence of all config files in parallel!
@@ -3762,9 +3743,9 @@ bool MainDialog::loadConfiguration(const std::vector<Zstring>& filePaths, bool i
void MainDialog::removeSelectedCfgHistoryItems(bool deleteFromDisk)
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
const std::vector<size_t> selectedRows = m_gridCfgHistory->getSelectedRows();
if (!selectedRows.empty())
@@ -3790,7 +3771,7 @@ void MainDialog::removeSelectedCfgHistoryItems(bool deleteFromDisk)
moveToRecycler) != ConfirmationButton::accept)
return;
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/,
false /*ignoreErrors*/,
@@ -4578,9 +4559,14 @@ void MainDialog::updateGlobalFilterButton()
void MainDialog::onCompare(wxCommandEvent& event)
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ /* mitigate unwanted reentrancy caused by wxApp::Yield():
+ disabling GUI elements is NOT enough! e.g. reentrancy when there's a second click event *already* in the Windows message queue
+
+ CAVEAT: This doesn't block all theoretically possible Window events that were queued *before* disableGuiElementsImpl() takes effect,
+ but at least the 90% case of (rare!) crashes caused by a duplicate click event on comparison or sync button. */
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
//wxBusyCursor dummy; -> redundant: progress already shown in progress dialog!
@@ -4604,7 +4590,7 @@ void MainDialog::onCompare(wxCommandEvent& event)
const std::vector<FolderPairCfg>& fpCfgList = extractCompareCfg(guiCfg.mainCfg);
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
//handle status display and error messages
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now(),
@@ -4839,9 +4825,10 @@ void MainDialog::onStartSync(wxCommandEvent& event)
return;
}
- SingleOperationBlocker opBlock(*this); //*after* simluated comparison button click!
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true)) //*after* simluated comparison button click!
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
+ //------------------------------------------------------------------
const auto& guiCfg = getConfig();
@@ -4858,10 +4845,6 @@ void MainDialog::onStartSync(wxCommandEvent& event)
globalCfg_.confirmDlgs.confirmSyncStart = !dontShowAgain;
}
- std::set<AbstractPath> logFilePathsToKeep;
- for (const ConfigFileItem& item : cfggrid::getDataView(*m_gridCfgHistory).get())
- logFilePathsToKeep.insert(item.lastRunStats.logFilePath);
-
const std::chrono::system_clock::time_point syncStartTime = std::chrono::system_clock::now();
const WindowLayout::Dimensions progressDim
@@ -4871,7 +4854,7 @@ void MainDialog::onStartSync(wxCommandEvent& event)
globalCfg_.dpiLayouts[getDpiScalePercent()].progressDlg.isMaximized
};
- opBlock.disableGui(false /*enableAbort*/); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, false /*enableAbort*/); //StatusHandlerFloatingDialog will internally process Window messages, so avoid unexpected callbacks!
//class handling status updates and error messages
StatusHandlerFloatingDialog statusHandler(this, getJobNames(), syncStartTime,
@@ -5013,23 +4996,33 @@ void MainDialog::onStartSync(wxCommandEvent& event)
}
//--------------------- save log file ----------------------
+ std::set<AbstractPath> logsToKeepPaths;
+ {
+ const std::set<Zstring /*cfg file path*/, LessNativePath> activeCfgSorted(activeConfigFiles_.begin(), activeConfigFiles_.end());
+
+ for (const ConfigFileItem& cfi : cfggrid::getDataView(*m_gridCfgHistory).get())
+ if (!activeCfgSorted.contains(cfi.cfgFilePath)) //exception: don't keep old logs for the selected cfg files!
+ logsToKeepPaths.insert(cfi.lastRunStats.logFilePath);
+ }
try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename
{
//do NOT use tryReportingError()! saving log files should not be cancellable!
- saveLogFile(logFilePath, fullSummary, fullLog, globalCfg_.logfilesMaxAgeDays, globalCfg_.logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
+ saveLogFile(logFilePath, fullSummary, fullLog, globalCfg_.logfilesMaxAgeDays, globalCfg_.logFormat, logsToKeepPaths, notifyStatusNoThrow); //throw FileError
}
catch (const FileError& e)
{
- logMsg2(e.toString(), MSG_TYPE_ERROR);
+ try //fallback: log file *must* be saved no matter what!
+ {
+ const AbstractPath logFileDefaultPath = AFS::appendRelPath(createAbstractPath(getLogFolderDefaultPath()), generateLogFileName(globalCfg_.logFormat, fullSummary));
+ if (logFilePath == logFileDefaultPath)
+ throw;
- const AbstractPath logFileDefaultPath = AFS::appendRelPath(createAbstractPath(getLogFolderDefaultPath()), generateLogFileName(globalCfg_.logFormat, fullSummary));
- if (logFilePath != logFileDefaultPath) //fallback: log file *must* be saved no matter what!
- try
- {
- logFilePath = logFileDefaultPath;
- saveLogFile(logFileDefaultPath, fullSummary, fullLog, globalCfg_.logfilesMaxAgeDays, globalCfg_.logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
- }
- catch (const FileError& e2) { logMsg2(e2.toString(), MSG_TYPE_ERROR); assert(false); } //should never happen!!!
+ logMsg2(e.toString(), MSG_TYPE_ERROR);
+
+ logFilePath = logFileDefaultPath;
+ saveLogFile(logFileDefaultPath, fullSummary, fullLog, globalCfg_.logfilesMaxAgeDays, globalCfg_.logFormat, logsToKeepPaths, notifyStatusNoThrow); //throw FileError
+ }
+ catch (const FileError& e2) { logMsg2(e2.toString(), MSG_TYPE_ERROR); logExtraError(e2.toString()); } //should never happen!!!
}
//--------- update last sync stats for the selected cfg files ---------
@@ -5043,8 +5036,8 @@ void MainDialog::onStartSync(wxCommandEvent& event)
fullSummary.statsProcessed.items,
fullSummary.statsProcessed.bytes,
fullSummary.totalTime,
- fullLogStats.error,
- fullLogStats.warning,
+ fullLogStats.errors,
+ fullLogStats.warnings,
});
//re-apply selection: sort order changed if sorted by last sync time
cfggrid::addAndSelect(*m_gridCfgHistory, activeConfigFiles_, false /*scrollToSelection*/);
@@ -5075,6 +5068,7 @@ void MainDialog::onStartSync(wxCommandEvent& event)
case FinalRequest::exit:
Destroy(); //don't use Close() which prompts to save current config in onClose()
+ uiBlock.dismiss(); //...or else: crash when ~UiInputDisabler() calls Yield() + enableGuiElementsImpl()!
break;
case FinalRequest::shutdown:
@@ -5113,9 +5107,9 @@ void appendInactive(ContainerObject& conObj, std::vector<FileSystemObject*>& ina
void MainDialog::startSyncForSelecction(const std::vector<FileSystemObject*>& selection)
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
//------------------ analyze selection ------------------
std::unordered_set<const BaseFolderPair*> basePairsSelect;
@@ -5211,7 +5205,7 @@ void MainDialog::startSyncForSelecction(const std::vector<FileSystemObject*>& se
//last sync log file? => let's go without; same behavior as manual deletion
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
StatusHandlerTemporaryPanel statusHandler(*this, syncStartTime,
guiCfg.mainCfg.ignoreErrors,
@@ -5278,9 +5272,9 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s
if (errorLog)
{
const ErrorLogStats logCount = getStats(*errorLog);
- if (logCount.error > 0)
+ if (logCount.errors > 0)
return loadImage("msg_error", dipToScreen(getMenuIconDipSize()));
- if (logCount.warning > 0)
+ if (logCount.warnings > 0)
return loadImage("msg_warning", dipToScreen(getMenuIconDipSize()));
//return loadImage("msg_success", dipToScreen(getMenuIconDipSize())); -> too noisy?
@@ -5426,9 +5420,11 @@ void MainDialog::onGridLabelLeftClickC(GridLabelClickEvent& event)
void MainDialog::swapSides()
{
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start())
+ if (std::exchange(operationInProgress_, true))
return;
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
+
+ FocusPreserver fp;
if (!folderCmp_.empty() && //require confirmation only *after* comparison
globalCfg_.confirmDlgs.confirmSwapSides)
@@ -5488,9 +5484,7 @@ void MainDialog::swapSides()
{
const auto& guiCfg = getConfig();
- FocusPreserver fp;
-
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/,
false /*ignoreErrors*/,
@@ -5724,16 +5718,17 @@ void MainDialog::applySyncDirections()
{
if (!folderCmp_.empty())
{
- const auto& guiCfg = getConfig();
- const auto& directCfgs = extractDirectionCfg(folderCmp_, getConfig().mainCfg);
-
- SingleOperationBlocker opBlock(*this);
- if (!opBlock.start()) //can't just skip, but now's a really bad time! Hopefully never happens!?
+ if (std::exchange(operationInProgress_, true))
+ //can't just skip:t now's a really bad time! Hopefully never happens!?
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Sync direction changed while other operation running.");
+ ZEN_ON_SCOPE_EXIT(operationInProgress_ = false);
FocusPreserver fp;
- opBlock.disableGui(true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+ UiInputDisabler uiBlock(*this, true /*enableAbort*/); //StatusHandlerTemporaryPanel calls wxApp::Yield(), so avoid unexpected callbacks!
+
+ const auto& guiCfg = getConfig();
+ const auto& directCfgs = extractDirectionCfg(folderCmp_, getConfig().mainCfg);
StatusHandlerTemporaryPanel statusHandler(*this, std::chrono::system_clock::now() /*startTime*/,
false /*ignoreErrors*/,
diff --git a/FreeFileSync/Source/ui/main_dlg.h b/FreeFileSync/Source/ui/main_dlg.h
index 7b5a42f2..7d88df76 100644
--- a/FreeFileSync/Source/ui/main_dlg.h
+++ b/FreeFileSync/Source/ui/main_dlg.h
@@ -66,7 +66,7 @@ private:
friend class FolderPairCallback;
friend class PanelMoveWindow;
- class SingleOperationBlocker; //mitigate unwanted reentrancy caused by wxApp::Yield()
+ class UiInputDisabler;
//configuration load/save
void setLastUsedConfig(const XmlGuiConfig& guiConfig, const std::vector<Zstring>& cfgFilePaths);
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index 525e4e24..f3a18eb2 100644
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -1511,7 +1511,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(TaskResult syncResult,
//show log instead of graph if errors occurred! (not required for ignored warnings)
const ErrorLogStats logCount = getStats(log.ref());
- if (logCount.error > 0)
+ if (logCount.errors > 0)
pnl_.m_notebookResult->ChangeSelection(pagePosLog);
//fill image list to cope with wxNotebook image setting design desaster...
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index d88c5e2d..a4f63015 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -1129,7 +1129,7 @@ void DeleteDialog::onLocalKeyEvent(wxKeyEvent& event)
void DeleteDialog::onOkay(wxCommandEvent& event)
{
- //additional safety net, similar to Windows Explorer: time delta between DEL and ENTER must be at least 50ms to avoid accidental deletion!
+ //additional safety net, similar to File Explorer: time delta between DEL and ENTER must be at least 50ms to avoid accidental deletion!
if (std::chrono::steady_clock::now() < dlgStartTime_ + std::chrono::milliseconds(50)) //considers chrono-wrap-around!
return;
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 1b4aa87e..885da83f 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "13.6"; //internal linkage!
+const char ffsVersion[] = "13.7"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/libcurl/curl_wrap.cpp b/libcurl/curl_wrap.cpp
index 86b2efa2..ca0c61d6 100644
--- a/libcurl/curl_wrap.cpp
+++ b/libcurl/curl_wrap.cpp
@@ -402,9 +402,10 @@ std::wstring zen::formatCurlStatusCode(CURLcode sc)
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_SSL_CLIENTCERT);
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_UNRECOVERABLE_POLL);
ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_TOO_LARGE);
+ ZEN_CHECK_CASE_FOR_CONSTANT(CURLE_ECH_REQUIRED);
ZEN_CHECK_CASE_FOR_CONSTANT(CURL_LAST);
}
- static_assert(CURL_LAST == CURLE_TOO_LARGE + 1);
+ static_assert(CURL_LAST == CURLE_ECH_REQUIRED + 1);
return replaceCpy<std::wstring>(L"Curl status %x", L"%x", numberTo<std::wstring>(static_cast<int>(sc)));
}
diff --git a/libssh2/libssh2_wrap.h b/libssh2/libssh2_wrap.h
index fcb49855..2c99ac13 100644
--- a/libssh2/libssh2_wrap.h
+++ b/libssh2/libssh2_wrap.h
@@ -107,6 +107,20 @@ inline int libssh2_sftp_readlink(LIBSSH2_SFTP* sftp, const std::string& path, ch
return libssh2_sftp_symlink_ex(sftp, path.c_str(), static_cast<unsigned int>(path.size()), buf, static_cast<unsigned int>(bufSize), LIBSSH2_SFTP_READLINK);
}
+#undef libssh2_sftp_symlink
+inline int libssh2_sftp_symlink(LIBSSH2_SFTP* sftp, const std::string& path, const std::string_view buf)
+{
+ return libssh2_sftp_symlink_ex(sftp,
+ /* CAVEAT: https://www.sftp.net/spec/openssh-sftp-extensions.txt
+ "When OpenSSH's sftp-server was implemented, the order of the arguments
+ to the SSH_FXP_SYMLINK method was inadvertently reversed."
+
+ => of course libssh2 didn't get the memo: fix this shit: */
+ /**/ buf .data (), static_cast<unsigned int>(buf .size()),
+ const_cast<char*>(path.c_str()), static_cast<unsigned int>(path.size()),
+ LIBSSH2_SFTP_SYMLINK);
+}
+
#undef libssh2_sftp_rename
inline int libssh2_sftp_rename(LIBSSH2_SFTP* sftp, const std::string& pathFrom, const std::string& pathTo, long flags)
{
diff --git a/wx+/grid.cpp b/wx+/grid.cpp
index 1c8897a1..3665c6cf 100644
--- a/wx+/grid.cpp
+++ b/wx+/grid.cpp
@@ -163,7 +163,7 @@ void GridData::drawCellText(wxDC& dc, const wxRect& rect, const std::wstring_vie
if (extentTrunc.GetWidth() > rect.width)
{
- //unlike Windows Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideograph encodes to TWO wchar_t: utfTo<std::wstring>("\xf0\xa4\xbd\x9c");
+ //unlike File Explorer, we truncate UTF-16 correctly: e.g. CJK-Ideograph encodes to TWO wchar_t: utfTo<std::wstring>("\xf0\xa4\xbd\x9c");
size_t low = 0; //number of Unicode chars!
size_t high = unicodeLength(text); //
if (high > 1)
diff --git a/wx+/window_tools.h b/wx+/window_tools.h
index 94cb2c32..45f43556 100644
--- a/wx+/window_tools.h
+++ b/wx+/window_tools.h
@@ -79,7 +79,10 @@ struct FocusPreserver
if (oldFocusId_ != wxID_ANY)
if (wxWindow* oldFocusWin = wxWindow::FindWindowById(oldFocusId_))
+ {
+ assert(oldFocusWin->IsEnabled()); //only enabled windows can have focus, so why wouldn't it be anymore?
setFocusIfActive(*oldFocusWin);
+ }
}
wxWindowID getFocusId() const { return oldFocusId_; }
diff --git a/zen/error_log.h b/zen/error_log.h
index ff630cb8..2e5c4eb0 100644
--- a/zen/error_log.h
+++ b/zen/error_log.h
@@ -38,9 +38,9 @@ void logMsg(ErrorLog& log, const std::wstring& msg, MessageType type, time_t tim
struct ErrorLogStats
{
- int info = 0;
- int warning = 0;
- int error = 0;
+ int infos = 0;
+ int warnings = 0;
+ int errors = 0;
};
ErrorLogStats getStats(const ErrorLog& log);
@@ -66,16 +66,16 @@ ErrorLogStats getStats(const ErrorLog& log)
switch (entry.type)
{
case MSG_TYPE_INFO:
- ++count.info;
+ ++count.infos;
break;
case MSG_TYPE_WARNING:
- ++count.warning;
+ ++count.warnings;
break;
case MSG_TYPE_ERROR:
- ++count.error;
+ ++count.errors;
break;
}
- assert(std::ssize(log) == count.info + count.warning + count.error);
+ assert(std::ssize(log) == count.infos + count.warnings + count.errors);
return count;
}
diff --git a/zen/file_access.h b/zen/file_access.h
index 42cf1a46..a3fdcd70 100644
--- a/zen/file_access.h
+++ b/zen/file_access.h
@@ -24,7 +24,7 @@ const int FAT_FILE_TIME_PRECISION_SEC = 2; //https://devblogs.microsoft.com/oldn
using FileIndex = ino_t;
using FileTimeNative = timespec;
-inline time_t nativeFileTimeToTimeT(const timespec& ft) { return ft.tv_sec; } //follow Windows Explorer: always round down!
+inline time_t nativeFileTimeToTimeT(const timespec& ft) { return ft.tv_sec; } //follow File Explorer: always round down!
inline timespec timetToNativeFileTime(time_t utcTime) { return {.tv_sec = utcTime}; }
enum class ItemType
diff --git a/zen/format_unit.h b/zen/format_unit.h
index d7321a97..6a8f6b7b 100644
--- a/zen/format_unit.h
+++ b/zen/format_unit.h
@@ -17,7 +17,7 @@ namespace zen
std::wstring formatFilesizeShort(int64_t filesize);
std::wstring formatRemainingTime(double timeInSec);
std::wstring formatProgressPercent(double fraction /*[0, 1]*/, int decPlaces = 0 /*[0, 9]*/); //rounded down!
-std::wstring formatUtcToLocalTime(time_t utcTime); //like Windows Explorer would...
+std::wstring formatUtcToLocalTime(time_t utcTime); //like File Explorer would...
std::wstring formatTwoDigitPrecision (double value); //format with fixed number of digits
std::wstring formatThreeDigitPrecision(double value); //(unless value is too large)
diff --git a/zen/socket.h b/zen/socket.h
index 0a311d01..461226d0 100644
--- a/zen/socket.h
+++ b/zen/socket.h
@@ -141,16 +141,6 @@ public:
}
setNonBlocking(testSocket, false); //throw SysError
- //-----------------------------------------------------------
-
- int noDelay = 1; //disable Nagle algorithm: https://brooker.co.za/blog/2024/05/09/nagle.html
- //e.g. test case "website sync": 23% shorter comparison time!
- if (::setsockopt(testSocket, //_In_ SOCKET s
- IPPROTO_TCP, //_In_ int level
- TCP_NODELAY, //_In_ int optname
- reinterpret_cast<char*>(&noDelay), //_In_ const char* optval
- sizeof(noDelay)) != 0) //_In_ int optlen
- THROW_LAST_SYS_ERROR_WSA("setsockopt(TCP_NODELAY)");
return testSocket;
};
@@ -163,11 +153,26 @@ public:
try
{
socket_ = getConnectedSocket(*si); //throw SysError; pass ownership
- return;
+ firstError = std::nullopt;
+ break;
}
catch (const SysError& e) { if (!firstError) firstError = e; }
- throw* firstError; //list was not empty, so there must have been an error!
+ if (firstError)
+ throw* firstError;
+ assert(socket_ != invalidSocket); //list was non-empty, so there's either an error, or a valid socket
+ ZEN_ON_SCOPE_FAIL(closeSocket(socket_));
+ //-----------------------------------------------------------
+ //configure *after* selecting appropriate socket: cfg-failure should not discard otherwise fine connection!
+
+ int noDelay = 1; //disable Nagle algorithm: https://brooker.co.za/blog/2024/05/09/nagle.html
+ //e.g. test case "website sync": 23% shorter comparison time!
+ if (::setsockopt(socket_, //_In_ SOCKET s
+ IPPROTO_TCP, //_In_ int level
+ TCP_NODELAY, //_In_ int optname
+ reinterpret_cast<const char*>(&noDelay), //_In_ const char* optval
+ sizeof(noDelay)) != 0) //_In_ int optlen
+ THROW_LAST_SYS_ERROR_WSA("setsockopt(TCP_NODELAY)");
}
~Socket() { closeSocket(socket_); }
diff --git a/zen/type_traits.h b/zen/type_traits.h
index 6186a2ae..e373ba07 100644
--- a/zen/type_traits.h
+++ b/zen/type_traits.h
@@ -30,6 +30,7 @@ public:
using Type = decltype(dummyFun(F()));
};
template <class F> using FunctionReturnTypeT = typename FunctionReturnType<F>::Type;
+//yes, there's std::invoke_result_t, but it requires to specify function argument types for no good reason
//=============================================================================
bgstack15