Skip to content

Commit

Permalink
Homebrew Store: Add support for cancelling downloads, fix progress bar.
Browse files Browse the repository at this point in the history
Fixes #9374
  • Loading branch information
hrydgard committed Mar 6, 2017
1 parent 56e0d5e commit 85c8d9b
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 27 deletions.
9 changes: 9 additions & 0 deletions Core/Util/GameManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
24 changes: 17 additions & 7 deletions Core/Util/GameManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
#include <thread>
#include "net/http_client.h"

enum class GameManagerState {
IDLE,
DOWNLOADING,
INSTALLING,
};

class GameManager {
public:
GameManager();
Expand All @@ -35,18 +41,22 @@ class GameManager {
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 {
Expand Down
8 changes: 4 additions & 4 deletions UI/InstallZipScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion UI/NativeApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
41 changes: 36 additions & 5 deletions UI/Store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -212,6 +212,7 @@ class ProductView : public UI::LinearLayout {
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);

Expand All @@ -220,8 +221,9 @@ class ProductView : public UI::LinearLayout {
}

StoreEntry entry_;
UI::Button *installButton_;
bool wasInstalled_;
UI::Button *installButton_ = nullptr;
UI::Button *cancelButton_ = nullptr;
bool wasInstalled_ = false;
};

void ProductView::CreateViews() {
Expand All @@ -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")));
Expand All @@ -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));

Expand All @@ -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);
}

Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<UIScreen>(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);

Expand Down
1 change: 1 addition & 0 deletions UI/Store.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,6 @@ class StoreScreen : public UIDialogScreenWithBackground {
std::string lang_;

UI::ViewGroup *productPanel_;
UI::TextView *titleText_;
};

4 changes: 3 additions & 1 deletion ext/native/base/buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<char> buf;
if (knownSize >= 65536 * 16) {
buf.resize(65536);
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion ext/native/base/buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions ext/native/net/http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "file/fd_util.h"
#include "net/resolve.h"
#include "net/url.h"
#include "thread/threadutil.h"

namespace net {

Expand Down Expand Up @@ -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";
Expand All @@ -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;
}
Expand Down Expand Up @@ -301,7 +302,7 @@ int Client::ReadResponseHeaders(Buffer *readbuf, std::vector<std::string> &respo
return code;
}

int Client::ReadResponseEntity(Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, float *progress) {
int Client::ReadResponseEntity(Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, float *progress, bool *cancelled) {
bool gzip = false;
bool chunked = false;
int contentLength = 0;
Expand Down Expand Up @@ -340,7 +341,7 @@ int Client::ReadResponseEntity(Buffer *readbuf, const std::vector<std::string> &
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;
}

Expand Down Expand Up @@ -391,6 +392,7 @@ void Download::SetFailed(int code) {
}

void Download::Do(std::shared_ptr<Download> 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<Download> self_ = self;
Expand Down Expand Up @@ -426,7 +428,7 @@ void Download::Do(std::shared_ptr<Download> 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())) {
Expand Down
6 changes: 3 additions & 3 deletions ext/native/net/http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class Client : public net::Connection {
~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);
Expand All @@ -70,7 +70,7 @@ class Client : public net::Connection {
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<std::string> &responseHeaders, float *progress = nullptr);
// If your response contains a response, you must read it.
int ReadResponseEntity(Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, float *progress = nullptr);
int ReadResponseEntity(Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, float *progress = nullptr, bool *cancelled = nullptr);

const char *userAgent_;
const char *httpVersion_;
Expand Down Expand Up @@ -136,7 +136,7 @@ class Download {
int resultCode_;
bool completed_;
bool failed_;
volatile bool cancelled_;
bool cancelled_;
bool hidden_;
std::function<void(Download &)> callback_;
};
Expand Down

0 comments on commit 85c8d9b

Please sign in to comment.