summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xChangelog.txt16
-rwxr-xr-xFreeFileSync/Build/Languages/german.lng8
-rwxr-xr-xFreeFileSync/Build/Languages/turkish.lng52
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/main_dlg.cpp2
-rwxr-xr-xFreeFileSync/Source/base/binary.cpp18
-rwxr-xr-xFreeFileSync/Source/base/comparison.cpp17
-rwxr-xr-xFreeFileSync/Source/base/dir_exist_async.h7
-rwxr-xr-xFreeFileSync/Source/base/file_hierarchy.h3
-rwxr-xr-xFreeFileSync/Source/base/icon_buffer.cpp14
-rwxr-xr-xFreeFileSync/Source/base/localization.cpp2
-rwxr-xr-xFreeFileSync/Source/base/parallel_scan.cpp14
-rwxr-xr-xFreeFileSync/Source/base/path_filter.cpp42
-rwxr-xr-xFreeFileSync/Source/base/path_filter.h4
-rwxr-xr-xFreeFileSync/Source/base/status_handler_impl.h18
-rwxr-xr-xFreeFileSync/Source/base/synchronization.cpp26
-rwxr-xr-xFreeFileSync/Source/fs/abstract.cpp18
-rwxr-xr-xFreeFileSync/Source/fs/abstract.h2
-rwxr-xr-xFreeFileSync/Source/fs/concrete_impl.h6
-rwxr-xr-xFreeFileSync/Source/ui/folder_selector.h4
-rwxr-xr-xFreeFileSync/Source/ui/progress_indicator.cpp2
-rwxr-xr-xFreeFileSync/Source/ui/version_check.cpp19
-rwxr-xr-xFreeFileSync/Source/version/version.h2
-rwxr-xr-xzen/file_access.cpp28
-rwxr-xr-xzen/thread.h22
-rwxr-xr-xzen/zstring.h13
25 files changed, 193 insertions, 166 deletions
diff --git a/Changelog.txt b/Changelog.txt
index 185970a9..bf64b73b 100755
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,19 @@
+FreeFileSync 10.7 [2018-12-12]
+------------------------------
+Correctly resolve ambiguous paths in (S)FTP folder picker
+Fixed path alias check to not rely on volume serial number
+Check already existing move target by ID instead of path (Linux, macOS)
+Use native image conversion routines in installer
+Added base folder info for unresolved conflicts message
+Avoid silent failure when setting epoch modTime (Windows)
+Fixed RealTimeSync failing to start FreeFileSync batch (macOS)
+Support command arguments and exit code with launcher (macOS)
+Consider UTF encoding when trimming long temp name during file copy
+Exclude failed item paths containing backslash in names (Linux)
+Fixed RealTimeSync GUI distortion after drag & drop (Linux)
+Fixed parsing locale with unexpected format (Linux)
+
+
FreeFileSync 10.6 [2018-11-12]
------------------------------
Detect and skip traversing folder path aliases
diff --git a/FreeFileSync/Build/Languages/german.lng b/FreeFileSync/Build/Languages/german.lng
index 093be509..2ee37021 100755
--- a/FreeFileSync/Build/Languages/german.lng
+++ b/FreeFileSync/Build/Languages/german.lng
@@ -212,7 +212,7 @@
<target>Ausschließen:</target>
<source>One base folder of a folder pair is contained in the other one.</source>
-<target>Ein Basisordner eines Ordnerpaares ist im anderen enthalben.</target>
+<target>Ein Basisordner eines Ordnerpaares ist im anderen enthalten.</target>
<source>The folder should be excluded from synchronization via filter.</source>
<target>Der Ordner sollte von der Synchronisation über den Filter ausgeschlossen werden.</target>
@@ -511,6 +511,9 @@
<source>The following items have unresolved conflicts and will not be synchronized:</source>
<target>Die folgenden Elemente haben ungelöste Konflikte und werden nicht synchronisiert werden:</target>
+<source>Folder pair:</source>
+<target>Ordnerpaar:</target>
+
<source>The following folders are significantly different. Please check that the correct folders are selected for synchronization.</source>
<target>Die folgenden Ordner unterscheiden sich erheblich. Überprüfen Sie, ob die richtigen Ordner für die Synchronisation ausgewählt wurden.</target>
@@ -1052,9 +1055,6 @@ Die Befehlszeile wird ausgelöst, wenn:
<source>Arrange folder pair</source>
<target>Ordnerpaar anordnen</target>
-<source>Folder pair:</source>
-<target>Ordnerpaar:</target>
-
<source>Main settings:</source>
<target>Haupteinstellungen:</target>
diff --git a/FreeFileSync/Build/Languages/turkish.lng b/FreeFileSync/Build/Languages/turkish.lng
index d06e670f..b7ebec78 100755
--- a/FreeFileSync/Build/Languages/turkish.lng
+++ b/FreeFileSync/Build/Languages/turkish.lng
@@ -161,7 +161,7 @@
<target>Yalnız öznitelikleri farklı olan ögeler</target>
<source>The name %x is used by more than one item in the folder.</source>
-<target></target>
+<target>%x adı klasördeki birden fazla öge için kullanılmış.</target>
<source>Resolving symbolic link %x</source>
<target>%x sembolik bağlantısı çözümleniyor</target>
@@ -335,10 +335,10 @@
<target>Sağdaki öznitelikler güncellensin</target>
<source>Errors:</source>
-<target></target>
+<target>Hatalar:</target>
<source>Warnings:</source>
-<target></target>
+<target>Uyarılar:</target>
<source>Items processed:</source>
<target>İşlenen öge:</target>
@@ -362,19 +362,19 @@
<target>%x dosyası işlenirken sorun çıktı, satır %y, sütun %z.</target>
<source>Services</source>
-<target></target>
+<target>Hizmetler</target>
<source>Show All</source>
-<target></target>
+<target>Tümünü Görüntüle</target>
<source>Hide Others</source>
-<target></target>
+<target>Diğerlerini Gizle</target>
<source>Hide %x</source>
-<target></target>
+<target>%x Ögesini Gizle</target>
<source>Quit %x</source>
-<target></target>
+<target>%x Uygulamasından Çık</target>
<source>Cannot set directory locks for the following folders:</source>
<target>Şu klasörler kilitlenemedi:</target>
@@ -392,10 +392,10 @@
<target>%x klasörü okunamadı.</target>
<source>%x/sec</source>
-<target></target>
+<target>%x/saniye</target>
<source>%x items</source>
-<target></target>
+<target>%x öge</target>
<source>Show in Explorer</source>
<target>Tarayıcıda Görüntüle</target>
@@ -545,10 +545,10 @@
<target>Veritabanı oluşturuluyor...</target>
<source>Searching for old file versions:</source>
-<target></target>
+<target>Önceki dosya sürümleri aranıyor:</target>
<source>Removing old file versions:</source>
-<target></target>
+<target>Önceki dosya sürümleri siliniyor:</target>
<source>Unable to create time stamp for versioning:</source>
<target>Sürümlendirme için zaman damgası oluşturulamadı:</target>
@@ -610,16 +610,16 @@ Gerçekleşen: %y bayt
<target>%x üzerine erişilemedi.</target>
<source>Authentication completed.</source>
-<target></target>
+<target>Kimlik doğrulandı.</target>
<source>Authentication failed.</source>
-<target></target>
+<target>Kimlik doğrulanamadı.</target>
<source>You may close this page now and continue with FreeFileSync.</source>
-<target></target>
+<target>Bu sayfayı kapatıp FreeFileSync ile çalışmayı sürdürebilirsiniz.</target>
<source>The server returned an error:</source>
-<target></target>
+<target>Sunucu şu hatayı bildirdi:</target>
<source>Cannot determine free disk space for %x.</source>
<target>%x için boş disk alanı belirlenemedi.</target>
@@ -942,7 +942,7 @@ Komut şu durumlarda yürütülür:
<target>&Toplu İş Olarak Kaydet...</target>
<source>Show &log</source>
-<target></target>
+<target>Gün&lüğü Görüntüle</target>
<source>Start &comparison</source>
<target>&Karşılaştırmayı Başlat</target>
@@ -1172,10 +1172,10 @@ Komut şu durumlarda yürütülür:
<target>Son x Gün:</target>
<source>&Override default log path:</source>
-<target></target>
+<target>Varsayılan yerine kullanılacak günlük y&olu:</target>
<source>Run a command:</source>
-<target></target>
+<target>Bir komut yürüt:</target>
<source>OK</source>
<target>Tamam</target>
@@ -1226,7 +1226,7 @@ Komut şu durumlarda yürütülür:
<target>Sunucudaki Klasör:</target>
<source>Access timeout (in seconds):</source>
-<target></target>
+<target>Erişim zaman aşımı (saniye):</target>
<source>SFTP channels per connection:</source>
<target>Bir Bağlantı için SFTP Kanalı Sayısı:</target>
@@ -1343,10 +1343,10 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y
<target>Kalıcı olarak gizlenmiş tüm ileti ve uyarılar yeniden görüntülenir</target>
<source>Default log path:</source>
-<target></target>
+<target>Varsayılan günlük dosyası yolu:</target>
<source>&Delete logs after x days:</source>
-<target></target>
+<target>&Günlük kayıtlarının silineceği gün sayısı:</target>
<source>Customize context menu:</source>
<target>Sağ Tık Menüsü Uyarlamaları:</target>
@@ -1358,7 +1358,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y
<target>&Varsayılan</target>
<source>Feedback and suggestions are welcome:</source>
-<target></target>
+<target>Geri bildirim ve önerilerinizi bekliyoruz:</target>
<source>Home page</source>
<target>Ana Sayfa</target>
@@ -1385,7 +1385,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y
<target>Kaynak kodu C++ kullanılarak yazılmıştır:</target>
<source>Published under the GNU General Public License:</source>
-<target></target>
+<target>GNU Genel Kamu Lisansı koşulları altında yayınlanmıştır:</target>
<source>Many thanks for localization:</source>
<target>Çeviriler için çok teşekkürler:</target>
@@ -1442,7 +1442,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y
<target>Bilgi</target>
<source>No log entries</source>
-<target></target>
+<target>Herhangi bir günlük kaydı yok</target>
<source>Select all</source>
<target>Tümünü Seç</target>
@@ -2060,7 +2060,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y
<target>FreeFileSync ile Düzenlensin</target>
<source>Instead of an ad, here's an animal.</source>
-<target>Bir reklam yerine burada bir hayvan var.</target>
+<target>Burada bir reklam yerine bir hayvan var.</target>
<source>The FreeFileSync portable version cannot install into a subfolder of %x.</source>
<target>FreeFileSync taşınabilir sürümü bir %x alt klasörüne yüklenemez.</target>
diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
index 2689955c..7e2a753a 100755
--- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
+++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
@@ -437,6 +437,8 @@ void MainDialog::insertAddFolder(const std::vector<Zstring>& newFolders, size_t
GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize()
+ m_scrolledWinFolders->Layout(); //fix GUI distortion after .ffs_batch drag & drop (Linux)
+
Refresh(); //remove a little flicker near the start button
}
diff --git a/FreeFileSync/Source/base/binary.cpp b/FreeFileSync/Source/base/binary.cpp
index 32fe37a6..643dba23 100755
--- a/FreeFileSync/Source/base/binary.cpp
+++ b/FreeFileSync/Source/base/binary.cpp
@@ -24,15 +24,15 @@ namespace
Impact of buffer size when files are on same disk:
-buffer MB/s
-------------
- 64 10
- 128 19
- 512 40
-1024 48
-2048 56
-4096 56
-8192 56
+ buffer MB/s
+ ------------
+ 64 10
+ 128 19
+ 512 40
+ 1024 48
+ 2048 56
+ 4096 56
+ 8192 56
*/
const size_t BLOCK_SIZE_MAX = 16 * 1024 * 1024;
diff --git a/FreeFileSync/Source/base/comparison.cpp b/FreeFileSync/Source/base/comparison.cpp
index 26c33dca..eaf5aacc 100755
--- a/FreeFileSync/Source/base/comparison.cpp
+++ b/FreeFileSync/Source/base/comparison.cpp
@@ -246,13 +246,15 @@ Zstringw getDescrDiffMetaShortnameCase(const FileSystemObject& fsObj)
}
+#if 0
template <class FileOrLinkPair>
-Zstringw getDescrDiffMetaDate(const FileOrLinkPair& file)
+Zstringw getDescrDiffMetaData(const FileOrLinkPair& file)
{
return copyStringTo<Zstringw>(_("Items differ in attributes only") + L"\n" +
arrowLeft + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime< LEFT_SIDE>()) + L"\n" +
arrowRight + L" " + _("Date:") + L" " + formatUtcToLocalTime(file.template getLastWriteTime<RIGHT_SIDE>()));
}
+#endif
Zstringw getConflictAmbiguousItemName(const Zstring& itemName)
@@ -386,7 +388,7 @@ void categorizeSymlinkByContent(SymlinkPair& symlink, ProcessCallback& callback)
symlink.setCategoryDiffMetadata(getDescrDiffMetaShortnameCase(symlink));
//else if (!sameFileTime(symlink.getLastWriteTime<LEFT_SIDE>(),
// symlink.getLastWriteTime<RIGHT_SIDE>(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift()))
- // symlink.setCategoryDiffMetadata(getDescrDiffMetaDate(symlink));
+ // symlink.setCategoryDiffMetadata(getDescrDiffMetaData(symlink));
else
symlink.setCategory<FILE_EQUAL>();
}
@@ -483,7 +485,7 @@ void categorizeFileByContent(FilePair& file, const std::wstring& txtComparingCon
#if 0 //don't synchronize modtime only see FolderPairSyncer::synchronizeFileInt(), SO_COPY_METADATA_TO_*
else if (!sameFileTime(file.getLastWriteTime<LEFT_SIDE>(),
file.getLastWriteTime<RIGHT_SIDE>(), file.base().getFileTimeTolerance(), file.base().getIgnoredTimeShift()))
- file.setCategoryDiffMetadata(getDescrDiffMetaDate(file));
+ file.setCategoryDiffMetadata(getDescrDiffMetaData(file));
#endif
else
file.setCategory<FILE_EQUAL>();
@@ -611,7 +613,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co
acb.notifyTaskBegin(statusPrio); //prioritize status messages according to natural order of folder pairs
ZEN_ON_SCOPE_EXIT(acb.notifyTaskEnd());
- std::lock_guard<std::mutex> dummy(singleThread); //protect ALL variable accesses unless explicitly not needed ("parallel" scope)!
+ std::lock_guard dummy(singleThread); //protect ALL variable accesses unless explicitly not needed ("parallel" scope)!
//---------------------------------------------------------------------------------------------------
ZEN_ON_SCOPE_SUCCESS(if (&posL != &posR) --posL.current;
/**/ --posR.current;
@@ -634,7 +636,7 @@ std::list<std::shared_ptr<BaseFolderPair>> ComparisonBuffer::compareByContent(co
};
{
- std::lock_guard<std::mutex> dummy(singleThread); //[!] potential race with worker threads!
+ std::lock_guard dummy(singleThread); //[!] potential race with worker threads!
scheduleMoreTasks(); //set initial load
}
@@ -945,6 +947,11 @@ std::shared_ptr<BaseFolderPair> ComparisonBuffer::performComparison(const Resolv
for (const auto& [relPath, errorMsg] : failedReads)
excludefilterFailedRead += relPath.upperCase + Zstr("\n"); //exclude item AND (potential) child items!
+ //somewhat obscure, but it's possible on Linux file systems to have a backslash as part of a file name
+ //=> avoid misinterpretation when parsing the filter phrase in PathFilter (see path_filter.cpp::addFilterEntry())
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(excludefilterFailedRead, Zstr('/'), Zstr('?'));
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(excludefilterFailedRead, Zstr('\\'), Zstr('?'));
+
std::shared_ptr<BaseFolderPair> output = std::make_shared<BaseFolderPair>(fp.folderPathLeft,
bufValueLeft != nullptr, //dir existence must be checked only once: available iff buffer entry exists!
fp.folderPathRight,
diff --git a/FreeFileSync/Source/base/dir_exist_async.h b/FreeFileSync/Source/base/dir_exist_async.h
index 749e79ce..ea524d88 100755
--- a/FreeFileSync/Source/base/dir_exist_async.h
+++ b/FreeFileSync/Source/base/dir_exist_async.h
@@ -108,7 +108,8 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath
const auto startTime = std::chrono::steady_clock::now();
FolderStatus output;
- std::map<AFS::FileId, AbstractPath> exFoldersById;
+ std::map<std::pair<AfsDevice, AFS::FileId>, AbstractPath> exFoldersById; //volume serial is NOT always globally unique!
+ //=> combine with AfsDevice https://freefilesync.org/forum/viewtopic.php?t=5815
for (auto& [folderPath, future] : futureDetails)
{
@@ -136,9 +137,9 @@ FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPath
//find folder aliases (e.g. path differing in case)
const AFS::FileId fileId = *folderInfo;
if (!fileId.empty())
- exFoldersById.emplace(fileId, folderPath);
+ exFoldersById.emplace(std::pair(folderPath.afsDevice, fileId), folderPath);
- output.normalizedPathsEx.emplace(folderPath, fileId.empty() ? folderPath : exFoldersById.find(fileId)->second);
+ output.normalizedPathsEx.emplace(folderPath, fileId.empty() ? folderPath : exFoldersById.find(std::pair(folderPath.afsDevice, fileId))->second);
}
else
output.notExisting.insert(folderPath);
diff --git a/FreeFileSync/Source/base/file_hierarchy.h b/FreeFileSync/Source/base/file_hierarchy.h
index 0b466c44..d8038994 100755
--- a/FreeFileSync/Source/base/file_hierarchy.h
+++ b/FreeFileSync/Source/base/file_hierarchy.h
@@ -8,7 +8,6 @@
#define FILE_HIERARCHY_H_257235289645296
#include <map>
-//#include <cstddef> //required by GCC 4.9 to find ptrdiff_t
#include <string>
#include <memory>
#include <list>
@@ -109,7 +108,7 @@ struct FolderContainer
//------------------------------------------------------------------
using FolderList = std::map<Zstring, std::pair<FolderAttributes, FolderContainer>>; //
using FileList = std::map<Zstring, FileAttributes>; //key: raw file name, without any (Unicode) normalization, preserving original upper-/lower-case
- using SymlinkList = std::map<Zstring, LinkAttributes>; //
+ using SymlinkList = std::map<Zstring, LinkAttributes>; //"Changing data [...] to NFC would cause interoperability problems. Always leave data as it is."
//------------------------------------------------------------------
FolderContainer() = default;
diff --git a/FreeFileSync/Source/base/icon_buffer.cpp b/FreeFileSync/Source/base/icon_buffer.cpp
index 39b5459b..e10d25ab 100755
--- a/FreeFileSync/Source/base/icon_buffer.cpp
+++ b/FreeFileSync/Source/base/icon_buffer.cpp
@@ -82,7 +82,7 @@ public:
{
assert(runningMainThread());
{
- std::lock_guard<std::mutex> dummy(lockFiles_);
+ std::lock_guard dummy(lockFiles_);
workLoad_.clear();
for (const AbstractPath& filePath : newLoad)
@@ -96,7 +96,7 @@ public:
{
assert(runningMainThread());
{
- std::lock_guard<std::mutex> dummy(lockFiles_);
+ std::lock_guard dummy(lockFiles_);
workLoad_.emplace_back(filePath); //set as next item to retrieve
}
conditionNewWork_.notify_all();
@@ -106,7 +106,7 @@ public:
AbstractPath extractNext() //throw ThreadInterruption
{
assert(!runningMainThread());
- std::unique_lock<std::mutex> dummy(lockFiles_);
+ std::unique_lock dummy(lockFiles_);
interruptibleWait(conditionNewWork_, dummy, [this] { return !workLoad_.empty(); }); //throw ThreadInterruption
@@ -129,7 +129,7 @@ public:
//called by main and worker thread:
bool hasIcon(const AbstractPath& filePath) const
{
- std::lock_guard<std::mutex> dummy(lockIconList_);
+ std::lock_guard dummy(lockIconList_);
return iconList.find(filePath) != iconList.end();
}
@@ -137,7 +137,7 @@ public:
std::optional<wxBitmap> retrieve(const AbstractPath& filePath)
{
assert(runningMainThread());
- std::lock_guard<std::mutex> dummy(lockIconList_);
+ std::lock_guard dummy(lockIconList_);
auto it = iconList.find(filePath);
if (it == iconList.end())
@@ -157,7 +157,7 @@ public:
//called by main and worker thread:
void insert(const AbstractPath& filePath, ImageHolder&& icon)
{
- std::lock_guard<std::mutex> dummy(lockIconList_);
+ std::lock_guard dummy(lockIconList_);
//thread safety: moving ImageHolder is free from side effects, but ~wxBitmap() is NOT! => do NOT delete items from iconList here!
auto rc = iconList.emplace(filePath, IconData());
@@ -174,7 +174,7 @@ public:
void limitSize()
{
assert(runningMainThread());
- std::lock_guard<std::mutex> dummy(lockIconList_);
+ std::lock_guard dummy(lockIconList_);
while (iconList.size() > BUFFER_SIZE_MAX)
{
diff --git a/FreeFileSync/Source/base/localization.cpp b/FreeFileSync/Source/base/localization.cpp
index 3b7faee3..2b97fb89 100755
--- a/FreeFileSync/Source/base/localization.cpp
+++ b/FreeFileSync/Source/base/localization.cpp
@@ -508,7 +508,7 @@ void fff::setLanguage(wxLanguage lng) //throw FileError
};
wxtrans->SetLanguage(lng); //!= wxLocale's language, which could be wxLANGUAGE_DEFAULT (see wxWidgetsLocale)
wxtrans->SetLoader(new MemoryTranslationLoader(lng, std::move(transMapping)));
- const bool catalogAdded = wxtrans->AddCatalog(wxString(), lng);
+ const bool catalogAdded = wxtrans->AddCatalog(wxString());
(void)catalogAdded;
assert(catalogAdded);
}
diff --git a/FreeFileSync/Source/base/parallel_scan.cpp b/FreeFileSync/Source/base/parallel_scan.cpp
index 35951a37..a535b6b0 100755
--- a/FreeFileSync/Source/base/parallel_scan.cpp
+++ b/FreeFileSync/Source/base/parallel_scan.cpp
@@ -162,7 +162,7 @@ public:
AFS::TraverserCallback::HandleError reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption
{
assert(!runningMainThread());
- std::unique_lock<std::mutex> dummy(lockRequest_);
+ std::unique_lock dummy(lockRequest_);
interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption
errorRequest_ = std::make_pair(msg, retryNumber);
@@ -189,7 +189,7 @@ public:
{
const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration;
- for (std::unique_lock<std::mutex> dummy(lockRequest_) ;;) //process all errors without delay
+ for (std::unique_lock dummy(lockRequest_) ;;) //process all errors without delay
{
const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || (threadsToFinish_ == 0); });
if (!rv) //time-out + condition not met
@@ -232,7 +232,7 @@ public:
void reportCurrentFile(const std::wstring& filePath) //context of worker thread
{
assert(!runningMainThread());
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
currentFile_ = filePath;
}
@@ -240,7 +240,7 @@ public:
void notifyWorkBegin(int threadIdx, const size_t parallelOps)
{
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
const auto it = activeThreadIdxs_.emplace(threadIdx, parallelOps);
assert(it.second);
@@ -252,7 +252,7 @@ public:
void notifyWorkEnd(int threadIdx)
{
{
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
const size_t no = activeThreadIdxs_.erase(threadIdx);
assert(no == 1);
@@ -261,7 +261,7 @@ public:
notifyingThreadIdx_ = activeThreadIdxs_.empty() ? 0 : activeThreadIdxs_.begin()->first;
}
{
- std::lock_guard<std::mutex> dummy(lockRequest_);
+ std::lock_guard dummy(lockRequest_);
assert(threadsToFinish_ > 0);
if (--threadsToFinish_ == 0)
conditionNewRequest.notify_all(); //perf: should unlock mutex before notify!? (insignificant)
@@ -276,7 +276,7 @@ private:
size_t parallelOpsTotal = 0;
std::wstring filePath;
{
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
for (const auto& [threadIdx, parallelOps] : activeThreadIdxs_)
parallelOpsTotal += parallelOps;
diff --git a/FreeFileSync/Source/base/path_filter.cpp b/FreeFileSync/Source/base/path_filter.cpp
index 79cb4a0f..f1fa1580 100755
--- a/FreeFileSync/Source/base/path_filter.cpp
+++ b/FreeFileSync/Source/base/path_filter.cpp
@@ -35,33 +35,29 @@ static_assert(FILE_NAME_SEPARATOR == '/');
void addFilterEntry(const Zstring& filterPhrase, std::vector<Zstring>& masksFileFolder, std::vector<Zstring>& masksFolder)
{
- warn_static("3. ignore path separator => bug regarding copyFilterAddingExclusion() after failed directory reads when dir has path separator from other OS in name")
-
//normalize filter input: 1. ignore Unicode normalization form 2. ignore case 3. ignore path separator
Zstring filterFmt = makeUpperCopy(filterPhrase);
if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(filterFmt, Zstr('/'), FILE_NAME_SEPARATOR);
if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(filterFmt, Zstr('\\'), FILE_NAME_SEPARATOR);
- /*
- phrase | action
- +---------+--------
- | \blah | remove \
- | \*blah | remove \
- | \*\blah | remove \
- | \*\* | remove \
- +---------+--------
- | *blah |
- | *\blah | -> add blah
- | *\*blah | -> add *blah
- +---------+--------
- | blah\ | remove \; folder only
- | blah*\ | remove \; folder only
- | blah\*\ | remove \; folder only
- +---------+--------
- | blah* |
- | blah\* | remove \*; folder only
- | blah*\* | remove \*; folder only
- +---------+--------
- */
+ /* phrase | action
+ +---------+--------
+ | \blah | remove \
+ | \*blah | remove \
+ | \*\blah | remove \
+ | \*\* | remove \
+ +---------+--------
+ | *blah |
+ | *\blah | -> add blah
+ | *\*blah | -> add *blah
+ +---------+--------
+ | blah\ | remove \; folder only
+ | blah*\ | remove \; folder only
+ | blah\*\ | remove \; folder only
+ +---------+--------
+ | blah* |
+ | blah\* | remove \*; folder only
+ | blah*\* | remove \*; folder only
+ +---------+-------- */
auto processTail = [&masksFileFolder, &masksFolder](const Zstring& phrase)
{
if (endsWith(phrase, FILE_NAME_SEPARATOR) || //only relevant for folder filtering
diff --git a/FreeFileSync/Source/base/path_filter.h b/FreeFileSync/Source/base/path_filter.h
index 0b80fbce..29705b06 100755
--- a/FreeFileSync/Source/base/path_filter.h
+++ b/FreeFileSync/Source/base/path_filter.h
@@ -31,6 +31,9 @@ NullFilter NameFilter CombinedFilter
class PathFilter;
using FilterRef = zen::SharedRef<const PathFilter>; //always bound by design! Thread-safety: internally synchronized!
+const Zchar FILTER_ITEM_SEPARATOR = Zstr('|');
+
+
class PathFilter //interface for filtering
{
public:
@@ -114,7 +117,6 @@ private:
const NameFilter second_;
};
-const Zchar FILTER_ITEM_SEPARATOR = Zstr('|');
diff --git a/FreeFileSync/Source/base/status_handler_impl.h b/FreeFileSync/Source/base/status_handler_impl.h
index bf4d7789..0153f748 100755
--- a/FreeFileSync/Source/base/status_handler_impl.h
+++ b/FreeFileSync/Source/base/status_handler_impl.h
@@ -36,7 +36,7 @@ public:
{
assert(!zen::runningMainThread());
{
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
if (ThreadStatus* ts = getThreadStatus()) //call while holding "lockCurrentStatus_" lock!!
ts->statusMsg = msg;
else assert(false);
@@ -57,7 +57,7 @@ public:
void logInfo(const std::wstring& msg) //throw ThreadInterruption
{
assert(!zen::runningMainThread());
- std::unique_lock<std::mutex> dummy(lockRequest_);
+ std::unique_lock dummy(lockRequest_);
zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !logInfoRequest_; }); //throw ThreadInterruption
logInfoRequest_ = /*std::move(taskPrefix) + */ msg;
@@ -70,7 +70,7 @@ public:
ProcessCallback::Response reportError(const std::wstring& msg, size_t retryNumber) //throw ThreadInterruption
{
assert(!zen::runningMainThread());
- std::unique_lock<std::mutex> dummy(lockRequest_);
+ std::unique_lock dummy(lockRequest_);
zen::interruptibleWait(conditionReadyForNewRequest_, dummy, [this] { return !errorRequest_ && !errorResponse_; }); //throw ThreadInterruption
errorRequest_ = ErrorInfo({ /*std::move(taskPrefix) + */ msg, retryNumber });
@@ -96,7 +96,7 @@ public:
{
const std::chrono::steady_clock::time_point callbackTime = std::chrono::steady_clock::now() + duration;
- for (std::unique_lock<std::mutex> dummy(lockRequest_) ;;) //process all errors without delay
+ for (std::unique_lock dummy(lockRequest_) ;;) //process all errors without delay
{
const bool rv = conditionNewRequest.wait_until(dummy, callbackTime, [this] { return (errorRequest_ && !errorResponse_) || logInfoRequest_ || finishNowRequest_; });
if (!rv) //time-out + condition not met
@@ -132,7 +132,7 @@ public:
{
assert(!zen::runningMainThread());
const uint64_t threadId = zen::getThreadId();
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
assert(!getThreadStatus());
//const size_t taskIdx = [&]() -> size_t
@@ -158,7 +158,7 @@ public:
{
assert(!zen::runningMainThread());
const uint64_t threadId = zen::getThreadId();
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
for (std::vector<ThreadStatus>& sbp : statusByPriority_)
for (ThreadStatus& ts : sbp)
@@ -174,7 +174,7 @@ public:
void notifyAllDone() //noexcept
{
- std::lock_guard<std::mutex> dummy(lockRequest_);
+ std::lock_guard dummy(lockRequest_);
assert(!finishNowRequest_);
finishNowRequest_ = true;
conditionNewRequest.notify_all(); //perf: should unlock mutex before notify!? (insignificant)
@@ -208,7 +208,7 @@ private:
{
const size_t taskIdx = [&]
{
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
const ThreadStatus* ts = getThreadStatus(); //call while holding "lockCurrentStatus_" lock!!
return ts ? ts->taskIdx : static_cast<size_t>(-2);
}();
@@ -243,7 +243,7 @@ private:
int parallelOpsTotal = 0;
std::wstring statusMsg;
{
- std::lock_guard<std::mutex> dummy(lockCurrentStatus_);
+ std::lock_guard dummy(lockCurrentStatus_);
for (const auto& sbp : statusByPriority_)
parallelOpsTotal += sbp.size();
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index fbb84f4d..04892fa9 100755
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -839,7 +839,7 @@ public:
{
interruptionPoint(); //throw ThreadInterruption
- std::unique_lock<std::mutex> dummy(lockWork_);
+ std::unique_lock dummy(lockWork_);
for (;;)
{
if (!workload_[threadIdx].empty())
@@ -889,7 +889,7 @@ public:
void addWorkItems(RingBuffer<WorkItems>&& buckets)
{
{
- std::lock_guard<std::mutex> dummy(lockWork_);
+ std::lock_guard dummy(lockWork_);
while (!buckets.empty())
{
pendingWorkload_.push_back(std::move(buckets. front()));
@@ -1071,7 +1071,7 @@ void FolderPairSyncer::runPass(PassNo pass, SyncCtx& syncCtx, BaseFolderPair& ba
acb.notifyTaskBegin(0 /*prio*/); //same prio, while processing only one folder pair at a time
ZEN_ON_SCOPE_EXIT(acb.notifyTaskEnd());
- std::lock_guard<std::mutex> dummy(singleThread); //protect ALL accesses to "fps" and workItem execution!
+ std::lock_guard dummy(singleThread); //protect ALL accesses to "fps" and workItem execution!
workItem(); //throw ThreadInterruption
}
});
@@ -2083,7 +2083,7 @@ AFS::FileCopyResult FolderPairSyncer::copyFileWithCallback(const FileDescriptor&
{
if (onDeleteTargetFile) //running *outside* singleThread_ lock! => onDeleteTargetFile-callback expects lock being held:
{
- std::lock_guard<std::mutex> dummy(singleThread_);
+ std::lock_guard dummy(singleThread_);
onDeleteTargetFile();
}
},
@@ -2275,7 +2275,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
std::vector<FolderPairJobType> jobType(folderCmp.size(), FolderPairJobType::PROCESS); //folder pairs may be skipped after fatal errors were found
- std::vector<SyncStatistics::ConflictInfo> unresolvedConflicts;
+ std::map<const BaseFolderPair*, std::vector<SyncStatistics::ConflictInfo>> unresolvedConflicts;
std::vector<std::tuple<AbstractPath, const PathFilter*, bool /*write access*/>> readWriteCheckBaseFolders;
@@ -2298,7 +2298,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
const SyncStatistics& folderPairStat = folderPairStats[folderIndex];
//aggregate all conflicts:
- append(unresolvedConflicts, folderPairStat.getConflicts());
+ unresolvedConflicts[&baseFolder] = folderPairStat.getConflicts();
//exclude a few pathological cases (including empty left, right folders)
if (baseFolder.getAbstractPath< LEFT_SIDE>() ==
@@ -2448,12 +2448,20 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
}
//check if unresolved conflicts exist
- if (!unresolvedConflicts.empty())
+ if (std::any_of(unresolvedConflicts.begin(), unresolvedConflicts.end(), [](const auto& item) { return !item.second.empty(); }))
{
std::wstring msg = _("The following items have unresolved conflicts and will not be synchronized:");
- for (const SyncStatistics::ConflictInfo& item : unresolvedConflicts) //show *all* conflicts in warning message
- msg += L"\n\n" + fmtPath(item.relPath) + L": " + item.msg;
+ for (const auto& [baseFolder, conflicts] : unresolvedConflicts)
+ if (!conflicts.empty())
+ {
+ msg += L"\n\n" + _("Folder pair:") + L" " +
+ AFS::getDisplayPath(baseFolder->getAbstractPath< LEFT_SIDE>()) + L" <-> " +
+ AFS::getDisplayPath(baseFolder->getAbstractPath<RIGHT_SIDE>());
+
+ for (const SyncStatistics::ConflictInfo& item : conflicts) //show *all* conflicts in warning message
+ msg += L"\n" + utfTo<std::wstring>(item.relPath) + L": " + item.msg;
+ }
callback.reportWarning(msg, warnings.warnUnresolvedConflicts);
}
diff --git a/FreeFileSync/Source/fs/abstract.cpp b/FreeFileSync/Source/fs/abstract.cpp
index 72b88fde..1c1b9c30 100755
--- a/FreeFileSync/Source/fs/abstract.cpp
+++ b/FreeFileSync/Source/fs/abstract.cpp
@@ -198,28 +198,23 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con
{
warn_static("doesnt make sense for Google Drive")
-
std::optional<AbstractPath> parentPath = AFS::getParentPath(apTarget);
if (!parentPath)
throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(AFS::getDisplayPath(apTarget))), L"Path is device root.");
const Zstring fileName = AFS::getItemName(apTarget);
//- generate (hopefully) unique file name to avoid clashing with some remnant ffs_tmp file
- //- do not loop and avoid pathological cases, e.g. https://freefilesync.org/forum/viewtopic.php?t=1592
+ //- do not loop: avoid pathological cases, e.g. https://freefilesync.org/forum/viewtopic.php?t=1592
const Zstring& shortGuid = printNumber<Zstring>(Zstr("%04x"), static_cast<unsigned int>(getCrc16(generateGUID())));
const Zstring& tmpExt = Zstr('.') + shortGuid + TEMP_FILE_ENDING;
- auto it = findLast(fileName.begin(), fileName.end(), Zstr('.')); //gracefully handle case of missing "."
-
- //don't make the temp name longer than the original; avoid hitting file system name length limitations: "lpMaximumComponentLength is commonly 255 characters"
- if (fileName.size() > 200) //BUT don't trim short names! we want early failure on filename-related issues
- it = std::min(it, fileName.end() - tmpExt.size());
- warn_static("utf8 anyone? atribtrarily trimming string, really?")
+ Zstring tmpName = beforeLast(fileName, Zstr('.'), IF_MISSING_RETURN_ALL);
- const Zstring& fileNameTmp = Zstring(fileName.begin(), it) + tmpExt;
+ //don't make the temp name longer than the original; avoid hitting file system name length limitations: "lpMaximumComponentLength is commonly 255 characters"
+ while (tmpName.size() > 200) //BUT don't trim short names! we want early failure on filename-related issues
+ tmpName = getUnicodeSubstring(tmpName, 0 /*uniPosFirst*/, unicodeLength(tmpName) / 2 /*uniPosLast*/); //consider UTF encoding when cutting in the middle! (e.g. for macOS)
- const AbstractPath apTargetTmp = AFS::appendRelPath(*parentPath, fileNameTmp);
- //AbstractPath apTargetTmp(apTarget.afsDevice, AfsPath(apTarget.afsPath.value + TEMP_FILE_ENDING));
+ const AbstractPath apTargetTmp = AFS::appendRelPath(*parentPath, tmpName + tmpExt);
//-------------------------------------------------------------------------------------------
const AFS::FileCopyResult result = copyFilePlain(apTargetTmp); //throw FileError, ErrorFileLocked
@@ -300,6 +295,7 @@ std::optional<AFS::ItemType> AFS::itemStillExistsViaFolderTraversal(const AfsPat
{
try
{
+ //fast check: 1. perf 2. expected by perfgetFolderStatusNonBlocking()
return getItemType(afsPath); //throw FileError
}
catch (const FileError& e) //not existing or access error
diff --git a/FreeFileSync/Source/fs/abstract.h b/FreeFileSync/Source/fs/abstract.h
index f7da2887..815fbf31 100755
--- a/FreeFileSync/Source/fs/abstract.h
+++ b/FreeFileSync/Source/fs/abstract.h
@@ -74,7 +74,7 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
static int geAccessTimeout(const AbstractPath& ap) { return ap.afsDevice.ref().getAccessTimeout(); } //returns "0" if no timeout in force
//----------------------------------------------------------------------------------------------------------------
- using FileId = zen::Zbase<char>;
+ using FileId = zen::Zbase<char>; //AfsDevice-dependent unique ID
enum class ItemType
{
diff --git a/FreeFileSync/Source/fs/concrete_impl.h b/FreeFileSync/Source/fs/concrete_impl.h
index c6ccd2d6..aa308372 100755
--- a/FreeFileSync/Source/fs/concrete_impl.h
+++ b/FreeFileSync/Source/fs/concrete_impl.h
@@ -77,7 +77,7 @@ public:
catch (...) { this->returnResult<Function>({ wi, std::current_exception(), {} }); }
}, insertFront);
- std::lock_guard<std::mutex> dummy(lockResult_);
+ std::lock_guard dummy(lockResult_);
++resultsPending_;
}
@@ -86,7 +86,7 @@ public:
{
std::apply([](auto&... r) { (..., r.clear()); }, results);
- std::unique_lock<std::mutex> dummy(lockResult_);
+ std::unique_lock dummy(lockResult_);
auto resultsReady = [&]
{
@@ -113,7 +113,7 @@ private:
void returnResult(TaskResult<Context, Function>&& r)
{
{
- std::lock_guard<std::mutex> dummy(lockResult_);
+ std::lock_guard dummy(lockResult_);
std::get<std::vector<TaskResult<Context, Function>>>(results_).push_back(std::move(r));
--resultsPending_;
diff --git a/FreeFileSync/Source/ui/folder_selector.h b/FreeFileSync/Source/ui/folder_selector.h
index 732ecd72..01ca8e2c 100755
--- a/FreeFileSync/Source/ui/folder_selector.h
+++ b/FreeFileSync/Source/ui/folder_selector.h
@@ -37,8 +37,8 @@ public:
wxButton& selectFolderButton,
wxButton& selectAltFolderButton,
FolderHistoryBox& folderComboBox,
- wxStaticText* staticText, //optional
- wxWindow* dropWindow2, //
+ wxStaticText* staticText, //optional
+ wxWindow* dropWindow2, //
const std::function<bool (const std::vector<Zstring>& shellItemPaths)>& droppedPathsFilter, //optional
const std::function<size_t(const Zstring& folderPathPhrase)>& getDeviceParallelOps, //mandatory
const std::function<void (const Zstring& folderPathPhrase, size_t parallelOps)>& setDeviceParallelOps); //optional
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index 573540b2..262f721b 100755
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -323,8 +323,6 @@ void CompareProgressDialog::Impl::updateProgressGui()
//status texts
setText(*m_staticTextStatus, replaceCpy(syncStat_->currentStatusText(), L'\n', L' ')); //no layout update for status texts!
- warn_static("harmonize phase handling!")
-
//write status information to taskbar, parent title etc.
switch (syncStat_->currentPhase())
{
diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp
index 7052dc23..d7d0926d 100755
--- a/FreeFileSync/Source/ui/version_check.cpp
+++ b/FreeFileSync/Source/ui/version_check.cpp
@@ -34,11 +34,13 @@ std::wstring getIso639Language()
{
assert(runningMainThread()); //this function is not thread-safe, consider wxWidgets usage
- const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()));
+ std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()));
+ localeName = beforeFirst(localeName, L"@", IF_MISSING_RETURN_ALL); //the locale may contain an @ on Linux, e.g. "en_US@morestuff"
+
if (!localeName.empty())
{
- assert(beforeLast(localeName, L"_", IF_MISSING_RETURN_ALL).size() == 2);
- return beforeLast(localeName, L"_", IF_MISSING_RETURN_ALL);
+ assert(beforeFirst(localeName, L"_", IF_MISSING_RETURN_ALL).size() == 2);
+ return beforeFirst(localeName, L"_", IF_MISSING_RETURN_ALL);
}
assert(false);
return L"zz";
@@ -49,12 +51,11 @@ std::wstring getIso3166Country()
{
assert(runningMainThread()); //this function is not thread-safe, consider wxWidgets usage
- const std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()));
- if (!localeName.empty())
- {
- if (contains(localeName, L"_"))
- return afterLast(localeName, L"_", IF_MISSING_RETURN_NONE);
- }
+ std::wstring localeName(wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()));
+ localeName = beforeFirst(localeName, L"@", IF_MISSING_RETURN_ALL); //the locale may contain an @ on Linux, e.g. "en_US@morestuff"
+
+ if (contains(localeName, L"_"))
+ return afterFirst(localeName, L"_", IF_MISSING_RETURN_NONE);
assert(false);
return L"ZZ";
}
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 0074ae18..9861b161 100755
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "10.6"; //internal linkage!
+const char ffsVersion[] = "10.7"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index 82c78760..ea19b6c7 100755
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -311,8 +311,8 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
{
//rename() will never fail with EEXIST, but always (atomically) overwrite!
//=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx + MOVEFILE_REPLACE_EXISTING
- //=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
- //=> OS X: no solution
+ //Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
+ //macOS: no solution
auto throwException = [&](int ec)
{
@@ -326,18 +326,19 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
throw FileError(errorMsg, errorDescr);
};
- if (!equalFilePath(pathSource, pathTarget)) //exception for OS X: changing file name case is not an "already exists" situation!
- {
- const bool alreadyExists = [&]
- {
- try { /*ItemType type = */getItemType(pathTarget); return true; } /*throw FileError*/
- catch (FileError&) { return false; }
- }();
+ struct ::stat infoSrc = {};
+ if (::lstat(pathSource.c_str(), &infoSrc) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathSource)), L"stat");
- if (alreadyExists)
- throwException(EEXIST);
- //else: nothing exists or other error (hopefully ::rename will also fail!)
- }
+ struct ::stat infoTrg = {};
+ if (::lstat(pathTarget.c_str(), &infoTrg) == 0)
+ {
+ if (infoSrc.st_dev != infoTrg.st_dev ||
+ infoSrc.st_ino != infoTrg.st_ino)
+ throwException(EEXIST); //that's what we're really here for
+ //else: continue with a rename in case
+ }
+ //else: not existing or access error (hopefully ::rename will also fail!)
if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0)
throwException(errno);
@@ -397,6 +398,7 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim
struct ::timespec newTimes[2] = {};
newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: https://freefilesync.org/forum/viewtopic.php?t=1701
newTimes[1] = modTime; //modification time
+ //test: even modTime == 0 is correctly applied (no NOOP!) test2: same behavior for "utime()"
if (procSl == ProcSymlink::FOLLOW)
{
diff --git a/zen/thread.h b/zen/thread.h
index 809bc771..8a9adc87 100755
--- a/zen/thread.h
+++ b/zen/thread.h
@@ -124,7 +124,7 @@ public:
template <class Function>
auto access(Function fun) //-> decltype(fun(std::declval<T&>()))
{
- std::lock_guard<std::mutex> dummy(lockValue_);
+ std::lock_guard dummy(lockValue_);
return fun(value_);
}
@@ -164,7 +164,7 @@ public:
void run(Function&& wi /*should throw ThreadInterruption when needed*/, bool insertFront = false)
{
{
- std::lock_guard<std::mutex> dummy(workLoad_->lock);
+ std::lock_guard dummy(workLoad_->lock);
if (insertFront)
workLoad_->tasks.push_front(std::move(wi));
@@ -194,7 +194,7 @@ public:
//non-blocking wait()-alternative: context of controlling thread:
void notifyWhenDone(const std::function<void()>& onCompletion /*noexcept! runs on worker thread!*/)
{
- std::lock_guard<std::mutex> dummy(workLoad_->lock);
+ std::lock_guard dummy(workLoad_->lock);
if (workLoad_->tasksPending == 0)
onCompletion();
@@ -217,7 +217,7 @@ private:
{
setCurrentThreadName(threadName.c_str());
- std::unique_lock<std::mutex> dummy(wl->lock);
+ std::unique_lock dummy(wl->lock);
for (;;)
{
interruptibleWait(wl->conditionNewTask, dummy, [&tasks = wl->tasks] { return !tasks.empty(); }); //throw ThreadInterruption
@@ -328,7 +328,7 @@ public:
void reportFinished(std::optional<T>&& result)
{
{
- std::lock_guard<std::mutex> dummy(lockResult_);
+ std::lock_guard dummy(lockResult_);
++jobsFinished_;
if (!result_)
result_ = std::move(result);
@@ -340,13 +340,13 @@ public:
template <class Duration>
bool waitForResult(size_t jobsTotal, const Duration& duration)
{
- std::unique_lock<std::mutex> dummy(lockResult_);
+ std::unique_lock dummy(lockResult_);
return conditionJobDone_.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); });
}
std::optional<T> getResult(size_t jobsTotal)
{
- std::unique_lock<std::mutex> dummy(lockResult_);
+ std::unique_lock dummy(lockResult_);
conditionJobDone_.wait(dummy, [&] { return this->jobDone(jobsTotal); });
return std::move(result_);
@@ -399,12 +399,12 @@ public:
interrupted_ = true;
{
- std::lock_guard<std::mutex> dummy(lockSleep_); //needed! makes sure the following signal is not lost!
+ std::lock_guard dummy(lockSleep_); //needed! makes sure the following signal is not lost!
//usually we'd make "interrupted" non-atomic, but this is already given due to interruptibleWait() handling
}
conditionSleepInterruption_.notify_all();
- std::lock_guard<std::mutex> dummy(lockConditionPtr_);
+ std::lock_guard dummy(lockConditionPtr_);
if (activeCondition_)
activeCondition_->notify_all(); //signal may get lost!
//alternative design locking the cv's mutex here could be dangerous: potential for dead lock!
@@ -436,7 +436,7 @@ public:
template <class Rep, class Period>
void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime) //throw ThreadInterruption
{
- std::unique_lock<std::mutex> lock(lockSleep_);
+ std::unique_lock lock(lockSleep_);
if (conditionSleepInterruption_.wait_for(lock, relTime, [this] { return static_cast<bool>(this->interrupted_); }))
throw ThreadInterruption();
}
@@ -444,7 +444,7 @@ public:
private:
void setConditionVar(std::condition_variable* cv)
{
- std::lock_guard<std::mutex> dummy(lockConditionPtr_);
+ std::lock_guard dummy(lockConditionPtr_);
activeCondition_ = cv;
}
diff --git a/zen/zstring.h b/zen/zstring.h
index 9fecdce3..a511e4e0 100755
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -32,11 +32,14 @@ Zstring makeUpperCopy(const Zstring& str);
//Windows, Linux: precomposed
//macOS: decomposed
Zstring getUnicodeNormalForm(const Zstring& str);
-
-Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const Zstring& newTerm);
+// "In fact, Unicode declares that there is an equivalence relationship between decomposed and composed sequences,
+// and conformant software should not treat canonically equivalent sequences, whether composed or decomposed or something inbetween, as different."
+// http://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html
struct LessUnicodeNormal { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return getUnicodeNormalForm(lhs) < getUnicodeNormalForm(rhs);} };
+Zstring replaceCpyAsciiNoCase(const Zstring& str, const Zstring& oldTerm, const Zstring& newTerm);
+
//------------------------------------------------------------------------------------------
inline bool equalNoCase(const Zstring& lhs, const Zstring& rhs) { return makeUpperCopy(lhs) == makeUpperCopy(rhs); }
@@ -53,7 +56,7 @@ inline bool operator<(const ZstringNoCase& lhs, const ZstringNoCase& rhs) { retu
//Compare *local* file paths:
// Windows: igore case
// Linux: byte-wise comparison
-// macOS: igore case + Unicode normalization forms
+// macOS: ignore case + Unicode normalization forms
int compareNativePath(const Zstring& lhs, const Zstring& rhs);
inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return compareNativePath(lhs, rhs) == 0; }
@@ -66,10 +69,6 @@ int compareNatural(const Zstring& lhs, const Zstring& rhs);
struct LessNaturalSort { bool operator()(const Zstring& lhs, const Zstring rhs) const { return compareNatural(lhs, rhs) < 0; } };
//------------------------------------------------------------------------------------------
-warn_static("get rid:")
-inline bool equalFilePath(const Zstring& lhs, const Zstring& rhs) { return compareNativePath(lhs, rhs) == 0; }
-//------------------------------------------------------------------------------------------
-
inline
bgstack15