1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 04:02:11 +00:00

[ci skip] Add DataTransfer* classes.

Adds the DataTransferTask and DataTransferProgressDisplay classes, which use repurposed logic from the DownloadTask and EtaProgressDisplay classes, respectively. The EtaProgressDisplay class and EtaProgressInfo struct have been removed.

Other changes include:

* DownloadTask: heavily simplified logic by using DataTransferTask as the base class.

* DumpOptionsFrame: implement some additional/experimental changes.

* OptionsTab: update logic to use the new DataTransferProgressDisplay class.
This commit is contained in:
Pablo Curiel 2024-04-15 01:53:43 +02:00
parent 8f8fc6af37
commit 9525f37e51
14 changed files with 449 additions and 377 deletions

View file

@ -84,7 +84,7 @@ namespace nxdt::tasks
/* Update status. */
this->m_status = AsyncTaskStatus::FINISHED;
/* Call appropiate post-execution function. */
/* Run appropiate post-execution callback. */
if (this->isCancelled())
{
this->onCancelled(this->m_result);
@ -193,7 +193,7 @@ namespace nxdt::tasks
/* Update task status. */
this->m_status = AsyncTaskStatus::RUNNING;
/* Run onPreExecute() callback. */
/* Run pre-execution callback. */
this->onPreExecute();
/* Start asynchronous task on a new thread. */

View file

@ -1,5 +1,5 @@
/*
* eta_progress_display.hpp
* data_transfer_progress_display.hpp
*
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
*
@ -21,24 +21,15 @@
#pragma once
#ifndef __ETA_PROGRESS_DISPLAY_HPP__
#define __ETA_PROGRESS_DISPLAY_HPP__
#ifndef __DATA_TRANSFER_PROGRESS_DISPLAY_HPP__
#define __DATA_TRANSFER_PROGRESS_DISPLAY_HPP__
#include <borealis.hpp>
#include "data_transfer_task.hpp"
namespace nxdt::views
{
/* Used to hold progress info. */
typedef struct {
size_t size; ///< Total process size.
size_t current; ///< Number of bytes processed thus far.
int percentage; ///< Progress percentage.
double speed; ///< Current speed expressed in bytes per second.
std::string eta; ///< Formatted ETA string.
} EtaProgressInfo;
/* Used to display the progress of a running task. Shows a progress bar, a spinner, a percentage value, the process speed and an ETA value. */
class EtaProgressDisplay: public brls::View
/* Used to display the progress of an ongoing data transfer task. Shows a progress bar, a spinner, a percentage value, the process speed and an ETA value. */
class DataTransferProgressDisplay: public brls::View
{
private:
brls::ProgressDisplay *progress_display = nullptr;
@ -47,22 +38,18 @@ namespace nxdt::views
std::string GetFormattedSizeString(double size);
protected:
/* Set class as non-copyable and non-moveable. */
NON_COPYABLE(EtaProgressDisplay);
NON_MOVEABLE(EtaProgressDisplay);
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:
EtaProgressDisplay(void);
~EtaProgressDisplay(void);
DataTransferProgressDisplay(void);
~DataTransferProgressDisplay(void);
void setProgress(const EtaProgressInfo& progress);
void setProgress(const nxdt::tasks::DataTransferProgress& progress);
void willAppear(bool resetState = false) override;
void willDisappear(bool resetState = false) override;
};
}
#endif /* __ETA_PROGRESS_DISPLAY_HPP__ */
#endif /* __DATA_TRANSFER_PROGRESS_DISPLAY_HPP__ */

View file

@ -0,0 +1,233 @@
/*
* data_transfer_task.hpp
*
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool 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, either version 3 of the License, or
* (at your option) any later version.
*
* nxdumptool 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef __DATA_TRANSFER_TASK_HPP__
#define __DATA_TRANSFER_TASK_HPP__
#include <borealis.hpp>
#include "core/nxdt_utils.h"
#include "async_task.hpp"
namespace nxdt::tasks
{
/* Used to hold data transfer progress info. */
typedef struct {
size_t total_size; ///< Total size for the data transfer process.
size_t xfer_size; ///< Number of bytes transferred thus far.
int percentage; ///< Progress percentage.
double speed; ///< Current speed expressed in bytes per second.
std::string eta; ///< Formatted ETA string.
} DataTransferProgress;
/* Custom event type used to push data transfer progress updates. */
typedef brls::Event<const DataTransferProgress&> DataTransferProgressEvent;
/* Class template to asynchronously transfer data on a background thread. */
/* Automatically allocates and registers a RepeatingTask on its own, which is started along with the actual task when AsyncTask::execute() is called. */
/* This internal RepeatingTask is guaranteed to work on the UI thread, and it is also automatically unregistered on object destruction. */
/* Progress updates are pushed through a DataTransferProgressEvent. Make sure to register all event listeners before executing the task. */
template<typename Result, typename... Params>
class DataTransferTask: public AsyncTask<DataTransferProgress, Result, Params...>
{
private:
/* Handles task progress updates on the calling thread. */
class Handler: public brls::RepeatingTask
{
private:
bool finished = false;
DataTransferTask<Result, Params...>* task = nullptr;
protected:
void run(retro_time_t current_time) override final
{
brls::RepeatingTask::run(current_time);
if (this->task && !this->finished) this->finished = this->task->loopCallback();
}
public:
Handler(retro_time_t interval, DataTransferTask<Result, Params...>* task) : brls::RepeatingTask(interval), task(task) { }
ALWAYS_INLINE bool IsFinished(void)
{
return this->finished;
}
};
typedef std::chrono::time_point<std::chrono::steady_clock> SteadyTimePoint;
static constexpr auto &CurrentSteadyTimePoint = std::chrono::steady_clock::now;
DataTransferProgressEvent progress_event{};
Handler *task_handler = nullptr;
SteadyTimePoint start_time{}, prev_time{}, end_time{};
size_t prev_xfer_size = 0;
protected:
/* Set class as non-copyable and non-moveable. */
NON_COPYABLE(DataTransferTask);
NON_MOVEABLE(DataTransferTask);
/* Make the background function overridable. */
virtual Result doInBackground(const Params&... params) override = 0;
/* Runs on the calling thread. */
void onCancelled(const Result& result) override final
{
NX_IGNORE_ARG(result);
/* Set end time. */
this->end_time = CurrentSteadyTimePoint();
/* Pause task handler. */
this->task_handler->pause();
/* Unset long running process state. */
utilsSetLongRunningProcessState(false);
}
/* Runs on the calling thread. */
void onPostExecute(const Result& result) override final
{
NX_IGNORE_ARG(result);
/* Set end time. */
this->end_time = CurrentSteadyTimePoint();
/* Fire task handler immediately to get the last result from AsyncTask::loopCallback(), then pause it. */
this->task_handler->fireNow();
this->task_handler->pause();
/* Update progress one last time. */
this->onProgressUpdate(this->getProgress());
/* Unset long running process state. */
utilsSetLongRunningProcessState(false);
}
/* Runs on the calling thread. */
void onPreExecute(void) override final
{
/* Set long running process state. */
utilsSetLongRunningProcessState(true);
/* Start task handler. */
this->task_handler->start();
/* Set start time. */
this->start_time = this->prev_time = CurrentSteadyTimePoint();
}
/* Runs on the calling thread. */
void onProgressUpdate(const DataTransferProgress& progress) override final
{
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.xfer_size > prev_xfer_size || (progress.xfer_size == prev_xfer_size && (!progress.total_size || progress.xfer_size >= progress.total_size)));
if (!proceed || this->isCancelled()) return;
/* Calculate time difference between the last progress update and the current one. */
/* Return immediately if it's less than 1 second, but only if this isn't the last chunk; or if we don't know the total size and the task is still running . */
SteadyTimePoint cur_time = std::chrono::steady_clock::now();
double diff_time = std::chrono::duration<double>(cur_time - this->prev_time).count();
if (diff_time < 1.0 && ((progress.total_size && progress.xfer_size < progress.total_size) || status == AsyncTaskStatus::RUNNING)) return;
/* Calculate transferred data size difference between the last progress update and the current one. */
double diff_xfer_size = static_cast<double>(progress.xfer_size - prev_xfer_size);
/* Calculate transfer speed in bytes per second. */
double speed = (diff_xfer_size / diff_time);
/* Fill struct. */
DataTransferProgress new_progress = progress;
new_progress.speed = speed;
if (progress.total_size)
{
/* Calculate remaining data size and ETA if we know the total size. */
double remaining = static_cast<double>(progress.total_size - progress.xfer_size);
double eta = (remaining / speed);
new_progress.eta = fmt::format("{:02.0F}H{:02.0F}M{:02.0F}S", std::fmod(eta, 86400.0) / 3600.0, std::fmod(eta, 3600.0) / 60.0, std::fmod(eta, 60.0));
} else {
/* No total size means no ETA calculation, sadly. */
new_progress.eta = "";
}
/* Set total size if we don't know it and if this is the final chunk. */
if (!new_progress.total_size && status == AsyncTaskStatus::FINISHED)
{
new_progress.total_size = new_progress.xfer_size;
new_progress.percentage = 100;
}
/* Update class variables. */
this->prev_time = cur_time;
this->prev_xfer_size = progress.xfer_size;
/* Send updated progress to all listeners. */
this->progress_event.fire(new_progress);
}
public:
DataTransferTask(void)
{
/* Create task handler. */
this->task_handler = new Handler(DATA_TRANSFER_TASK_INTERVAL, this);
}
~DataTransferTask(void)
{
/* Stop task handler. Borealis' task manager will take care of deleting it. */
this->task_handler->stop();
/* Unregister all event listeners. */
this->progress_event.unsubscribeAll();
}
/* Returns the last result from AsyncTask::loopCallback(). Runs on the calling thread. */
ALWAYS_INLINE bool IsFinished(void)
{
return this->task_handler->IsFinished();
}
/* Returns the task duration expressed in seconds. */
/* If the task hasn't finished yet, it returns the number of seconds that have passed since the task was started. */
ALWAYS_INLINE double GetDuration(void)
{
return std::chrono::duration<double>(this->IsFinished() ? (this->end_time - this->start_time) : (CurrentSteadyTimePoint() - this->start_time)).count();
}
ALWAYS_INLINE DataTransferProgressEvent::Subscription RegisterListener(DataTransferProgressEvent::Callback cb)
{
return this->progress_event.subscribe(cb);
}
ALWAYS_INLINE void UnregisterListener(DataTransferProgressEvent::Subscription subscription)
{
this->progress_event.unsubscribe(subscription);
}
};
}
#endif /* __DATA_TRANSFER_TASK_HPP__ */

View file

@ -104,7 +104,13 @@
#define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:"
#define DOWNLOAD_TASK_INTERVAL 100 /* 100 milliseconds. */
/// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits.
/// Reference: https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits.
/// Most modern filesystems use a 255-byte limit instead of 255-character/codepoint limit, so that's what we're gonna use.
#define FS_MAX_FILENAME_LENGTH 255
#define SDMC_MAX_FILENAME_LENGTH 128 /* Arbitrarily set, I'm tired of FS sysmodule shenanigans. */
#define DATA_TRANSFER_TASK_INTERVAL 100 /* 100 milliseconds. */
#define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)"
#define HTTP_CONNECT_TIMEOUT 10L /* 10 seconds. */

View file

@ -24,254 +24,83 @@
#ifndef __DOWNLOAD_TASK_HPP__
#define __DOWNLOAD_TASK_HPP__
#include <borealis.hpp>
#include "core/nxdt_utils.h"
#include "async_task.hpp"
#include "eta_progress_display.hpp"
#include "data_transfer_task.hpp"
namespace nxdt::tasks
{
/* Custom event type used to push download progress updates. */
typedef brls::Event<const nxdt::views::EtaProgressInfo&> DownloadProgressEvent;
/* Used to hold a buffer + size pair with downloaded data. */
typedef std::pair<char*, size_t> DownloadDataResult;
/* Class template to asynchronously download data on a background thread. */
/* Automatically allocates and registers a RepeatingTask on its own, which is started along with the actual task when execute() is called. */
/* This internal RepeatingTask is guaranteed to work on the UI thread, and it is also automatically unregistered on object destruction. */
/* Progress updates are pushed through a DownloadProgressEvent. Make sure to register all event listeners before executing the task. */
/* Uses both AsyncTask and DataTransferTask class templates. */
template<typename Result, typename... Params>
class DownloadTask: public AsyncTask<nxdt::views::EtaProgressInfo, Result, Params...>
class DownloadTask: public DataTransferTask<Result, Params...>
{
public:
/* Handles task progress updates on the calling thread. */
class DownloadTaskHandler: public brls::RepeatingTask
{
private:
bool finished = false;
DownloadTask<Result, Params...>* task = nullptr;
protected:
void run(retro_time_t current_time) override final;
/* Set class as non-copyable and non-moveable. */
NON_COPYABLE(DownloadTask);
NON_MOVEABLE(DownloadTask);
public:
DownloadTaskHandler(retro_time_t interval, DownloadTask<Result, Params...>* task);
DownloadTask(void) = default;
ALWAYS_INLINE bool isFinished(void)
{
return this->finished;
}
};
private:
DownloadProgressEvent progress_event;
DownloadTaskHandler *task_handler = nullptr;
std::chrono::time_point<std::chrono::steady_clock> start_time{}, prev_time{};
size_t prev_current = 0;
protected:
/* Make the background function overridable. */
virtual Result doInBackground(const Params&... params) override = 0;
/* These functions run on the calling thread. */
void onCancelled(const Result& result) override final;
void onPostExecute(const Result& result) override final;
void onPreExecute(void) override final;
void onProgressUpdate(const nxdt::views::EtaProgressInfo& progress) override final;
public:
DownloadTask(void);
~DownloadTask(void);
/* Runs on the asynchronous task thread. Required by CURL. */
/* Runs on the asynchronous task thread. Required by cURL. */
/* Make sure to pass it to either httpDownloadFile() or httpDownloadData() with 'this' as the user pointer. */
static int HttpProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
/* Returns the last result from loopCallback(). Runs on the calling thread. */
ALWAYS_INLINE bool isFinished(void)
{
return this->task_handler->isFinished();
}
ALWAYS_INLINE DownloadProgressEvent::Subscription RegisterListener(DownloadProgressEvent::Callback cb)
{
return this->progress_event.subscribe(cb);
}
ALWAYS_INLINE void UnregisterListener(DownloadProgressEvent::Subscription subscription)
{
this->progress_event.unsubscribe(subscription);
}
};
template<typename Result, typename... Params>
DownloadTask<Result, Params...>::DownloadTaskHandler::DownloadTaskHandler(retro_time_t interval, DownloadTask<Result, Params...>* task) : brls::RepeatingTask(interval), task(task)
{
/* Do nothing. */
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::DownloadTaskHandler::run(retro_time_t current_time)
{
brls::RepeatingTask::run(current_time);
if (this->task && !this->finished) this->finished = this->task->loopCallback();
}
template<typename Result, typename... Params>
DownloadTask<Result, Params...>::DownloadTask(void)
{
/* Create task handler. */
this->task_handler = new DownloadTaskHandler(DOWNLOAD_TASK_INTERVAL, this);
}
template<typename Result, typename... Params>
DownloadTask<Result, Params...>::~DownloadTask(void)
{
/* Stop task handler. Borealis' task manager will take care of deleting it. */
this->task_handler->stop();
/* Unregister all event listeners. */
this->progress_event.unsubscribeAll();
}
template<typename Result, typename... Params>
int DownloadTask<Result, Params...>::HttpProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
static int HttpProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
NX_IGNORE_ARG(ultotal);
NX_IGNORE_ARG(ulnow);
nxdt::views::EtaProgressInfo progress = {0};
DataTransferProgress progress{};
DownloadTask<Result, Params...>* task = static_cast<DownloadTask<Result, Params...>*>(clientp);
/* Don't proceed if we're dealing with an invalid task pointer, or if the task has been cancelled. */
if (!task || task->isCancelled()) return 1;
/* Fill struct. */
progress.size = static_cast<size_t>(dltotal);
progress.current = static_cast<size_t>(dlnow);
progress.percentage = (progress.size ? static_cast<int>((progress.current * 100) / progress.size) : 0);
progress.total_size = static_cast<size_t>(dltotal);
progress.xfer_size = static_cast<size_t>(dlnow);
progress.percentage = (progress.total_size ? static_cast<int>((progress.xfer_size * 100) / progress.total_size) : 0);
/* Push progress onto the class. */
task->publishProgress(progress);
return 0;
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onCancelled(const Result& result)
{
NX_IGNORE_ARG(result);
/* Pause task handler. */
this->task_handler->pause();
/* Unset long running process state. */
utilsSetLongRunningProcessState(false);
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onPostExecute(const Result& result)
{
NX_IGNORE_ARG(result);
/* Fire task handler immediately to get the last result from loopCallback(), then pause it. */
this->task_handler->fireNow();
this->task_handler->pause();
/* Update progress one last time. */
this->onProgressUpdate(this->getProgress());
/* Unset long running process state. */
utilsSetLongRunningProcessState(false);
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onPreExecute(void)
{
/* Set long running process state. */
utilsSetLongRunningProcessState(true);
/* Start task handler. */
this->task_handler->start();
/* Set start time. */
this->start_time = this->prev_time = std::chrono::steady_clock::now();
}
template<typename Result, typename... Params>
void DownloadTask<Result, Params...>::onProgressUpdate(const nxdt::views::EtaProgressInfo& 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;
/* Calculate time difference between the last progress update and the current one. */
/* Return immediately if it's less than 1 second, but only if this isn't the last chunk (or if the task is still running, if we don't know the download size). */
std::chrono::time_point<std::chrono::steady_clock> cur_time = std::chrono::steady_clock::now();
std::chrono::duration<double> 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) || status == AsyncTaskStatus::RUNNING)) return;
/* Calculate transferred data size difference between the last progress update and the current one. */
double diff_current = static_cast<double>(progress.current - prev_current);
/* Calculate download speed in bytes per second. */
double speed = (diff_current / diff_time_conv);
/* Fill struct. */
nxdt::views::EtaProgressInfo new_progress = progress;
new_progress.speed = speed;
if (progress.size)
{
/* Calculate remaining data size and ETA if we know the download size. */
double remaining = static_cast<double>(progress.size - progress.current);
double eta = (remaining / speed);
new_progress.eta = fmt::format("{:02.0F}H{:02.0F}M{:02.0F}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;
this->prev_current = progress.current;
/* Send updated progress to all listeners. */
this->progress_event.fire(new_progress);
}
};
/* Asynchronous task to download a file using an output path and a URL. */
class DownloadFileTask: public DownloadTask<bool, std::string, std::string, bool>
{
protected:
/* Set class as non-copyable and non-moveable. */
NON_COPYABLE(DownloadFileTask);
NON_MOVEABLE(DownloadFileTask);
/* Runs in the background thread. */
bool doInBackground(const std::string& path, const std::string& url, const bool& force_https) override final
{
/* If the process fails or if it's cancelled, httpDownloadFile() will take care of closing the incomplete output file and delete it. */
return httpDownloadFile(path.c_str(), url.c_str(), force_https, DownloadFileTask::HttpProgressCallback, this);
}
public:
DownloadFileTask(void) = default;
};
/* Asynchronous task to store downloaded data into a dynamically allocated buffer using a URL. */
/* The buffer returned by std::pair::first() must be manually freed by the calling function using free(). */
class DownloadDataTask: public DownloadTask<DownloadDataResult, std::string, bool>
{
protected:
/* Set class as non-copyable and non-moveable. */
NON_COPYABLE(DownloadDataTask);
NON_MOVEABLE(DownloadDataTask);
/* Runs in the background thread. */
DownloadDataResult doInBackground(const std::string& url, const bool& force_https) override final
{
char *buf = NULL;
char *buf = nullptr;
size_t buf_size = 0;
/* If the process fails or if it's cancelled, httpDownloadData() will take care of freeing up the allocated memory and return NULL. */
@ -279,6 +108,9 @@ namespace nxdt::tasks
return std::make_pair(buf, buf_size);
}
public:
DownloadDataTask(void) = default;
};
}

View file

@ -43,26 +43,25 @@ namespace nxdt::views
RootView *root_view = nullptr;
nxdt::tasks::UmsEvent::Subscription ums_task_sub;
char *raw_filename = NULL;
const char *extension = NULL;
std::string raw_filename = "";
std::string extension = "";
brls::List *list = nullptr;
brls::InputListItem *filename_input = nullptr;
brls::SelectListItem *output_storage = nullptr;
brls::ToggleListItem *prepend_key_area = nullptr;
brls::ToggleListItem *keep_certificate = nullptr;
brls::ToggleListItem *trim_dump = nullptr;
brls::ToggleListItem *calculate_checksum = nullptr;
brls::SelectListItem *checksum_lookup_method = nullptr;
brls::InputListItem *filename_item = nullptr;
brls::SelectListItem *output_storage_item = nullptr;
brls::ToggleListItem *prepend_key_area_item = nullptr;
brls::ToggleListItem *keep_certificate_item = nullptr;
brls::ToggleListItem *trim_dump_item = nullptr;
brls::ToggleListItem *calculate_checksum_item = nullptr;
brls::SelectListItem *checksum_lookup_method_item = nullptr;
std::string RegenerateFileName(void)
std::string SanitizeFileName(void)
{
if (!this->raw_filename) return "dummy";
char *raw_filename_dup = nullptr;
char *raw_filename_dup = strdup(this->raw_filename);
if (!raw_filename_dup) return "dummy";
if (raw_filename.empty() || !(raw_filename_dup = strdup(this->raw_filename.c_str()))) return "dummy";
u8 selected = static_cast<u8>(this->output_storage ? this->output_storage->getSelectedValue() : configGetInteger("output_storage"));
u8 selected = static_cast<u8>(this->output_storage_item ? this->output_storage_item->getSelectedValue() : configGetInteger("output_storage"));
utilsReplaceIllegalCharacters(raw_filename_dup, selected == ConfigOutputStorage_SdCard);
std::string output = std::string(raw_filename_dup);
@ -71,37 +70,55 @@ namespace nxdt::views
return output;
}
void UpdateRawFileName(void)
{
if (this->raw_filename) free(this->raw_filename);
this->raw_filename = strdup(this->filename_input->getValue().c_str());
}
void UpdateStorages(const nxdt::tasks::UmsDeviceVector* ums_devices)
{
if (!this->output_storage) return;
if (!this->output_storage_item) return;
std::vector<std::string> *storages = this->output_storage->getValues();
std::vector<std::string> *storages = this->output_storage_item->getValues();
storages->clear();
storages->push_back("dump_options/output_storage/value_00"_i18n);
storages->push_back("dump_options/output_storage/value_01"_i18n);
size_t elem_count = (ConfigOutputStorage_Count + ums_devices->size());
u32 selected = this->output_storage_item->getSelectedValue();
for(const UsbHsFsDevice& cur_ums_device : *ums_devices)
for(size_t i = 0; i < elem_count; i++)
{
std::string device_str = (std::string(cur_ums_device.name) + ", ");
if (cur_ums_device.product_name[0]) device_str += (std::string(cur_ums_device.product_name) + ", ");
device_str += fmt::format("LUN {}, FS #{}, {}", cur_ums_device.lun, cur_ums_device.fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device.fs_type));
storages->push_back(brls::i18n::getStr("dump_options/output_storage/value_02", device_str));
if (i == 1)
{
storages->push_back("dump_options/output_storage/value_01"_i18n);
continue;
}
if (this->output_storage->getSelectedValue() > ConfigOutputStorage_UsbHost)
u64 total_sz = 0, free_sz = 0;
char total_sz_str[64] = {0}, free_sz_str[64] = {0};
const UsbHsFsDevice *cur_ums_device = (i >= ConfigOutputStorage_Count ? (ums_devices->data() + (i - ConfigOutputStorage_Count)) : nullptr);
sprintf(total_sz_str, "%s/", cur_ums_device ? cur_ums_device->name : DEVOPTAB_SDMC_DEVICE);
utilsGetFileSystemStatsByPath(total_sz_str, &total_sz, &free_sz);
utilsGenerateFormattedSizeString(total_sz, total_sz_str, sizeof(total_sz_str));
utilsGenerateFormattedSizeString(free_sz, free_sz_str, sizeof(free_sz_str));
if (cur_ums_device)
{
std::string ums_extra_info = (cur_ums_device->product_name[0] ? (std::string(cur_ums_device->product_name) + ", ") : "");
ums_extra_info += fmt::format("LUN {}, FS #{}, {}", cur_ums_device->lun, cur_ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(cur_ums_device->fs_type));
storages->push_back(brls::i18n::getStr("dump_options/output_storage/value_02", static_cast<int>(strlen(cur_ums_device->name + 3) - 1), cur_ums_device->name + 3, free_sz_str, total_sz_str, ums_extra_info));
} else {
storages->push_back(brls::i18n::getStr("dump_options/output_storage/value_00", free_sz_str, total_sz_str));
}
}
if (selected > ConfigOutputStorage_UsbHost)
{
/* Set the SD card as the current output storage. */
this->output_storage->setSelectedValue(ConfigOutputStorage_SdCard);
this->output_storage_item->setSelectedValue(ConfigOutputStorage_SdCard);
/* Regenerate filename. */
this->output_storage->getValueSelectedEvent()->fire(ConfigOutputStorage_SdCard);
/* Manually trigger selection event. */
/* This will take care of both updating the JSON configuration and saniziting the filename provided by the user. */
this->output_storage_item->getValueSelectedEvent()->fire(ConfigOutputStorage_SdCard);
} else {
/* Set the current output storage once more. This will make sure the device string gets updated. */
this->output_storage_item->setSelectedValue(selected);
}
}
@ -115,7 +132,7 @@ namespace nxdt::views
}
public:
DumpOptionsFrame(RootView *root_view, std::string title, brls::Image *icon, char *raw_filename, const char *extension) : brls::ThumbnailFrame(), root_view(root_view), raw_filename(raw_filename), extension(extension)
DumpOptionsFrame(RootView *root_view, std::string title, brls::Image *icon, std::string raw_filename, std::string extension) : brls::ThumbnailFrame(), root_view(root_view), raw_filename(raw_filename), extension(extension)
{
/* Set UI properties. */
this->setTitle(title);
@ -132,14 +149,14 @@ namespace nxdt::views
this->filename_input = new brls::InputListItem("dump_options/filename/label"_i18n, this->RegenerateFileName(), "", "dump_options/filename/description"_i18n, 255);
this->filename_item = new brls::InputListItem("dump_options/filename/label"_i18n, this->SanitizeFileName(), "", "dump_options/filename/description"_i18n, FS_MAX_FILENAME_LENGTH);
this->filename_input->getClickEvent()->subscribe([this](brls::View *view) {
this->UpdateRawFileName();
this->filename_input->setValue(this->RegenerateFileName());
this->filename_item->getClickEvent()->subscribe([this](brls::View *view) {
this->raw_filename = this->filename_item->getValue();
this->filename_item->setValue(this->SanitizeFileName());
});
this->list->addView(this->filename_input);
this->list->addView(this->filename_item);
@ -148,28 +165,25 @@ namespace nxdt::views
this->output_storage = new brls::SelectListItem("dump_options/output_storage/label"_i18n, {
"dump_options/output_storage/value_00"_i18n,
"dump_options/output_storage/value_01"_i18n
}, configGetInteger("output_storage"),
this->output_storage_item = new brls::SelectListItem("dump_options/output_storage/label"_i18n, { "dummy0", "dummy1" }, configGetInteger("output_storage"),
brls::i18n::getStr("dump_options/output_storage/description", GITHUB_REPOSITORY_URL));
/* Subscribe to SelectListItem's value selected event. */
this->output_storage->getValueSelectedEvent()->subscribe([this](int selected) {
this->output_storage_item->getValueSelectedEvent()->subscribe([this](int selected) {
/* Make sure the current value isn't out of bounds. */
if (selected < ConfigOutputStorage_SdCard || selected >= static_cast<int>(this->root_view->GetUmsDevices()->size() + ConfigOutputStorage_Count)) return;
/* Update configuration. */
if (selected == ConfigOutputStorage_SdCard || selected == ConfigOutputStorage_UsbHost) configSetInteger("output_storage", selected);
/* Update output filename. */
this->filename_input->setValue(this->RegenerateFileName());
/* Sanitize output filename for the selected storage. */
this->filename_item->setValue(this->SanitizeFileName());
});
/* Update output storages vector. */
/* Manually update output storages vector. */
this->UpdateStorages(this->root_view->GetUmsDevices());
this->list->addView(this->output_storage);
this->list->addView(this->output_storage_item);
@ -190,11 +204,11 @@ namespace nxdt::views
this->prepend_key_area = new brls::ToggleListItem("dump_options/prepend_key_area/label"_i18n, configGetBoolean("gamecard/prepend_key_area"), \
"dump_options/prepend_key_area/description"_i18n, "generic/value_enabled"_i18n, \
this->prepend_key_area_item = new brls::ToggleListItem("dump_options/prepend_key_area/label"_i18n, configGetBoolean("gamecard/prepend_key_area"),
"dump_options/prepend_key_area/description"_i18n, "generic/value_enabled"_i18n,
"generic/value_disabled"_i18n);
this->prepend_key_area->getClickEvent()->subscribe([](brls::View* view) {
this->prepend_key_area_item->getClickEvent()->subscribe([](brls::View* view) {
/* Get current value. */
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
bool value = item->getToggleState();
@ -205,7 +219,7 @@ namespace nxdt::views
LOG_MSG_DEBUG("Prepend Key Area setting changed by user.");
});
this->list->addView(this->prepend_key_area);
this->list->addView(this->prepend_key_area_item);
@ -213,11 +227,11 @@ namespace nxdt::views
this->keep_certificate = new brls::ToggleListItem("dump_options/keep_certificate/label"_i18n, configGetBoolean("gamecard/keep_certificate"), \
"dump_options/keep_certificate/description"_i18n, "generic/value_enabled"_i18n, \
this->keep_certificate_item = new brls::ToggleListItem("dump_options/keep_certificate/label"_i18n, configGetBoolean("gamecard/keep_certificate"),
"dump_options/keep_certificate/description"_i18n, "generic/value_enabled"_i18n,
"generic/value_disabled"_i18n);
this->keep_certificate->getClickEvent()->subscribe([](brls::View* view) {
this->keep_certificate_item->getClickEvent()->subscribe([](brls::View* view) {
/* Get current value. */
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
bool value = item->getToggleState();
@ -228,7 +242,7 @@ namespace nxdt::views
LOG_MSG_DEBUG("Keep certificate setting changed by user.");
});
this->list->addView(this->keep_certificate);
this->list->addView(this->keep_certificate_item);
@ -238,11 +252,11 @@ namespace nxdt::views
this->trim_dump = new brls::ToggleListItem("dump_options/trim_dump/label"_i18n, configGetBoolean("gamecard/trim_dump"), \
"dump_options/trim_dump/description"_i18n, "generic/value_enabled"_i18n, \
this->trim_dump_item = new brls::ToggleListItem("dump_options/trim_dump/label"_i18n, configGetBoolean("gamecard/trim_dump"),
"dump_options/trim_dump/description"_i18n, "generic/value_enabled"_i18n,
"generic/value_disabled"_i18n);
this->trim_dump->getClickEvent()->subscribe([](brls::View* view) {
this->trim_dump_item->getClickEvent()->subscribe([](brls::View* view) {
/* Get current value. */
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
bool value = item->getToggleState();
@ -253,7 +267,7 @@ namespace nxdt::views
LOG_MSG_DEBUG("Trim dump setting changed by user.");
});
this->list->addView(this->trim_dump);
this->list->addView(this->trim_dump_item);
@ -263,11 +277,11 @@ namespace nxdt::views
this->calculate_checksum = new brls::ToggleListItem("dump_options/calculate_checksum/label"_i18n, configGetBoolean("gamecard/calculate_checksum"), \
"dump_options/calculate_checksum/description"_i18n, "generic/value_enabled"_i18n, \
this->calculate_checksum_item = new brls::ToggleListItem("dump_options/calculate_checksum/label"_i18n, configGetBoolean("gamecard/calculate_checksum"),
"dump_options/calculate_checksum/description"_i18n, "generic/value_enabled"_i18n,
"generic/value_disabled"_i18n);
this->calculate_checksum->getClickEvent()->subscribe([](brls::View* view) {
this->calculate_checksum_item->getClickEvent()->subscribe([](brls::View* view) {
/* Get current value. */
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
bool value = item->getToggleState();
@ -278,7 +292,7 @@ namespace nxdt::views
LOG_MSG_DEBUG("Calculate checksum setting changed by user.");
});
this->list->addView(this->calculate_checksum);
this->list->addView(this->calculate_checksum_item);
@ -287,7 +301,7 @@ namespace nxdt::views
this->checksum_lookup_method = new brls::SelectListItem("dump_options/checksum_lookup_method/label"_i18n, {
this->checksum_lookup_method_item = new brls::SelectListItem("dump_options/checksum_lookup_method/label"_i18n, {
"dump_options/checksum_lookup_method/value_00"_i18n,
"NSWDB",
"No-Intro"
@ -296,7 +310,7 @@ namespace nxdt::views
"dump_options/calculate_checksum/label"_i18n, "NSWDB", NSWDB_XML_NAME, "No-Intro"));
/* Subscribe to SelectListItem's value selected event. */
this->checksum_lookup_method->getValueSelectedEvent()->subscribe([this](int selected) {
this->checksum_lookup_method_item->getValueSelectedEvent()->subscribe([this](int selected) {
/* Make sure the current value isn't out of bounds. */
if (selected < ConfigChecksumLookupMethod_None || selected >= ConfigChecksumLookupMethod_Count) return;
@ -304,7 +318,7 @@ namespace nxdt::views
configSetInteger("gamecard/checksum_lookup_method", selected);
});
this->list->addView(this->checksum_lookup_method);
this->list->addView(this->checksum_lookup_method_item);
@ -314,8 +328,28 @@ namespace nxdt::views
brls::Button *button = this->getSidebar()->getButton();
button->setLabel("dump_options/start_dump"_i18n);
button->getClickEvent()->subscribe([](brls::View *view) {
brls::Application::notify("test");
button->getClickEvent()->subscribe([this](brls::View *view) {
/* Retrieve configuration values set by the user. */
//bool prepend_key_area = this->prepend_key_area_item->getToggleState();
//bool keep_certificate = this->keep_certificate_item->getToggleState();
bool trim_dump = this->trim_dump_item->getToggleState();
//bool calculate_checksum = this->calculate_checksum_item->getToggleState();
/* Get gamecard size. */
u64 gc_size = 0;
if ((!trim_dump && !gamecardGetTotalSize(&gc_size)) || (trim_dump && !gamecardGetTrimmedSize(&gc_size)) || !gc_size)
{
brls::Application::notify("fail");
}
/* Display update frame. */
//brls::Application::pushView(new OptionsTabUpdateApplicationFrame(), brls::ViewAnimation::SLIDE_LEFT, false);
brls::Application::notify(fmt::format("0x{:X}", gc_size));
});
@ -331,8 +365,6 @@ namespace nxdt::views
{
/* Unregister task listener. */
this->root_view->UnregisterUmsTaskListener(this->ums_task_sub);
if (this->raw_filename) free(this->raw_filename);
}
};
}

View file

@ -24,10 +24,8 @@
#ifndef __OPTIONS_TAB_HPP__
#define __OPTIONS_TAB_HPP__
#include <borealis.hpp>
#include "root_view.hpp"
#include "eta_progress_display.hpp"
#include "data_transfer_progress_display.hpp"
namespace nxdt::views
{
@ -35,6 +33,7 @@ namespace nxdt::views
class OptionsTabUpdateFileDialog: public brls::Dialog
{
private:
DataTransferProgressDisplay *update_progress = nullptr;
nxdt::tasks::DownloadFileTask download_task;
std::string success_str;
@ -53,7 +52,7 @@ namespace nxdt::views
brls::Label *wait_lbl = nullptr; /// First stage.
brls::List *changelog_list = nullptr; /// Second stage.
EtaProgressDisplay *update_progress = nullptr; /// Third stage.
DataTransferProgressDisplay *update_progress = nullptr; /// Third stage.
nxdt::tasks::DownloadFileTask nro_task;

View file

@ -26,7 +26,7 @@
#include <borealis.hpp>
#include "defines.h"
#include "core/nxdt_includes.h"
#include "core/gamecard.h"
#include "core/title.h"
#include "core/ums.h"

View file

@ -6,10 +6,10 @@
"output_storage": {
"label": "Output storage",
"description": "Storage where the dumped data will be written to. Changing it will automatically update the output filename to better suit the output filesystem limitations.\nUsing a connected USB host requires a libusb-based driver, as well as the Python host script. For more information, please visit \"{}\".",
"value_00": "SD card",
"description": "Storage where the dumped data will be written to. Changing it will automatically update the output filename to better suit the output filesystem limitations.\nUsing a connected USB host requires a libusb-based driver, as well as the Python host script. For more information, please visit \"{0}\".",
"value_00": "SD card ({0} free / {1} total)",
"value_01": "USB host",
"value_02": "USB Mass Storage ({})"
"value_02": "USB Mass Storage {1:.{0}} ({2} free / {3} total) ({4})"
},
"prepend_key_area": {

View file

@ -39,7 +39,7 @@
"dump_initial_data": {
"label": "Dump InitialData area",
"description": "The InitialData area holds cryptographic information used by the Lotus ASIC to communicate with the gamecard.\nIt can't be dumped through normal means -- it's not part of the storage areas from gamecard images."
"description": "The InitialData area holds cryptographic information used by the Lotus ASIC to communicate with the gamecard.\nIt can't be dumped through normal means it's not part of the storage areas from gamecard images."
},
"dump_certificate": {
@ -49,12 +49,12 @@
"dump_card_id_set": {
"label": "Dump card ID set",
"description": "The card ID set is composed of three 32-bit integers that hold information such as the gamecard's memory type and manufacturer.\nIt can't be dumped through normal means -- it's not part of the storage areas from gamecard images."
"description": "The card ID set is composed of three 32-bit integers that hold information such as the gamecard's memory type and manufacturer.\nIt can't be dumped through normal means it's not part of the storage areas from gamecard images."
},
"dump_card_uid": {
"label": "Dump card UID",
"description": "The card UID is a 64-byte long area that serves as a unique identifier for the gamecard. It's mostly used in security contexts.\nIt can't be dumped through normal means -- it's not part of the storage areas from gamecard images."
"description": "The card UID is a 64-byte long area that serves as a unique identifier for the gamecard. It's mostly used in security contexts.\nIt can't be dumped through normal means it's not part of the storage areas from gamecard images."
},
"dump_header": {

View file

@ -33,12 +33,6 @@
#include "nxdt_devoptab.h"
#include "fatfs/ff.h"
/// Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits.
/// Reference: https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits.
/// Most modern filesystems use a 255-byte limit instead of 255-character/codepoint limit, so that's what we're gonna use.
#define FS_MAX_FILENAME_LENGTH 255
#define SDMC_MAX_FILENAME_LENGTH 128 /* Arbitrarily set, I'm tired of FS sysmodule shenanigans. */
/* Type definitions. */
/* Reference: https://github.com/Atmosphere-NX/Atmosphere/blob/master/exosphere/program/source/smc/secmon_smc_info.hpp. */

View file

@ -1,5 +1,5 @@
/*
* eta_progress_display.cpp
* data_transfer_progress_display.cpp
*
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
*
@ -19,15 +19,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <nxdt_utils.h>
#include <eta_progress_display.hpp>
namespace i18n = brls::i18n; /* For getStr(). */
using namespace i18n::literals; /* For _i18n. */
#include <data_transfer_progress_display.hpp>
namespace nxdt::views
{
EtaProgressDisplay::EtaProgressDisplay(void)
DataTransferProgressDisplay::DataTransferProgressDisplay(void)
{
this->progress_display = new brls::ProgressDisplay();
this->progress_display->setParent(this);
@ -41,14 +37,14 @@ namespace nxdt::views
this->speed_eta_lbl->setParent(this);
}
EtaProgressDisplay::~EtaProgressDisplay(void)
DataTransferProgressDisplay::~DataTransferProgressDisplay(void)
{
delete this->progress_display;
delete this->size_lbl;
delete this->speed_eta_lbl;
}
void EtaProgressDisplay::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
void DataTransferProgressDisplay::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
/* Progress display. */
this->progress_display->frame(ctx);
@ -60,7 +56,7 @@ namespace nxdt::views
this->speed_eta_lbl->frame(ctx);
}
void EtaProgressDisplay::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
void DataTransferProgressDisplay::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
unsigned elem_width = roundf(static_cast<float>(this->width) * 0.90f);
@ -94,17 +90,17 @@ namespace nxdt::views
this->speed_eta_lbl->getHeight());
}
void EtaProgressDisplay::setProgress(const EtaProgressInfo& progress)
void DataTransferProgressDisplay::setProgress(const nxdt::tasks::DataTransferProgress& progress)
{
/* Update progress percentage. */
this->progress_display->setProgress(progress.percentage, 100);
/* Update size string. */
this->size_lbl->setText(fmt::format("{} / {}", this->GetFormattedSizeString(static_cast<double>(progress.current)), \
progress.size ? this->GetFormattedSizeString(static_cast<double>(progress.size)) : "?"));
this->size_lbl->setText(fmt::format("{} / {}", this->GetFormattedSizeString(static_cast<double>(progress.xfer_size)), \
progress.total_size ? this->GetFormattedSizeString(static_cast<double>(progress.total_size)) : "?"));
/* Update speed / ETA string. */
if (progress.eta.length())
if (!progress.eta.empty())
{
this->speed_eta_lbl->setText(fmt::format("{}/s - ETA: {}", this->GetFormattedSizeString(progress.speed), progress.eta));
} else {
@ -114,17 +110,17 @@ namespace nxdt::views
this->invalidate();
}
void EtaProgressDisplay::willAppear(bool resetState)
void DataTransferProgressDisplay::willAppear(bool resetState)
{
this->progress_display->willAppear(resetState);
}
void EtaProgressDisplay::willDisappear(bool resetState)
void DataTransferProgressDisplay::willDisappear(bool resetState)
{
this->progress_display->willDisappear(resetState);
}
std::string EtaProgressDisplay::GetFormattedSizeString(double size)
std::string DataTransferProgressDisplay::GetFormattedSizeString(double size)
{
char strbuf[0x40] = {0};
utilsGenerateFormattedSizeString(size, strbuf, sizeof(strbuf));

View file

@ -252,7 +252,9 @@ namespace nxdt::views
icon->setImage(BOREALIS_ASSET("icon/" APP_TITLE ".jpg"));
icon->setScaleType(brls::ImageScaleType::SCALE);
brls::Application::pushView(new DumpOptionsFrame(this->root_view, "gamecard_tab/list/dump_card_image/label"_i18n, icon, raw_filename, ".xci"), brls::ViewAnimation::SLIDE_LEFT);
brls::Application::pushView(new DumpOptionsFrame(this->root_view, "gamecard_tab/list/dump_card_image/label"_i18n, icon, std::string(raw_filename), ".xci"), brls::ViewAnimation::SLIDE_LEFT);
free(raw_filename);
});
this->list->addView(dump_card_image);

View file

@ -19,9 +19,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <nxdt_utils.h>
#include <options_tab.hpp>
#include <root_view.hpp>
#include <focusable_item.hpp>
#include <title.h>
#include <sstream>
@ -34,11 +32,11 @@ namespace nxdt::views
OptionsTabUpdateFileDialog::OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str) : brls::Dialog(), success_str(success_str)
{
/* Set content view. */
EtaProgressDisplay *update_progress = new EtaProgressDisplay();
this->setContentView(update_progress);
this->update_progress = new DataTransferProgressDisplay();
this->setContentView(this->update_progress);
/* Add cancel button. */
this->addButton("options_tab/update_dialog/cancel"_i18n, [this](brls::View* view) {
this->addButton("options_tab/update_dialog/cancel"_i18n, [&](brls::View* view) {
/* Cancel download task. */
this->download_task.cancel();
@ -50,15 +48,15 @@ namespace nxdt::views
this->setCancelable(false);
/* Subscribe to the download task. */
this->download_task.RegisterListener([this, update_progress](const EtaProgressInfo& progress) {
this->download_task.RegisterListener([&](const nxdt::tasks::DataTransferProgress& progress) {
/* Update progress. */
update_progress->setProgress(progress);
this->update_progress->setProgress(progress);
/* Check if the download task has finished. */
if (this->download_task.isFinished())
if (this->download_task.IsFinished())
{
/* Stop spinner. */
update_progress->willDisappear();
this->update_progress->willDisappear();
/* Update button label. */
this->setButtonText(0, "options_tab/update_dialog/close"_i18n);
@ -90,15 +88,14 @@ namespace nxdt::views
this->addStage(this->changelog_list);
/* Add third stage. */
this->update_progress = new EtaProgressDisplay();
this->update_progress = new DataTransferProgressDisplay();
this->addStage(this->update_progress);
/* Subscribe to the JSON task. */
this->json_task.RegisterListener([this](const EtaProgressInfo& progress) {
this->json_task.RegisterListener([&](const nxdt::tasks::DataTransferProgress& progress) {
/* Return immediately if the JSON task hasn't finished. */
if (!this->json_task.isFinished()) return;
if (!this->json_task.IsFinished()) return;
bool pop_view = false;
std::string notification = "";
/* Retrieve task result. */
@ -115,9 +112,6 @@ namespace nxdt::views
/* Display changelog. */
this->DisplayChangelog();
} else {
/* Update flag. */
pop_view = true;
/* Set notification string. */
notification = "options_tab/notifications/up_to_date"_i18n;
}
@ -125,15 +119,12 @@ namespace nxdt::views
/* Log downloaded data. */
LOG_DATA_ERROR(this->json_buf, this->json_buf_size, "Failed to parse GitHub release JSON. Downloaded data:");
/* Update flag. */
pop_view = true;
/* Set notification string. */
notification = "options_tab/notifications/github_json_failed"_i18n;
}
/* Pop view (if needed). */
if (pop_view)
if (!notification.empty())
{
/* Display notification. */
brls::Application::notify(notification);
@ -252,12 +243,12 @@ namespace nxdt::views
this->updateActionHint(brls::Key::B, "options_tab/update_dialog/cancel"_i18n);
/* Subscribe to the NRO task. */
this->nro_task.RegisterListener([this](const EtaProgressInfo& progress) {
this->nro_task.RegisterListener([&](const nxdt::tasks::DataTransferProgress& progress) {
/* Update progress. */
this->update_progress->setProgress(progress);
/* Check if the download task has finished. */
if (this->nro_task.isFinished())
if (this->nro_task.IsFinished())
{
/* Get NRO task result and immediately set application updated state if the task succeeded. */
bool ret = this->nro_task.get();