Skip to content

Commit

Permalink
Version 13.2 (2023-11-23)
Browse files Browse the repository at this point in the history
  • Loading branch information
hkneptune committed Nov 25, 2023
1 parent 23ab55b commit 44f7cf2
Show file tree
Hide file tree
Showing 57 changed files with 5,547 additions and 5,508 deletions.
12 changes: 12 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
FreeFileSync 13.2
------------------------------
Complete high-DPI/Retina display support (macOS)
Prevent versioning files from being moved to versioning recursively
Fixed tooltip line wrap bug for moved files (Windows)
Return first FTP parsing error when trying multiple variants
Allow file times from the future for Linux-style FTP listing
Fixed setting modification times on certain storage devices (Windows)
Fixed bogus "Sound playback failed" error message (macOS)
Fixed rename dialog text selection wobble (macOS)


FreeFileSync 13.1 [2023-10-23]
------------------------------
Keep comparison results when only changing cloud connection settings
Expand Down
Binary file modified FreeFileSync/Build/Resources/Languages.zip
Binary file not shown.
4 changes: 2 additions & 2 deletions FreeFileSync/Source/RealTimeSync/app_icon.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ inline
wxIcon getRtsIcon() //see FFS/app_icon.h
{
assert(loadImage("RealTimeSync").GetWidth () == loadImage("RealTimeSync").GetHeight() &&
loadImage("RealTimeSync").GetWidth() == fastFromDIP(128));
loadImage("RealTimeSync").GetWidth() == dipToScreen(128));
wxIcon icon;
icon.CopyFromBitmap(loadImage("RealTimeSync", fastFromDIP(64)));
icon.CopyFromBitmap(loadImage("RealTimeSync", dipToScreen(64)));
return icon;

}
Expand Down
8 changes: 4 additions & 4 deletions FreeFileSync/Source/RealTimeSync/main_dlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,19 +104,19 @@ MainDialog::MainDialog(const Zstring& cfgFilePath) :
const int scrollDelta = m_buttonSelectFolderMain->GetSize().y; //more approriate than GetCharHeight() here
m_scrolledWinFolders->SetScrollRate(scrollDelta, scrollDelta);

m_txtCtrlDirectoryMain->SetMinSize({fastFromDIP(300), -1});
m_txtCtrlDirectoryMain->SetMinSize({dipToWxsize(300), -1});
setDefaultWidth(*m_spinCtrlDelay);

m_bpButtonRemoveTopFolder->Hide();
m_panelMainFolder->Layout();

setImage(*m_bitmapBatch, loadImage("cfg_batch", fastFromDIP(20)));
setImage(*m_bitmapBatch, loadImage("cfg_batch", dipToScreen(20)));
setImage(*m_bitmapFolders, fff::IconBuffer::genericDirIcon(fff::IconBuffer::IconSize::small));
setImage(*m_bitmapConsole, loadImage("command_line", fastFromDIP(20)));
setImage(*m_bitmapConsole, loadImage("command_line", dipToScreen(20)));

setImage(*m_bpButtonAddFolder, loadImage("item_add"));
setImage(*m_bpButtonRemoveTopFolder, loadImage("item_remove"));
setBitmapTextLabel(*m_buttonStart, loadImage("start_rts"), m_buttonStart->GetLabelText(), fastFromDIP(5), fastFromDIP(8));
setBitmapTextLabel(*m_buttonStart, loadImage("start_rts"), m_buttonStart->GetLabelText(), dipToWxsize(5), dipToWxsize(8));

Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& event) { onLocalKeyEvent(event); });

Expand Down
2 changes: 1 addition & 1 deletion FreeFileSync/Source/RealTimeSync/tray_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class TrayIconObject : public wxTaskBarIcon

const wxString jobName_; //RTS job name, may be empty

const wxImage trayImg_ = loadImage("start_rts", fastFromDIP(24)); //use 24x24 bitmap for perfect fit
const wxImage trayImg_ = loadImage("start_rts", dipToScreen(24)); //use 24x24 bitmap for perfect fit
};


