Improved progress bar popups for downloads

Now shows the filename, and also there's a delay mode where they'll only
be visible if the download takes more than a second, plus they can be
named.
This commit is contained in:
Henrik Rydgård 2023-07-18 15:13:44 +02:00
parent 04932d98a7
commit ecea3844b0
18 changed files with 167 additions and 157 deletions

View file

@ -38,7 +38,7 @@ bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *can
http::RequestParams req(baseURL.Resource(), "text/plain, text/html; q=0.9, */*; q=0.8"); http::RequestParams req(baseURL.Resource(), "text/plain, text/html; q=0.9, */*; q=0.8");
if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) { if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) {
if (http.Connect(2, 20.0, cancel)) { if (http.Connect(2, 20.0, cancel)) {
http::RequestProgress progress(cancel); net::RequestProgress progress(cancel);
code = http.GET(req, &result, responseHeaders, &progress); code = http.GET(req, &result, responseHeaders, &progress);
http.Disconnect(); http.Disconnect();
} }

View file

@ -2,6 +2,7 @@
#include "Common/TimeUtil.h" #include "Common/TimeUtil.h"
#include "Common/StringUtils.h" #include "Common/StringUtils.h"
#include "Common/System/OSD.h"
#ifndef _WIN32 #ifndef _WIN32
#include <netinet/in.h> #include <netinet/in.h>
@ -217,7 +218,7 @@ bool GetHeaderValue(const std::vector<std::string> &responseHeaders, const std::
return found; return found;
} }
void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *progress) { void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength) {
int dechunkedBytes = 0; int dechunkedBytes = 0;
while (true) { while (true) {
std::string line; std::string line;
@ -236,14 +237,11 @@ void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *prog
return; return;
} }
dechunkedBytes += chunkSize; dechunkedBytes += chunkSize;
if (progress && contentLength) {
*progress = (float)dechunkedBytes / contentLength;
}
inbuffer->Skip(2); inbuffer->Skip(2);
} }
} }
int Client::GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, RequestProgress *progress) { int Client::GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, net::RequestProgress *progress) {
const char *otherHeaders = const char *otherHeaders =
"Accept-Encoding: gzip\r\n"; "Accept-Encoding: gzip\r\n";
int err = SendRequest("GET", req, otherHeaders, progress); int err = SendRequest("GET", req, otherHeaders, progress);
@ -264,13 +262,13 @@ int Client::GET(const RequestParams &req, Buffer *output, std::vector<std::strin
return code; return code;
} }
int Client::GET(const RequestParams &req, Buffer *output, RequestProgress *progress) { int Client::GET(const RequestParams &req, Buffer *output, net::RequestProgress *progress) {
std::vector<std::string> responseHeaders; std::vector<std::string> responseHeaders;
int code = GET(req, output, responseHeaders, progress); int code = GET(req, output, responseHeaders, progress);
return code; return code;
} }
int Client::POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress) { int Client::POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, net::RequestProgress *progress) {
char otherHeaders[2048]; char otherHeaders[2048];
if (mime.empty()) { if (mime.empty()) {
snprintf(otherHeaders, sizeof(otherHeaders), "Content-Length: %lld\r\n", (long long)data.size()); snprintf(otherHeaders, sizeof(otherHeaders), "Content-Length: %lld\r\n", (long long)data.size());
@ -296,16 +294,16 @@ int Client::POST(const RequestParams &req, const std::string &data, const std::s
return code; return code;
} }
int Client::POST(const RequestParams &req, const std::string &data, Buffer *output, RequestProgress *progress) { int Client::POST(const RequestParams &req, const std::string &data, Buffer *output, net::RequestProgress *progress) {
return POST(req, data, "", output, progress); return POST(req, data, "", output, progress);
} }
int Client::SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, RequestProgress *progress) { int Client::SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress) {
return SendRequestWithData(method, req, "", otherHeaders, progress); return SendRequestWithData(method, req, "", otherHeaders, progress);
} }
int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, RequestProgress *progress) { int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress) {
progress->progress = 0.01f; progress->Update(0.01f);
net::Buffer buffer; net::Buffer buffer;
const char *tpl = const char *tpl =
@ -331,7 +329,7 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, co
return 0; return 0;
} }
int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, RequestProgress *progress) { int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, net::RequestProgress *progress) {
// Snarf all the data we can into RAM. A little unsafe but hey. // Snarf all the data we can into RAM. A little unsafe but hey.
static constexpr float CANCEL_INTERVAL = 0.25f; static constexpr float CANCEL_INTERVAL = 0.25f;
bool ready = false; bool ready = false;
@ -384,7 +382,7 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &
return code; return code;
} }
int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, RequestProgress *progress) { int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, net::RequestProgress *progress) {
bool gzip = false; bool gzip = false;
bool chunked = false; bool chunked = false;
int contentLength = 0; int contentLength = 0;
@ -418,24 +416,19 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
if (!contentLength) { if (!contentLength) {
// Content length is unknown. // Content length is unknown.
// Set progress to 1% so it looks like something is happening... // Set progress to 2% so it looks like something is happening...
progress->progress = 0.1f; // The later progress updates won't include a reliable percentage.
progress->Update(0.02f);
} }
if (!contentLength) { if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress))
// No way to know how far along we are. Let's just not update the progress counter. return -1;
if (!readbuf->ReadAllWithProgress(sock(), contentLength, nullptr, &progress->kBps, progress->cancelled))
return -1;
} else {
// Let's read in chunks, updating progress between each.
if (!readbuf->ReadAllWithProgress(sock(), contentLength, &progress->progress, &progress->kBps, progress->cancelled))
return -1;
}
// output now contains the rest of the reply. Dechunk it. // output now contains the rest of the reply. Dechunk it.
if (!output->IsVoid()) { if (!output->IsVoid()) {
if (chunked) { if (chunked) {
DeChunk(readbuf, output, contentLength, &progress->progress); DeChunk(readbuf, output, contentLength);
progress->Update(1.0f);
} else { } else {
output->Append(*readbuf); output->Append(*readbuf);
} }
@ -447,19 +440,39 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
bool result = decompress_string(compressed, &decompressed); bool result = decompress_string(compressed, &decompressed);
if (!result) { if (!result) {
ERROR_LOG(IO, "Error decompressing using zlib"); ERROR_LOG(IO, "Error decompressing using zlib");
progress->progress = 0.0f; progress->Update(0.0f); // TJDO: is this right?
return -1; return -1;
} }
output->Append(decompressed); output->Append(decompressed);
} }
} }
progress->progress = 1.0f; progress->Update(1.0f);
return 0; return 0;
} }
Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile) Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode)
: method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile) { : method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile), progressBarMode_(progressBarMode) {
progress_.callback = [=](float progress) {
std::string message;
if (!name_.empty()) {
message = name_;
} else {
std::size_t pos = url.rfind('/');
if (pos != std::string::npos) {
message = url.substr(pos + 1);
} else {
message = url;
}
}
if (progressBarMode_ != ProgressBarMode::NONE) {
g_OSD.SetProgressBar(url, std::move(message), 0.0f, 1.0f, progress, progressBarMode_ == ProgressBarMode::DELAYED ? 1.0f : 0.0f); // delay 0.5 seconds before showing.
if (progress == 1.0f) {
g_OSD.RemoveProgressBar(url, Failed() ? false : true, 0.5f);
}
}
};
} }
Download::~Download() { Download::~Download() {
@ -480,7 +493,7 @@ void Download::Join() {
void Download::SetFailed(int code) { void Download::SetFailed(int code) {
failed_ = true; failed_ = true;
progress_.progress = 1.0f; progress_.Update(1.0f);
completed_ = true; completed_ = true;
} }
@ -575,15 +588,16 @@ void Download::Do() {
resultCode_ = resultCode; resultCode_ = resultCode;
} }
progress_.progress = 1.0f; progress_.Update(1.0f);
// Set this last to ensure no race conditions when checking Done. Users must always check // Set this last to ensure no race conditions when checking Done. Users must always check
// Done before looking at the result code. // Done before looking at the result code.
completed_ = true; completed_ = true;
} }
std::shared_ptr<Download> Downloader::StartDownload(const std::string &url, const Path &outfile, const char *acceptMime) { std::shared_ptr<Download> Downloader::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) {
std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile)); std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile, mode));
if (!userAgent_.empty()) if (!userAgent_.empty())
dl->SetUserAgent(userAgent_); dl->SetUserAgent(userAgent_);
if (acceptMime) if (acceptMime)
@ -596,9 +610,10 @@ std::shared_ptr<Download> Downloader::StartDownload(const std::string &url, cons
std::shared_ptr<Download> Downloader::StartDownloadWithCallback( std::shared_ptr<Download> Downloader::StartDownloadWithCallback(
const std::string &url, const std::string &url,
const Path &outfile, const Path &outfile,
ProgressBarMode mode,
std::function<void(Download &)> callback, std::function<void(Download &)> callback,
const char *acceptMime) { const char *acceptMime) {
std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile)); std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile, mode));
if (!userAgent_.empty()) if (!userAgent_.empty())
dl->SetUserAgent(userAgent_); dl->SetUserAgent(userAgent_);
if (acceptMime) if (acceptMime)
@ -613,8 +628,9 @@ std::shared_ptr<Download> Downloader::AsyncPostWithCallback(
const std::string &url, const std::string &url,
const std::string &postData, const std::string &postData,
const std::string &postMime, const std::string &postMime,
ProgressBarMode mode,
std::function<void(Download &)> callback) { std::function<void(Download &)> callback) {
std::shared_ptr<Download> dl(new Download(RequestMethod::POST, url, postData, postMime, Path())); std::shared_ptr<Download> dl(new Download(RequestMethod::POST, url, postData, postMime, Path(), mode));
if (!userAgent_.empty()) if (!userAgent_.empty())
dl->SetUserAgent(userAgent_); dl->SetUserAgent(userAgent_);
dl->SetCallback(callback); dl->SetCallback(callback);
@ -649,15 +665,6 @@ void Downloader::WaitForAll() {
} }
} }
std::vector<float> Downloader::GetCurrentProgress() {
std::vector<float> progress;
for (size_t i = 0; i < downloads_.size(); i++) {
if (!downloads_[i]->IsHidden())
progress.push_back(downloads_[i]->Progress());
}
return progress;
}
void Downloader::CancelAll() { void Downloader::CancelAll() {
for (size_t i = 0; i < downloads_.size(); i++) { for (size_t i = 0; i < downloads_.size(); i++) {
downloads_[i]->Cancel(); downloads_[i]->Cancel();

View file

@ -44,16 +44,8 @@ namespace http {
bool GetHeaderValue(const std::vector<std::string> &responseHeaders, const std::string &header, std::string *value); bool GetHeaderValue(const std::vector<std::string> &responseHeaders, const std::string &header, std::string *value);
struct RequestProgress { class RequestParams {
RequestProgress() {} public:
explicit RequestProgress(bool *c) : cancelled(c) {}
float progress = 0.0f;
float kBps = 0.0f;
bool *cancelled = nullptr;
};
struct RequestParams {
RequestParams() {} RequestParams() {}
explicit RequestParams(const char *r) : resource(r) {} explicit RequestParams(const char *r) : resource(r) {}
RequestParams(const std::string &r, const char *a) : resource(r), acceptMime(a) {} RequestParams(const std::string &r, const char *a) : resource(r), acceptMime(a) {}
@ -68,20 +60,20 @@ public:
~Client(); ~Client();
// Return value is the HTTP return code. 200 means OK. < 0 means some local error. // Return value is the HTTP return code. 200 means OK. < 0 means some local error.
int GET(const RequestParams &req, Buffer *output, RequestProgress *progress); int GET(const RequestParams &req, Buffer *output, net::RequestProgress *progress);
int GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, RequestProgress *progress); int GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, net::RequestProgress *progress);
// Return value is the HTTP return code. // Return value is the HTTP return code.
int POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress); int POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, net::RequestProgress *progress);
int POST(const RequestParams &req, const std::string &data, Buffer *output, RequestProgress *progress); int POST(const RequestParams &req, const std::string &data, Buffer *output, net::RequestProgress *progress);
// HEAD, PUT, DELETE aren't implemented yet, but can be done with SendRequest. // HEAD, PUT, DELETE aren't implemented yet, but can be done with SendRequest.
int SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, RequestProgress *progress); int SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress);
int SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, RequestProgress *progress); int SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress);
int ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, RequestProgress *progress); int ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, net::RequestProgress *progress);
// If your response contains a response, you must read it. // If your response contains a response, you must read it.
int ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, RequestProgress *progress); int ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, net::RequestProgress *progress);
void SetDataTimeout(double t) { void SetDataTimeout(double t) {
dataTimeout_ = t; dataTimeout_ = t;
@ -102,12 +94,26 @@ enum class RequestMethod {
POST, POST,
}; };
enum class ProgressBarMode {
NONE,
VISIBLE,
DELAYED,
};
// Really an asynchronous request. // Really an asynchronous request.
class Download { class Download {
public: public:
Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile); Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED);
~Download(); ~Download();
void SetAccept(const char *mime) {
acceptMime_ = mime;
}
void SetUserAgent(const std::string &userAgent) {
userAgent_ = userAgent;
}
void Start(); void Start();
void Join(); void Join();
@ -125,10 +131,6 @@ public:
std::string url() const { return url_; } std::string url() const { return url_; }
const Path &outfile() const { return outfile_; } const Path &outfile() const { return outfile_; }
void SetAccept(const char *mime) {
acceptMime_ = mime;
}
// If not downloading to a file, access this to get the result. // If not downloading to a file, access this to get the result.
Buffer &buffer() { return buffer_; } Buffer &buffer() { return buffer_; }
const Buffer &buffer() const { return buffer_; } const Buffer &buffer() const { return buffer_; }
@ -141,7 +143,7 @@ public:
return cancelled_; return cancelled_;
} }
// NOTE: Callbacks are NOT executed until RunCallback is called. This is so that // NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that
// the call will end up on the thread that calls g_DownloadManager.Update(). // the call will end up on the thread that calls g_DownloadManager.Update().
void SetCallback(std::function<void(Download &)> callback) { void SetCallback(std::function<void(Download &)> callback) {
callback_ = callback; callback_ = callback;
@ -151,14 +153,8 @@ public:
callback_(*this); callback_(*this);
} }
} }
// Visual name for the download, to be displayed in progress bars.
// Just metadata. Convenient for download managers, for example, if set, void SetName(const std::string &&name) { name_ = std::move(name); }
// Downloader::GetCurrentProgress won't return it in the results.
bool IsHidden() const { return hidden_; }
void SetHidden(bool hidden) { hidden_ = hidden; }
void SetUserAgent(const std::string &userAgent) {
userAgent_ = userAgent;
}
private: private:
void Do(); // Actually does the download. Runs on thread. void Do(); // Actually does the download. Runs on thread.
@ -166,8 +162,8 @@ private:
std::string RedirectLocation(const std::string &baseUrl); std::string RedirectLocation(const std::string &baseUrl);
void SetFailed(int code); void SetFailed(int code);
RequestProgress progress_;
RequestMethod method_; RequestMethod method_;
net::RequestProgress progress_;
std::string postData_; std::string postData_;
std::string userAgent_; std::string userAgent_;
Buffer buffer_; Buffer buffer_;
@ -181,8 +177,9 @@ private:
bool completed_ = false; bool completed_ = false;
bool failed_ = false; bool failed_ = false;
bool cancelled_ = false; bool cancelled_ = false;
bool hidden_ = false; ProgressBarMode progressBarMode_;
bool joined_ = false; bool joined_ = false;
std::string name_;
std::function<void(Download &)> callback_; std::function<void(Download &)> callback_;
}; };
@ -194,11 +191,12 @@ public:
CancelAll(); CancelAll();
} }
std::shared_ptr<Download> StartDownload(const std::string &url, const Path &outfile, const char *acceptMime = nullptr); std::shared_ptr<Download> StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime = nullptr);
std::shared_ptr<Download> StartDownloadWithCallback( std::shared_ptr<Download> StartDownloadWithCallback(
const std::string &url, const std::string &url,
const Path &outfile, const Path &outfile,
ProgressBarMode mode,
std::function<void(Download &)> callback, std::function<void(Download &)> callback,
const char *acceptMime = nullptr); const char *acceptMime = nullptr);
@ -206,6 +204,7 @@ public:
const std::string &url, const std::string &url,
const std::string &postData, const std::string &postData,
const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder. const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder.
ProgressBarMode mode,
std::function<void(Download &)> callback); std::function<void(Download &)> callback);
// Drops finished downloads from the list. // Drops finished downloads from the list.
@ -217,12 +216,6 @@ public:
userAgent_ = userAgent; userAgent_ = userAgent;
} }
std::vector<float> GetCurrentProgress();
size_t GetActiveCount() const {
return downloads_.size();
}
private: private:
std::vector<std::shared_ptr<Download>> downloads_; std::vector<std::shared_ptr<Download>> downloads_;
// These get copied to downloads_ in Update(). It's so that callbacks can add new downloads // These get copied to downloads_ in Update(). It's so that callbacks can add new downloads

