Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package FreeFileSync for openSUSE:Factory 
checked in at 2024-07-04 16:24:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/FreeFileSync (Old)
 and      /work/SRC/openSUSE:Factory/.FreeFileSync.new.2080 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "FreeFileSync"

Thu Jul  4 16:24:14 2024 rev:25 rq:1185084 version:13.7

Changes:
--------
--- /work/SRC/openSUSE:Factory/FreeFileSync/FreeFileSync.changes        
2024-05-15 21:29:10.755200524 +0200
+++ /work/SRC/openSUSE:Factory/.FreeFileSync.new.2080/FreeFileSync.changes      
2024-07-04 16:25:29.768379035 +0200
@@ -1,0 +2,9 @@
+Wed Jul  3 08:22:31 UTC 2024 - Christophe Marin <christo...@krop.fr>
+
+- Update to 13.7
+  * 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)
+
+-------------------------------------------------------------------

Old:
----
  FreeFileSync_13.6_Source.zip

New:
----
  FreeFileSync_13.7_Source.zip

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ FreeFileSync.spec ++++++
--- /var/tmp/diff_new_pack.QqmAjM/_old  2024-07-04 16:25:31.080426951 +0200
+++ /var/tmp/diff_new_pack.QqmAjM/_new  2024-07-04 16:25:31.080426951 +0200
@@ -21,7 +21,7 @@
 %endif
 
 Name:           FreeFileSync
-Version:        13.6
+Version:        13.7
 Release:        0
 Summary:        Backup software to synchronize files and folders
 License:        GPL-3.0-or-later



++++++ FreeFileSync_13.6_Source.zip -> FreeFileSync_13.7_Source.zip ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Changelog.txt new/Changelog.txt
--- old/Changelog.txt   2024-05-10 18:03:31.000000000 +0200
+++ new/Changelog.txt   2024-06-23 09:46:57.000000000 +0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/afs/sftp.cpp 
new/FreeFileSync/Source/afs/sftp.cpp
--- old/FreeFileSync/Source/afs/sftp.cpp        2024-05-10 18:03:33.000000000 
+0200
+++ new/FreeFileSync/Source/afs/sftp.cpp        2024-06-23 09:46:59.000000000 
+0200
@@ -1474,10 +1474,10 @@
     {
         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 @@
         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 @@
             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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/application.cpp 
new/FreeFileSync/Source/application.cpp
--- old/FreeFileSync/Source/application.cpp     2024-05-10 18:03:33.000000000 
+0200
+++ new/FreeFileSync/Source/application.cpp     2024-06-23 09:46:58.000000000 
+0200
@@ -530,10 +530,6 @@
         -> 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 @@
     }
 
     //--------------------- 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 @@
                 r.summary.statsProcessed.items,
                 r.summary.statsProcessed.bytes,
                 r.summary.totalTime,
-                logStats.error,
-                logStats.warning,
+                logStats.errors,
+                logStats.warnings,
             };
             break;
         }
@@ -726,9 +729,9 @@
     }
 
     //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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/base/db_file.cpp 
new/FreeFileSync/Source/base/db_file.cpp
--- old/FreeFileSync/Source/base/db_file.cpp    2024-05-10 18:03:33.000000000 
+0200
+++ new/FreeFileSync/Source/base/db_file.cpp    2024-06-23 09:46:59.000000000 
+0200
@@ -55,7 +55,7 @@
         - 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/log_file.cpp 
new/FreeFileSync/Source/log_file.cpp
--- old/FreeFileSync/Source/log_file.cpp        2024-05-10 18:03:33.000000000 
+0200
+++ new/FreeFileSync/Source/log_file.cpp        2024-06-23 09:46:59.000000000 
+0200
@@ -50,8 +50,8 @@
 
     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 @@
     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 @@
 
     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 @@
 )";
 
     //------------ 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 @@
 
 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 @@
 
         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 @@
                       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 @@
     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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/log_file.h 
new/FreeFileSync/Source/log_file.h
--- old/FreeFileSync/Source/log_file.h  2024-05-10 18:03:31.000000000 +0200
+++ new/FreeFileSync/Source/log_file.h  2024-06-23 09:46:57.000000000 +0200
@@ -29,7 +29,7 @@
                  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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/ui/batch_status_handler.cpp 
new/FreeFileSync/Source/ui/batch_status_handler.cpp
--- old/FreeFileSync/Source/ui/batch_status_handler.cpp 2024-05-10 
18:03:33.000000000 +0200
+++ new/FreeFileSync/Source/ui/batch_status_handler.cpp 2024-06-23 
09:46:59.000000000 +0200
@@ -85,9 +85,9 @@
             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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/ui/gui_status_handler.cpp 