Expand Down
33 changes: 20 additions & 13 deletions FreeFileSync/Source/afs/ftp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,19 +390,25 @@ class FtpSession

//CURLOPT_TIMEOUT: "Since this puts a hard limit for how long time a request is allowed to take, it has limited use in dynamic use cases with varying transfer times."
setCurlOption({CURLOPT_LOW_SPEED_TIME, *timeoutSec}); //throw SysError
setCurlOption({CURLOPT_LOW_SPEED_LIMIT, 1}); //throw SysError
; //[bytes], can't use "0" which means "inactive", so use some low number
setCurlOption({CURLOPT_LOW_SPEED_LIMIT, 1 /*[bytes]*/}); //throw SysError
//can't use "0" which means "inactive", so use some low number

//unlike CURLOPT_TIMEOUT, this one is NOT a limit on the total transfer time
setCurlOption({CURLOPT_SERVER_RESPONSE_TIMEOUT, *timeoutSec}); //throw SysError
//== alias of CURLOPT_SERVER_RESPONSE_TIMEOUT
//FTP only; unlike CURLOPT_TIMEOUT, this one is NOT a limit on the total transfer time

//CURLOPT_ACCEPTTIMEOUT_MS? => only relevant for "active" FTP connections

//long-running file uploads require us to send keep-alives for the TCP control connection: https://freefilesync.org/forum/viewtopic.php?t=6928
//long-running file uploads require keep-alives for the TCP control connection: https://freefilesync.org/forum/viewtopic.php?t=6928
setCurlOption({CURLOPT_TCP_KEEPALIVE, 1}); //throw SysError
//=> CURLOPT_TCP_KEEPIDLE (=delay) and CURLOPT_TCP_KEEPINTVL both default to 60 sec

warn_static("remove after test") //https://freefilesync.org/forum/viewtopic.php?p=41482#p41482
#if 0
setCurlOption({CURLOPT_TCP_KEEPIDLE, 30 /*[sec]*/}); //throw SysError
setCurlOption({CURLOPT_TCP_KEEPINTVL, 30 /*[sec]*/}); //throw SysError
#endif



std::optional<SysError> socketException;
//libcurl does *not* set FD_CLOEXEC for us! https://github.com/curl/curl/issues/2252
Expand Down Expand Up @@ -1443,9 +1449,9 @@ class FtpDirectoryReader
switch (line[0])
{
//*INDENT-OFF*
case 'd': return dirOwnerGroupCount;
case 'd': return dirOwnerGroupCount;
case 'l': return linkOwnerGroupCount;
default: return fileOwnerGroupCount;
default : return fileOwnerGroupCount;
//*INDENT-ON*
}
}();
Expand All @@ -1454,19 +1460,20 @@ class FtpDirectoryReader
if (!ownerGroupCount)
ownerGroupCount = [&]
{
std::optional<SysError> firstError;

for (int i = 3; i-- > 0;)
try
{
parseUnixLine(line, utcTimeNow, utcCurrentYear, i /*ownerGroupCount*/, session); //throw SysError
return i;
}
catch (SysError&)
catch (const SysError& e)
{
if (i == 0)
throw;
if (!firstError)
firstError = e;
}
assert(false);
return 1234; //placate bogus compiler warning: "not all control paths return a value"
throw* firstError; //most likely the relevant one: https://freefilesync.org/forum/viewtopic.php?t=10798
}();

const FtpItem item = parseUnixLine(line, utcTimeNow, utcCurrentYear, *ownerGroupCount, session); //throw SysError
Expand Down Expand Up @@ -1585,7 +1592,7 @@ class FtpDirectoryReader
{
timeComp.year = stringTo<int>(timeOrYear);

if (timeComp.year < 1600 || timeComp.year > utcCurrentYear + 1 /*leeway*/)
if (timeComp.year < 1600 || timeComp.year >= 3000)
throw SysError(L"Failed to parse modification time.");
}
else
Expand Down
4 changes: 3 additions & 1 deletion FreeFileSync/Source/afs/native.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,13 +380,15 @@ struct OutputStreamNative : public AFS::OutputStreamImpl