View file

@ -48,7 +48,7 @@ bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) {
return true; return true;
} }
bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float *kBps, bool *cancelled) { bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress) {
static constexpr float CANCEL_INTERVAL = 0.25f; static constexpr float CANCEL_INTERVAL = 0.25f;
std::vector<char> buf; std::vector<char> buf;
// We're non-blocking and reading from an OS buffer, so try to read as much as we can at a time. // We're non-blocking and reading from an OS buffer, so try to read as much as we can at a time.
@ -64,8 +64,8 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float *
int total = 0; int total = 0;
while (true) { while (true) {
bool ready = false; bool ready = false;
while (!ready && cancelled) { while (!ready && (!progress || !*progress->cancelled)) {
if (*cancelled) if (*progress->cancelled)
return false; return false;
ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false); ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false);
} }
@ -88,10 +88,10 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float *
char *p = Append((size_t)retval); char *p = Append((size_t)retval);
memcpy(p, &buf[0], retval); memcpy(p, &buf[0], retval);
total += retval; total += retval;
if (progress) if (progress) {
*progress = (float)total / (float)knownSize; progress->Update((float)total / (float)knownSize);
if (kBps) progress->kBps = (float)(total / (time_now_d() - st)) / 1024.0f;
*kBps = (float)(total / (time_now_d() - st)) / 1024.0f; }
} }
return true; return true;
} }
@ -114,4 +114,4 @@ int Buffer::Read(int fd, size_t sz) {
return (int)received; return (int)received;
} }
} } // namespace

