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:
parent
8f8fc6af37
commit
9525f37e51
14 changed files with 449 additions and 377 deletions
|
@ -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. */
|
||||
|
|
|
@ -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__ */
|
233
include/data_transfer_task.hpp
Normal file
233
include/data_transfer_task.hpp
Normal 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__ */
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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));
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue