From dbbc5c7ebd0b7c25070aefc6372ddcb304fc782b Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Thu, 29 Jul 2021 03:50:17 -0400 Subject: [PATCH] Implemented OptionsTabUpdateFileDialog and OptionsTabUpdateFileDialogContent classes. Other changes include: * utils: utilsGenerateFormattedSizeString() now takes an input double instead of size_t. * DownloadTask: calculated speed is now expressed in bytes per second, so now it's up to the caller to convert it to other units. * DownloadTask: set download size and percentage if the download size isn't known and we're dealing with the final chunk. * http: slightly improved CURL error info formatting. * OptionsTab: fully implemented NSWDB XML update option. --- include/core/nxdt_utils.h | 2 +- include/download_task.hpp | 38 ++++--- include/options_tab.hpp | 39 +++++++ libs/borealis | 2 +- romfs/i18n/en-US/options_tab.json | 9 +- source/core/http.c | 7 +- source/core/nxdt_utils.c | 10 +- source/core/title.c | 2 +- source/error_frame.cpp | 2 +- source/gamecard_tab.cpp | 2 +- source/options_tab.cpp | 172 +++++++++++++++++++++++++++++- 11 files changed, 253 insertions(+), 32 deletions(-) diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index c9d366e..7168055 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -109,7 +109,7 @@ void utilsTrimString(char *str); void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, size_t src_size, bool uppercase); /// Formats the provided 'size' value to a human-readable size string and stores it in 'dst'. -void utilsGenerateFormattedSizeString(u64 size, char *dst, size_t dst_size); +void utilsGenerateFormattedSizeString(double size, char *dst, size_t dst_size); /// Saves the total size and free space available from the filesystem pointed to by the input path (e.g. "sdmc:/") to 'out_total' and 'out_free', respectively. /// Either 'out_total' or 'out_free' can be NULL, but at least one of them must be a valid pointer. diff --git a/include/download_task.hpp b/include/download_task.hpp index 9e0682e..db10902 100644 --- a/include/download_task.hpp +++ b/include/download_task.hpp @@ -39,7 +39,7 @@ namespace nxdt::tasks int percentage; ///< Progress percentage. /// Fields set by DownloadTask::onProgressUpdate(). - double speed; ///< Download speed expressed in KiB/s. + double speed; ///< Download speed expressed in bytes per second. std::string eta; ///< Formatted ETA string. } DownloadTaskProgress; @@ -205,6 +205,8 @@ namespace nxdt::tasks template void DownloadTask::onProgressUpdate(const DownloadTaskProgress& progress) { + AsyncTaskStatus status = this->getStatus(); + /* Return immediately if there has been no progress at all, or if it the task has been cancelled. */ bool proceed = (progress.current > prev_current || (progress.current == prev_current && (!progress.size || progress.current >= progress.size))); if (!proceed || this->isCancelled()) return; @@ -215,27 +217,35 @@ namespace nxdt::tasks std::chrono::duration diff_time = (cur_time - this->prev_time); double diff_time_conv = diff_time.count(); - if (diff_time_conv < 1.0 && ((progress.size && progress.current < progress.size) || this->getStatus() == AsyncTaskStatus::RUNNING)) return; + if (diff_time_conv < 1.0 && ((progress.size && progress.current < progress.size) || status == AsyncTaskStatus::RUNNING)) return; /* Calculate transferred data size difference between the last progress update and the current one. */ double diff_current = static_cast(progress.current - prev_current); - /* Calculate download speed in kibibytes per second (KiB/s). */ - double speed = ((diff_current / diff_time_conv) / 1024.0); - - /* Calculate remaining data size in kibibytes (KiB) and ETA if we know the download size. */ - double eta = 0.0; - - if (progress.size) - { - double remaining = (static_cast(progress.size - progress.current) / 1024.0); - eta = (remaining / speed); - } + /* Calculate download speed in bytes per second. */ + double speed = (diff_current / diff_time_conv); /* Fill struct. */ DownloadTaskProgress new_progress = progress; new_progress.speed = speed; - new_progress.eta = (progress.size ? fmt::format("{:02}H{:02}M{:02}S", std::fmod(eta, 86400.0) / 3600.0, std::fmod(eta, 3600.0) / 60.0, std::fmod(eta, 60.0)) : ""); + + if (progress.size) + { + /* Calculate remaining data size and ETA if we know the download size. */ + double remaining = static_cast(progress.size - progress.current); + double eta = (remaining / speed); + new_progress.eta = fmt::format("{:02}H{:02}M{:02}S", std::fmod(eta, 86400.0) / 3600.0, std::fmod(eta, 3600.0) / 60.0, std::fmod(eta, 60.0)); + } else { + /* No download size means no ETA calculation, sadly. */ + new_progress.eta = ""; + } + + /* Set download size if we don't know it and if this is the final chunk. */ + if (!new_progress.size && status == AsyncTaskStatus::FINISHED) + { + new_progress.size = new_progress.current; + new_progress.percentage = 100; + } /* Update class variables. */ this->prev_time = cur_time; diff --git a/include/options_tab.hpp b/include/options_tab.hpp index ab67bc7..ee2736c 100644 --- a/include/options_tab.hpp +++ b/include/options_tab.hpp @@ -26,8 +26,47 @@ #include +#include "download_task.hpp" + namespace nxdt::views { + /* Used as the content view for OptionsTabUpdateFileDialog. */ + class OptionsTabUpdateFileDialogContent: public brls::View + { + private: + brls::ProgressDisplay *progress_display = nullptr; + brls::Label *size_label = nullptr, *speed_eta_label = nullptr; + + std::string GetFormattedSizeString(size_t size); + std::string GetFormattedSizeString(double size); + + protected: + void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override; + void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override; + + public: + OptionsTabUpdateFileDialogContent(void); + ~OptionsTabUpdateFileDialogContent(void); + + void SetProgress(const nxdt::tasks::DownloadTaskProgress& progress); + + void willAppear(bool resetState = false) override; + void willDisappear(bool resetState = false) override; + }; + + /* Update file dialog. */ + class OptionsTabUpdateFileDialog: public brls::Dialog + { + private: + nxdt::tasks::DownloadFileTask download_task; + std::string success_str; + + public: + OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str); + + bool onCancel(void) override; + }; + class OptionsTab: public brls::List { private: diff --git a/libs/borealis b/libs/borealis index ef8e8e9..1e2134a 160000 --- a/libs/borealis +++ b/libs/borealis @@ -1 +1 @@ -Subproject commit ef8e8e96302064e52171204b75cd8f0145adc1ad +Subproject commit 1e2134a2ef140a7bca3fb799aba294dda1db1075 diff --git a/romfs/i18n/en-US/options_tab.json b/romfs/i18n/en-US/options_tab.json index 39bb03c..820c5a5 100644 --- a/romfs/i18n/en-US/options_tab.json +++ b/romfs/i18n/en-US/options_tab.json @@ -25,8 +25,15 @@ "description": "Checks if an update is available in nxdumptool's GitHub repository. Requires Internet connectivity." }, + "update_dialog": { + "cancel": "Cancel", + "close": "Close" + }, + "notifications": { "is_nso": "The application is running as an NSO. Unable to update.", - "already_updated": "The application has already been updated. Please reload." + "already_updated": "The application has already been updated. Please reload.", + "update_failed": "Update failed! For more information, please check the logfile.", + "nswdb_xml_updated": "NSWDB XML successfully updated!" } } diff --git a/source/core/http.c b/source/core/http.c index b6551f6..bd0b71b 100644 --- a/source/core/http.c +++ b/source/core/http.c @@ -105,6 +105,7 @@ bool httpPerformGetRequest(const char *url, bool force_https, size_t *outsize, H long http_code = 0; curl_off_t download_size = 0, content_length = 0; char curl_err_buf[CURL_ERROR_SIZE] = {0}; + const char *error_str = NULL; /* Start CURL session. */ curl = curl_easy_init(); @@ -167,10 +168,12 @@ bool httpPerformGetRequest(const char *url, bool force_https, size_t *outsize, H if (curl_err_buf[curl_err_buf_len - 1] == '\n') curl_err_buf[--curl_err_buf_len] = '\0'; if (curl_err_buf[curl_err_buf_len - 1] == '\r') curl_err_buf[--curl_err_buf_len] = '\0'; - LOG_MSG("%s", curl_err_buf); + error_str = curl_err_buf; } else { - LOG_MSG("%s", curl_easy_strerror(res)); + error_str = curl_easy_strerror(res); } + + if (error_str) LOG_MSG("CURL error info: \"%s\".", error_str); } } } diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 0b225bd..ad60ecf 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -572,18 +572,18 @@ void utilsGenerateHexStringFromData(char *dst, size_t dst_size, const void *src, dst[j] = '\0'; } -void utilsGenerateFormattedSizeString(u64 size, char *dst, size_t dst_size) +void utilsGenerateFormattedSizeString(double size, char *dst, size_t dst_size) { if (!dst || dst_size < 2) return; - double converted_size = (double)size; + size = fabs(size); for(u32 i = 0; i < g_sizeSuffixesCount; i++) { - if (converted_size >= pow(1024.0, i + 1) && (i + 1) < g_sizeSuffixesCount) continue; + if (size >= pow(1024.0, i + 1) && (i + 1) < g_sizeSuffixesCount) continue; - converted_size /= pow(1024.0, i); - snprintf(dst, dst_size, "%.*f %s", (converted_size >= 100.0 ? 0 : (converted_size >= 10.0 ? 1 : 2)), converted_size, g_sizeSuffixes[i]); + size /= pow(1024.0, i); + snprintf(dst, dst_size, "%.2F %s", size, g_sizeSuffixes[i]); break; } } diff --git a/source/core/title.c b/source/core/title.c index 35c192a..ffcd534 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -1760,7 +1760,7 @@ static bool titleGenerateTitleInfoEntriesForTitleStorage(TitleStorage *title_sto cur_title_info->storage_id = storage_id; memcpy(&(cur_title_info->meta_key), cur_meta_key, sizeof(NcmContentMetaKey)); cur_title_info->version.value = cur_title_info->meta_key.version; - utilsGenerateFormattedSizeString(cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); + utilsGenerateFormattedSizeString((double)cur_title_info->size, cur_title_info->size_str, sizeof(cur_title_info->size_str)); /* Retrieve application metadata. */ u64 app_id = (cur_title_info->meta_key.type <= NcmContentMetaType_Application ? cur_title_info->meta_key.id : \ diff --git a/source/error_frame.cpp b/source/error_frame.cpp index b36de3f..0b02779 100644 --- a/source/error_frame.cpp +++ b/source/error_frame.cpp @@ -79,7 +79,7 @@ namespace nxdt::views void ErrorFrame::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) { - this->label->setWidth(roundf((float)this->width * 0.90f)); + this->label->setWidth(roundf(static_cast(this->width) * 0.90f)); this->label->invalidate(true); this->label->setBoundaries( diff --git a/source/gamecard_tab.cpp b/source/gamecard_tab.cpp index bb6f2f4..bb68507 100644 --- a/source/gamecard_tab.cpp +++ b/source/gamecard_tab.cpp @@ -150,7 +150,7 @@ namespace nxdt::views char strbuf[0x40] = {0}; func(&size); - utilsGenerateFormattedSizeString(size, strbuf, sizeof(strbuf)); + utilsGenerateFormattedSizeString(static_cast(size), strbuf, sizeof(strbuf)); return std::string(strbuf); } diff --git a/source/options_tab.cpp b/source/options_tab.cpp index a5642bb..413702c 100644 --- a/source/options_tab.cpp +++ b/source/options_tab.cpp @@ -27,6 +27,164 @@ using namespace brls::i18n::literals; /* For _i18n. */ namespace nxdt::views { + OptionsTabUpdateFileDialogContent::OptionsTabUpdateFileDialogContent(void) + { + this->progress_display = new brls::ProgressDisplay(); + this->progress_display->setParent(this); + + this->size_label = new brls::Label(brls::LabelStyle::MEDIUM, "", false); + this->size_label->setVerticalAlign(NVG_ALIGN_BOTTOM); + this->size_label->setParent(this); + + this->speed_eta_label = new brls::Label(brls::LabelStyle::MEDIUM, "", false); + this->speed_eta_label->setVerticalAlign(NVG_ALIGN_TOP); + this->speed_eta_label->setParent(this); + } + + OptionsTabUpdateFileDialogContent::~OptionsTabUpdateFileDialogContent(void) + { + delete this->progress_display; + delete this->size_label; + delete this->speed_eta_label; + } + + void OptionsTabUpdateFileDialogContent::SetProgress(const nxdt::tasks::DownloadTaskProgress& progress) + { + /* Update progress percentage. */ + this->progress_display->setProgress(progress.size ? progress.percentage : 0, 100); + + /* Update size string. */ + this->size_label->setText(fmt::format("{} / {}", this->GetFormattedSizeString(progress.current), progress.size ? this->GetFormattedSizeString(progress.size) : "?")); + + /* Update speed / ETA string. */ + if (progress.eta != "") + { + this->speed_eta_label->setText(fmt::format("{}/s - ETA: {}", this->GetFormattedSizeString(progress.speed), progress.eta)); + } else { + this->speed_eta_label->setText(fmt::format("{}/s", this->GetFormattedSizeString(progress.speed))); + } + + this->invalidate(); + } + + void OptionsTabUpdateFileDialogContent::willAppear(bool resetState) + { + this->progress_display->willAppear(resetState); + } + + void OptionsTabUpdateFileDialogContent::willDisappear(bool resetState) + { + this->progress_display->willDisappear(resetState); + } + + void OptionsTabUpdateFileDialogContent::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) + { + /* Progress display. */ + this->progress_display->frame(ctx); + + /* Size label. */ + this->size_label->frame(ctx); + + /* Speed / ETA label. */ + this->speed_eta_label->frame(ctx); + } + + void OptionsTabUpdateFileDialogContent::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) + { + unsigned elem_width = roundf(static_cast(this->width) * 0.90f); + + /* Progress display. */ + this->progress_display->setBoundaries( + this->x + (this->width - elem_width) / 2, + this->y + (this->height - style->CrashFrame.buttonHeight) / 2, + elem_width, + style->CrashFrame.buttonHeight); + + this->progress_display->invalidate(true); + + /* Size label. */ + this->size_label->setWidth(elem_width); + this->size_label->invalidate(true); + + this->size_label->setBoundaries( + this->x + (this->width - this->size_label->getWidth()) / 2, + this->progress_display->getY() - this->progress_display->getHeight() / 8, + this->size_label->getWidth(), + this->size_label->getHeight()); + + /* Speed / ETA label. */ + this->speed_eta_label->setWidth(elem_width); + this->speed_eta_label->invalidate(true); + + this->speed_eta_label->setBoundaries( + this->x + (this->width - this->speed_eta_label->getWidth()) / 2, + this->progress_display->getY() + this->progress_display->getHeight() + this->progress_display->getHeight() / 8, + this->speed_eta_label->getWidth(), + this->speed_eta_label->getHeight()); + } + + std::string OptionsTabUpdateFileDialogContent::GetFormattedSizeString(size_t size) + { + char strbuf[0x40] = {0}; + utilsGenerateFormattedSizeString(static_cast(size), strbuf, sizeof(strbuf)); + return std::string(strbuf); + } + + std::string OptionsTabUpdateFileDialogContent::GetFormattedSizeString(double size) + { + char strbuf[0x40] = {0}; + utilsGenerateFormattedSizeString(size, strbuf, sizeof(strbuf)); + return std::string(strbuf); + } + + OptionsTabUpdateFileDialog::OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str) : brls::Dialog(), success_str(success_str) + { + /* Set content view. */ + OptionsTabUpdateFileDialogContent *content = new OptionsTabUpdateFileDialogContent(); + this->setContentView(content); + + /* Add cancel button. */ + this->addButton("options_tab/update_dialog/cancel"_i18n, [this](brls::View* view) { + this->onCancel(); + }); + + /* Disable cancelling with B button. */ + this->setCancelable(false); + + /* Subscribe to the download task. */ + this->download_task.RegisterListener([this, content](const nxdt::tasks::DownloadTaskProgress& progress) { + /* Update progress. */ + content->SetProgress(progress); + + /* Check if the download task has finished. */ + if (this->download_task.isFinished()) + { + /* Stop spinner. */ + content->willDisappear(); + + /* Update button label. */ + this->setButtonText(0, "options_tab/update_dialog/close"_i18n); + + /* Display notification. */ + brls::Application::notify(this->download_task.get() ? this->success_str : "options_tab/notifications/update_failed"_i18n); + } + }); + + /* Start download task. */ + this->download_task.execute(path, url, force_https); + } + + bool OptionsTabUpdateFileDialog::onCancel(void) + { + /* Cancel download task. */ + this->download_task.cancel(); + + /* Close dialog. */ + this->close(); + + return true; + } + OptionsTab::OptionsTab(void) : brls::List() { /* Set custom spacing. */ @@ -42,6 +200,7 @@ namespace nxdt::views brls::ToggleListItem *overclock = new brls::ToggleListItem("options_tab/overclock/label"_i18n, configGetBoolean("overclock"), \ "options_tab/overclock/description"_i18n, "options_tab/overclock/value_enabled"_i18n, \ "options_tab/overclock/value_disabled"_i18n); + overclock->getClickEvent()->subscribe([](brls::View* view) { brls::ToggleListItem *item = static_cast(view); @@ -56,6 +215,7 @@ namespace nxdt::views brls::Logger::debug("Overclock setting changed by user."); }); + this->addView(overclock); /* Naming convention. */ @@ -64,6 +224,7 @@ namespace nxdt::views "options_tab/naming_convention/value_01"_i18n }, static_cast(configGetInteger("naming_convention")), "options_tab/naming_convention/description"_i18n); + naming_convention->getValueSelectedEvent()->subscribe([](int selected){ /* Make sure the current value isn't out of bounds. */ if (selected < 0 || selected > static_cast(TitleNamingConvention_Count)) return; @@ -73,22 +234,22 @@ namespace nxdt::views brls::Logger::debug("Naming convention setting changed by user."); }); + this->addView(naming_convention); /* Update NSWDB XML. */ brls::ListItem *update_nswdb_xml = new brls::ListItem("options_tab/update_nswdb_xml/label"_i18n, "options_tab/update_nswdb_xml/description"_i18n); + update_nswdb_xml->getClickEvent()->subscribe([this](brls::View* view) { - brls::Dialog *dialog = new brls::Dialog("this is a test"); - dialog->setCancelable(false); - dialog->addButton("cancel?", [dialog](brls::View *view) { - dialog->close(); - }); + OptionsTabUpdateFileDialog *dialog = new OptionsTabUpdateFileDialog(NSWDB_XML_PATH, NSWDB_XML_URL, false, "options_tab/notifications/nswdb_xml_updated"_i18n); dialog->open(false); }); + this->addView(update_nswdb_xml); /* Update application. */ brls::ListItem *update_app = new brls::ListItem("options_tab/update_app/label"_i18n, "options_tab/update_app/description"_i18n); + update_app->getClickEvent()->subscribe([this](brls::View* view) { if (envIsNso()) { @@ -108,6 +269,7 @@ namespace nxdt::views brls::Application::pushView(staged_frame);*/ }); + this->addView(update_app); }