View file

@ -1,16 +1,35 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <functional>
#include "Common/Buffer.h" #include "Common/Buffer.h"
namespace net { namespace net {
class RequestProgress {
public:
RequestProgress() {}
explicit RequestProgress(bool *c) : cancelled(c) {}
void Update(float newProgress) {
progress = newProgress;
if (callback) {
callback(newProgress);
}
}
float progress = 0.0f;
float kBps = 0.0f;
bool *cancelled = nullptr;
std::function<void(float)> callback;
};
class Buffer : public ::Buffer { class Buffer : public ::Buffer {
public: public:
bool FlushSocket(uintptr_t sock, double timeout, bool *cancelled = nullptr); bool FlushSocket(uintptr_t sock, double timeout, bool *cancelled = nullptr);
bool ReadAllWithProgress(int fd, int knownSize, float *progress, float *kBps, bool *cancelled); bool ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress);
// < 0: error // < 0: error
// >= 0: number of bytes read // >= 0: number of bytes read

View file

@ -215,7 +215,7 @@ void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float durat
Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s); Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s);
} }
void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress) { void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, float minValue, float maxValue, float progress, float delay) {
std::lock_guard<std::mutex> guard(mutex_); std::lock_guard<std::mutex> guard(mutex_);
double now = time_now_d(); double now = time_now_d();
bool found = false; bool found = false;
@ -236,16 +236,27 @@ void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int
bar.minValue = minValue; bar.minValue = minValue;
bar.maxValue = maxValue; bar.maxValue = maxValue;
bar.progress = progress; bar.progress = progress;
bar.startTime = now + delay;
bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out. bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out.
bars_.push_back(bar); bars_.push_back(bar);
} }
void OnScreenDisplay::RemoveProgressBar(std::string id) { void OnScreenDisplay::RemoveProgressBar(std::string id, bool success, float delay_s) {
std::lock_guard<std::mutex> guard(mutex_); std::lock_guard<std::mutex> guard(mutex_);
for (auto iter = bars_.begin(); iter != bars_.end(); iter++) { for (auto iter = bars_.begin(); iter != bars_.end(); iter++) {
if (iter->id == id) { if (iter->id == id) {
iter->progress = iter->maxValue; if (success) {
iter->endTime = time_now_d() + FadeoutTime(); // Quickly shoot up to max, if we weren't there.
if (iter->maxValue != 0.0f) {
iter->progress = iter->maxValue;
} else {
// Fake a full progress
iter->minValue = 0;
iter->maxValue = 1;
iter->progress = 1;
}
}
iter->endTime = time_now_d() + delay_s + FadeoutTime();
break; break;
} }
} }

View file

@ -50,8 +50,8 @@ public:
// Progress bar controls // Progress bar controls
// Set is both create and update. If you set maxValue <= minValue, you'll create an "indeterminate" progress // Set is both create and update. If you set maxValue <= minValue, you'll create an "indeterminate" progress
// bar that doesn't show a specific amount of progress. // bar that doesn't show a specific amount of progress.
void SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress); void SetProgressBar(std::string id, std::string &&message, float minValue, float maxValue, float progress, float delay_s);
void RemoveProgressBar(std::string id); void RemoveProgressBar(std::string id, bool success, float delay_s);
// Call every frame to keep the sidebar visible. Otherwise it'll fade out. // Call every frame to keep the sidebar visible. Otherwise it'll fade out.
void NudgeSidebar(); void NudgeSidebar();
@ -74,9 +74,10 @@ public:
struct ProgressBar { struct ProgressBar {
std::string id; std::string id;
std::string message; std::string message;
int minValue; float minValue;
int maxValue; float maxValue;
int progress; float progress;
double startTime;
double endTime; double endTime;
}; };

View file

@ -1174,8 +1174,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
if (iRunCount % 10 == 0 && bCheckForNewVersion) { if (iRunCount % 10 == 0 && bCheckForNewVersion) {
const char *versionUrl = "http://www.ppsspp.org/version.json"; const char *versionUrl = "http://www.ppsspp.org/version.json";
const char *acceptMime = "application/json, text/*; q=0.9, */*; q=0.8"; const char *acceptMime = "application/json, text/*; q=0.9, */*; q=0.8";
auto dl = g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), &DownloadCompletedCallback, acceptMime); g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), http::ProgressBarMode::NONE, &DownloadCompletedCallback, acceptMime);
dl->SetHidden(true);
} }
INFO_LOG(LOADER, "Loading controller config: %s", controllerIniFilename_.c_str()); INFO_LOG(LOADER, "Loading controller config: %s", controllerIniFilename_.c_str());

