From ee29c6c50fb73b58fad72f4a34b58d379d9af677 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 10:18:54 -0700 Subject: [PATCH 01/19] http: Allow binding a random port, timeout. The timeout is useful to allow for shutdown of the thread. --- CMakeLists.txt | 8 +++- ext/native/net/http_server.cpp | 81 ++++++++++++++++++++++------------ ext/native/net/http_server.h | 7 ++- 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5878540b6697..db814c4b2f2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -995,10 +995,12 @@ add_library(native STATIC ext/native/math/lin/vec3.h ext/native/math/math_util.cpp ext/native/math/math_util.h - ext/native/net/http_server.cpp - ext/native/net/http_server.h ext/native/net/http_client.cpp ext/native/net/http_client.h + ext/native/net/http_headers.cpp + ext/native/net/http_headers.h + ext/native/net/http_server.cpp + ext/native/net/http_server.h ext/native/net/resolve.cpp ext/native/net/resolve.h ext/native/net/sinks.cpp @@ -1010,6 +1012,8 @@ add_library(native STATIC ext/native/thin3d/thin3d.cpp ext/native/thin3d/thin3d.h ext/native/thin3d/thin3d_gl.cpp + ext/native/thread/executor.cpp + ext/native/thread/executor.h ext/native/thread/prioritizedworkqueue.cpp ext/native/thread/prioritizedworkqueue.h ext/native/thread/threadutil.cpp diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index c5a6ccd06537..6af89f5287ee 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -112,7 +112,7 @@ void Request::Close() { } } -Server::Server(threading::Executor *executor) +Server::Server(threading::Executor *executor) : port_(0), executor_(executor) { RegisterHandler("/", std::bind(&Server::HandleListing, this, placeholder::_1)); SetFallbackHandler(std::bind(&Server::Handle404, this, placeholder::_1)); @@ -126,43 +126,70 @@ void Server::SetFallbackHandler(UrlHandlerFunc handler) { fallback_ = handler; } -bool Server::Run(int port) { - ILOG("HTTP server started on port %i", port); - port_ = port; +bool Server::Listen(int port) { + listener_ = socket(AF_INET, SOCK_STREAM, 0); + CHECK_GE(listener_, 0); - int listener = socket(AF_INET, SOCK_STREAM, 0); - CHECK_GE(listener, 0); + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + server_addr.sin_port = htons(port); - struct sockaddr_in server_addr; - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; - server_addr.sin_addr.s_addr = htonl(INADDR_ANY); - server_addr.sin_port = htons(port); + int opt = 1; + // Enable re-binding to avoid the pain when restarting the server quickly. + setsockopt(listener_, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); - int opt = 1; - // Enable re-binding to avoid the pain when restarting the server quickly. - setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); + if (bind(listener_, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + ELOG("Failed to bind to port %i. Bailing.", port); + return false; + } - if (bind(listener, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { - ELOG("Failed to bind to port %i. Bailing.", port); - return false; - } + fd_util::SetNonBlocking(listener_, true); + + // 1024 is the max number of queued requests. + CHECK_GE(listen(listener_, 1024), 0); + + socklen_t len = sizeof(server_addr); + if (getsockname(listener_, (struct sockaddr *)&server_addr, &len) == 0) { + port = ntohs(server_addr.sin_port); + } + + ILOG("HTTP server started on port %i", port); + port_ = port; + + return true; +} + +bool Server::RunSlice(double timeout) { + if (timeout <= 0.0) { + timeout = 86400.0; + } + if (!fd_util::WaitUntilReady(listener_, timeout, false)) { + return false; + } - // 1024 is the max number of queued requests. - CHECK_GE(listen(listener, 1024), 0); - while (true) { sockaddr client_addr; socklen_t client_addr_size = sizeof(client_addr); - int conn_fd = accept(listener, &client_addr, &client_addr_size); + int conn_fd = accept(listener_, &client_addr, &client_addr_size); if (conn_fd >= 0) { - executor_->Run(std::bind(&Server::HandleConnection, this, conn_fd)); + executor_->Run(std::bind(&Server::HandleConnection, this, conn_fd)); + return true; } else { - FLOG("socket accept failed: %i", conn_fd); + FLOG("socket accept failed: %i", conn_fd); + return false; } - } +} + +bool Server::Run(int port) { + Listen(port); + + while (true) { + RunSlice(0.0); + } - // We'll never get here. Ever. - return true; + // We'll never get here. Ever. + return true; } void Server::HandleConnection(int conn_fd) { diff --git a/ext/native/net/http_server.h b/ext/native/net/http_server.h index 6ff92dc0f0dd..1001df80f947 100644 --- a/ext/native/net/http_server.h +++ b/ext/native/net/http_server.h @@ -70,6 +70,10 @@ class Server { // better put this on a thread. Returns false if failed to start serving, never // returns if successful. bool Run(int port); + // May run for (significantly) longer than timeout, but won't wait longer than that + // for a new connection to handle. + bool RunSlice(double timeout); + bool Listen(int port); void RegisterHandler(const char *url_path, UrlHandlerFunc handler); void SetFallbackHandler(UrlHandlerFunc handler); @@ -86,11 +90,12 @@ class Server { // Things like default 404, etc. void HandleRequestDefault(const Request &request); - + // Neat built-in handlers that are tied to the server. void HandleListing(const Request &request); void Handle404(const Request &request); + int listener_; int port_; UrlHandlerMap handlers_; From 0c0525ed8717f56fc0fa985c8c60bfaea8e78db2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 10:21:03 -0700 Subject: [PATCH 02/19] http: Send listing back as text/plain. Since it isn't text/html. --- ext/native/net/http_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index 6af89f5287ee..9221d9d37af5 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -230,7 +230,7 @@ void Server::Handle404(const Request &request) { } void Server::HandleListing(const Request &request) { - request.WriteHttpResponseHeader(200, -1); + request.WriteHttpResponseHeader(200, -1, "text/plain"); for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) { request.Out()->Printf("%s\n", iter->first.c_str()); } From f4e2ca0359e5d2f343cbe8059394ee7b81fd275e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 10:24:33 -0700 Subject: [PATCH 03/19] http: Add a screen under tools for the server. --- CMakeLists.txt | 1 + UI/GameSettingsScreen.cpp | 10 ++- UI/GameSettingsScreen.h | 1 + UI/RemoteISOScreen.cpp | 141 ++++++++++++++++++++++++++++++++++++++ UI/RemoteISOScreen.h | 34 +++++++++ UI/UI.vcxproj | 2 + UI/UI.vcxproj.filters | 6 ++ android/jni/Android.mk | 1 + 8 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 UI/RemoteISOScreen.cpp create mode 100644 UI/RemoteISOScreen.h diff --git a/CMakeLists.txt b/CMakeLists.txt index db814c4b2f2a..126aced1016b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -893,6 +893,7 @@ set(NativeAppSource UI/GamepadEmu.cpp UI/OnScreenDisplay.cpp UI/ControlMappingScreen.cpp + UI/RemoteISOScreen.cpp UI/ReportScreen.cpp UI/SavedataScreen.cpp UI/Store.cpp diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index f8b03288ace3..79c76fd42b38 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -34,6 +34,7 @@ #include "UI/ControlMappingScreen.h" #include "UI/DevScreens.h" #include "UI/DisplayLayoutScreen.h" +#include "UI/RemoteISOScreen.h" #include "UI/SavedataScreen.h" #include "UI/TouchControlLayoutScreen.h" #include "UI/TouchControlVisibilityScreen.h" @@ -499,6 +500,7 @@ void GameSettingsScreen::CreateViews() { tools->Add(new Choice(sa->T("Savedata Manager")))->OnClick.Handle(this, &GameSettingsScreen::OnSavedataManager); tools->Add(new Choice(dev->T("System Information")))->OnClick.Handle(this, &GameSettingsScreen::OnSysInfo); tools->Add(new Choice(sy->T("Developer Tools")))->OnClick.Handle(this, &GameSettingsScreen::OnDeveloperTools); + tools->Add(new Choice(sy->T("Remote disc streaming")))->OnClick.Handle(this, &GameSettingsScreen::OnRemoteISO); // System ViewGroup *systemSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); @@ -993,11 +995,17 @@ UI::EventReturn GameSettingsScreen::OnPostProcShaderChange(UI::EventParams &e) { Reporting::UpdateConfig(); return UI::EVENT_DONE; } + UI::EventReturn GameSettingsScreen::OnDeveloperTools(UI::EventParams &e) { screenManager()->push(new DeveloperToolsScreen()); return UI::EVENT_DONE; } +UI::EventReturn GameSettingsScreen::OnRemoteISO(UI::EventParams &e) { + screenManager()->push(new RemoteISOScreen()); + return UI::EVENT_DONE; +} + UI::EventReturn GameSettingsScreen::OnControlMapping(UI::EventParams &e) { screenManager()->push(new ControlMappingScreen()); return UI::EVENT_DONE; @@ -1006,7 +1014,7 @@ UI::EventReturn GameSettingsScreen::OnControlMapping(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnTouchControlLayout(UI::EventParams &e) { screenManager()->push(new TouchControlLayoutScreen()); return UI::EVENT_DONE; -}; +} //when the tilt event type is modified, we need to reset all tilt settings. //refer to the ResetTiltEvents() function for a detailed explanation. diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 578dbe101a65..5600382e78f6 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -73,6 +73,7 @@ class GameSettingsScreen : public UIDialogScreenWithGameBackground { UI::EventReturn OnPostProcShader(UI::EventParams &e); UI::EventReturn OnPostProcShaderChange(UI::EventParams &e); UI::EventReturn OnDeveloperTools(UI::EventParams &e); + UI::EventReturn OnRemoteISO(UI::EventParams &e); UI::EventReturn OnChangeNickname(UI::EventParams &e); UI::EventReturn OnChangeproAdhocServerAddress(UI::EventParams &e); UI::EventReturn OnChangeMacAddress(UI::EventParams &e); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp new file mode 100644 index 000000000000..f347021b914a --- /dev/null +++ b/UI/RemoteISOScreen.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2014- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "i18n/i18n.h" +#include "net/http_server.h" +#include "net/resolve.h" +#include "net/sinks.h" +#include "thread/thread.h" +#include "thread/threadutil.h" +#include "Common/Common.h" +#include "Common/FileUtil.h" +#include "Core/Config.h" +#include "UI/RemoteISOScreen.h" + +using namespace UI; + +enum class ServerStatus { + STOPPED, + STARTING, + RUNNING, + STOPPING, +}; + +static std::thread *serverThread = nullptr; +static ServerStatus serverStatus; +static recursive_mutex serverStatusLock; +static condition_variable serverStatusCond; + +static void UpdateStatus(ServerStatus s) { + lock_guard guard(serverStatusLock); + serverStatus = s; + serverStatusCond.notify_one(); +} + +static ServerStatus RetrieveStatus() { + lock_guard guard(serverStatusLock); + return serverStatus; +} + +static void ExecuteServer() { + setCurrentThreadName("HTTPServer"); + + net::Init(); + auto http = new http::Server(new threading::SameThreadExecutor()); + + http->Listen(0); + // TODO: Report local IP and port. + UpdateStatus(ServerStatus::RUNNING); + + while (RetrieveStatus() == ServerStatus::RUNNING) { + http->RunSlice(5.0); + } + + net::Shutdown(); + + UpdateStatus(ServerStatus::STOPPED); +} + +RemoteISOScreen::RemoteISOScreen() { +} + +void RemoteISOScreen::CreateViews() { + I18NCategory *rp = GetI18NCategory("Reporting"); + I18NCategory *di = GetI18NCategory("Dialog"); + I18NCategory *sy = GetI18NCategory("System"); + + Margins actionMenuMargins(0, 20, 15, 0); + Margins contentMargins(0, 20, 5, 5); + ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, FILL_PARENT, 0.4f, contentMargins)); + LinearLayout *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(WRAP_CONTENT, FILL_PARENT)); + ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); + LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); + + leftColumnItems->Add(new TextView(sy->T("RemoteISOWifi", "Note: Connect both devices to the same wifi"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + + rightColumnItems->SetSpacing(0.0f); + rightColumnItems->Add(new Choice(rp->T("Browse Games"))); + if (serverStatus != ServerStatus::STOPPED) { + rightColumnItems->Add(new Choice(rp->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); + } else { + rightColumnItems->Add(new Choice(rp->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer); + } + + rightColumnItems->Add(new Spacer(25.0)); + rightColumnItems->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle(this, &UIScreen::OnBack); + + root_ = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); + root_->Add(leftColumn); + root_->Add(rightColumn); + + leftColumn->Add(leftColumnItems); + rightColumn->Add(rightColumnItems); +} + +UI::EventReturn RemoteISOScreen::HandleStartServer(UI::EventParams &e) { + lock_guard guard(serverStatusLock); + + if (serverStatus != ServerStatus::STOPPED) { + return EVENT_SKIPPED; + } + + serverStatus = ServerStatus::STARTING; + serverThread = new std::thread(&ExecuteServer); + serverThread->detach(); + + serverStatusCond.wait(serverStatusLock); + RecreateViews(); + + return EVENT_DONE; +} + +UI::EventReturn RemoteISOScreen::HandleStopServer(UI::EventParams &e) { + lock_guard guard(serverStatusLock); + + if (serverStatus != ServerStatus::RUNNING) { + return EVENT_SKIPPED; + } + + serverStatus = ServerStatus::STOPPING; + serverStatusCond.wait(serverStatusLock); + delete serverThread; + serverThread = nullptr; + + RecreateViews(); + + return EVENT_DONE; +} diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h new file mode 100644 index 000000000000..04b92722c169 --- /dev/null +++ b/UI/RemoteISOScreen.h @@ -0,0 +1,34 @@ +// Copyright (c) 2016- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "base/functional.h" +#include "ui/ui_screen.h" +#include "ui/viewgroup.h" +#include "UI/MiscScreens.h" + +class RemoteISOScreen : public UIScreenWithBackground { +public: + RemoteISOScreen(); + +protected: + void CreateViews() override; + + UI::EventReturn HandleStartServer(UI::EventParams &e); + UI::EventReturn HandleStopServer(UI::EventParams &e); +}; diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 8fbcf5c79fda..15093677a854 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -37,6 +37,7 @@ + @@ -66,6 +67,7 @@ + diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index 4ee956b3508b..dec781d42af2 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -66,6 +66,9 @@ Screens + + Screens + @@ -133,6 +136,9 @@ + + Screens + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 3cdb8d29b36a..797102bc5353 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -395,6 +395,7 @@ LOCAL_SRC_FILES := \ $(SRC)/UI/EmuScreen.cpp \ $(SRC)/UI/MainScreen.cpp \ $(SRC)/UI/MiscScreens.cpp \ + $(SRC)/UI/RemoteISOScreen.cpp \ $(SRC)/UI/ReportScreen.cpp \ $(SRC)/UI/PauseScreen.cpp \ $(SRC)/UI/SavedataScreen.cpp \ From 0edc4d16527af043d67cb725c59855359ae02321 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 10:43:35 -0700 Subject: [PATCH 04/19] http: Check server status asynchronously. --- UI/RemoteISOScreen.cpp | 28 ++++++++++++++++++---------- UI/RemoteISOScreen.h | 3 +++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index f347021b914a..3453660a45c0 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -70,7 +70,23 @@ static void ExecuteServer() { UpdateStatus(ServerStatus::STOPPED); } -RemoteISOScreen::RemoteISOScreen() { +RemoteISOScreen::RemoteISOScreen() : serverRunning_(false) { +} + +void RemoteISOScreen::update(InputState &input) { + UIScreenWithBackground::update(input); + + bool nowRunning = RetrieveStatus() != ServerStatus::STOPPED; + if (serverRunning_ && !nowRunning) { + // Server stopped, delete the thread. + delete serverThread; + serverThread = nullptr; + } + if (serverRunning_ != nowRunning) { + RecreateViews(); + } + + serverRunning_ = nowRunning; } void RemoteISOScreen::CreateViews() { @@ -89,7 +105,7 @@ void RemoteISOScreen::CreateViews() { rightColumnItems->SetSpacing(0.0f); rightColumnItems->Add(new Choice(rp->T("Browse Games"))); - if (serverStatus != ServerStatus::STOPPED) { + if (RetrieveStatus() != ServerStatus::STOPPED) { rightColumnItems->Add(new Choice(rp->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); } else { rightColumnItems->Add(new Choice(rp->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer); @@ -117,9 +133,6 @@ UI::EventReturn RemoteISOScreen::HandleStartServer(UI::EventParams &e) { serverThread = new std::thread(&ExecuteServer); serverThread->detach(); - serverStatusCond.wait(serverStatusLock); - RecreateViews(); - return EVENT_DONE; } @@ -131,11 +144,6 @@ UI::EventReturn RemoteISOScreen::HandleStopServer(UI::EventParams &e) { } serverStatus = ServerStatus::STOPPING; - serverStatusCond.wait(serverStatusLock); - delete serverThread; - serverThread = nullptr; - - RecreateViews(); return EVENT_DONE; } diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index 04b92722c169..26340c0f3ac5 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -27,8 +27,11 @@ class RemoteISOScreen : public UIScreenWithBackground { RemoteISOScreen(); protected: + void update(InputState &input) override; void CreateViews() override; UI::EventReturn HandleStartServer(UI::EventParams &e); UI::EventReturn HandleStopServer(UI::EventParams &e); + + bool serverRunning_; }; From bc614b6f857c9ebfcf817d927016a66ffa463711 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 11:32:18 -0700 Subject: [PATCH 05/19] http: Add actual serving of recent ISOs. --- UI/RemoteISOScreen.cpp | 75 +++++++++++++++++++++++++++++++++++- ext/native/net/http_server.h | 64 +++++++++++++++--------------- 2 files changed, 107 insertions(+), 32 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 3453660a45c0..c4a8f12b390d 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -57,12 +57,85 @@ static void ExecuteServer() { net::Init(); auto http = new http::Server(new threading::SameThreadExecutor()); + std::map paths; + for (std::string filename : g_Config.recentIsos) { +#ifdef _WIN32 + static const std::string sep = "\\/"; +#else + static const std::string sep = "/"; +#endif + size_t basepos = filename.find_last_of(sep); + std::string basename = "/" + (basepos == filename.npos ? filename : filename.substr(basepos + 1)); + + // Let's not serve directories, since they won't work. Only single files. + // Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems. + if (endsWithNoCase(basename, ".cso") || endsWithNoCase(basename, ".iso")) { + paths[basename] = filename; + } + } + + auto handler = [&](const http::Request &request) { + std::string filename = paths[request.resource()]; + s64 sz = File::GetFileSize(filename); + + std::string range; + if (request.Method() == http::RequestHeader::HEAD) { + request.WriteHttpResponseHeader(200, sz, "application/octet-stream", "Accept-Ranges: bytes\r\n"); + } else if (request.GetHeader("range", &range)) { + s64 begin = 0, last = 0; + if (sscanf(range.c_str(), "bytes=%lld-%lld", &begin, &last) != 2) { + request.WriteHttpResponseHeader(400, -1, "text/plain"); + request.Out()->Push("Could not understand range request."); + return; + } + + if (begin < 0 || begin > last || last >= sz) { + request.WriteHttpResponseHeader(416, -1, "text/plain"); + request.Out()->Push("Range goes outside of file."); + return; + } + + FILE *fp = File::OpenCFile(filename, "rb"); + if (!fp || fseek(fp, begin, SEEK_SET) != 0) { + request.WriteHttpResponseHeader(500, -1, "text/plain"); + request.Out()->Push("File access failed."); + if (fp) { + fclose(fp); + } + return; + } + + s64 len = last - begin + 1; + char contentRange[1024]; + sprintf(contentRange, "Content-Range: bytes %lld-%lld/%lld\r\n", begin, last, sz); + request.WriteHttpResponseHeader(206, len, "application/octet-stream", contentRange); + + const size_t CHUNK_SIZE = 16 * 1024; + char *buf = new char[CHUNK_SIZE]; + for (s64 pos = 0; pos < len; pos += CHUNK_SIZE) { + s64 chunklen = std::min(len - pos, (s64)CHUNK_SIZE); + fread(buf, chunklen, 1, fp); + request.Out()->Push(buf, chunklen); + } + fclose(fp); + delete [] buf; + request.Out()->Flush(); + } else { + request.WriteHttpResponseHeader(418, -1, "text/plain"); + request.Out()->Push("This server only supports range requests."); + } + }; + + for (auto pair : paths) { + http->RegisterHandler(pair.first.c_str(), handler); + } + http->Listen(0); // TODO: Report local IP and port. UpdateStatus(ServerStatus::RUNNING); while (RetrieveStatus() == ServerStatus::RUNNING) { - http->RunSlice(5.0); + http->RunSlice(5.0); } net::Shutdown(); diff --git a/ext/native/net/http_server.h b/ext/native/net/http_server.h index 1001df80f947..1658e45d1886 100644 --- a/ext/native/net/http_server.h +++ b/ext/native/net/http_server.h @@ -60,48 +60,50 @@ class Request { // Register handlers on this class to serve stuff. class Server { - public: - Server(threading::Executor *executor); +public: + Server(threading::Executor *executor); - typedef std::function UrlHandlerFunc; - typedef std::map UrlHandlerMap; + typedef std::function UrlHandlerFunc; + typedef std::map UrlHandlerMap; - // Runs forever, serving request. If you want to do something else than serve pages, - // better put this on a thread. Returns false if failed to start serving, never - // returns if successful. - bool Run(int port); - // May run for (significantly) longer than timeout, but won't wait longer than that - // for a new connection to handle. - bool RunSlice(double timeout); - bool Listen(int port); + // Runs forever, serving request. If you want to do something else than serve pages, + // better put this on a thread. Returns false if failed to start serving, never + // returns if successful. + bool Run(int port); + // May run for (significantly) longer than timeout, but won't wait longer than that + // for a new connection to handle. + bool RunSlice(double timeout); + bool Listen(int port); - void RegisterHandler(const char *url_path, UrlHandlerFunc handler); - void SetFallbackHandler(UrlHandlerFunc handler); + void RegisterHandler(const char *url_path, UrlHandlerFunc handler); + void SetFallbackHandler(UrlHandlerFunc handler); - // If you want to customize things at a lower level than just a simple path handler, - // then inherit and override this. Implementations should forward to HandleRequestDefault - // if they don't recognize the url. - virtual void HandleRequest(const Request &request); + // If you want to customize things at a lower level than just a simple path handler, + // then inherit and override this. Implementations should forward to HandleRequestDefault + // if they don't recognize the url. + virtual void HandleRequest(const Request &request); - private: - void HandleConnection(int conn_fd); + int Port() { + return port_; + } - void GetRequest(Request *request); +private: + void HandleConnection(int conn_fd); - // Things like default 404, etc. - void HandleRequestDefault(const Request &request); + // Things like default 404, etc. + void HandleRequestDefault(const Request &request); - // Neat built-in handlers that are tied to the server. - void HandleListing(const Request &request); - void Handle404(const Request &request); + // Neat built-in handlers that are tied to the server. + void HandleListing(const Request &request); + void Handle404(const Request &request); - int listener_; - int port_; + int listener_; + int port_; - UrlHandlerMap handlers_; - UrlHandlerFunc fallback_; + UrlHandlerMap handlers_; + UrlHandlerFunc fallback_; - threading::Executor *executor_; + threading::Executor *executor_; }; } // namespace http From a67c103063e7d46ef563f36163a458d67416078e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 11:36:23 -0700 Subject: [PATCH 06/19] http: Add a note about the recent list. --- UI/RemoteISOScreen.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index c4a8f12b390d..006e553d5052 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -174,6 +174,7 @@ void RemoteISOScreen::CreateViews() { ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); + leftColumnItems->Add(new TextView(sy->T("RemoteISODesc", "Games in your recent list will be shared"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); leftColumnItems->Add(new TextView(sy->T("RemoteISOWifi", "Note: Connect both devices to the same wifi"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); rightColumnItems->SetSpacing(0.0f); From 42080f05e20ffe5ab0399f75664aaa81b7974aa3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 11:41:27 -0700 Subject: [PATCH 07/19] http: Show a stopping status for clarity. --- UI/RemoteISOScreen.cpp | 14 ++++++++++---- UI/RemoteISOScreen.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 006e553d5052..5bc0f2517cd2 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -143,22 +143,23 @@ static void ExecuteServer() { UpdateStatus(ServerStatus::STOPPED); } -RemoteISOScreen::RemoteISOScreen() : serverRunning_(false) { +RemoteISOScreen::RemoteISOScreen() : serverRunning_(false), serverStopping_(false) { } void RemoteISOScreen::update(InputState &input) { UIScreenWithBackground::update(input); bool nowRunning = RetrieveStatus() != ServerStatus::STOPPED; - if (serverRunning_ && !nowRunning) { + if (serverStopping_ && !nowRunning) { // Server stopped, delete the thread. delete serverThread; serverThread = nullptr; + serverStopping_ = false; } + if (serverRunning_ != nowRunning) { RecreateViews(); } - serverRunning_ = nowRunning; } @@ -179,7 +180,10 @@ void RemoteISOScreen::CreateViews() { rightColumnItems->SetSpacing(0.0f); rightColumnItems->Add(new Choice(rp->T("Browse Games"))); - if (RetrieveStatus() != ServerStatus::STOPPED) { + ServerStatus status = RetrieveStatus(); + if (status == ServerStatus::STOPPING) { + rightColumnItems->Add(new Choice(rp->T("Stopping..")))->SetDisabledPtr(&serverStopping_); + } else if (status != ServerStatus::STOPPED) { rightColumnItems->Add(new Choice(rp->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); } else { rightColumnItems->Add(new Choice(rp->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer); @@ -218,6 +222,8 @@ UI::EventReturn RemoteISOScreen::HandleStopServer(UI::EventParams &e) { } serverStatus = ServerStatus::STOPPING; + serverStopping_ = true; + RecreateViews(); return EVENT_DONE; } diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index 26340c0f3ac5..49928f3b0a83 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -34,4 +34,5 @@ class RemoteISOScreen : public UIScreenWithBackground { UI::EventReturn HandleStopServer(UI::EventParams &e); bool serverRunning_; + bool serverStopping_; }; From bde07bf9e7958e60e2c88b84b0ac34bf78e3e774 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 11:41:56 -0700 Subject: [PATCH 08/19] http: Fix translations. --- UI/RemoteISOScreen.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 5bc0f2517cd2..242649f8164f 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -164,7 +164,6 @@ void RemoteISOScreen::update(InputState &input) { } void RemoteISOScreen::CreateViews() { - I18NCategory *rp = GetI18NCategory("Reporting"); I18NCategory *di = GetI18NCategory("Dialog"); I18NCategory *sy = GetI18NCategory("System"); @@ -179,14 +178,14 @@ void RemoteISOScreen::CreateViews() { leftColumnItems->Add(new TextView(sy->T("RemoteISOWifi", "Note: Connect both devices to the same wifi"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); rightColumnItems->SetSpacing(0.0f); - rightColumnItems->Add(new Choice(rp->T("Browse Games"))); + rightColumnItems->Add(new Choice(sy->T("Browse Games"))); ServerStatus status = RetrieveStatus(); if (status == ServerStatus::STOPPING) { - rightColumnItems->Add(new Choice(rp->T("Stopping..")))->SetDisabledPtr(&serverStopping_); + rightColumnItems->Add(new Choice(sy->T("Stopping..")))->SetDisabledPtr(&serverStopping_); } else if (status != ServerStatus::STOPPED) { - rightColumnItems->Add(new Choice(rp->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); + rightColumnItems->Add(new Choice(sy->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); } else { - rightColumnItems->Add(new Choice(rp->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer); + rightColumnItems->Add(new Choice(sy->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer); } rightColumnItems->Add(new Spacer(25.0)); From 3eee81953a9f4d12dfc113b90fc84be010adb623 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 12:38:55 -0700 Subject: [PATCH 09/19] http: Report local address to server. This allows matching inside a network. --- UI/RemoteISOScreen.cpp | 25 ++++++++++++++++++++++++- ext/native/file/fd_util.cpp | 20 +++++++++++++++++++- ext/native/file/fd_util.h | 2 ++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 242649f8164f..8a1e90cc5d02 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -16,6 +16,8 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "i18n/i18n.h" +#include "file/fd_util.h" +#include "net/http_client.h" #include "net/http_server.h" #include "net/resolve.h" #include "net/sinks.h" @@ -51,6 +53,26 @@ static ServerStatus RetrieveStatus() { return serverStatus; } +// This reports the local IP address to report.ppsspp.org, which can then +// relay that address to a mobile device searching for the server. +static void RegisterServer(int port) { + bool result = false; + net::AutoInit netInit; + http::Client http; + Buffer theVoid; + + if (http.Resolve("report.ppsspp.org", 80)) { + http.Connect(); + + char resource[1024] = {}; + std::string ip = fd_util::GetLocalIP(http.sock()); + snprintf(resource, sizeof(resource) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); + + http.GET(resource, &theVoid); + http.Disconnect(); + } +} + static void ExecuteServer() { setCurrentThreadName("HTTPServer"); @@ -131,9 +153,10 @@ static void ExecuteServer() { } http->Listen(0); - // TODO: Report local IP and port. UpdateStatus(ServerStatus::RUNNING); + RegisterServer(http->Port()); + while (RetrieveStatus() == ServerStatus::RUNNING) { http->RunSlice(5.0); } diff --git a/ext/native/file/fd_util.cpp b/ext/native/file/fd_util.cpp index ecb0027e986c..8663d4476060 100644 --- a/ext/native/file/fd_util.cpp +++ b/ext/native/file/fd_util.cpp @@ -4,11 +4,16 @@ #include #include #ifndef _WIN32 -#include +#include +#include +#include +#include #include +#include #else #include #include +#include #endif #include @@ -127,4 +132,17 @@ void SetNonBlocking(int sock, bool non_blocking) { #endif } +std::string GetLocalIP(int sock) { + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + socklen_t len = sizeof(server_addr); + if (getsockname(sock, (struct sockaddr *)&server_addr, &len) == 0) { + char *result = inet_ntoa(*(in_addr *)&server_addr.sin_addr); + if (result) { + return result; + } + } + return ""; +} + } // fd_util diff --git a/ext/native/file/fd_util.h b/ext/native/file/fd_util.h index 6d64e018ab55..922a50eb7065 100644 --- a/ext/native/file/fd_util.h +++ b/ext/native/file/fd_util.h @@ -22,6 +22,8 @@ bool WaitUntilReady(int fd, double timeout, bool for_write = false); void SetNonBlocking(int fd, bool non_blocking); +std::string GetLocalIP(int sock); + } // fd_util #endif // _FD_UTIL From 60afdc40b9918a5b49a5763fdb1a97b5e3e6582f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 17:38:29 -0700 Subject: [PATCH 10/19] http: Implement discovery for game share server. --- UI/RemoteISOScreen.cpp | 167 +++++++++++++++++++++++++++++++++++++++-- UI/RemoteISOScreen.h | 35 +++++++++ 2 files changed, 196 insertions(+), 6 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 8a1e90cc5d02..4a46a4bca827 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -15,8 +15,10 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include "i18n/i18n.h" +#include "base/timeutil.h" +#include "ext/vjson/json.h" #include "file/fd_util.h" +#include "i18n/i18n.h" #include "net/http_client.h" #include "net/http_server.h" #include "net/resolve.h" @@ -30,6 +32,9 @@ using namespace UI; +static const char *REPORT_HOSTNAME = "report.ppsspp.org"; +static const int REPORT_PORT = 80; + enum class ServerStatus { STOPPED, STARTING, @@ -56,12 +61,10 @@ static ServerStatus RetrieveStatus() { // This reports the local IP address to report.ppsspp.org, which can then // relay that address to a mobile device searching for the server. static void RegisterServer(int port) { - bool result = false; - net::AutoInit netInit; http::Client http; Buffer theVoid; - if (http.Resolve("report.ppsspp.org", 80)) { + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { http.Connect(); char resource[1024] = {}; @@ -156,9 +159,15 @@ static void ExecuteServer() { UpdateStatus(ServerStatus::RUNNING); RegisterServer(http->Port()); - + double lastRegister = real_time_now(); while (RetrieveStatus() == ServerStatus::RUNNING) { http->RunSlice(5.0); + + double now = real_time_now(); + if (now > lastRegister + 540.0) { + RegisterServer(http->Port()); + lastRegister = now; + } } net::Shutdown(); @@ -166,6 +175,57 @@ static void ExecuteServer() { UpdateStatus(ServerStatus::STOPPED); } +static std::string FindServer() { + http::Client http; + Buffer result; + int code = 500; + + // Start by requesting a list of recent local ips for this network. + if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { + http.Connect(); + code = http.GET("/match/list", &result); + http.Disconnect(); + } + + if (code != 200) { + return ""; + } + + std::string json; + result.TakeAll(&json); + + JsonReader reader(json.c_str(), json.size()); + if (!reader.ok()) { + return ""; + } + + const json_value *entries = reader.root(); + if (!entries) { + return ""; + } + + std::vector servers; + const json_value *entry = entries->first_child; + while (entry) { + const char *host = entry->getString("ip", ""); + int port = entry->getInt("p", 0); + + char url[1024] = {}; + snprintf(url, sizeof(url), "http://%s:%d", host, port); + servers.push_back(url); + + if (http.Resolve(host, port) && http.Connect()) { + http.Disconnect(); + return url; + } + + entry = entry->next_sibling; + } + + // None of the local IPs were reachable. + return ""; +} + RemoteISOScreen::RemoteISOScreen() : serverRunning_(false), serverStopping_(false) { } @@ -200,8 +260,10 @@ void RemoteISOScreen::CreateViews() { leftColumnItems->Add(new TextView(sy->T("RemoteISODesc", "Games in your recent list will be shared"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); leftColumnItems->Add(new TextView(sy->T("RemoteISOWifi", "Note: Connect both devices to the same wifi"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + // TODO: Could display server address for manual entry. + rightColumnItems->SetSpacing(0.0f); - rightColumnItems->Add(new Choice(sy->T("Browse Games"))); + rightColumnItems->Add(new Choice(sy->T("Browse Games")))->OnClick.Handle(this, &RemoteISOScreen::HandleBrowse); ServerStatus status = RetrieveStatus(); if (status == ServerStatus::STOPPING) { rightColumnItems->Add(new Choice(sy->T("Stopping..")))->SetDisabledPtr(&serverStopping_); @@ -249,3 +311,96 @@ UI::EventReturn RemoteISOScreen::HandleStopServer(UI::EventParams &e) { return EVENT_DONE; } + +UI::EventReturn RemoteISOScreen::HandleBrowse(UI::EventParams &e) { + screenManager()->push(new RemoteISOConnectScreen()); + return EVENT_DONE; +} + +RemoteISOConnectScreen::RemoteISOConnectScreen() : scanComplete_(false), nextRetry_(0.0) { + scanLock_ = new recursive_mutex(); + + scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) { + thiz->ExecuteScan(); + }, this); + scanThread_->detach(); +} + +RemoteISOConnectScreen::~RemoteISOConnectScreen() { + while (!scanComplete_) { + sleep_ms(1); + } + delete scanThread_; + delete scanLock_; +} + +void RemoteISOConnectScreen::CreateViews() { + I18NCategory *sy = GetI18NCategory("System"); + + Margins actionMenuMargins(0, 20, 15, 0); + Margins contentMargins(0, 20, 5, 5); + ViewGroup *leftColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, FILL_PARENT, 0.4f, contentMargins)); + LinearLayout *leftColumnItems = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(WRAP_CONTENT, FILL_PARENT)); + ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); + LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); + + leftColumnItems->Add(new TextView(sy->T("RemoteISOScanning", "Scanning... click Share Games on your desktop"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + + // TODO: Here would be a good place for manual entry. + + rightColumnItems->SetSpacing(0.0f); + rightColumnItems->Add(new Choice(sy->T("Cancel"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle(this, &UIScreen::OnBack); + + root_ = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); + root_->Add(leftColumn); + root_->Add(rightColumn); + + leftColumn->Add(leftColumnItems); + rightColumn->Add(rightColumnItems); +} + +void RemoteISOConnectScreen::update(InputState &input) { + UIScreenWithBackground::update(input); + + if (IsComplete()) { + if (!url_.empty()) { + BrowseToURL(url_); + } else if (nextRetry_ <= 0.0f) { + nextRetry_ = real_time_now() + 30.0; + } else if (nextRetry_ < real_time_now()) { + scanComplete_ = false; + nextRetry_ = 0.0; + + delete scanThread_; + scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) { + thiz->ExecuteScan(); + }, this); + scanThread_->detach(); + } + } +} + +void RemoteISOConnectScreen::ExecuteScan() { + url_ = FindServer(); + + lock_guard guard(*scanLock_); + scanComplete_ = true; +} + +bool RemoteISOConnectScreen::IsComplete() { + lock_guard guard(*scanLock_); + return scanComplete_; +} + +void RemoteISOConnectScreen::BrowseToURL(const std::string &url) { + screenManager()->finishDialog(this, DR_OK); + screenManager()->push(new RemoteISOBrowseScreen(url)); +} + +RemoteISOBrowseScreen::RemoteISOBrowseScreen(const std::string &url) { + // TODO +} + +void RemoteISOBrowseScreen::CreateViews() { + // TODO +} diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index 49928f3b0a83..9895215f410a 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -22,6 +22,12 @@ #include "ui/viewgroup.h" #include "UI/MiscScreens.h" +namespace std { + class thread; +} + +class recursive_mutex; + class RemoteISOScreen : public UIScreenWithBackground { public: RemoteISOScreen(); @@ -32,7 +38,36 @@ class RemoteISOScreen : public UIScreenWithBackground { UI::EventReturn HandleStartServer(UI::EventParams &e); UI::EventReturn HandleStopServer(UI::EventParams &e); + UI::EventReturn HandleBrowse(UI::EventParams &e); bool serverRunning_; bool serverStopping_; }; + +class RemoteISOConnectScreen : public UIScreenWithBackground { +public: + RemoteISOConnectScreen(); + ~RemoteISOConnectScreen() override; + +protected: + void update(InputState &input) override; + void CreateViews() override; + + void ExecuteScan(); + bool IsComplete(); + void BrowseToURL(const std::string &url); + + bool scanComplete_; + double nextRetry_; + std::thread *scanThread_; + recursive_mutex *scanLock_; + std::string url_; +}; + +class RemoteISOBrowseScreen : public UIScreenWithBackground { +public: + RemoteISOBrowseScreen(const std::string &url); + +protected: + void CreateViews() override; +}; From 41e167f384393586b1e26ffb29bd4292554ebaf4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 20:43:44 -0700 Subject: [PATCH 11/19] http: Oops, fix crash. Huh, I thought I tested this? --- Core/FileLoaders/DiskCachingFileLoader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/FileLoaders/DiskCachingFileLoader.cpp b/Core/FileLoaders/DiskCachingFileLoader.cpp index 7935e0f021b9..e51c84541be8 100644 --- a/Core/FileLoaders/DiskCachingFileLoader.cpp +++ b/Core/FileLoaders/DiskCachingFileLoader.cpp @@ -54,7 +54,7 @@ DiskCachingFileLoader::~DiskCachingFileLoader() { } bool DiskCachingFileLoader::Exists() { - if (cache_->HasData()) { + if (cache_ && cache_->HasData()) { // It may require a slow operation to check - if we have data, let's say yes. // This helps initial load, since we check each recent file for existence. return true; From 2b613443cda18b2aef17ecf12c5139988a0d2f42 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 20:44:39 -0700 Subject: [PATCH 12/19] http: Fix paths with spaces in them. Should ideally escape other characters, but only space is necessary for our server to parse. --- UI/RemoteISOScreen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 4a46a4bca827..af4fa2a06f8f 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -95,7 +95,7 @@ static void ExecuteServer() { // Let's not serve directories, since they won't work. Only single files. // Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems. if (endsWithNoCase(basename, ".cso") || endsWithNoCase(basename, ".iso")) { - paths[basename] = filename; + paths[ReplaceAll(basename, " ", "%20")] = filename; } } From 66479add9220098e1c6cb06de86b02b134d5b17d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 20:46:06 -0700 Subject: [PATCH 13/19] http: Disallow browsing when sharing. Because it's probably not what you want to do. This is less confusing. --- UI/RemoteISOScreen.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index af4fa2a06f8f..d9bca543cd00 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -263,14 +263,18 @@ void RemoteISOScreen::CreateViews() { // TODO: Could display server address for manual entry. rightColumnItems->SetSpacing(0.0f); - rightColumnItems->Add(new Choice(sy->T("Browse Games")))->OnClick.Handle(this, &RemoteISOScreen::HandleBrowse); + Choice *browseChoice = new Choice(sy->T("Browse Games")); + rightColumnItems->Add(browseChoice)->OnClick.Handle(this, &RemoteISOScreen::HandleBrowse); ServerStatus status = RetrieveStatus(); if (status == ServerStatus::STOPPING) { rightColumnItems->Add(new Choice(sy->T("Stopping..")))->SetDisabledPtr(&serverStopping_); + browseChoice->SetEnabled(false); } else if (status != ServerStatus::STOPPED) { rightColumnItems->Add(new Choice(sy->T("Stop Sharing")))->OnClick.Handle(this, &RemoteISOScreen::HandleStopServer); + browseChoice->SetEnabled(false); } else { rightColumnItems->Add(new Choice(sy->T("Share Games (Server)")))->OnClick.Handle(this, &RemoteISOScreen::HandleStartServer); + browseChoice->SetEnabled(true); } rightColumnItems->Add(new Spacer(25.0)); From c3e70c11f8f16c94386d5ed1989575c6154c6729 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 21:09:17 -0700 Subject: [PATCH 14/19] http: Save last-used port. This is necessary for better caching, in case you share often. --- Core/Config.cpp | 6 +++++- Core/Config.h | 1 + UI/RemoteISOScreen.cpp | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 137194c47571..313b7ecdc7af 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -339,6 +339,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("ReportingHost", &g_Config.sReportHost, "default"), ConfigSetting("AutoSaveSymbolMap", &g_Config.bAutoSaveSymbolMap, false, true, true), ConfigSetting("CacheFullIsoInRam", &g_Config.bCacheFullIsoInRam, false, true, true), + ConfigSetting("RemoteISOPort", &g_Config.iRemoteISOPort, 0, true, false), #ifdef ANDROID ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, 1), @@ -356,6 +357,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("PauseWhenMinimized", &g_Config.bPauseWhenMinimized, false, true, true), ConfigSetting("DumpDecryptedEboots", &g_Config.bDumpDecryptedEboot, false, true, true), ConfigSetting("FullscreenOnDoubleclick", &g_Config.bFullscreenOnDoubleclick, true, false, false), + ConfigSetting(false), }; @@ -510,7 +512,8 @@ static ConfigSetting graphicsSettings[] = { ReportedConfigSetting("FragmentTestCache", &g_Config.bFragmentTestCache, true, true, true), ConfigSetting("GfxDebugOutput", &g_Config.bGfxDebugOutput, false, false, false), - ConfigSetting(false), + + ConfigSetting(false), }; static ConfigSetting soundSettings[] = { @@ -666,6 +669,7 @@ static ConfigSetting controlSettings[] = { static ConfigSetting networkSettings[] = { ConfigSetting("EnableWlan", &g_Config.bEnableWlan, false, true, true), ConfigSetting("EnableAdhocServer", &g_Config.bEnableAdhocServer, false, true, true), + ConfigSetting(false), }; diff --git a/Core/Config.h b/Core/Config.h index 6a2016997b0c..1a3afc10cc89 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -138,6 +138,7 @@ struct Config { int iLockedCPUSpeed; bool bAutoSaveSymbolMap; bool bCacheFullIsoInRam; + int iRemoteISOPort; int iScreenRotation; // The rotation angle of the PPSSPP UI. Only supported on Android and possibly other mobile platforms. int iInternalScreenRotation; // The internal screen rotation angle. Useful for vertical SHMUPs and similar. diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index d9bca543cd00..0e9082c72aaa 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -155,9 +155,12 @@ static void ExecuteServer() { http->RegisterHandler(pair.first.c_str(), handler); } - http->Listen(0); + if (!http->Listen(g_Config.iRemoteISOPort)) { + http->Listen(0); + } UpdateStatus(ServerStatus::RUNNING); + g_Config.iRemoteISOPort = http->Port(); RegisterServer(http->Port()); double lastRegister = real_time_now(); while (RetrieveStatus() == ServerStatus::RUNNING) { From c793efd69acc1693aed9a70bf838bee84343abf1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 3 Jul 2016 20:48:27 -0700 Subject: [PATCH 15/19] http: Load actual game list. --- UI/MainScreen.cpp | 21 ++++- UI/MainScreen.h | 13 ++- UI/RemoteISOScreen.cpp | 197 ++++++++++++++++++++++++++++++++++------- UI/RemoteISOScreen.h | 30 +++++-- 4 files changed, 217 insertions(+), 44 deletions(-) diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 533d47aba776..ec68e3f4106c 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -457,6 +457,18 @@ UI::EventReturn GameBrowser::PinToggleClick(UI::EventParams &e) { return UI::EVENT_DONE; } +bool GameBrowser::DisplayTopBar() { + return path_.GetPath() != "!RECENT"; +} + +bool GameBrowser::HasSpecialFiles(std::vector &filenames) { + if (path_.GetPath() == "!RECENT") { + filenames = g_Config.recentIsos; + return true; + } + return false; +} + void GameBrowser::Refresh() { using namespace UI; @@ -468,7 +480,7 @@ void GameBrowser::Refresh() { I18NCategory *mm = GetI18NCategory("MainMenu"); // No topbar on recent screen - if (path_.GetPath() != "!RECENT") { + if (DisplayTopBar()) { LinearLayout *topBar = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); if (allowBrowsing_) { topBar->Add(new Spacer(2.0f)); @@ -504,9 +516,10 @@ void GameBrowser::Refresh() { std::vector dirButtons; std::vector gameButtons; - if (path_.GetPath() == "!RECENT") { - for (size_t i = 0; i < g_Config.recentIsos.size(); i++) { - gameButtons.push_back(new GameButton(g_Config.recentIsos[i], *gridStyle_, new UI::LinearLayoutParams(*gridStyle_ == true ? UI::WRAP_CONTENT : UI::FILL_PARENT, UI::WRAP_CONTENT))); + 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 { std::vector fileInfo; diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 0cab05423916..8e1018fdaf21 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -35,8 +35,13 @@ class GameBrowser : public UI::LinearLayout { void FocusGame(std::string gamePath); -private: +protected: + virtual bool DisplayTopBar(); + virtual bool HasSpecialFiles(std::vector &filenames); + void Refresh(); + +private: bool IsCurrentPathPinned(); const std::vector GetPinnedPaths(); const std::string GetBaseName(const std::string &path); @@ -61,6 +66,8 @@ class GameBrowser : public UI::LinearLayout { std::string focusGamePath_; }; +class RemoteISOBrowseScreen; + class MainScreen : public UIScreenWithBackground { public: MainScreen(); @@ -78,7 +85,7 @@ class MainScreen : public UIScreenWithBackground { virtual void sendMessage(const char *message, const char *value); virtual void dialogFinished(const Screen *dialog, DialogResult result); -private: + bool UseVerticalLayout() const; bool DrawBackgroundFor(UIContext &dc, const std::string &gamePath, float progress); UI::EventReturn OnGameSelected(UI::EventParams &e); @@ -112,7 +119,7 @@ class MainScreen : public UIScreenWithBackground { bool lockBackgroundAudio_; bool lastVertical_; - bool UseVerticalLayout() const; + friend class RemoteISOBrowseScreen; }; class UmdReplaceScreen : public UIDialogScreenWithBackground { diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 0e9082c72aaa..82a0bb17db89 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -178,7 +178,7 @@ static void ExecuteServer() { UpdateStatus(ServerStatus::STOPPED); } -static std::string FindServer() { +static bool FindServer(std::string &resultHost, int &resultPort) { http::Client http; Buffer result; int code = 500; @@ -191,7 +191,7 @@ static std::string FindServer() { } if (code != 200) { - return ""; + return false; } std::string json; @@ -199,12 +199,12 @@ static std::string FindServer() { JsonReader reader(json.c_str(), json.size()); if (!reader.ok()) { - return ""; + return false; } const json_value *entries = reader.root(); if (!entries) { - return ""; + return false; } std::vector servers; @@ -219,14 +219,50 @@ static std::string FindServer() { if (http.Resolve(host, port) && http.Connect()) { http.Disconnect(); - return url; + resultHost = host; + resultPort = port; + return true; } entry = entry->next_sibling; } // None of the local IPs were reachable. - return ""; + return false; +} + +static bool LoadGameList(const std::string &host, int port, std::vector &games) { + http::Client http; + Buffer result; + int code = 500; + + // Start by requesting a list of recent local ips for this network. + if (http.Resolve(host.c_str(), port)) { + http.Connect(); + code = http.GET("/", &result); + http.Disconnect(); + } + + if (code != 200) { + return false; + } + + std::string listing; + std::vector items; + result.TakeAll(&listing); + + SplitString(listing, '\n', items); + for (const std::string &item : items) { + if (!endsWithNoCase(item, ".cso") && !endsWithNoCase(item, ".iso") && !endsWithNoCase(item, ".pbp")) { + continue; + } + + char temp[1024] = {}; + snprintf(temp, sizeof(temp) - 1, "http://%s:%d%s", host.c_str(), port, item.c_str()); + games.push_back(temp); + } + + return !games.empty(); } RemoteISOScreen::RemoteISOScreen() : serverRunning_(false), serverStopping_(false) { @@ -324,8 +360,8 @@ UI::EventReturn RemoteISOScreen::HandleBrowse(UI::EventParams &e) { return EVENT_DONE; } -RemoteISOConnectScreen::RemoteISOConnectScreen() : scanComplete_(false), nextRetry_(0.0) { - scanLock_ = new recursive_mutex(); +RemoteISOConnectScreen::RemoteISOConnectScreen() : status_(ScanStatus::SCANNING), nextRetry_(0.0) { + statusLock_ = new recursive_mutex(); scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) { thiz->ExecuteScan(); @@ -334,11 +370,11 @@ RemoteISOConnectScreen::RemoteISOConnectScreen() : scanComplete_(false), nextRet } RemoteISOConnectScreen::~RemoteISOConnectScreen() { - while (!scanComplete_) { + while (GetStatus() == ScanStatus::SCANNING || GetStatus() == ScanStatus::LOADING) { sleep_ms(1); } delete scanThread_; - delete scanLock_; + delete statusLock_; } void RemoteISOConnectScreen::CreateViews() { @@ -351,7 +387,7 @@ void RemoteISOConnectScreen::CreateViews() { ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); - leftColumnItems->Add(new TextView(sy->T("RemoteISOScanning", "Scanning... click Share Games on your desktop"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + statusView_ = leftColumnItems->Add(new TextView(sy->T("RemoteISOScanning", "Scanning... click Share Games on your desktop"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); // TODO: Here would be a good place for manual entry. @@ -367,15 +403,36 @@ void RemoteISOConnectScreen::CreateViews() { } void RemoteISOConnectScreen::update(InputState &input) { + I18NCategory *sy = GetI18NCategory("System"); + UIScreenWithBackground::update(input); - if (IsComplete()) { - if (!url_.empty()) { - BrowseToURL(url_); - } else if (nextRetry_ <= 0.0f) { - nextRetry_ = real_time_now() + 30.0; - } else if (nextRetry_ < real_time_now()) { - scanComplete_ = false; + ScanStatus s = GetStatus(); + switch (s) { + case ScanStatus::SCANNING: + case ScanStatus::LOADING: + break; + + case ScanStatus::FOUND: + statusView_->SetText(sy->T("RemoteISOLoading", "Connected - loading game list")); + status_ = ScanStatus::LOADING; + + // Let's reuse scanThread_. + delete scanThread_; + scanThread_ = new std::thread([](RemoteISOConnectScreen *thiz) { + thiz->ExecuteLoad(); + }, this); + scanThread_->detach(); + break; + + case ScanStatus::FAILED: + nextRetry_ = real_time_now() + 30.0; + status_ = ScanStatus::RETRY_SCAN; + break; + + case ScanStatus::RETRY_SCAN: + if (nextRetry_ < real_time_now()) { + status_ = ScanStatus::SCANNING; nextRetry_ = 0.0; delete scanThread_; @@ -384,30 +441,110 @@ void RemoteISOConnectScreen::update(InputState &input) { }, this); scanThread_->detach(); } + break; + + case ScanStatus::LOADED: + screenManager()->finishDialog(this, DR_OK); + screenManager()->push(new RemoteISOBrowseScreen(games_)); + break; } } void RemoteISOConnectScreen::ExecuteScan() { - url_ = FindServer(); + FindServer(host_, port_); - lock_guard guard(*scanLock_); - scanComplete_ = true; + lock_guard guard(*statusLock_); + status_ = host_.empty() ? ScanStatus::FAILED : ScanStatus::FOUND; } -bool RemoteISOConnectScreen::IsComplete() { - lock_guard guard(*scanLock_); - return scanComplete_; +ScanStatus RemoteISOConnectScreen::GetStatus() { + lock_guard guard(*statusLock_); + return status_; } -void RemoteISOConnectScreen::BrowseToURL(const std::string &url) { - screenManager()->finishDialog(this, DR_OK); - screenManager()->push(new RemoteISOBrowseScreen(url)); +void RemoteISOConnectScreen::ExecuteLoad() { + bool result = LoadGameList(host_, port_, games_); + + lock_guard guard(*statusLock_); + status_ = result ? ScanStatus::LOADED : ScanStatus::FAILED; } -RemoteISOBrowseScreen::RemoteISOBrowseScreen(const std::string &url) { - // TODO +class RemoteGameBrowser : public GameBrowser { +public: + RemoteGameBrowser(const std::vector &games, bool allowBrowsing, bool *gridStyle_, std::string lastText, std::string lastLink, int flags = 0, UI::LayoutParams *layoutParams = 0) + : GameBrowser("!REMOTE", allowBrowsing, gridStyle_, lastText, lastLink, flags, layoutParams) { + games_ = games; + Refresh(); + } + +protected: + bool DisplayTopBar() override { + return false; + } + + bool HasSpecialFiles(std::vector &filenames) override; + + std::vector games_; +}; + +bool RemoteGameBrowser::HasSpecialFiles(std::vector &filenames) { + filenames = games_; + return true; +} + +RemoteISOBrowseScreen::RemoteISOBrowseScreen(const std::vector &games) : games_(games) { } void RemoteISOBrowseScreen::CreateViews() { - // TODO + bool vertical = UseVerticalLayout(); + + I18NCategory *mm = GetI18NCategory("MainMenu"); + I18NCategory *di = GetI18NCategory("Dialog"); + + Margins actionMenuMargins(0, 10, 10, 0); + + TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + tabHolder_ = leftColumn; + tabHolder_->SetTag("RemoteGames"); + gameBrowsers_.clear(); + + leftColumn->SetClip(true); + + ScrollView *scrollRecentGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + scrollRecentGames->SetTag("RemoteGamesTab"); + RemoteGameBrowser *tabRemoteGames = new RemoteGameBrowser( + games_, false, &g_Config.bGridView1, "", "", 0, + new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); + scrollRecentGames->Add(tabRemoteGames); + gameBrowsers_.push_back(tabRemoteGames); + + leftColumn->AddTab(mm->T("Remote Server"), scrollRecentGames); + tabRemoteGames->OnChoice.Handle(this, &MainScreen::OnGameSelectedInstant); + tabRemoteGames->OnHoldChoice.Handle(this, &MainScreen::OnGameSelected); + tabRemoteGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight); + + ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL); + LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + rightColumnItems->SetSpacing(0.0f); + rightColumn->Add(rightColumnItems); + + rightColumnItems->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, WRAP_CONTENT, 10, NONE, NONE, 10)))->OnClick.Handle(this, &UIScreen::OnBack); + + if (vertical) { + root_ = new LinearLayout(ORIENT_VERTICAL); + rightColumn->ReplaceLayoutParams(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + leftColumn->ReplaceLayoutParams(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0)); + root_->Add(rightColumn); + root_->Add(leftColumn); + } else { + root_ = new LinearLayout(ORIENT_HORIZONTAL); + leftColumn->ReplaceLayoutParams(new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0)); + rightColumn->ReplaceLayoutParams(new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); + root_->Add(leftColumn); + root_->Add(rightColumn); + } + + root_->SetDefaultFocusView(tabHolder_); + + upgradeBar_ = 0; } diff --git a/UI/RemoteISOScreen.h b/UI/RemoteISOScreen.h index 9895215f410a..fe72637b5a84 100644 --- a/UI/RemoteISOScreen.h +++ b/UI/RemoteISOScreen.h @@ -21,6 +21,7 @@ #include "ui/ui_screen.h" #include "ui/viewgroup.h" #include "UI/MiscScreens.h" +#include "UI/MainScreen.h" namespace std { class thread; @@ -44,6 +45,15 @@ class RemoteISOScreen : public UIScreenWithBackground { bool serverStopping_; }; +enum class ScanStatus { + SCANNING, + RETRY_SCAN, + FOUND, + FAILED, + LOADING, + LOADED, +}; + class RemoteISOConnectScreen : public UIScreenWithBackground { public: RemoteISOConnectScreen(); @@ -53,21 +63,27 @@ class RemoteISOConnectScreen : public UIScreenWithBackground { void update(InputState &input) override; void CreateViews() override; + ScanStatus GetStatus(); void ExecuteScan(); - bool IsComplete(); - void BrowseToURL(const std::string &url); + void ExecuteLoad(); - bool scanComplete_; + UI::TextView *statusView_; + + ScanStatus status_; double nextRetry_; std::thread *scanThread_; - recursive_mutex *scanLock_; - std::string url_; + recursive_mutex *statusLock_; + std::string host_; + int port_; + std::vector games_; }; -class RemoteISOBrowseScreen : public UIScreenWithBackground { +class RemoteISOBrowseScreen : public MainScreen { public: - RemoteISOBrowseScreen(const std::string &url); + RemoteISOBrowseScreen(const std::vector &games); protected: void CreateViews() override; + + std::vector games_; }; From 429346bb9411e1eb3cd2223e0c639ba0006aa1e2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 4 Jul 2016 00:27:49 -0700 Subject: [PATCH 16/19] http: Make sure we don't hang checking existence. It's not great to delay loading when the server is down - we'll do a proper check when we display the games. This also fixes shutdown being slow. --- Core/Config.cpp | 2 +- Core/FileLoaders/CachingFileLoader.cpp | 22 +++++++++++++++-- Core/FileLoaders/CachingFileLoader.h | 3 +++ Core/FileLoaders/DiskCachingFileLoader.cpp | 28 +++++++++++++++------- Core/FileLoaders/DiskCachingFileLoader.h | 3 +++ Core/FileLoaders/HTTPFileLoader.cpp | 21 +++++++++++++--- Core/FileLoaders/HTTPFileLoader.h | 4 ++++ Core/FileLoaders/RamCachingFileLoader.cpp | 8 +++++++ Core/FileLoaders/RamCachingFileLoader.h | 1 + Core/FileLoaders/RetryingFileLoader.cpp | 8 +++++++ Core/FileLoaders/RetryingFileLoader.h | 1 + Core/Loaders.h | 3 +++ 12 files changed, 90 insertions(+), 14 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 313b7ecdc7af..ef19b9ca7073 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1141,7 +1141,7 @@ void Config::CleanRecent() { std::vector cleanedRecent; for (size_t i = 0; i < recentIsos.size(); i++) { FileLoader *loader = ConstructFileLoader(recentIsos[i]); - if (loader->Exists()) { + if (loader->ExistsFast()) { // Make sure we don't have any redundant items. auto duplicate = std::find(cleanedRecent.begin(), cleanedRecent.end(), recentIsos[i]); if (duplicate == cleanedRecent.end()) { diff --git a/Core/FileLoaders/CachingFileLoader.cpp b/Core/FileLoaders/CachingFileLoader.cpp index 0b34e2c7bb57..1c46c99dbb9b 100644 --- a/Core/FileLoaders/CachingFileLoader.cpp +++ b/Core/FileLoaders/CachingFileLoader.cpp @@ -23,8 +23,16 @@ // Takes ownership of backend. CachingFileLoader::CachingFileLoader(FileLoader *backend) - : filesize_(0), filepos_(0), backend_(backend), exists_(-1), isDirectory_(-1), aheadThread_(false) { - filesize_ = backend->FileSize(); + : filesize_(0), filepos_(0), backend_(backend), exists_(-1), isDirectory_(-1), aheadThread_(false), prepared_(false) { +} + +void CachingFileLoader::Prepare() { + if (prepared_) { + return; + } + prepared_ = true; + + filesize_ = backend_->FileSize(); if (filesize_ > 0) { InitCache(); } @@ -46,6 +54,14 @@ bool CachingFileLoader::Exists() { return exists_ == 1; } +bool CachingFileLoader::ExistsFast() { + if (exists_ == -1) { + lock_guard guard(backendMutex_); + return backend_->ExistsFast(); + } + return exists_ == 1; +} + bool CachingFileLoader::IsDirectory() { if (isDirectory_ == -1) { lock_guard guard(backendMutex_); @@ -55,6 +71,7 @@ bool CachingFileLoader::IsDirectory() { } s64 CachingFileLoader::FileSize() { + Prepare(); return filesize_; } @@ -68,6 +85,7 @@ void CachingFileLoader::Seek(s64 absolutePos) { } size_t CachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data) { + Prepare(); if (absolutePos >= filesize_) { bytes = 0; } else if (absolutePos + (s64)bytes >= filesize_) { diff --git a/Core/FileLoaders/CachingFileLoader.h b/Core/FileLoaders/CachingFileLoader.h index 903fb8f88506..e3c2cc9b4aa3 100644 --- a/Core/FileLoaders/CachingFileLoader.h +++ b/Core/FileLoaders/CachingFileLoader.h @@ -28,6 +28,7 @@ class CachingFileLoader : public FileLoader { virtual ~CachingFileLoader() override; virtual bool Exists() override; + virtual bool ExistsFast() override; virtual bool IsDirectory() override; virtual s64 FileSize() override; virtual std::string Path() const override; @@ -45,6 +46,7 @@ class CachingFileLoader : public FileLoader { virtual size_t ReadAt(s64 absolutePos, size_t bytes, void *data) override; private: + void Prepare(); void InitCache(); void ShutdownCache(); size_t ReadFromCache(s64 pos, size_t bytes, void *data); @@ -84,4 +86,5 @@ class CachingFileLoader : public FileLoader { recursive_mutex blocksMutex_; mutable recursive_mutex backendMutex_; bool aheadThread_; + bool prepared_; }; diff --git a/Core/FileLoaders/DiskCachingFileLoader.cpp b/Core/FileLoaders/DiskCachingFileLoader.cpp index e51c84541be8..2efaff810c44 100644 --- a/Core/FileLoaders/DiskCachingFileLoader.cpp +++ b/Core/FileLoaders/DiskCachingFileLoader.cpp @@ -38,8 +38,16 @@ recursive_mutex DiskCachingFileLoader::cachesMutex_; // Takes ownership of backend. DiskCachingFileLoader::DiskCachingFileLoader(FileLoader *backend) - : filesize_(0), filepos_(0), backend_(backend), cache_(nullptr) { - filesize_ = backend->FileSize(); + : prepared_(false), filesize_(0), filepos_(0), backend_(backend), cache_(nullptr) { +} + +void DiskCachingFileLoader::Prepare() { + if (prepared_) { + return; + } + prepared_ = true; + + filesize_ = backend_->FileSize(); if (filesize_ > 0) { InitCache(); } @@ -54,19 +62,22 @@ DiskCachingFileLoader::~DiskCachingFileLoader() { } bool DiskCachingFileLoader::Exists() { - if (cache_ && cache_->HasData()) { - // It may require a slow operation to check - if we have data, let's say yes. - // This helps initial load, since we check each recent file for existence. - return true; - } + Prepare(); return backend_->Exists(); } +bool DiskCachingFileLoader::ExistsFast() { + // It may require a slow operation to check - if we have data, let's say yes. + // This helps initial load, since we check each recent file for existence. + return true; +} + bool DiskCachingFileLoader::IsDirectory() { - return backend_->IsDirectory() ? 1 : 0; + return backend_->IsDirectory(); } s64 DiskCachingFileLoader::FileSize() { + Prepare(); return filesize_; } @@ -79,6 +90,7 @@ void DiskCachingFileLoader::Seek(s64 absolutePos) { } size_t DiskCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data) { + Prepare(); size_t readSize; if (absolutePos >= filesize_) { diff --git a/Core/FileLoaders/DiskCachingFileLoader.h b/Core/FileLoaders/DiskCachingFileLoader.h index 511507d240c6..e0ca3e9adc75 100644 --- a/Core/FileLoaders/DiskCachingFileLoader.h +++ b/Core/FileLoaders/DiskCachingFileLoader.h @@ -31,6 +31,7 @@ class DiskCachingFileLoader : public FileLoader { virtual ~DiskCachingFileLoader() override; virtual bool Exists() override; + virtual bool ExistsFast() override; virtual bool IsDirectory() override; virtual s64 FileSize() override; virtual std::string Path() const override; @@ -50,9 +51,11 @@ class DiskCachingFileLoader : public FileLoader { static std::vector GetCachedPathsInUse(); private: + void Prepare(); void InitCache(); void ShutdownCache(); + bool prepared_; s64 filesize_; s64 filepos_; FileLoader *backend_; diff --git a/Core/FileLoaders/HTTPFileLoader.cpp b/Core/FileLoaders/HTTPFileLoader.cpp index 8cc8b9e104c7..725f6b0cadf8 100644 --- a/Core/FileLoaders/HTTPFileLoader.cpp +++ b/Core/FileLoaders/HTTPFileLoader.cpp @@ -21,7 +21,15 @@ #include "Core/FileLoaders/HTTPFileLoader.h" HTTPFileLoader::HTTPFileLoader(const std::string &filename) - : filesize_(0), filepos_(0), url_(filename), filename_(filename), connected_(false) { + : filesize_(0), filepos_(0), url_(filename), filename_(filename), connected_(false), prepared_(false) { +} + +void HTTPFileLoader::Prepare() { + if (prepared_) { + return; + } + prepared_ = true; + if (!client_.Resolve(url_.Host().c_str(), url_.Port())) { // TODO: Should probably set some flag? return; @@ -39,7 +47,7 @@ HTTPFileLoader::HTTPFileLoader(const std::string &filename) int code = client_.ReadResponseHeaders(&readbuf, responseHeaders); if (code != 200) { // Leave size at 0, invalid. - ERROR_LOG(LOADER, "HTTP request failed, got %03d for %s", code, filename.c_str()); + ERROR_LOG(LOADER, "HTTP request failed, got %03d for %s", code, filename_.c_str()); Disconnect(); return; } @@ -78,7 +86,7 @@ HTTPFileLoader::HTTPFileLoader(const std::string &filename) WARN_LOG(LOADER, "HTTP server did not advertise support for range requests."); } if (filesize_ == 0) { - ERROR_LOG(LOADER, "Could not determine file size for %s", filename.c_str()); + ERROR_LOG(LOADER, "Could not determine file size for %s", filename_.c_str()); } // If we didn't end up with a filesize_ (e.g. chunked response), give up. File invalid. @@ -89,15 +97,21 @@ HTTPFileLoader::~HTTPFileLoader() { } bool HTTPFileLoader::Exists() { + Prepare(); return url_.Valid() && filesize_ > 0; } +bool HTTPFileLoader::ExistsFast() { + return url_.Valid(); +} + bool HTTPFileLoader::IsDirectory() { // Only files. return false; } s64 HTTPFileLoader::FileSize() { + Prepare(); return filesize_; } @@ -110,6 +124,7 @@ void HTTPFileLoader::Seek(s64 absolutePos) { } size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data) { + Prepare(); s64 absoluteEnd = std::min(absolutePos + (s64)bytes, filesize_); if (absolutePos >= filesize_ || bytes == 0) { // Read outside of the file or no read at all, just fail immediately. diff --git a/Core/FileLoaders/HTTPFileLoader.h b/Core/FileLoaders/HTTPFileLoader.h index de4cd53adbcb..69c0ce5e5029 100644 --- a/Core/FileLoaders/HTTPFileLoader.h +++ b/Core/FileLoaders/HTTPFileLoader.h @@ -29,6 +29,7 @@ class HTTPFileLoader : public FileLoader { virtual ~HTTPFileLoader() override; virtual bool Exists() override; + virtual bool ExistsFast() override; virtual bool IsDirectory() override; virtual s64 FileSize() override; virtual std::string Path() const override; @@ -46,6 +47,8 @@ class HTTPFileLoader : public FileLoader { virtual size_t ReadAt(s64 absolutePos, size_t bytes, void *data) override; private: + void Prepare(); + void Connect() { if (!connected_) { connected_ = client_.Connect(); @@ -66,4 +69,5 @@ class HTTPFileLoader : public FileLoader { http::Client client_; std::string filename_; bool connected_; + bool prepared_; }; diff --git a/Core/FileLoaders/RamCachingFileLoader.cpp b/Core/FileLoaders/RamCachingFileLoader.cpp index 20598d631c3d..b9e75e1c2f6c 100644 --- a/Core/FileLoaders/RamCachingFileLoader.cpp +++ b/Core/FileLoaders/RamCachingFileLoader.cpp @@ -48,6 +48,14 @@ bool RamCachingFileLoader::Exists() { return exists_ == 1; } +bool RamCachingFileLoader::ExistsFast() { + if (exists_ == -1) { + lock_guard guard(backendMutex_); + return backend_->ExistsFast(); + } + return exists_ == 1; +} + bool RamCachingFileLoader::IsDirectory() { if (isDirectory_ == -1) { lock_guard guard(backendMutex_); diff --git a/Core/FileLoaders/RamCachingFileLoader.h b/Core/FileLoaders/RamCachingFileLoader.h index e1678ccd4ca1..38d05826f708 100644 --- a/Core/FileLoaders/RamCachingFileLoader.h +++ b/Core/FileLoaders/RamCachingFileLoader.h @@ -28,6 +28,7 @@ class RamCachingFileLoader : public FileLoader { ~RamCachingFileLoader() override; bool Exists() override; + bool ExistsFast() override; bool IsDirectory() override; s64 FileSize() override; std::string Path() const override; diff --git a/Core/FileLoaders/RetryingFileLoader.cpp b/Core/FileLoaders/RetryingFileLoader.cpp index 4a998f17777a..28fa17a955a0 100644 --- a/Core/FileLoaders/RetryingFileLoader.cpp +++ b/Core/FileLoaders/RetryingFileLoader.cpp @@ -35,6 +35,14 @@ bool RetryingFileLoader::Exists() { return true; } +bool RetryingFileLoader::ExistsFast() { + if (!backend_->ExistsFast()) { + // Retry once, immediately. + return backend_->ExistsFast(); + } + return true; +} + bool RetryingFileLoader::IsDirectory() { // Can't tell if it's an error either way. return backend_->IsDirectory(); diff --git a/Core/FileLoaders/RetryingFileLoader.h b/Core/FileLoaders/RetryingFileLoader.h index 962e7fc5cd00..24cd0c284aec 100644 --- a/Core/FileLoaders/RetryingFileLoader.h +++ b/Core/FileLoaders/RetryingFileLoader.h @@ -26,6 +26,7 @@ class RetryingFileLoader : public FileLoader { virtual ~RetryingFileLoader() override; virtual bool Exists() override; + virtual bool ExistsFast() override; virtual bool IsDirectory() override; virtual s64 FileSize() override; virtual std::string Path() const override; diff --git a/Core/Loaders.h b/Core/Loaders.h index 4fbe126a5ae4..2fe2e8cc46e9 100644 --- a/Core/Loaders.h +++ b/Core/Loaders.h @@ -54,6 +54,9 @@ class FileLoader { virtual ~FileLoader() {} virtual bool Exists() = 0; + virtual bool ExistsFast() { + return Exists(); + } virtual bool IsDirectory() = 0; virtual s64 FileSize() = 0; virtual std::string Path() const = 0; From 287d1967a5041f3b39e5c00e08cceb57172dca6b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 4 Jul 2016 07:24:04 -0700 Subject: [PATCH 17/19] http: Properly stop server on stop server. --- UI/RemoteISOScreen.cpp | 10 +++++++--- ext/native/net/http_server.cpp | 12 +++++++++++- ext/native/net/http_server.h | 1 + 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 82a0bb17db89..7991256b5228 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -79,7 +79,7 @@ static void RegisterServer(int port) { static void ExecuteServer() { setCurrentThreadName("HTTPServer"); - net::Init(); + net::AutoInit netInit; auto http = new http::Server(new threading::SameThreadExecutor()); std::map paths; @@ -156,7 +156,11 @@ static void ExecuteServer() { } if (!http->Listen(g_Config.iRemoteISOPort)) { - http->Listen(0); + if (!http->Listen(0)) { + ERROR_LOG(COMMON, "Unable to listen on any port"); + UpdateStatus(ServerStatus::STOPPED); + return; + } } UpdateStatus(ServerStatus::RUNNING); @@ -173,7 +177,7 @@ static void ExecuteServer() { } } - net::Shutdown(); + http->Stop(); UpdateStatus(ServerStatus::STOPPED); } diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index 9221d9d37af5..27de698b9caf 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -162,6 +162,10 @@ bool Server::Listen(int port) { } bool Server::RunSlice(double timeout) { + if (listener_ < 0 || port_ == 0) { + return false; + } + if (timeout <= 0.0) { timeout = 86400.0; } @@ -182,7 +186,9 @@ bool Server::RunSlice(double timeout) { } bool Server::Run(int port) { - Listen(port); + if (!Listen(port)) { + return false; + } while (true) { RunSlice(0.0); @@ -192,6 +198,10 @@ bool Server::Run(int port) { return true; } +void Server::Stop() { + closesocket(listener_); +} + void Server::HandleConnection(int conn_fd) { Request request(conn_fd); if (!request.IsOK()) { diff --git a/ext/native/net/http_server.h b/ext/native/net/http_server.h index 1658e45d1886..9035015f98fb 100644 --- a/ext/native/net/http_server.h +++ b/ext/native/net/http_server.h @@ -74,6 +74,7 @@ class Server { // for a new connection to handle. bool RunSlice(double timeout); bool Listen(int port); + void Stop(); void RegisterHandler(const char *url_path, UrlHandlerFunc handler); void SetFallbackHandler(UrlHandlerFunc handler); From f53735f1d8ab584ea0f163d2ddcc41c04968bb61 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 4 Jul 2016 07:32:49 -0700 Subject: [PATCH 18/19] http: Add a safety bailout on list load. Hopefully this will prevent a crash. Currently http::Client uses blocking reads so it will just hang. --- UI/RemoteISOScreen.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 7991256b5228..f5e2a519330d 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -47,6 +47,8 @@ static ServerStatus serverStatus; static recursive_mutex serverStatusLock; static condition_variable serverStatusCond; +static bool scanCancelled = false; + static void UpdateStatus(ServerStatus s) { lock_guard guard(serverStatusLock); serverStatus = s; @@ -194,7 +196,7 @@ static bool FindServer(std::string &resultHost, int &resultPort) { http.Disconnect(); } - if (code != 200) { + if (code != 200 || scanCancelled) { return false; } @@ -247,7 +249,7 @@ static bool LoadGameList(const std::string &host, int port, std::vector Date: Mon, 4 Jul 2016 07:34:40 -0700 Subject: [PATCH 19/19] http: Set an explicit timeout on HTTP connect. Before we were just relying on whatever timeout connect() wanted. --- UI/RemoteISOScreen.cpp | 28 ++++++++-------- ext/native/net/http_client.cpp | 59 ++++++++++++++++++++++++++-------- ext/native/net/http_client.h | 2 +- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index f5e2a519330d..48435ed598f8 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -67,14 +67,14 @@ static void RegisterServer(int port) { Buffer theVoid; if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { - http.Connect(); + if (http.Connect()) { + char resource[1024] = {}; + std::string ip = fd_util::GetLocalIP(http.sock()); + snprintf(resource, sizeof(resource) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); - char resource[1024] = {}; - std::string ip = fd_util::GetLocalIP(http.sock()); - snprintf(resource, sizeof(resource) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port); - - http.GET(resource, &theVoid); - http.Disconnect(); + http.GET(resource, &theVoid); + http.Disconnect(); + } } } @@ -191,9 +191,10 @@ static bool FindServer(std::string &resultHost, int &resultPort) { // Start by requesting a list of recent local ips for this network. if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { - http.Connect(); - code = http.GET("/match/list", &result); - http.Disconnect(); + if (http.Connect()) { + code = http.GET("/match/list", &result); + http.Disconnect(); + } } if (code != 200 || scanCancelled) { @@ -244,9 +245,10 @@ static bool LoadGameList(const std::string &host, int port, std::vector +#include #include +#include #include #define closesocket close #else @@ -12,6 +14,7 @@ #include #endif +#include #include #include @@ -19,12 +22,13 @@ #include "base/buffer.h" #include "base/stringutil.h" #include "data/compression.h" +#include "file/fd_util.h" #include "net/resolve.h" #include "net/url.h" namespace net { -Connection::Connection() +Connection::Connection() : port_(-1), resolved_(NULL), sock_(-1) { } @@ -68,33 +72,60 @@ bool Connection::Resolve(const char *host, int port) { return true; } -bool Connection::Connect(int maxTries) { +bool Connection::Connect(int maxTries, double timeout) { if (port_ <= 0) { ELOG("Bad port"); return false; } - sock_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if ((intptr_t)sock_ == -1) { - ELOG("Bad socket"); - return false; - } + sock_ = -1; for (int tries = maxTries; tries > 0; --tries) { - for (addrinfo *possible = resolved_; possible != NULL; possible = possible->ai_next) { + std::vector sockets; + fd_set fds; + int maxfd = 1; + FD_ZERO(&fds); + for (addrinfo *possible = resolved_; possible != nullptr; possible = possible->ai_next) { // TODO: Could support ipv6 without huge difficulty... if (possible->ai_family != AF_INET) continue; - int retval = connect(sock_, possible->ai_addr, (int)possible->ai_addrlen); - if (retval >= 0) - return true; + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if ((intptr_t)sock == -1) { + ELOG("Bad socket"); + continue; + } + fd_util::SetNonBlocking(sock, true); + + // Start trying to connect (async with timeout.) + connect(sock, possible->ai_addr, (int)possible->ai_addrlen); + sockets.push_back(sock); + FD_SET(sock, &fds); + if (maxfd < sock + 1) { + maxfd = sock + 1; + } + } + + struct timeval tv; + tv.tv_sec = floor(timeout); + tv.tv_usec = (timeout - floor(timeout)) * 1000000.0; + if (select(maxfd, NULL, &fds, NULL, &tv) > 0) { + // Something connected. Pick the first one that did (if multiple.) + for (int sock : sockets) { + if ((intptr_t)sock_ == -1 && FD_ISSET(sock, &fds)) { + fd_util::SetNonBlocking(sock, false); + sock_ = sock; + } else { + closesocket(sock); + } + } + + // Great, now we're good to go. + return true; } sleep_ms(1); } - // Let's not leak this socket. - closesocket(sock_); - sock_ = -1; + // Nothing connected, unfortunately. return false; } diff --git a/ext/native/net/http_client.h b/ext/native/net/http_client.h index e8ca917da974..cb150631e6ee 100644 --- a/ext/native/net/http_client.h +++ b/ext/native/net/http_client.h @@ -27,7 +27,7 @@ class Connection { // Inits the sockaddr_in. bool Resolve(const char *host, int port); - bool Connect(int maxTries = 2); + bool Connect(int maxTries = 2, double timeout = 20.0f); void Disconnect(); // Only to be used for bring-up and debugging.