fileOut_.close(); //throw FileError
/* is setting modtime after closing the file handle a pessimization?
no, needed for functional correctness, see file_access.cpp::copyNewFile() for macOS/Linux */
no, needed for functional correctness, see file_access.cpp::copyNewFile() for macOS/Linux
even required on Windows: https://freefilesync.org/forum/viewtopic.php?t=10781 */
try
{
if (modTime_)
setFileTime(fileOut_.getFilePath(), *modTime_, ProcSymlink::follow); //throw FileError
}
catch (const FileError& e) { result.errorModTime = e; /*might slice derived class?*/ }

return result;
}

Expand Down
32 changes: 21 additions & 11 deletions FreeFileSync/Source/base/algorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1184,15 +1184,14 @@ void fff::applyTimeSpanFilter(FolderComparison& folderCmp, time_t timeFrom, time
}


std::optional<PathDependency> fff::getPathDependency(const AbstractPath& folderPathL, const PathFilter& filterL,
const AbstractPath& folderPathR, const PathFilter& filterR)
std::optional<PathDependency> fff::getPathDependency(const AbstractPath& itemPathL, const AbstractPath& itemPathR)
{
if (!AFS::isNullPath(folderPathL) && !AFS::isNullPath(folderPathR))
if (!AFS::isNullPath(itemPathL) && !AFS::isNullPath(itemPathR))
{
if (folderPathL.afsDevice == folderPathR.afsDevice)
if (itemPathL.afsDevice == itemPathR.afsDevice)
{
const std::vector<Zstring> relPathL = splitCpy(folderPathL.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip);
const std::vector<Zstring> relPathR = splitCpy(folderPathR.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip);
const std::vector<Zstring> relPathL = splitCpy(itemPathL.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip);
const std::vector<Zstring> relPathR = splitCpy(itemPathR.afsPath.value, FILE_NAME_SEPARATOR, SplitOnEmpty::skip);

const bool leftParent = relPathL.size() <= relPathR.size();

Expand All @@ -1207,16 +1206,27 @@ std::optional<PathDependency> fff::getPathDependency(const AbstractPath& folderP
relDirPath = appendPath(relDirPath, itemName);
});

const PathFilter& filterP = leftParent ? filterL : filterR;
return PathDependency{leftParent ? itemPathL : itemPathR, relDirPath};
}
}
}
return {};
}


std::optional<PathDependency> fff::getFolderPathDependency(const AbstractPath& folderPathL, const PathFilter& filterL,
const AbstractPath& folderPathR, const PathFilter& filterR)
{
if (std::optional<PathDependency> pd = getPathDependency(folderPathL, folderPathR))
{
const PathFilter& filterP = pd->itemPathParent == folderPathL ? filterL : filterR;
//if there's a dependency, check if the sub directory is (fully) excluded via filter
//=> easy to check but still insufficient in general:
// - one folder may have a *.txt include-filter, the other a *.lng include filter => no dependencies, but "childItemMightMatch = true" below!
// - user may have manually excluded the conflicting items or changed the filter settings without running a re-compare
bool childItemMightMatch = true;
if (relDirPath.empty() || filterP.passDirFilter(relDirPath, &childItemMightMatch) || childItemMightMatch)
return PathDependency{leftParent ? folderPathL : folderPathR, relDirPath};
}
}
if (pd->relPath.empty() || filterP.passDirFilter(pd->relPath, &childItemMightMatch) || childItemMightMatch)
return pd;
}
return {};
}
Expand Down
5 changes: 3 additions & 2 deletions FreeFileSync/Source/base/algorithm.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ void setActiveStatus(bool newStatus, FileSystemObject& fsObj); //activate or

