UI: Handle remote browsing asynchronously.

This commit is contained in:
Unknown W. Brackets 2019-10-06 11:49:35 -07:00
parent 8d3da2ce88
commit e5eb849e8b
5 changed files with 157 additions and 35 deletions

View File

@ -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<std::string> &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<DirButton *> dirButtons;
std::vector<GameButton *> gameButtons;
listingPending_ = !path_.IsListingReady();
std::vector<std::string> 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> 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()) {

View File

@ -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<std::string> &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;

View File

@ -160,7 +160,9 @@ static bool LoadGameList(const std::string &host, int port, std::vector<std::str
return false;
}
for (auto &file : files) {
games.push_back(file.fullName);
if (RemoteISOFileSupported(file.name)) {
games.push_back(file.fullName);
}
}
// Save for next time unless manual is true

View File

@ -1,11 +1,13 @@
#include <algorithm>
#include <set>
#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<FileInfo> &files, const char *filter) {
bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector<FileInfo> &files) {
http::Client http;
Buffer result;
int code = 500;
@ -58,6 +60,28 @@ bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector<FileIn
return false;
}
for (std::string item : items) {
// Apply some workarounds.
if (item.empty())
continue;
if (item.back() == '\r')
item.pop_back();
FileInfo info;
info.name = item;
info.fullName = baseURL.Relative(item).ToString();
info.isDirectory = endsWith(item, "/");
info.exists = true;
info.size = 0;
info.isWritable = false;
files.push_back(info);
}
return !files.empty();
}
std::vector<FileInfo> ApplyFilter(std::vector<FileInfo> files, const char *filter) {
std::set<std::string> filters;
if (filter) {
std::string tmp;
@ -73,40 +97,33 @@ bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector<FileIn
filters.insert(std::move(tmp));
}
for (std::string item : items) {
// Apply some workarounds.
if (item.empty())
continue;
if (item.back() == '\r')
item.pop_back();
auto pred = [&](const FileInfo &info) {
if (info.isDirectory || !filter)
return false;
std::string ext = getFileExtension(info.fullName);
return filters.find(ext) == filters.end();
};
files.erase(std::remove_if(files.begin(), files.end(), pred), files.end());
return files;
}
FileInfo info;
info.name = item;
info.fullName = baseURL.Relative(item).ToString();
info.isDirectory = endsWith(item, "/");
info.exists = true;
info.size = 0;
info.isWritable = false;
if (!info.isDirectory) {
std::string ext = getFileExtension(info.fullName);
if (filter) {
if (filters.find(ext) == filters.end())
continue;
}
}
PathBrowser::~PathBrowser() {
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> guard(pendingLock_);
std::vector<FileInfo> 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> &fileInfo, const char *filter, bool *cancel) {
std::unique_lock<std::mutex> 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> &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();
}

View File

@ -1,7 +1,10 @@
#pragma once
#include <condition_variable>
#include <mutex>
#include <string>
#include <string.h>
#include <thread>
#include <vector>
#include <stdlib.h>
@ -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> &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<FileInfo> pendingFiles_;
std::condition_variable pendingCond_;
std::mutex pendingLock_;
std::thread pendingThread_;
bool pendingCancel_ = false;
bool pendingStop_ = false;
bool ready_ = false;
};