new/FreeFileSync/Source/ui/gui_status_handler.cpp
--- old/FreeFileSync/Source/ui/gui_status_handler.cpp   2024-05-10 
18:03:33.000000000 +0200
+++ new/FreeFileSync/Source/ui/gui_status_handler.cpp   2024-06-23 
09:46:59.000000000 +0200
@@ -156,9 +156,9 @@
         }
 
         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 @@
         }
 
         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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/ui/log_panel.cpp 
new/FreeFileSync/Source/ui/log_panel.cpp
--- old/FreeFileSync/Source/ui/log_panel.cpp    2024-05-10 18:03:33.000000000 
+0200
+++ new/FreeFileSync/Source/ui/log_panel.cpp    2024-06-23 09:46:59.000000000 
+0200
@@ -354,17 +354,17 @@
         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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/ui/main_dlg.cpp 
new/FreeFileSync/Source/ui/main_dlg.cpp
--- old/FreeFileSync/Source/ui/main_dlg.cpp     2024-05-10 18:03:33.000000000 
+0200
+++ new/FreeFileSync/Source/ui/main_dlg.cpp     2024-06-23 09:46:59.000000000 
+0200
@@ -302,64 +302,38 @@
 
 
 
//---------------------------------------------------------------------------------------------
-/*  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) {}
+    UiInputDisabler(MainDialog& mainDlg, bool enableAbort) : mainDlg_(mainDlg)
+    {
+        disableGuiElementsImpl(enableAbort);
+    }
 
-    ~SingleOperationBlocker()
+    ~UiInputDisabler()
     {
-        if (opStarted_)
+        if (!dismissed_ )
         {
-            if (guiDisabled_)
-            {
-                wxTheApp->Yield(); //GUI update before enabling buttons again: 
prevent strange behaviour of delayed button clicks
-                enableGuiElementsImpl();
-            }
-            assert(mainDlg_.operationInProgress_);
-            mainDlg_.operationInProgress_ = false;
+            wxTheApp->Yield(); //GUI update before enabling buttons again: 
prevent strange behaviour of delayed button clicks
+            enableGuiElementsImpl();
         }
-        else assert(!guiDisabled_);
     }
 
-    bool start() //disabling GUI elements is NOT enough! e.g. reentrancy when 
there's a second click event *already* in the Windows message queue
-    {
-        if (mainDlg_.operationInProgress_)
-            return false;
-
-        return mainDlg_.operationInProgress_ = opStarted_ = true;
-    }
-
-    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()
-    {
-          opStarted_ = guiDisabled_ = false;
-    }
+    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
@@ -410,7 +384,7 @@
 }
 
 
-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);
@@ -1154,7 +1128,7 @@
             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);
             }
@@ -1626,9 +1600,9 @@
 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;
@@ -1668,7 +1642,7 @@
 
     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*/,
@@ -1698,9 +1672,9 @@
 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;
@@ -1730,7 +1704,7 @@
     //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*/,
@@ -1763,9 +1737,9 @@
 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;
@@ -1793,7 +1767,7 @@
     //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*/,
@@ -1936,9 +1910,9 @@
                                          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
     {
@@ -2027,7 +2001,7 @@
 
                 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*/,
@@ -3286,6 +3260,8 @@
 
 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!
@@ -3767,9 +3743,9 @@
 
 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())
@@ -3795,7 +3771,7 @@
                                  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*/,
@@ -4583,9 +4559,14 @@
 
 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!
 
@@ -4609,7 +4590,7 @@
 
     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(),
@@ -4844,9 +4825,10 @@
             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();
 
@@ -4863,10 +4845,6 @@
         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
@@ -4876,7 +4854,7 @@
         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,
@@ -5018,23 +4996,33 @@
     }
 
     //--------------------- 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 ---------
@@ -5048,8 +5036,8 @@
         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*/);
@@ -5080,7 +5068,7 @@
 
         case FinalRequest::exit:
             Destroy(); //don't use Close() which prompts to save current 
config in onClose()
-            opBlock.dismiss(); //...or else we'll crash when 
~SingleOperationBlocker() calls Yield()!
+            uiBlock.dismiss(); //...or else: crash when ~UiInputDisabler() 
calls Yield() + enableGuiElementsImpl()!
             break;
 
         case FinalRequest::shutdown:
@@ -5119,9 +5107,9 @@
 
 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;
@@ -5217,7 +5205,7 @@
 
         //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,
