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) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-24 02:44:17 +02:00
1 parent c14fc1d1d0
commit adfbd7a259
5 files changed
+29 -10

No files matched your search

+11 -6
View File
@@ -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<bool(NetListing &)> func, std::vector<FileInfo> *files) {
static bool ListNetworkWithTimeout(const Path &directory, std::function<bool(NetListing &)> func, std::vector<FileInfo> *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<NetListing>();
std::thread([listing, func] {
SetCurrentThreadName("NetListing");
@@ -166,11 +167,15 @@ static bool ListNetworkWithTimeout(const Path &directory, std::function<bool(Net
if (!listing->files.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<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const
return files;
}
bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const char *filter, int flags, std::string_view prefix) {
bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *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<FileInfo> *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;
+2 -1
View File
@@ -41,7 +41,8 @@ enum {
};
// Note: extensionFilter is ignored for directories, so you can recurse.
bool GetFilesInDir(const Path &directory, std::vector<FileInfo> *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<FileInfo> *files, const char *extensionFilter = nullptr, int flags = 0, std::string_view prefix = std::string_view(), const bool *cancel = nullptr);
std::vector<File::FileInfo> ApplyFilter(std::vector<File::FileInfo> files, const char *extensionFilter, std::string_view prefix);
#ifdef _WIN32
+12 -1
View File
@@ -115,6 +115,7 @@ PathBrowser::~PathBrowser() {
std::unique_lock<std::mutex> 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());
+2
View File
@@ -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;
};
+2 -2
View File
@@ -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;