diff options
author | B. Stack <bgstack15@gmail.com> | 2024-06-23 16:51:52 -0400 |
---|---|---|
committer | B. Stack <bgstack15@gmail.com> | 2024-06-23 16:51:52 -0400 |
commit | 994ec9e1cba547bed6eef02f996ca2a7beaf777a (patch) | |
tree | c6bdfb8c2096a1c579d3603e050d5f697d2306e0 | |
parent | add upstream 13.6 (diff) | |
download | FreeFileSync-994ec9e1cba547bed6eef02f996ca2a7beaf777a.tar.gz FreeFileSync-994ec9e1cba547bed6eef02f996ca2a7beaf777a.tar.bz2 FreeFileSync-994ec9e1cba547bed6eef02f996ca2a7beaf777a.zip |
add upstream 13.713.7
-rw-r--r-- | Changelog.txt | 8 | ||||
-rw-r--r-- | FreeFileSync/Source/afs/abstract.h | 2 | ||||
-rw-r--r-- | FreeFileSync/Source/afs/sftp.cpp | 56 | ||||
-rw-r--r-- | FreeFileSync/Source/application.cpp | 41 | ||||
-rw-r--r-- | FreeFileSync/Source/base/db_file.cpp | 2 | ||||
-rw-r--r-- | FreeFileSync/Source/log_file.cpp | 27 | ||||
-rw-r--r-- | FreeFileSync/Source/log_file.h | 2 | ||||
-rw-r--r-- | FreeFileSync/Source/ui/batch_status_handler.cpp | 4 | ||||
-rw-r--r-- | FreeFileSync/Source/ui/gui_status_handler.cpp | 8 | ||||
-rw-r--r-- | FreeFileSync/Source/ui/log_panel.cpp | 14 | ||||
-rw-r--r-- | FreeFileSync/Source/ui/main_dlg.cpp | 175 | ||||
-rw-r--r-- | FreeFileSync/Source/ui/main_dlg.h | 2 | ||||
-rw-r--r-- | FreeFileSync/Source/ui/progress_indicator.cpp | 2 | ||||
-rw-r--r-- | FreeFileSync/Source/ui/small_dlgs.cpp | 2 | ||||
-rw-r--r-- | FreeFileSync/Source/version/version.h | 2 | ||||
-rw-r--r-- | libcurl/curl_wrap.cpp | 3 | ||||
-rw-r--r-- | libssh2/libssh2_wrap.h | 14 | ||||
-rw-r--r-- | wx+/grid.cpp | 2 | ||||
-rw-r--r-- | wx+/window_tools.h | 3 | ||||
-rw-r--r-- | zen/error_log.h | 14 | ||||
-rw-r--r-- | zen/file_access.h | 2 | ||||
-rw-r--r-- | zen/format_unit.h | 2 | ||||
-rw-r--r-- | zen/socket.h | 29 | ||||
-rw-r--r-- | zen/type_traits.h | 1 |
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 //============================================================================= |