From 85c8d9b806a5834f158cbfd9e0d2962cf6598cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 6 Mar 2017 15:43:38 +0100 Subject: [PATCH] Homebrew Store: Add support for cancelling downloads, fix progress bar. Fixes #9374 --- Core/Util/GameManager.cpp | 9 ++++++++ Core/Util/GameManager.h | 24 ++++++++++++++------ UI/InstallZipScreen.cpp | 8 +++---- UI/NativeApp.cpp | 2 +- UI/Store.cpp | 41 +++++++++++++++++++++++++++++----- UI/Store.h | 1 + ext/native/base/buffer.cpp | 4 +++- ext/native/base/buffer.h | 2 +- ext/native/net/http_client.cpp | 12 +++++----- ext/native/net/http_client.h | 6 ++--- 10 files changed, 82 insertions(+), 27 deletions(-) diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index 06dbdceb44..5e1bdd3d1a 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -73,6 +73,15 @@ bool GameManager::DownloadAndInstall(std::string storeZipUrl) { return true; } +bool GameManager::CancelDownload() { + if (!curDownload_) + return false; + + curDownload_->Cancel(); + curDownload_.reset(); + return true; +} + bool GameManager::Uninstall(std::string name) { if (name.empty()) { ERROR_LOG(HLE, "Cannot remove an empty-named game"); diff --git a/Core/Util/GameManager.h b/Core/Util/GameManager.h index 36e5301c55..55e22312ba 100644 --- a/Core/Util/GameManager.h +++ b/Core/Util/GameManager.h @@ -25,6 +25,12 @@ #include #include "net/http_client.h" +enum class GameManagerState { + IDLE, + DOWNLOADING, + INSTALLING, +}; + class GameManager { public: GameManager(); @@ -35,18 +41,22 @@ public: bool DownloadAndInstall(std::string storeZipUrl); bool Uninstall(std::string name); + // Cancels the download in progress, if any. + bool CancelDownload(); + // Call from time to time to check on completed downloads from the // main UI thread. void Update(); - // Returns false if no install is in progress. - bool IsInstallInProgress() const { - return installInProgress_ || IsDownloadInProgress(); + GameManagerState GetState() { + if (installInProgress_) + return GameManagerState::INSTALLING; + if (curDownload_) + return GameManagerState::DOWNLOADING; + return GameManagerState::IDLE; } - bool IsDownloadInProgress() const { - return curDownload_.get() != nullptr; - } - float GetCurrentInstallProgress() const { + + float GetCurrentInstallProgressPercentage() const { return installProgress_; } std::string GetInstallError() const { diff --git a/UI/InstallZipScreen.cpp b/UI/InstallZipScreen.cpp index a66a795aa2..14a00827d3 100644 --- a/UI/InstallZipScreen.cpp +++ b/UI/InstallZipScreen.cpp @@ -61,8 +61,8 @@ void InstallZipScreen::CreateViews() { } bool InstallZipScreen::key(const KeyInput &key) { - // Ignore all key presses during installation to avoid user escape - if (!g_GameManager.IsInstallInProgress()) { + // Ignore all key presses during download and installation to avoid user escape + if (g_GameManager.GetState() == GameManagerState::IDLE) { return UIScreen::key(key); } return false; @@ -80,9 +80,9 @@ void InstallZipScreen::update(InputState &input) { I18NCategory *iz = GetI18NCategory("InstallZip"); using namespace UI; - if (g_GameManager.IsInstallInProgress()) { + if (g_GameManager.GetState() != GameManagerState::IDLE) { progressBar_->SetVisibility(V_VISIBLE); - progressBar_->SetProgress(g_GameManager.GetCurrentInstallProgress()); + progressBar_->SetProgress(g_GameManager.GetCurrentInstallProgressPercentage()); backChoice_->SetEnabled(false); } else { progressBar_->SetVisibility(V_GONE); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index a683ea6d6d..f2e4f40b99 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -784,7 +784,7 @@ void NativeRender(GraphicsContext *graphicsContext) { // At this point, the vulkan context has been "ended" already, no more drawing can be done in this frame. // TODO: Integrate the download overlay with the screen system - // DrawDownloadsOverlay(*screenManager->getUIContext()); + DrawDownloadsOverlay(*screenManager->getUIContext()); if (g_TakeScreenshot) { TakeScreenshot(); diff --git a/UI/Store.cpp b/UI/Store.cpp index e2b6e3f1b7..7d2c76d4a2 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -201,7 +201,7 @@ void ProductItemView::Update(const InputState &input_state) { class ProductView : public UI::LinearLayout { public: ProductView(const StoreEntry &entry) - : LinearLayout(UI::ORIENT_VERTICAL), entry_(entry), installButton_(0), wasInstalled_(false) { + : LinearLayout(UI::ORIENT_VERTICAL), entry_(entry) { CreateViews(); } @@ -212,6 +212,7 @@ public: private: void CreateViews(); UI::EventReturn OnInstall(UI::EventParams &e); + UI::EventReturn OnCancel(UI::EventParams &e); UI::EventReturn OnUninstall(UI::EventParams &e); UI::EventReturn OnLaunchClick(UI::EventParams &e); @@ -220,8 +221,9 @@ private: } StoreEntry entry_; - UI::Button *installButton_; - bool wasInstalled_; + UI::Button *installButton_ = nullptr; + UI::Button *cancelButton_ = nullptr; + bool wasInstalled_ = false; }; void ProductView::CreateViews() { @@ -235,6 +237,7 @@ void ProductView::CreateViews() { Add(new TextView(entry_.author)); I18NCategory *st = GetI18NCategory("Store"); + I18NCategory *di = GetI18NCategory("Dialog"); wasInstalled_ = IsGameInstalled(); if (!wasInstalled_) { installButton_ = Add(new Button(st->T("Install"))); @@ -246,6 +249,10 @@ void ProductView::CreateViews() { Add(new Button(st->T("Launch Game")))->OnClick.Handle(this, &ProductView::OnLaunchClick); } + cancelButton_ = Add(new Button(di->T("Cancel"))); + cancelButton_->OnClick.Handle(this, &ProductView::OnCancel); + cancelButton_->SetVisibility(V_GONE); + // Add star rating, comments etc? Add(new TextView(entry_.description)); @@ -261,8 +268,10 @@ void ProductView::Update(const InputState &input_state) { CreateViews(); } if (installButton_) { - installButton_->SetEnabled(!g_GameManager.IsInstallInProgress()); + installButton_->SetEnabled(g_GameManager.GetState() == GameManagerState::IDLE); } + if (cancelButton_ && g_GameManager.GetState() != GameManagerState::DOWNLOADING) + cancelButton_->SetVisibility(UI::V_GONE); View::Update(input_state); } @@ -278,11 +287,19 @@ UI::EventReturn ProductView::OnInstall(UI::EventParams &e) { if (installButton_) { installButton_->SetEnabled(false); } + if (cancelButton_) { + cancelButton_->SetVisibility(UI::V_VISIBLE); + } INFO_LOG(SYSTEM, "Triggering install of %s", zipUrl.c_str()); g_GameManager.DownloadAndInstall(zipUrl); return UI::EVENT_DONE; } +UI::EventReturn ProductView::OnCancel(UI::EventParams &e) { + g_GameManager.CancelDownload(); + return UI::EVENT_DONE; +} + UI::EventReturn ProductView::OnUninstall(UI::EventParams &e) { g_GameManager.Uninstall(entry_.file); CreateViews(); @@ -345,6 +362,19 @@ void StoreScreen::update(InputState &input) { // Forget the listing. listing_.reset(); } + + const char *storeName = "PPSSPP Homebrew Store"; + switch (g_GameManager.GetState()) { + case GameManagerState::DOWNLOADING: + titleText_->SetText(std::string(storeName) + " - downloading"); + break; + case GameManagerState::INSTALLING: + titleText_->SetText(std::string(storeName) + " - installing"); + break; + default: + titleText_->SetText(storeName); + break; + } } void StoreScreen::ParseListing(std::string json) { @@ -391,7 +421,8 @@ void StoreScreen::CreateViews() { // Top bar LinearLayout *topBar = root_->Add(new LinearLayout(ORIENT_HORIZONTAL)); topBar->Add(new Button(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); - topBar->Add(new TextView("PPSSPP Homebrew Store")); + titleText_ = new TextView("PPSSPP Homebrew Store"); + topBar->Add(titleText_); UI::Drawable solid(0xFFbd9939); topBar->SetBG(solid); diff --git a/UI/Store.h b/UI/Store.h index cbca3d0985..9e55531ba7 100644 --- a/UI/Store.h +++ b/UI/Store.h @@ -99,5 +99,6 @@ private: std::string lang_; UI::ViewGroup *productPanel_; + UI::TextView *titleText_; }; diff --git a/ext/native/base/buffer.cpp b/ext/native/base/buffer.cpp index da87602a02..1c9aff81ee 100644 --- a/ext/native/base/buffer.cpp +++ b/ext/native/base/buffer.cpp @@ -183,7 +183,7 @@ bool Buffer::ReadAll(int fd, int hintSize) { return true; } -bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress) { +bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, bool *cancelled) { std::vector buf; if (knownSize >= 65536 * 16) { buf.resize(65536); @@ -195,6 +195,8 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress) { int total = 0; while (true) { + if (cancelled && *cancelled) + return false; int retval = recv(fd, &buf[0], (int)buf.size(), 0); if (retval == 0) { return true; diff --git a/ext/native/base/buffer.h b/ext/native/base/buffer.h index d98ca94c9f..bca84d9ba7 100644 --- a/ext/native/base/buffer.h +++ b/ext/native/base/buffer.h @@ -67,7 +67,7 @@ class Buffer { bool FlushSocket(uintptr_t sock); // Windows portability bool ReadAll(int fd, int hintSize = 0); - bool ReadAllWithProgress(int fd, int knownSize, float *progress); + bool ReadAllWithProgress(int fd, int knownSize, float *progress, bool *cancelled); // < 0: error // >= 0: number of bytes read diff --git a/ext/native/net/http_client.cpp b/ext/native/net/http_client.cpp index cb652d93be..a9ddf9bbf5 100644 --- a/ext/native/net/http_client.cpp +++ b/ext/native/net/http_client.cpp @@ -25,6 +25,7 @@ #include "file/fd_util.h" #include "net/resolve.h" #include "net/url.h" +#include "thread/threadutil.h" namespace net { @@ -179,7 +180,7 @@ void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *prog } } -int Client::GET(const char *resource, Buffer *output, float *progress) { +int Client::GET(const char *resource, Buffer *output, float *progress, bool *cancelled) { const char *otherHeaders = "Accept: */*\r\n" "Accept-Encoding: gzip\r\n"; @@ -195,7 +196,7 @@ int Client::GET(const char *resource, Buffer *output, float *progress) { return code; } - err = ReadResponseEntity(&readbuf, responseHeaders, output, progress); + err = ReadResponseEntity(&readbuf, responseHeaders, output, progress, cancelled); if (err < 0) { return err; } @@ -301,7 +302,7 @@ int Client::ReadResponseHeaders(Buffer *readbuf, std::vector &respo return code; } -int Client::ReadResponseEntity(Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, float *progress) { +int Client::ReadResponseEntity(Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, float *progress, bool *cancelled) { bool gzip = false; bool chunked = false; int contentLength = 0; @@ -340,7 +341,7 @@ int Client::ReadResponseEntity(Buffer *readbuf, const std::vector & return -1; } else { // Let's read in chunks, updating progress between each. - if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress)) + if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress, cancelled)) return -1; } @@ -391,6 +392,7 @@ void Download::SetFailed(int code) { } void Download::Do(std::shared_ptr self) { + setCurrentThreadName("Downloader::Do"); // as long as this is in scope, we won't get destructed. // yeah this is ugly, I need to think about how life time should be managed for these... std::shared_ptr self_ = self; @@ -426,7 +428,7 @@ void Download::Do(std::shared_ptr self) { } // TODO: Allow cancelling during a GET somehow... - int resultCode = client.GET(fileUrl.Resource().c_str(), &buffer_, &progress_); + int resultCode = client.GET(fileUrl.Resource().c_str(), &buffer_, &progress_, &cancelled_); if (resultCode == 200) { ILOG("Completed downloading %s to %s", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str()); if (!outfile_.empty() && !buffer_.FlushToFile(outfile_.c_str())) { diff --git a/ext/native/net/http_client.h b/ext/native/net/http_client.h index d7e4a682b6..ec2c40cb81 100644 --- a/ext/native/net/http_client.h +++ b/ext/native/net/http_client.h @@ -58,7 +58,7 @@ public: ~Client(); // Return value is the HTTP return code. 200 means OK. < 0 means some local error. - int GET(const char *resource, Buffer *output, float *progress = nullptr); + int GET(const char *resource, Buffer *output, float *progress = nullptr, bool *cancelled = nullptr); // Return value is the HTTP return code. int POST(const char *resource, const std::string &data, const std::string &mime, Buffer *output, float *progress = nullptr); @@ -70,7 +70,7 @@ public: int SendRequestWithData(const char *method, const char *resource, const std::string &data, const char *otherHeaders = nullptr, float *progress = nullptr); int ReadResponseHeaders(Buffer *readbuf, std::vector &responseHeaders, float *progress = nullptr); // If your response contains a response, you must read it. - int ReadResponseEntity(Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, float *progress = nullptr); + int ReadResponseEntity(Buffer *readbuf, const std::vector &responseHeaders, Buffer *output, float *progress = nullptr, bool *cancelled = nullptr); const char *userAgent_; const char *httpVersion_; @@ -136,7 +136,7 @@ private: int resultCode_; bool completed_; bool failed_; - volatile bool cancelled_; + bool cancelled_; bool hidden_; std::function callback_; };