diff --git a/Core/Config.cpp b/Core/Config.cpp index 200c316029..e2142ce102 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1141,8 +1141,9 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { vPinnedPaths.clear(); for (auto it = pinnedPaths.begin(), end = pinnedPaths.end(); it != end; ++it) { // Unpin paths that are deleted automatically. - if (File::Exists(it->second)) { - vPinnedPaths.push_back(File::ResolvePath(it->second)); + const std::string &path = it->second; + if (startsWith(path, "http://") || startsWith(path, "https://") || File::Exists(path)) { + vPinnedPaths.push_back(File::ResolvePath(path)); } } diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index e2028f5fda..d03c01e6ae 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -577,7 +577,7 @@ void GameBrowser::Refresh() { if (!isGame && !isSaveData) { if (allowBrowsing_) { - dirButtons.push_back(new DirButton(fileInfo[i].name, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT))); + dirButtons.push_back(new DirButton(fileInfo[i].fullName, fileInfo[i].name, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT))); } } else { gameButtons.push_back(new GameButton(fileInfo[i].fullName, *gridStyle_, new UI::LinearLayoutParams(*gridStyle_ == true ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT))); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index bae96cefff..8cf0a19ab9 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -20,6 +20,7 @@ #include #include "base/timeutil.h" +#include "file/path.h" #include "i18n/i18n.h" #include "json/json_reader.h" #include "net/http_client.h" @@ -146,84 +147,21 @@ static bool FindServer(std::string &resultHost, int &resultPort) { return false; } -bool LoadRemoteGameList(const std::string &url, bool *cancel, std::vector &games) { - http::Client http; - Buffer result; - int code = 500; - std::vector responseHeaders; - - Url baseURL(url); - if (!baseURL.Valid()) { - return false; - } - - // Start by requesting the list of games from the server. - if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) { - if (http.Connect(2, 20.0, cancel)) { - code = http.GET(baseURL.Resource().c_str(), &result, responseHeaders); - http.Disconnect(); - } - } - - if (code != 200 || (cancel && *cancel)) { - return false; - } - - std::string listing; - std::vector items; - result.TakeAll(&listing); - - std::string contentType; - for (const std::string &header : responseHeaders) { - if (startsWithNoCase(header, "Content-Type:")) { - contentType = header.substr(strlen("Content-Type:")); - // Strip any whitespace (TODO: maybe move this to stringutil?) - contentType.erase(0, contentType.find_first_not_of(" \t\r\n")); - contentType.erase(contentType.find_last_not_of(" \t\r\n") + 1); - } - } - - // TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not. - // But unlikely that'll be an issue. - bool parseHtml = startsWithNoCase(contentType, "text/html"); - bool parseText = startsWithNoCase(contentType, "text/plain"); - - if (parseText) { - // Plain text format - easy. - SplitString(listing, '\n', items); - } else if (parseHtml) { - // Try to extract from an automatic webserver directory listing... - GetQuotedStrings(listing, items); - } else { - ERROR_LOG(FILESYS, "Unsupported Content-Type: %s", contentType.c_str()); - return false; - } - - for (std::string item : items) { - // Apply some workarounds. - if (item.empty()) - continue; - - if (item.back() == '\r') - item.pop_back(); - - if (!RemoteISOFileSupported(item)) { - continue; - } - - games.push_back(baseURL.Relative(item).ToString()); - } - - return !games.empty(); -} - static bool LoadGameList(const std::string &host, int port, std::vector &games) { std::string subdir = RemoteSubdir(); char temp[1024]; snprintf(temp, sizeof(temp) - 1, "http://%s:%d%s", host.c_str(), port, subdir.c_str()); - LoadRemoteGameList(temp, &scanCancelled, games); + PathBrowser browser(temp); + std::vector files; + browser.GetListing(files, "iso:cso:pbp:elf:prx:ppdmp:", &scanCancelled); + if (scanCancelled) { + return false; + } + for (auto &file : files) { + games.push_back(file.fullName); + } // Save for next time unless manual is true if (!games.empty() && !g_Config.bRemoteISOManual) { diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index 9a4ca9fde7..b7f7ff0f5c 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -25,8 +25,6 @@ #include "UI/MiscScreens.h" #include "UI/MainScreen.h" -bool LoadRemoteGameList(const std::string &url, bool *cancel, std::vector &games); - class RemoteISOScreen : public UIScreenWithBackground { public: RemoteISOScreen(); diff --git a/ext/native/file/path.cpp b/ext/native/file/path.cpp index d50a64f237..ced159863d 100644 --- a/ext/native/file/path.cpp +++ b/ext/native/file/path.cpp @@ -1,4 +1,107 @@ +#include +#include +#include "base/stringutil.h" #include "file/path.h" +#include "net/http_client.h" +#include "net/url.h" + +bool LoadRemoteFileList(const std::string &url, bool *cancel, std::vector &files, const char *filter) { + http::Client http; + Buffer result; + int code = 500; + std::vector responseHeaders; + + Url baseURL(url); + if (!baseURL.Valid()) { + return false; + } + + // Start by requesting the list of files from the server. + if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) { + if (http.Connect(2, 20.0, cancel)) { + code = http.GET(baseURL.Resource().c_str(), &result, responseHeaders); + http.Disconnect(); + } + } + + if (code != 200 || (cancel && *cancel)) { + return false; + } + + std::string listing; + std::vector items; + result.TakeAll(&listing); + + std::string contentType; + for (const std::string &header : responseHeaders) { + if (startsWithNoCase(header, "Content-Type:")) { + contentType = header.substr(strlen("Content-Type:")); + // Strip any whitespace (TODO: maybe move this to stringutil?) + contentType.erase(0, contentType.find_first_not_of(" \t\r\n")); + contentType.erase(contentType.find_last_not_of(" \t\r\n") + 1); + } + } + + // TODO: Technically, "TExt/hTml ; chaRSet = Utf8" should pass, but "text/htmlese" should not. + // But unlikely that'll be an issue. + bool parseHtml = startsWithNoCase(contentType, "text/html"); + bool parseText = startsWithNoCase(contentType, "text/plain"); + + if (parseText) { + // Plain text format - easy. + SplitString(listing, '\n', items); + } else if (parseHtml) { + // Try to extract from an automatic webserver directory listing... + GetQuotedStrings(listing, items); + } else { + ELOG("Unsupported Content-Type: %s", contentType.c_str()); + return false; + } + + std::set filters; + if (filter) { + std::string tmp; + while (*filter) { + if (*filter == ':') { + filters.insert(std::move(tmp)); + } else { + tmp.push_back(*filter); + } + filter++; + } + if (!tmp.empty()) + filters.insert(std::move(tmp)); + } + + 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; + if (!info.isDirectory) { + std::string ext = getFileExtension(info.fullName); + if (filter) { + if (filters.find(ext) == filters.end()) + continue; + } + } + + files.push_back(info); + } + + + std::sort(files.begin(), files.end()); + return !files.empty(); +} // Normalize slashes. void PathBrowser::SetPath(const std::string &path) { @@ -14,7 +117,7 @@ void PathBrowser::SetPath(const std::string &path) { path_ += "/"; } -void PathBrowser::GetListing(std::vector &fileInfo, const char *filter) { +bool PathBrowser::GetListing(std::vector &fileInfo, const char *filter, bool *cancel) { #ifdef _WIN32 if (path_ == "/") { // Special path that means root of file system. @@ -34,7 +137,12 @@ void PathBrowser::GetListing(std::vector &fileInfo, const char *filter } #endif - getFilesInDir(path_.c_str(), &fileInfo, filter); + if (startsWith(path_, "http://") || startsWith(path_, "https://")) { + return LoadRemoteFileList(path_, cancel, fileInfo, filter); + } else { + getFilesInDir(path_.c_str(), &fileInfo, filter); + return true; + } } // TODO: Support paths like "../../hello" @@ -52,7 +160,7 @@ void PathBrowser::Navigate(const std::string &path) { path_ = path_.substr(0, slash + 1); } } else { - if (path[1] == ':' && path_ == "/") + if (path.size() > 2 && path[1] == ':' && path_ == "/") path_ = path; else path_ = path_ + path; diff --git a/ext/native/file/path.h b/ext/native/file/path.h index b54e9be494..fe4b78b776 100644 --- a/ext/native/file/path.h +++ b/ext/native/file/path.h @@ -16,7 +16,7 @@ public: PathBrowser(std::string path) { SetPath(path); } void SetPath(const std::string &path); - void GetListing(std::vector &fileInfo, const char *filter = 0); + bool GetListing(std::vector &fileInfo, const char *filter = nullptr, bool *cancel = nullptr); void Navigate(const std::string &path); std::string GetPath() const {