diff options
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 |