mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-26 23:10:38 +00:00
UI: Handle remote browsing asynchronously.
This commit is contained in:
parent
8d3da2ce88
commit
e5eb849e8b
@ -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)
|
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;
|
using namespace UI;
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
@ -510,10 +510,17 @@ bool GameBrowser::HasSpecialFiles(std::vector<std::string> &filenames) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameBrowser::Update() {
|
||||||
|
LinearLayout::Update();
|
||||||
|
if (listingPending_ && path_.IsListingReady()) {
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GameBrowser::Refresh() {
|
void GameBrowser::Refresh() {
|
||||||
using namespace UI;
|
using namespace UI;
|
||||||
|
|
||||||
homebrewStoreButton_ = 0;
|
homebrewStoreButton_ = nullptr;
|
||||||
// Kill all the contents
|
// Kill all the contents
|
||||||
Clear();
|
Clear();
|
||||||
|
|
||||||
@ -556,12 +563,14 @@ void GameBrowser::Refresh() {
|
|||||||
std::vector<DirButton *> dirButtons;
|
std::vector<DirButton *> dirButtons;
|
||||||
std::vector<GameButton *> gameButtons;
|
std::vector<GameButton *> gameButtons;
|
||||||
|
|
||||||
|
listingPending_ = !path_.IsListingReady();
|
||||||
|
|
||||||
std::vector<std::string> filenames;
|
std::vector<std::string> filenames;
|
||||||
if (HasSpecialFiles(filenames)) {
|
if (HasSpecialFiles(filenames)) {
|
||||||
for (size_t i = 0; i < filenames.size(); i++) {
|
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)));
|
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;
|
std::vector<FileInfo> fileInfo;
|
||||||
path_.GetListing(fileInfo, "iso:cso:pbp:elf:prx:ppdmp:");
|
path_.GetListing(fileInfo, "iso:cso:pbp:elf:prx:ppdmp:");
|
||||||
for (size_t i = 0; i < fileInfo.size(); i++) {
|
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++) {
|
for (size_t i = 0; i < dirButtons.size(); i++) {
|
||||||
gameList_->Add(dirButtons[i])->OnClick.Handle(this, &GameBrowser::NavigateClick);
|
gameList_->Add(dirButtons[i])->OnClick.Handle(this, &GameBrowser::NavigateClick);
|
||||||
}
|
}
|
||||||
@ -645,7 +658,7 @@ void GameBrowser::Refresh() {
|
|||||||
Add(new Spacer());
|
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)));
|
homebrewStoreButton_ = Add(new Choice(mm->T("DownloadFromStore", "Download from the PPSSPP Homebrew Store"), new UI::LinearLayoutParams(UI::WRAP_CONTENT, UI::WRAP_CONTENT)));
|
||||||
} else {
|
} else {
|
||||||
homebrewStoreButton_ = 0;
|
homebrewStoreButton_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lastText_.empty() && gameButtons.empty()) {
|
if (!lastText_.empty() && gameButtons.empty()) {
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
class GameBrowser : public UI::LinearLayout {
|
class GameBrowser : public UI::LinearLayout {
|
||||||
public:
|
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 OnChoice;
|
||||||
UI::Event OnHoldChoice;
|
UI::Event OnHoldChoice;
|
||||||
@ -37,6 +37,8 @@ public:
|
|||||||
void FocusGame(const std::string &gamePath);
|
void FocusGame(const std::string &gamePath);
|
||||||
void SetPath(const std::string &path);
|
void SetPath(const std::string &path);
|
||||||
|
|
||||||
|
void Update() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool DisplayTopBar();
|
virtual bool DisplayTopBar();
|
||||||
virtual bool HasSpecialFiles(std::vector<std::string> &filenames);
|
virtual bool HasSpecialFiles(std::vector<std::string> &filenames);
|
||||||
@ -57,15 +59,16 @@ private:
|
|||||||
UI::EventReturn HomeClick(UI::EventParams &e);
|
UI::EventReturn HomeClick(UI::EventParams &e);
|
||||||
UI::EventReturn PinToggleClick(UI::EventParams &e);
|
UI::EventReturn PinToggleClick(UI::EventParams &e);
|
||||||
|
|
||||||
UI::ViewGroup *gameList_;
|
UI::ViewGroup *gameList_ = nullptr;
|
||||||
PathBrowser path_;
|
PathBrowser path_;
|
||||||
bool *gridStyle_;
|
bool *gridStyle_;
|
||||||
bool allowBrowsing_;
|
bool allowBrowsing_;
|
||||||
std::string lastText_;
|
std::string lastText_;
|
||||||
std::string lastLink_;
|
std::string lastLink_;
|
||||||
int flags_;
|
int flags_;
|
||||||
UI::Choice *homebrewStoreButton_;
|
UI::Choice *homebrewStoreButton_ = nullptr;
|
||||||
std::string focusGamePath_;
|
std::string focusGamePath_;
|
||||||
|
bool listingPending_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RemoteISOBrowseScreen;
|
class RemoteISOBrowseScreen;
|
||||||
|
@ -160,7 +160,9 @@ static bool LoadGameList(const std::string &host, int port, std::vector<std::str
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (auto &file : files) {
|
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
|
// Save for next time unless manual is true
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include "base/stringutil.h"
|
#include "base/stringutil.h"
|
||||||
|
#include "base/timeutil.h"
|
||||||
#include "file/path.h"
|
#include "file/path.h"
|
||||||
#include "net/http_client.h"
|
#include "net/http_client.h"
|
||||||
#include "net/url.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;
|
http::Client http;
|
||||||
Buffer result;
|
Buffer result;
|
||||||
int code = 500;
|
int code = 500;
|
||||||
@ -58,6 +60,28 @@ bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector<FileIn
|
|||||||
return false;
|
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;
|
std::set<std::string> filters;
|
||||||
if (filter) {
|
if (filter) {
|
||||||
std::string tmp;
|
std::string tmp;
|
||||||
@ -73,40 +97,33 @@ bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector<FileIn
|
|||||||
filters.insert(std::move(tmp));
|
filters.insert(std::move(tmp));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::string item : items) {
|
auto pred = [&](const FileInfo &info) {
|
||||||
// Apply some workarounds.
|
if (info.isDirectory || !filter)
|
||||||
if (item.empty())
|
return false;
|
||||||
continue;
|
std::string ext = getFileExtension(info.fullName);
|
||||||
if (item.back() == '\r')
|
return filters.find(ext) == filters.end();
|
||||||
item.pop_back();
|
};
|
||||||
|
files.erase(std::remove_if(files.begin(), files.end(), pred), files.end());
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
FileInfo info;
|
PathBrowser::~PathBrowser() {
|
||||||
info.name = item;
|
std::unique_lock<std::mutex> guard(pendingLock_);
|
||||||
info.fullName = baseURL.Relative(item).ToString();
|
pendingCancel_ = true;
|
||||||
info.isDirectory = endsWith(item, "/");
|
pendingStop_ = true;
|
||||||
info.exists = true;
|
pendingCond_.notify_all();
|
||||||
info.size = 0;
|
guard.unlock();
|
||||||
info.isWritable = false;
|
|
||||||
if (!info.isDirectory) {
|
|
||||||
std::string ext = getFileExtension(info.fullName);
|
|
||||||
if (filter) {
|
|
||||||
if (filters.find(ext) == filters.end())
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files.push_back(info);
|
if (pendingThread_.joinable()) {
|
||||||
|
pendingThread_.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::sort(files.begin(), files.end());
|
|
||||||
return !files.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize slashes.
|
// Normalize slashes.
|
||||||
void PathBrowser::SetPath(const std::string &path) {
|
void PathBrowser::SetPath(const std::string &path) {
|
||||||
if (path[0] == '!') {
|
if (path[0] == '!') {
|
||||||
path_ = path;
|
path_ = path;
|
||||||
|
HandlePath();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
path_ = path;
|
path_ = path;
|
||||||
@ -115,9 +132,78 @@ void PathBrowser::SetPath(const std::string &path) {
|
|||||||
}
|
}
|
||||||
if (!path_.size() || (path_[path_.size() - 1] != '/'))
|
if (!path_.size() || (path_[path_.size() - 1] != '/'))
|
||||||
path_ += "/";
|
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) {
|
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
|
#ifdef _WIN32
|
||||||
if (path_ == "/") {
|
if (path_ == "/") {
|
||||||
// Special path that means root of file system.
|
// Special path that means root of file system.
|
||||||
@ -138,7 +224,8 @@ bool PathBrowser::GetListing(std::vector<FileInfo> &fileInfo, const char *filter
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (startsWith(path_, "http://") || startsWith(path_, "https://")) {
|
if (startsWith(path_, "http://") || startsWith(path_, "https://")) {
|
||||||
return LoadRemoteFileList(path_, cancel, fileInfo, filter);
|
fileInfo = ApplyFilter(pendingFiles_, filter);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
getFilesInDir(path_.c_str(), &fileInfo, filter);
|
getFilesInDir(path_.c_str(), &fileInfo, filter);
|
||||||
return true;
|
return true;
|
||||||
@ -167,4 +254,5 @@ void PathBrowser::Navigate(const std::string &path) {
|
|||||||
if (path_[path_.size() - 1] != '/')
|
if (path_[path_.size() - 1] != '/')
|
||||||
path_ += "/";
|
path_ += "/";
|
||||||
}
|
}
|
||||||
|
HandlePath();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
@ -14,8 +17,10 @@ class PathBrowser {
|
|||||||
public:
|
public:
|
||||||
PathBrowser() {}
|
PathBrowser() {}
|
||||||
PathBrowser(std::string path) { SetPath(path); }
|
PathBrowser(std::string path) { SetPath(path); }
|
||||||
|
~PathBrowser();
|
||||||
|
|
||||||
void SetPath(const std::string &path);
|
void SetPath(const std::string &path);
|
||||||
|
bool IsListingReady();
|
||||||
bool GetListing(std::vector<FileInfo> &fileInfo, const char *filter = nullptr, bool *cancel = nullptr);
|
bool GetListing(std::vector<FileInfo> &fileInfo, const char *filter = nullptr, bool *cancel = nullptr);
|
||||||
void Navigate(const std::string &path);
|
void Navigate(const std::string &path);
|
||||||
|
|
||||||
@ -39,6 +44,17 @@ public:
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandlePath();
|
||||||
|
|
||||||
std::string path_;
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user