View file

@ -71,7 +71,7 @@ private:
s64 filepos_ = 0; s64 filepos_ = 0;
Url url_; Url url_;
http::Client client_; http::Client client_;
http::RequestProgress progress_; net::RequestProgress progress_;
::Path filename_; ::Path filename_;
bool connected_ = false; bool connected_ = false;
bool cancel_ = false; bool cancel_ = false;

View file

@ -131,7 +131,7 @@ static int sceNpMatching2ContextStart(int ctxId)
//npMatching2Ctx.started = true; //npMatching2Ctx.started = true;
Url url("http://static-resource.np.community.playstation.net/np/resource/psp-title/" + std::string(npTitleId.data) + "_00/matching/" + std::string(npTitleId.data) + "_00-matching.xml"); Url url("http://static-resource.np.community.playstation.net/np/resource/psp-title/" + std::string(npTitleId.data) + "_00/matching/" + std::string(npTitleId.data) + "_00-matching.xml");
http::Client client; http::Client client;
http::RequestProgress progress; net::RequestProgress progress;
if (!client.Resolve(url.Host().c_str(), url.Port())) { if (!client.Resolve(url.Host().c_str(), url.Port())) {
return hleLogError(SCENET, SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE, "HTTP failed to resolve %s", url.Resource().c_str()); return hleLogError(SCENET, SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE, "HTTP failed to resolve %s", url.Resource().c_str());
} }

