diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 5df357ba96..364e552f9d 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -441,7 +441,7 @@ void DirButton::Draw(UIContext &dc) { } GameBrowser::GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle, std::string lastText, std::string lastLink, int flags, UI::LayoutParams *layoutParams) - : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), gameList_(0), path_(path), gridStyle_(gridStyle), allowBrowsing_(allowBrowsing), lastText_(lastText), lastLink_(lastLink), flags_(flags) { + : LinearLayout(UI::ORIENT_VERTICAL, layoutParams), path_(path), gridStyle_(gridStyle), allowBrowsing_(allowBrowsing), lastText_(lastText), lastLink_(lastLink), flags_(flags) { using namespace UI; Refresh(); } @@ -510,10 +510,17 @@ bool GameBrowser::HasSpecialFiles(std::vector &filenames) { return false; } +void GameBrowser::Update() { + LinearLayout::Update(); + if (listingPending_ && path_.IsListingReady()) { + Refresh(); + } +} + void GameBrowser::Refresh() { using namespace UI; - homebrewStoreButton_ = 0; + homebrewStoreButton_ = nullptr; // Kill all the contents Clear(); @@ -556,12 +563,14 @@ void GameBrowser::Refresh() { std::vector dirButtons; std::vector gameButtons; + listingPending_ = !path_.IsListingReady(); + std::vector filenames; if (HasSpecialFiles(filenames)) { for (size_t i = 0; i < filenames.size(); i++) { gameButtons.push_back(new GameButton(filenames[i], *gridStyle_, new UI::LinearLayoutParams(*gridStyle_ == true ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT))); } - } else { + } else if (!listingPending_) { std::vector fileInfo; path_.GetListing(fileInfo, "iso:cso:pbp:elf:prx:ppdmp:"); for (size_t i = 0; i < fileInfo.size(); i++) { @@ -616,6 +625,10 @@ void GameBrowser::Refresh() { } } + if (listingPending_) { + gameList_->Add(new UI::TextView(mm->T("Loading..."), ALIGN_CENTER, false, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT))); + } + for (size_t i = 0; i < dirButtons.size(); i++) { gameList_->Add(dirButtons[i])->OnClick.Handle(this, &GameBrowser::NavigateClick); } @@ -645,7 +658,7 @@ void GameBrowser::Refresh() { Add(new Spacer()); homebrewStoreButton_ = Add(new Choice(mm->T("DownloadFromStore", "Download from the PPSSPP Homebrew Store"), new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT))); } else { - homebrewStoreButton_ = 0; + homebrewStoreButton_ = nullptr; } if (!lastText_.empty() && gameButtons.empty()) { diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 93d21a2984..199c379187 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -26,7 +26,7 @@ class GameBrowser : public UI::LinearLayout { public: - GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle_, std::string lastText, std::string lastLink, int flags = 0, UI::LayoutParams *layoutParams = 0); + GameBrowser(std::string path, bool allowBrowsing, bool *gridStyle, std::string lastText, std::string lastLink, int flags = 0, UI::LayoutParams *layoutParams = nullptr); UI::Event OnChoice; UI::Event OnHoldChoice; @@ -37,6 +37,8 @@ public: void FocusGame(const std::string &gamePath); void SetPath(const std::string &path); + void Update() override; + protected: virtual bool DisplayTopBar(); virtual bool HasSpecialFiles(std::vector &filenames); @@ -57,15 +59,16 @@ private: UI::EventReturn HomeClick(UI::EventParams &e); UI::EventReturn PinToggleClick(UI::EventParams &e); - UI::ViewGroup *gameList_; + UI::ViewGroup *gameList_ = nullptr; PathBrowser path_; bool *gridStyle_; bool allowBrowsing_; std::string lastText_; std::string lastLink_; int flags_; - UI::Choice *homebrewStoreButton_; + UI::Choice *homebrewStoreButton_ = nullptr; std::string focusGamePath_; + bool listingPending_ = false; }; class RemoteISOBrowseScreen; diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 8cf0a19ab9..f853e49e22 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -160,7 +160,9 @@ static bool LoadGameList(const std::string &host, int port, std::vector #include #include "base/stringutil.h" +#include "base/timeutil.h" #include "file/path.h" #include "net/http_client.h" #include "net/url.h" +#include "thread/threadutil.h" -bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector &files, const char *filter) { +bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector &files) { http::Client http; Buffer result; int code = 500; @@ -58,6 +60,28 @@ bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector ApplyFilter(std::vector files, const char *filter) { std::set filters; if (filter) { std::string tmp; @@ -73,40 +97,33 @@ bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector guard(pendingLock_); + pendingCancel_ = true; + pendingStop_ = true; + pendingCond_.notify_all(); + guard.unlock(); - files.push_back(info); + if (pendingThread_.joinable()) { + pendingThread_.join(); } - - - std::sort(files.begin(), files.end()); - return !files.empty(); } // Normalize slashes. void PathBrowser::SetPath(const std::string &path) { if (path[0] == '!') { path_ = path; + HandlePath(); return; } path_ = path; @@ -115,9 +132,78 @@ void PathBrowser::SetPath(const std::string &path) { } if (!path_.size() || (path_[path_.size() - 1] != '/')) path_ += "/"; + HandlePath(); +} + +void PathBrowser::HandlePath() { + std::lock_guard guard(pendingLock_); + + if (!path_.empty() && path_[0] == '!') { + ready_ = true; + pendingCancel_ = true; + pendingPath_.clear(); + return; + } + if (!startsWith(path_, "http://") && !startsWith(path_, "https://")) { + ready_ = true; + pendingCancel_ = true; + pendingPath_.clear(); + return; + } + + ready_ = false; + pendingCancel_ = false; + pendingFiles_.clear(); + pendingPath_ = path_; + pendingCond_.notify_all(); + + if (pendingThread_.joinable()) + return; + + pendingThread_ = std::thread([&] { + setCurrentThreadName("PathBrowser"); + + std::unique_lock guard(pendingLock_); + std::vector results; + std::string lastPath; + while (!pendingStop_) { + while (lastPath == pendingPath_ && !pendingCancel_) { + pendingCond_.wait(guard); + } + lastPath = pendingPath_; + bool success = false; + if (!lastPath.empty()) { + guard.unlock(); + results.clear(); + success = LoadRemoteFileList(lastPath, &pendingCancel_, results); + guard.lock(); + } + + if (pendingPath_ == lastPath) { + if (success && !pendingCancel_) { + pendingFiles_ = results; + } + pendingPath_.clear(); + lastPath.clear(); + ready_ = true; + } + } + }); +} + +bool PathBrowser::IsListingReady() { + return ready_; } bool PathBrowser::GetListing(std::vector &fileInfo, const char *filter, bool *cancel) { + std::unique_lock guard(pendingLock_); + while (!IsListingReady() && (!cancel || !*cancel)) { + // In case cancel changes, just sleep. + guard.unlock(); + sleep_ms(100); + guard.lock(); + } + #ifdef _WIN32 if (path_ == "/") { // Special path that means root of file system. @@ -138,7 +224,8 @@ bool PathBrowser::GetListing(std::vector &fileInfo, const char *filter #endif if (startsWith(path_, "http://") || startsWith(path_, "https://")) { - return LoadRemoteFileList(path_, cancel, fileInfo, filter); + fileInfo = ApplyFilter(pendingFiles_, filter); + return true; } else { getFilesInDir(path_.c_str(), &fileInfo, filter); return true; @@ -167,4 +254,5 @@ void PathBrowser::Navigate(const std::string &path) { if (path_[path_.size() - 1] != '/') path_ += "/"; } + HandlePath(); } diff --git a/ext/native/file/path.h b/ext/native/file/path.h index fe4b78b776..ed14962156 100644 --- a/ext/native/file/path.h +++ b/ext/native/file/path.h @@ -1,7 +1,10 @@ #pragma once +#include +#include #include #include +#include #include #include @@ -14,8 +17,10 @@ class PathBrowser { public: PathBrowser() {} PathBrowser(std::string path) { SetPath(path); } + ~PathBrowser(); void SetPath(const std::string &path); + bool IsListingReady(); bool GetListing(std::vector &fileInfo, const char *filter = nullptr, bool *cancel = nullptr); void Navigate(const std::string &path); @@ -39,6 +44,17 @@ public: return str; } +private: + void HandlePath(); + std::string path_; + std::string pendingPath_; + std::vector pendingFiles_; + std::condition_variable pendingCond_; + std::mutex pendingLock_; + std::thread pendingThread_; + bool pendingCancel_ = false; + bool pendingStop_ = false; + bool ready_ = false; };