From d2c4bc7276ea099ee8fc53ddb918c0f3458b4bb5 Mon Sep 17 00:00:00 2001 From: B Stack Date: Sat, 15 Dec 2018 08:58:32 -0500 Subject: 10.7 --- Changelog.txt | 16 ++++++++ FreeFileSync/Build/Languages/german.lng | 8 ++-- FreeFileSync/Build/Languages/turkish.lng | 52 +++++++++++++------------- FreeFileSync/Source/RealTimeSync/main_dlg.cpp | 2 + FreeFileSync/Source/base/binary.cpp | 18 ++++----- FreeFileSync/Source/base/comparison.cpp | 17 ++++++--- FreeFileSync/Source/base/dir_exist_async.h | 7 ++-- FreeFileSync/Source/base/file_hierarchy.h | 3 +- FreeFileSync/Source/base/icon_buffer.cpp | 14 +++---- FreeFileSync/Source/base/localization.cpp | 2 +- FreeFileSync/Source/base/parallel_scan.cpp | 14 +++---- FreeFileSync/Source/base/path_filter.cpp | 42 ++++++++++----------- FreeFileSync/Source/base/path_filter.h | 4 +- FreeFileSync/Source/base/status_handler_impl.h | 18 ++++----- FreeFileSync/Source/base/synchronization.cpp | 26 ++++++++----- FreeFileSync/Source/fs/abstract.cpp | 18 ++++----- FreeFileSync/Source/fs/abstract.h | 2 +- FreeFileSync/Source/fs/concrete_impl.h | 6 +-- FreeFileSync/Source/ui/folder_selector.h | 4 +- FreeFileSync/Source/ui/progress_indicator.cpp | 2 - FreeFileSync/Source/ui/version_check.cpp | 19 +++++----- FreeFileSync/Source/version/version.h | 2 +- zen/file_access.cpp | 28 +++++++------- zen/thread.h | 22 +++++------ zen/zstring.h | 13 +++---- 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 @@ Ausschließen: One base folder of a folder pair is contained in the other one. -Ein Basisordner eines Ordnerpaares ist im anderen enthalben. +Ein Basisordner eines Ordnerpaares ist im anderen enthalten. The folder should be excluded from synchronization via filter. Der Ordner sollte von der Synchronisation über den Filter ausgeschlossen werden. @@ -511,6 +511,9 @@ The following items have unresolved conflicts and will not be synchronized: Die folgenden Elemente haben ungelöste Konflikte und werden nicht synchronisiert werden: +Folder pair: +Ordnerpaar: + The following folders are significantly different. Please check that the correct folders are selected for synchronization. Die folgenden Ordner unterscheiden sich erheblich. Überprüfen Sie, ob die richtigen Ordner für die Synchronisation ausgewählt wurden. @@ -1052,9 +1055,6 @@ Die Befehlszeile wird ausgelöst, wenn: Arrange folder pair Ordnerpaar anordnen -Folder pair: -Ordnerpaar: - Main settings: Haupteinstellungen: 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 @@ Yalnız öznitelikleri farklı olan ögeler The name %x is used by more than one item in the folder. - +%x adı klasördeki birden fazla öge için kullanılmış. Resolving symbolic link %x %x sembolik bağlantısı çözümleniyor @@ -335,10 +335,10 @@ Sağdaki öznitelikler güncellensin Errors: - +Hatalar: Warnings: - +Uyarılar: Items processed: İşlenen öge: @@ -362,19 +362,19 @@ %x dosyası işlenirken sorun çıktı, satır %y, sütun %z. Services - +Hizmetler Show All - +Tümünü Görüntüle Hide Others - +Diğerlerini Gizle Hide %x - +%x Ögesini Gizle Quit %x - +%x Uygulamasından Çık Cannot set directory locks for the following folders: Şu klasörler kilitlenemedi: @@ -392,10 +392,10 @@ %x klasörü okunamadı. %x/sec - +%x/saniye %x items - +%x öge Show in Explorer Tarayıcıda Görüntüle @@ -545,10 +545,10 @@ Veritabanı oluşturuluyor... Searching for old file versions: - +Önceki dosya sürümleri aranıyor: Removing old file versions: - +Önceki dosya sürümleri siliniyor: Unable to create time stamp for versioning: Sürümlendirme için zaman damgası oluşturulamadı: @@ -610,16 +610,16 @@ Gerçekleşen: %y bayt %x üzerine erişilemedi. Authentication completed. - +Kimlik doğrulandı. Authentication failed. - +Kimlik doğrulanamadı. You may close this page now and continue with FreeFileSync. - +Bu sayfayı kapatıp FreeFileSync ile çalışmayı sürdürebilirsiniz. The server returned an error: - +Sunucu şu hatayı bildirdi: Cannot determine free disk space for %x. %x için boş disk alanı belirlenemedi. @@ -942,7 +942,7 @@ Komut şu durumlarda yürütülür: &Toplu İş Olarak Kaydet... Show &log - +Gün&lüğü Görüntüle Start &comparison &Karşılaştırmayı Başlat @@ -1172,10 +1172,10 @@ Komut şu durumlarda yürütülür: Son x Gün: &Override default log path: - +Varsayılan yerine kullanılacak günlük y&olu: Run a command: - +Bir komut yürüt: OK Tamam @@ -1226,7 +1226,7 @@ Komut şu durumlarda yürütülür: Sunucudaki Klasör: Access timeout (in seconds): - +Erişim zaman aşımı (saniye): SFTP channels per connection: Bir Bağlantı için SFTP Kanalı Sayısı: @@ -1343,10 +1343,10 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Kalıcı olarak gizlenmiş tüm ileti ve uyarılar yeniden görüntülenir Default log path: - +Varsayılan günlük dosyası yolu: &Delete logs after x days: - +&Günlük kayıtlarının silineceği gün sayısı: Customize context menu: Sağ Tık Menüsü Uyarlamaları: @@ -1358,7 +1358,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y &Varsayılan Feedback and suggestions are welcome: - +Geri bildirim ve önerilerinizi bekliyoruz: Home page Ana Sayfa @@ -1385,7 +1385,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Kaynak kodu C++ kullanılarak yazılmıştır: Published under the GNU General Public License: - +GNU Genel Kamu Lisansı koşulları altında yayınlanmıştır: Many thanks for localization: Çeviriler için çok teşekkürler: @@ -1442,7 +1442,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y Bilgi No log entries - +Herhangi bir günlük kaydı yok Select all Tümünü Seç @@ -2060,7 +2060,7 @@ Bu yöntem, ciddi bir sorun çıkması durumunda bile işlemin tutarlı olarak y FreeFileSync ile Düzenlensin Instead of an ad, here's an animal. -Bir reklam yerine burada bir hayvan var. +Burada bir reklam yerine bir hayvan var. The FreeFileSync portable version cannot install into a subfolder of %x. FreeFileSync taşınabilir sürümü bir %x alt klasörüne yüklenemez. 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& 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 -Zstringw getDescrDiffMetaDate(const FileOrLinkPair& file) +Zstringw getDescrDiffMetaData(const FileOrLinkPair& file) { return copyStringTo(_("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())); } +#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(), // symlink.getLastWriteTime(), symlink.base().getFileTimeTolerance(), symlink.base().getIgnoredTimeShift())) - // symlink.setCategoryDiffMetadata(getDescrDiffMetaDate(symlink)); + // symlink.setCategoryDiffMetadata(getDescrDiffMetaData(symlink)); else symlink.setCategory(); } @@ -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(), file.getLastWriteTime(), file.base().getFileTimeTolerance(), file.base().getIgnoredTimeShift())) - file.setCategoryDiffMetadata(getDescrDiffMetaDate(file)); + file.setCategoryDiffMetadata(getDescrDiffMetaData(file)); #endif else file.setCategory(); @@ -611,7 +613,7 @@ std::list> 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 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> ComparisonBuffer::compareByContent(co }; { - std::lock_guard 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 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 output = std::make_shared(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& folderPath const auto startTime = std::chrono::steady_clock::now(); FolderStatus output; - std::map exFoldersById; + std::map, 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& 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 -//#include //required by GCC 4.9 to find ptrdiff_t #include #include #include @@ -109,7 +108,7 @@ struct FolderContainer //------------------------------------------------------------------ using FolderList = std::map>; // using FileList = std::map; //key: raw file name, without any (Unicode) normalization, preserving original upper-/lower-case - using SymlinkList = std::map; // + using SymlinkList = std::map; //"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 dummy(lockFiles_); + std::lock_guard dummy(lockFiles_); workLoad_.clear(); for (const AbstractPath& filePath : newLoad) @@ -96,7 +96,7 @@ public: { assert(runningMainThread()); { - std::lock_guard 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 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 dummy(lockIconList_); + std::lock_guard dummy(lockIconList_); return iconList.find(filePath) != iconList.end(); } @@ -137,7 +137,7 @@ public: std::optional retrieve(const AbstractPath& filePath) { assert(runningMainThread()); - std::lock_guard 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 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 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 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 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 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 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 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 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 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& masksFileFolder, std::vector& 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; //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 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 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 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 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 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 dummy(lockCurrentStatus_); + std::lock_guard dummy(lockCurrentStatus_); for (std::vector& sbp : statusByPriority_) for (ThreadStatus& ts : sbp) @@ -174,7 +174,7 @@ public: void notifyAllDone() //noexcept { - std::lock_guard 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 dummy(lockCurrentStatus_); + std::lock_guard dummy(lockCurrentStatus_); const ThreadStatus* ts = getThreadStatus(); //call while holding "lockCurrentStatus_" lock!! return ts ? ts->taskIdx : static_cast(-2); }(); @@ -243,7 +243,7 @@ private: int parallelOpsTotal = 0; std::wstring statusMsg; { - std::lock_guard 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 dummy(lockWork_); + std::unique_lock dummy(lockWork_); for (;;) { if (!workload_[threadIdx].empty()) @@ -889,7 +889,7 @@ public: void addWorkItems(RingBuffer&& buckets) { { - std::lock_guard 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 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 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 jobType(folderCmp.size(), FolderPairJobType::PROCESS); //folder pairs may be skipped after fatal errors were found - std::vector unresolvedConflicts; + std::map> unresolvedConflicts; std::vector> 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()); + + for (const SyncStatistics::ConflictInfo& item : conflicts) //show *all* conflicts in warning message + msg += L"\n" + utfTo(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 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(Zstr("%04x"), static_cast(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::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; + using FileId = zen::Zbase; //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({ wi, std::current_exception(), {} }); } }, insertFront); - std::lock_guard dummy(lockResult_); + std::lock_guard dummy(lockResult_); ++resultsPending_; } @@ -86,7 +86,7 @@ public: { std::apply([](auto&... r) { (..., r.clear()); }, results); - std::unique_lock dummy(lockResult_); + std::unique_lock dummy(lockResult_); auto resultsReady = [&] { @@ -113,7 +113,7 @@ private: void returnResult(TaskResult&& r) { { - std::lock_guard dummy(lockResult_); + std::lock_guard dummy(lockResult_); std::get>>(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& shellItemPaths)>& droppedPathsFilter, //optional const std::function& getDeviceParallelOps, //mandatory const std::function& 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 auto access(Function fun) //-> decltype(fun(std::declval())) { - std::lock_guard 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 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& onCompletion /*noexcept! runs on worker thread!*/) { - std::lock_guard 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 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&& result) { { - std::lock_guard dummy(lockResult_); + std::lock_guard dummy(lockResult_); ++jobsFinished_; if (!result_) result_ = std::move(result); @@ -340,13 +340,13 @@ public: template bool waitForResult(size_t jobsTotal, const Duration& duration) { - std::unique_lock dummy(lockResult_); + std::unique_lock dummy(lockResult_); return conditionJobDone_.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); }); } std::optional getResult(size_t jobsTotal) { - std::unique_lock 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 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 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 void interruptibleSleep(const std::chrono::duration& relTime) //throw ThreadInterruption { - std::unique_lock lock(lockSleep_); + std::unique_lock lock(lockSleep_); if (conditionSleepInterruption_.wait_for(lock, relTime, [this] { return static_cast(this->interrupted_); })) throw ThreadInterruption(); } @@ -444,7 +444,7 @@ public: private: void setConditionVar(std::condition_variable* cv) { - std::lock_guard 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 -- cgit