View file

@ -272,7 +272,7 @@ namespace Reporting
bool SendReportRequest(const char *uri, const std::string &data, const std::string &mimeType, Buffer *output = NULL) bool SendReportRequest(const char *uri, const std::string &data, const std::string &mimeType, Buffer *output = NULL)
{ {
http::Client http; http::Client http;
http::RequestProgress progress(&pendingMessagesDone); net::RequestProgress progress(&pendingMessagesDone);
Buffer theVoid = Buffer::Void(); Buffer theVoid = Buffer::Void();
http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));

View file

@ -176,7 +176,7 @@ static void server_call_callback(const rc_api_request_t *request,
{ {
// If post data is provided, we need to make a POST request, otherwise, a GET request will suffice. // If post data is provided, we need to make a POST request, otherwise, a GET request will suffice.
if (request->post_data) { if (request->post_data) {
g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", [=](http::Download &download) { g_DownloadManager.AsyncPostWithCallback(std::string(request->url), std::string(request->post_data), "application/x-www-form-urlencoded", http::ProgressBarMode::DELAYED, [=](http::Download &download) {
std::string buffer; std::string buffer;
download.buffer().TakeAll(&buffer); download.buffer().TakeAll(&buffer);
rc_api_server_response_t response{}; rc_api_server_response_t response{};
@ -186,7 +186,7 @@ static void server_call_callback(const rc_api_request_t *request,
callback(&response, callback_data); callback(&response, callback_data);
}); });
} else { } else {
g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), [=](http::Download &download) { g_DownloadManager.StartDownloadWithCallback(std::string(request->url), Path(), http::ProgressBarMode::DELAYED, [=](http::Download &download) {
std::string buffer; std::string buffer;
download.buffer().TakeAll(&buffer); download.buffer().TakeAll(&buffer);
rc_api_server_response_t response{}; rc_api_server_response_t response{};
@ -394,7 +394,7 @@ static void login_password_callback(int result, const char *error_message, rc_cl
} }
} }
g_OSD.RemoveProgressBar("cheevos_async_login"); g_OSD.RemoveProgressBar("cheevos_async_login", true, 0.1f);
g_isLoggingIn = false; g_isLoggingIn = false;
} }
@ -403,7 +403,7 @@ bool LoginAsync(const char *username, const char *password) {
if (IsLoggedIn() || std::strlen(username) == 0 || std::strlen(password) == 0 || IsUsingRAIntegration()) if (IsLoggedIn() || std::strlen(username) == 0 || std::strlen(password) == 0 || IsUsingRAIntegration())
return false; return false;
g_OSD.SetProgressBar("cheevos_async_login", di->T("Logging in..."), 0, 0, 0); g_OSD.SetProgressBar("cheevos_async_login", di->T("Logging in..."), 0, 0, 0, 0.0f);
g_isLoggingIn = true; g_isLoggingIn = true;
rc_client_begin_login_with_password(g_rcClient, username, password, &login_password_callback, nullptr); rc_client_begin_login_with_password(g_rcClient, username, password, &login_password_callback, nullptr);
@ -540,7 +540,7 @@ bool HasAchievementsOrLeaderboards() {
void DownloadImageIfMissing(const std::string &cache_key, std::string &&url) { void DownloadImageIfMissing(const std::string &cache_key, std::string &&url) {
if (g_iconCache.MarkPending(cache_key)) { if (g_iconCache.MarkPending(cache_key)) {
INFO_LOG(ACHIEVEMENTS, "Downloading image: %s (%s)", url.c_str(), cache_key.c_str()); INFO_LOG(ACHIEVEMENTS, "Downloading image: %s (%s)", url.c_str(), cache_key.c_str());
g_DownloadManager.StartDownloadWithCallback(url, Path(), [cache_key](http::Download &download) { g_DownloadManager.StartDownloadWithCallback(url, Path(), http::ProgressBarMode::NONE, [cache_key](http::Download &download) {
if (download.ResultCode() != 200) if (download.ResultCode() != 200)
return; return;
std::string data; std::string data;

View file

@ -102,7 +102,7 @@ bool GameManager::DownloadAndInstall(std::string storeFileUrl) {
Path filename = GetTempFilename(); Path filename = GetTempFilename();
const char *acceptMime = "application/zip, application/x-cso, application/x-iso9660-image, application/octet-stream; q=0.9, */*; q=0.8"; const char *acceptMime = "application/zip, application/x-cso, application/x-iso9660-image, application/octet-stream; q=0.9, */*; q=0.8";
curDownload_ = g_DownloadManager.StartDownload(storeFileUrl, filename, acceptMime); curDownload_ = g_DownloadManager.StartDownload(storeFileUrl, filename, http::ProgressBarMode::VISIBLE, acceptMime);
return true; return true;
} }

View file

@ -64,7 +64,7 @@ static ServerStatus RetrieveStatus() {
static bool RegisterServer(int port) { static bool RegisterServer(int port) {
bool success = false; bool success = false;
http::Client http; http::Client http;
http::RequestProgress progress; net::RequestProgress progress;
Buffer theVoid = Buffer::Void(); Buffer theVoid = Buffer::Void();
http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION)); http.SetUserAgent(StringFromFormat("PPSSPP/%s", PPSSPP_GIT_VERSION));

View file

@ -819,19 +819,23 @@ void SystemInfoScreen::CreateTabs() {
}); });
internals->Add(new ItemHeader(si->T("Progress tests"))); internals->Add(new ItemHeader(si->T("Progress tests")));
internals->Add(new Choice(si->T("30%")))->OnClick.Add([&](UI::EventParams &) { internals->Add(new Choice(si->T("30%")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30); g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 30, 0.0f);
return UI::EVENT_DONE; return UI::EVENT_DONE;
}); });
internals->Add(new Choice(si->T("100%")))->OnClick.Add([&](UI::EventParams &) { internals->Add(new Choice(si->T("100%")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100); g_OSD.SetProgressBar("testprogress", "Test Progress", 1, 100, 100, 1.0f);
return UI::EVENT_DONE; return UI::EVENT_DONE;
}); });
internals->Add(new Choice(si->T("N/A%")))->OnClick.Add([&](UI::EventParams &) { internals->Add(new Choice(si->T("N/A%")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0); g_OSD.SetProgressBar("testprogress", "Test Progress", 0, 0, 0, 0.0f);
return UI::EVENT_DONE; return UI::EVENT_DONE;
}); });
internals->Add(new Choice(si->T("Clear")))->OnClick.Add([&](UI::EventParams &) { internals->Add(new Choice(si->T("Success")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.RemoveProgressBar("testprogress"); g_OSD.RemoveProgressBar("testprogress", true, 0.5f);
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Failure")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.RemoveProgressBar("testprogress", false, 0.5f);
return UI::EVENT_DONE; return UI::EVENT_DONE;
}); });
internals->Add(new ItemHeader(si->T("Achievement tests"))); internals->Add(new ItemHeader(si->T("Achievement tests")));
@ -1336,7 +1340,7 @@ void FrameDumpTestScreen::update() {
if (!listing_) { if (!listing_) {
const char *acceptMime = "text/html, */*; q=0.8"; const char *acceptMime = "text/html, */*; q=0.8";
listing_ = g_DownloadManager.StartDownload(framedumpsBaseUrl, Path(), acceptMime); listing_ = g_DownloadManager.StartDownload(framedumpsBaseUrl, Path(), http::ProgressBarMode::DELAYED, acceptMime);
} }
if (listing_ && listing_->Done() && files_.empty()) { if (listing_ && listing_->Done() && files_.empty()) {

View file

@ -292,13 +292,9 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
} }
} }
// Get height
float w, h;
dc.MeasureText(dc.theme->uiFont, 1.0f, 1.0f, "Wg", &w, &h);
y = 10.0f; y = 10.0f;
// Then draw them all. // Draw the progress bars.
const std::vector<OnScreenDisplay::ProgressBar> bars = g_OSD.ProgressBars(); const std::vector<OnScreenDisplay::ProgressBar> bars = g_OSD.ProgressBars();
for (auto &bar : bars) { for (auto &bar : bars) {
float tw, th; float tw, th;
@ -306,11 +302,14 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
Bounds b(0.0f, y, tw, th); Bounds b(0.0f, y, tw, th);
b.x = (bounds_.w - b.w) * 0.5f; b.x = (bounds_.w - b.w) * 0.5f;
float alpha = Clamp((float)(bar.endTime - now) * 4.0f, 0.0f, 1.0f); float enterAlpha = saturatef((float)(now - bar.startTime) * 4.0f);
float leaveAlpha = saturatef((float)(bar.endTime - now) * 4.0f);
float alpha = std::min(enterAlpha, leaveAlpha);
RenderOSDProgressBar(dc, bar, b, 0, alpha); RenderOSDProgressBar(dc, bar, b, 0, alpha);
y += (b.h + 4.0f) * alpha; // including alpha here gets us smooth animations. y += (b.h + 4.0f) * alpha; // including alpha here gets us smooth animations.
} }
// Draw the rest of the top-center messages.
const std::vector<OnScreenDisplay::Entry> entries = g_OSD.Entries(); const std::vector<OnScreenDisplay::Entry> entries = g_OSD.Entries();
for (const auto &entry : entries) { for (const auto &entry : entries) {
dc.SetFontScale(1.0f, 1.0f); dc.SetFontScale(1.0f, 1.0f);
@ -365,28 +364,6 @@ void OnScreenMessagesView::Draw(UIContext &dc) {
RenderOSDEntry(dc, entry, b, h1, align, alpha); RenderOSDEntry(dc, entry, b, h1, align, alpha);
y += (b.h * scale + 4.0f) * alpha; // including alpha here gets us smooth animations. y += (b.h * scale + 4.0f) * alpha; // including alpha here gets us smooth animations.
} }
// Thin bar at the top of the screen.
// TODO: Remove and replace with "proper" progress bars.
std::vector<float> progress = g_DownloadManager.GetCurrentProgress();
if (!progress.empty()) {
static const uint32_t colors[4] = {
0xFFFFFFFF,
0xFFCCCCCC,
0xFFAAAAAA,
0xFF777777,
};
dc.Begin();
int h = 5;
for (size_t i = 0; i < progress.size(); i++) {
float barWidth = 10 + (dc.GetBounds().w - 10) * progress[i];
Bounds bounds(0, h * i, barWidth, h);
UI::Drawable solid(colors[i & 3]);
dc.FillRect(solid, bounds);
}
dc.Flush();
}
} }
std::string OnScreenMessagesView::DescribeText() const { std::string OnScreenMessagesView::DescribeText() const {

View file

@ -139,7 +139,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort
} }
SetStatus("Loading game list from [URL]...", host, port); SetStatus("Loading game list from [URL]...", host, port);
http::RequestProgress progress(&scanCancelled); net::RequestProgress progress(&scanCancelled);
code = http.GET(http::RequestParams(subdir.c_str()), &result, &progress); code = http.GET(http::RequestParams(subdir.c_str()), &result, &progress);
http.Disconnect(); http.Disconnect();
@ -193,7 +193,7 @@ bool RemoteISOConnectScreen::FindServer(std::string &resultHost, int &resultPort
SetStatus("Looking for peers...", "", 0); SetStatus("Looking for peers...", "", 0);
if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) { if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT)) {
if (http.Connect(2, 20.0, &scanCancelled)) { if (http.Connect(2, 20.0, &scanCancelled)) {
http::RequestProgress progress(&scanCancelled); net::RequestProgress progress(&scanCancelled);
code = http.GET(http::RequestParams("/match/list"), &result, &progress); code = http.GET(http::RequestParams("/match/list"), &result, &progress);
http.Disconnect(); http.Disconnect();
} }

View file

@ -58,7 +58,7 @@ public:
if (useIconCache && g_iconCache.MarkPending(path_)) { if (useIconCache && g_iconCache.MarkPending(path_)) {
const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8"; const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8";
downloader_->StartDownloadWithCallback(path_, Path(), [&](http::Download &download) { downloader_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::DELAYED, [&](http::Download &download) {
if (download.ResultCode() == 200) { if (download.ResultCode() == 200) {
std::string data; std::string data;
download.buffer().TakeAll(&data); download.buffer().TakeAll(&data);
@ -165,8 +165,7 @@ void HttpImageFileView::Draw(UIContext &dc) {
if (!texture_ && !textureFailed_ && !path_.empty() && !download_) { if (!texture_ && !textureFailed_ && !path_.empty() && !download_) {
auto cb = std::bind(&HttpImageFileView::DownloadCompletedCallback, this, std::placeholders::_1); auto cb = std::bind(&HttpImageFileView::DownloadCompletedCallback, this, std::placeholders::_1);
const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8"; const char *acceptMime = "image/png, image/jpeg, image/*; q=0.9, */*; q=0.8";
download_ = downloader_->StartDownloadWithCallback(path_, Path(), cb, acceptMime); downloader_->StartDownloadWithCallback(path_, Path(), http::ProgressBarMode::NONE, cb, acceptMime);
download_->SetHidden(true);
} }
if (!textureData_.empty()) { if (!textureData_.empty()) {
@ -404,7 +403,7 @@ StoreScreen::StoreScreen() {
std::string indexPath = storeBaseUrl + "index.json"; std::string indexPath = storeBaseUrl + "index.json";
const char *acceptMime = "application/json, */*; q=0.8"; const char *acceptMime = "application/json, */*; q=0.8";
listing_ = g_DownloadManager.StartDownload(indexPath, Path(), acceptMime); listing_ = g_DownloadManager.StartDownload(indexPath, Path(), http::ProgressBarMode::DELAYED, acceptMime);
} }
StoreScreen::~StoreScreen() { StoreScreen::~StoreScreen() {