struct PathDependency
{
AbstractPath folderPathParent;
AbstractPath itemPathParent;
Zstring relPath; //filled if child path is subfolder of parent path; empty if child path == parent path
};
std::optional<PathDependency> getPathDependency(const AbstractPath& folderPathL, const PathFilter& filterL,
std::optional<PathDependency> getPathDependency(const AbstractPath& itemPathL, const AbstractPath& itemPathR);
std::optional<PathDependency> getFolderPathDependency(const AbstractPath& folderPathL, const PathFilter& filterL,
const AbstractPath& folderPathR, const PathFilter& filterR);

//manual copy to alternate folder:
Expand Down
2 changes: 1 addition & 1 deletion FreeFileSync/Source/base/comparison.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1122,7 +1122,7 @@ FolderComparison fff::compare(WarningDialogs& warnings,
bool shouldExclude = false;

for (const auto& [folderPair, fpCfg] : workLoad)
if (std::optional<PathDependency> pd = getPathDependency(folderPair.folderPathLeft, fpCfg.filter.nameFilter.ref(),
if (std::optional<PathDependency> pd = getFolderPathDependency(folderPair.folderPathLeft, fpCfg.filter.nameFilter.ref(),
folderPair.folderPathRight, fpCfg.filter.nameFilter.ref()))
{
msg += L"\n\n" +
Expand Down
13 changes: 7 additions & 6 deletions FreeFileSync/Source/base/synchronization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2580,7 +2580,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
continue;
}

//============ Warnings (*after* potential folder pair skips) ============
//================= Warnings (*after* folder pair skips) =================
//========================================================================

//prepare conflict preview:
Expand Down Expand Up @@ -2612,7 +2612,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
if (folderPairCfg.versionMaxAgeDays > 0 || folderPairCfg.versionCountMax > 0) //same check as in applyVersioningLimit()
checkVersioningLimitPaths.insert(versioningFolderPath);

//check if more than 50% of total number of files/dirs are to be created/overwritten/deleted
//check if more than 50% of total number of files/dirs will be created/overwritten/deleted
if (significantDifferenceDetected(folderPairStat))
checkSignificantDiffPairs.emplace_back(baseFolder.getAbstractPath<SelectSide::left >(),
baseFolder.getAbstractPath<SelectSide::right>());
Expand Down Expand Up @@ -2793,20 +2793,21 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
std::set<AbstractPath> foldersWithWarnings; //=> at most one msg per base folder (*and* per versioningFolderPath)

for (const auto& [folderPath, filter] : checkVersioningBasePaths) //may contain duplicate paths, but with *different* hard filter!
if (std::optional<PathDependency> pd = getPathDependency(versioningFolderPath, NullFilter(), folderPath, *filter))
if (std::optional<PathDependency> pd = getFolderPathDependency(versioningFolderPath, NullFilter(), folderPath, *filter))
if (const auto [it, inserted] = foldersWithWarnings.insert(folderPath);
inserted)
{
msg += L"\n\n" +
_("Selected folder:") + L" \t" + AFS::getDisplayPath(folderPath) + L'\n' +
_("Versioning folder:") + L" \t" + AFS::getDisplayPath(versioningFolderPath);

if (pd->folderPathParent == folderPath) //if versioning folder is a subfolder of a base folder
if (pd->itemPathParent == folderPath) //if versioning folder is a subfolder of a base folder
if (!pd->relPath.empty()) //this can be fixed via an exclude filter
{
assert(pd->itemPathParent == folderPath); //otherwise: what the fuck!?
shouldExclude = true;
msg += std::wstring() + L'\n' + L"" +
_("Exclude:") + L" \t" + utfTo<std::wstring>(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR);
msg += std::wstring() + L'\n' +
L"" + _("Exclude:") + L" \t" + utfTo<std::wstring>(FILE_NAME_SEPARATOR + pd->relPath + FILE_NAME_SEPARATOR);
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions FreeFileSync/Source/base/versioning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,25 @@ void moveExistingItemToVersioning(const AbstractPath& sourcePath, const Abstract
}


void FileVersioner::checkPathConflict(const AbstractPath& itemPath) const //throw FileError
{
if (std::optional<PathDependency> pd = getPathDependency(itemPath, versioningFolderPath_))
{
assert(pd->itemPathParent == versioningFolderPath_); //otherwise: what the fuck!?
//user ignored warning about versioning folder being part of sync =>
//prevent files from being moved to versioning recursively:
throw FileError(trimCpy(replaceCpy(replaceCpy(_("Cannot move %x to %y."),
L"%x", L'\n' + fmtPath(AFS::getDisplayPath(itemPath))),
L"%y", L'\n' + fmtPath(AFS::getDisplayPath(versioningFolderPath_)))),
_("Item already located in the versioning folder."));
}
}


void FileVersioner::revisionFile(const FileDescriptor& fileDescr, const Zstring& relativePath, const IoCallback& notifyUnbufferedIO /*throw X*/) const //throw FileError, X
{
checkPathConflict(fileDescr.path); //throw FileError

if (const std::optional<AFS::ItemType> type = AFS::getItemTypeIfExists(fileDescr.path)) //throw FileError
{
assert(*type != AFS::ItemType::symlink);
Expand Down Expand Up @@ -224,6 +241,8 @@ void FileVersioner::revisionFileImpl(const FileDescriptor& fileDescr, const Zstr

void FileVersioner::revisionSymlink(const AbstractPath& linkPath, const Zstring& relativePath) const //throw FileError
{
checkPathConflict(linkPath); //throw FileError

if (AFS::itemExists(linkPath)) //throw FileError
revisionSymlinkImpl(linkPath, relativePath, nullptr /*onBeforeMove*/); //throw FileError
//else -> missing source item is not an error => check BEFORE deleting target
Expand All @@ -248,6 +267,8 @@ void FileVersioner::revisionFolder(const AbstractPath& folderPath, const Zstring
const std::function<void(const std::wstring& displayPathFrom, const std::wstring& displayPathTo)>& onBeforeFolderMove /*throw X*/,
const IoCallback& notifyUnbufferedIO /*throw X*/) const
{
checkPathConflict(folderPath); //throw FileError

//no error situation if directory is not existing! manual deletion relies on it!
if (const std::optional<AFS::ItemType> type = AFS::getItemTypeIfExists(folderPath)) //throw FileError
{
Expand Down
2 changes: 2 additions & 0 deletions FreeFileSync/Source/base/versioning.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class FileVersioner
FileVersioner (const FileVersioner&) = delete;
FileVersioner& operator=(const FileVersioner&) = delete;

void checkPathConflict(const AbstractPath& itemPath) const; //throw FileError

void revisionFileImpl(const FileDescriptor& fileDescr, const Zstring& relativePath, //throw FileError, X
const std::function<void(const std::wstring& displayPathFrom, const std::wstring& displayPathTo)>& onBeforeMove,
const zen::IoCallback& notifyUnbufferedIO) const;
Expand Down
18 changes: 0 additions & 18 deletions FreeFileSync/Source/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,24 +1015,6 @@ void readConfig(const XmlIn& in, SyncDirectionConfig& dirCfg, int formatVer)
inCustDir["RightNewer"](std::get<DirectionByDiff>(dirCfg.dirs).rightNewer); //
}
}
else if (formatVer < 22) //TODO: remove if parameter migration after some time! 2023-08-20
{
warn_static("formatVer == 21 was development-internal => get rid, once the betas are out of likely use")

if (XmlIn inDir = in["Differences"])
{
dirCfg.dirs = DirectionByDiff();
inDir["LeftOnly" ](std::get<DirectionByDiff>(dirCfg.dirs).leftOnly);
inDir["RightOnly" ](std::get<DirectionByDiff>(dirCfg.dirs).rightOnly);
inDir["LeftNewer" ](std::get<DirectionByDiff>(dirCfg.dirs).leftNewer);
inDir["RightNewer"](std::get<DirectionByDiff>(dirCfg.dirs).rightNewer);
}
else
{
assert(in["Changes"]);
dirCfg = getDefaultSyncCfg(SyncVariant::twoWay);
}
}
else
{
if (XmlIn inDirs = in["Differences"])
Expand Down
Loading

0 comments on commit 44f7cf2

Please sign in to comment.