@@ -5284,9 +5272,9 @@
         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?
@@ -5432,9 +5420,11 @@
 
 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)
@@ -5494,9 +5484,7 @@
     {
         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*/,
@@ -5730,16 +5718,17 @@
 {
     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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/ui/main_dlg.h 
new/FreeFileSync/Source/ui/main_dlg.h
--- old/FreeFileSync/Source/ui/main_dlg.h       2024-05-10 18:03:32.000000000 
+0200
+++ new/FreeFileSync/Source/ui/main_dlg.h       2024-06-23 09:46:58.000000000 
+0200
@@ -66,7 +66,7 @@
     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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/ui/progress_indicator.cpp 
new/FreeFileSync/Source/ui/progress_indicator.cpp
--- old/FreeFileSync/Source/ui/progress_indicator.cpp   2024-05-10 
18:03:33.000000000 +0200
+++ new/FreeFileSync/Source/ui/progress_indicator.cpp   2024-06-23 
09:46:59.000000000 +0200
@@ -1511,7 +1511,7 @@
 
     //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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/ui/small_dlgs.cpp 
new/FreeFileSync/Source/ui/small_dlgs.cpp
--- old/FreeFileSync/Source/ui/small_dlgs.cpp   2024-05-10 18:03:33.000000000 
+0200
+++ new/FreeFileSync/Source/ui/small_dlgs.cpp   2024-06-23 09:46:59.000000000 
+0200
@@ -1129,7 +1129,7 @@
 
 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/FreeFileSync/Source/version/version.h 
new/FreeFileSync/Source/version/version.h
--- old/FreeFileSync/Source/version/version.h   2024-05-10 18:03:32.000000000 
+0200
+++ new/FreeFileSync/Source/version/version.h   2024-06-23 09:46:58.000000000 
+0200
@@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libcurl/curl_wrap.cpp new/libcurl/curl_wrap.cpp
--- old/libcurl/curl_wrap.cpp   2024-05-10 18:03:33.000000000 +0200
+++ new/libcurl/curl_wrap.cpp   2024-06-23 09:46:59.000000000 +0200
@@ -402,9 +402,10 @@
             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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libssh2/libssh2_wrap.h new/libssh2/libssh2_wrap.h
--- old/libssh2/libssh2_wrap.h  2024-05-10 18:03:32.000000000 +0200
+++ new/libssh2/libssh2_wrap.h  2024-06-23 09:46:58.000000000 +0200
@@ -107,6 +107,20 @@
     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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wx+/grid.cpp new/wx+/grid.cpp
--- old/wx+/grid.cpp    2024-05-10 18:03:33.000000000 +0200
+++ new/wx+/grid.cpp    2024-06-23 09:46:59.000000000 +0200
@@ -163,7 +163,7 @@
 
     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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/wx+/window_tools.h new/wx+/window_tools.h
--- old/wx+/window_tools.h      2024-05-10 18:03:32.000000000 +0200
+++ new/wx+/window_tools.h      2024-06-23 09:46:58.000000000 +0200
@@ -79,7 +79,10 @@
 
         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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zen/error_log.h new/zen/error_log.h
--- old/zen/error_log.h 2024-05-10 18:03:32.000000000 +0200
+++ new/zen/error_log.h 2024-06-23 09:46:58.000000000 +0200
@@ -38,9 +38,9 @@
 
 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 @@
         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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zen/file_access.h new/zen/file_access.h
--- old/zen/file_access.h       2024-05-10 18:03:32.000000000 +0200
+++ new/zen/file_access.h       2024-06-23 09:46:58.000000000 +0200
@@ -24,7 +24,7 @@
 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zen/format_unit.h new/zen/format_unit.h
--- old/zen/format_unit.h       2024-05-10 18:03:32.000000000 +0200
+++ new/zen/format_unit.h       2024-06-23 09:46:58.000000000 +0200
@@ -17,7 +17,7 @@
 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zen/socket.h new/zen/socket.h
--- old/zen/socket.h    2024-05-10 18:03:32.000000000 +0200
+++ new/zen/socket.h    2024-06-23 09:46:58.000000000 +0200
@@ -141,16 +141,6 @@
             }
 
             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 @@
             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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/zen/type_traits.h new/zen/type_traits.h
--- old/zen/type_traits.h       2024-05-10 18:03:33.000000000 +0200
+++ new/zen/type_traits.h       2024-06-23 09:46:58.000000000 +0200
@@ -30,6 +30,7 @@
     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
 
 //=============================================================================
 

Reply via email to