From adfbd7a259f36088b8d85f9701fdd1f751372def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 24 Oct 2026 02:29:00 +0200 Subject: [PATCH] PathBrowser: Abandon a network listing when navigating away PathBrowser lists on one thread, so going up from the network folder waited for its listing to finish (up to 8 seconds) before the drive list showed. Pass GetFilesInDir a cancel flag that PathBrowser sets whenever the path changes, or on destruction, and check it while waiting. Also don't probe the drives in the drive list for game folders. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/File/DirListing.cpp | 17 +++++++++++------ Common/File/DirListing.h | 3 ++- Common/File/PathBrowser.cpp | 13 ++++++++++++- Common/File/PathBrowser.h | 2 ++ UI/GameBrowser.cpp | 4 ++-- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Common/File/DirListing.cpp b/Common/File/DirListing.cpp index f96691251c..cdfa4a41bf 100644 --- a/Common/File/DirListing.cpp +++ b/Common/File/DirListing.cpp @@ -145,10 +145,11 @@ struct NetListing { // Network calls can block for a long time (half a minute for the shell's network folder, even after it // has found everything), and can't be cancelled. So run them on a thread of their own, and abandon it // when the deadline passes, or when it has found something and then gone quiet for a while. -static bool ListNetworkWithTimeout(const Path &directory, std::function func, std::vector *files) { +static bool ListNetworkWithTimeout(const Path &directory, std::function func, std::vector *files, const bool *cancel) { // Servers turn up in the network folder after 2 to 6 seconds. constexpr auto timeout = std::chrono::seconds(8); constexpr auto quietTime = std::chrono::seconds(1); + constexpr auto cancelPollTime = std::chrono::milliseconds(20); auto listing = std::make_shared(); std::thread([listing, func] { SetCurrentThreadName("NetListing"); @@ -166,11 +167,15 @@ static bool ListNetworkWithTimeout(const Path &directory, std::functionfiles.empty()) { wakeup = std::min(deadline, listing->lastAdd + quietTime); } - if (std::chrono::steady_clock::now() >= wakeup) { + if (cancel && *cancel) { + return false; + } + const auto now = std::chrono::steady_clock::now(); + if (now >= wakeup) { INFO_LOG(Log::IO, "Stopped waiting for the listing of %s, found %d entries", directory.c_str(), (int)listing->files.size()); break; } - listing->cond.wait_until(guard, wakeup); + listing->cond.wait_until(guard, cancel ? std::min(wakeup, now + cancelPollTime) : wakeup); } files->insert(files->end(), listing->files.begin(), listing->files.end()); return listing->success || !listing->files.empty(); @@ -387,7 +392,7 @@ std::vector ApplyFilter(std::vector files, const return files; } -bool GetFilesInDir(const Path &directory, std::vector *files, const char *filter, int flags, std::string_view prefix) { +bool GetFilesInDir(const Path &directory, std::vector *files, const char *filter, int flags, std::string_view prefix, const bool *cancel) { if (LOG_IO) { INFO_LOG(Log::IO, "GetFilesInDir '%s' (ext %s, prefix %.*s)", directory.ToVisualString().c_str(), filter, (int)prefix.size(), prefix.data()); } @@ -452,11 +457,11 @@ bool GetFilesInDir(const Path &directory, std::vector *files, const ch if (server.empty()) { success = ListNetworkWithTimeout(directory, [prefixCopy](NetListing &listing) { return GetNetworkServers(prefixCopy, listing); - }, files); + }, files, cancel); } else { success = ListNetworkWithTimeout(directory, [directory, server, prefixCopy](NetListing &listing) { return GetUNCServerShares(directory, server, prefixCopy, listing); - }, files); + }, files, cancel); } std::sort(files->begin(), files->end()); return success; diff --git a/Common/File/DirListing.h b/Common/File/DirListing.h index 60e6ccc900..93ff57d6d5 100644 --- a/Common/File/DirListing.h +++ b/Common/File/DirListing.h @@ -41,7 +41,8 @@ enum { }; // Note: extensionFilter is ignored for directories, so you can recurse. -bool GetFilesInDir(const Path &directory, std::vector *files, const char *extensionFilter = nullptr, int flags = 0, std::string_view prefix = std::string_view()); +// cancel is only checked while waiting for a network listing (Windows), which can take seconds. +bool GetFilesInDir(const Path &directory, std::vector *files, const char *extensionFilter = nullptr, int flags = 0, std::string_view prefix = std::string_view(), const bool *cancel = nullptr); std::vector ApplyFilter(std::vector files, const char *extensionFilter, std::string_view prefix); #ifdef _WIN32 diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index 2928705514..0ee471f0ed 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -115,6 +115,7 @@ PathBrowser::~PathBrowser() { std::unique_lock guard(pendingLock_); pendingCancel_ = true; pendingStop_ = true; + listingAbort_ = true; pendingCond_.notify_all(); } if (pendingThread_.joinable()) { @@ -136,6 +137,10 @@ void PathBrowser::RestrictToRoot(const Path &root) { } void PathBrowser::HandlePath() { + // Asking for the path that's being listed again shouldn't restart it. + if (path_ != pendingPath_) { + listingAbort_ = true; + } if (!path_.empty() && path_.ToString()[0] == '!') { if (pendingActive_) { pendingCancel_ = true; @@ -171,6 +176,7 @@ void PathBrowser::HandlePath() { break; } lastPath = pendingPath_; + listingAbort_ = false; if (lastPath.Type() == PathType::HTTP) { std::string userAgentCopy = userAgent_; guard.unlock(); @@ -184,8 +190,13 @@ void PathBrowser::HandlePath() { } else { guard.unlock(); results.clear(); - bool tempSuccess = File::GetFilesInDir(lastPath, &results, nullptr); + bool tempSuccess = File::GetFilesInDir(lastPath, &results, nullptr, 0, std::string_view(), &listingAbort_); guard.lock(); + if (listingAbort_) { + // We navigated away, maybe and back again, in which case this lists it again. + lastPath.clear(); + continue; + } success_ = tempSuccess; if (!success_) { WARN_LOG(Log::IO, "PathBrowser: Failed to list directory: %s", lastPath.c_str()); diff --git a/Common/File/PathBrowser.h b/Common/File/PathBrowser.h index 0fdee76888..0c68b2a5d4 100644 --- a/Common/File/PathBrowser.h +++ b/Common/File/PathBrowser.h @@ -68,6 +68,8 @@ private: bool pendingActive_ = false; bool pendingCancel_ = false; bool pendingStop_ = false; + // Abandons a slow listing (network) when the path changes. + bool listingAbort_ = false; bool ready_ = false; bool success_ = true; }; diff --git a/UI/GameBrowser.cpp b/UI/GameBrowser.cpp index 9ba41574cb..447054b946 100644 --- a/UI/GameBrowser.cpp +++ b/UI/GameBrowser.cpp @@ -1133,8 +1133,8 @@ void GameBrowser::Refresh() { path_.GetListing(fileInfo, "iso:cso:chd:pbp:elf:prx:ppdmp:cue:ccd:mds:bin:img:"); // PS1 discs: a .bin or .img shows up as its cue sheet or .ccd, when it has one. RemoveDescribedDiscImages(&fileInfo); - // Servers aren't games, and probing one that's gone away can take seconds. - const bool probeDirs = !path_.GetPath().IsNetworkRoot(); + // Drives and servers aren't games, and probing a server that's gone away can take seconds. + const bool probeDirs = !path_.GetPath().IsRoot() && !path_.GetPath().IsNetworkRoot(); for (size_t i = 0; i < fileInfo.size(); i++) { bool isGame = !fileInfo[i].isDirectory; bool isSaveData = false;