From f80573e5c4551f2eab026c676cc40dc7df6669ce Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Tue, 27 Jul 2021 19:47:12 -0400 Subject: [PATCH] Move DownloadTask classes to their own header file. --- include/async_task.hpp | 2 +- include/defines.h | 2 + include/download_task.hpp | 277 ++++++++++++++++++++++++++++++++++++++ include/tasks.hpp | 217 +---------------------------- 4 files changed, 281 insertions(+), 217 deletions(-) create mode 100644 include/download_task.hpp diff --git a/include/async_task.hpp b/include/async_task.hpp index 3d65c2e..a94ec45 100644 --- a/include/async_task.hpp +++ b/include/async_task.hpp @@ -31,7 +31,7 @@ #include #include -namespace nxdt::utils +namespace nxdt::tasks { /* Used by AsyncTask to throw exceptions whenever required. */ class AsyncTaskException : std::exception diff --git a/include/defines.h b/include/defines.h index b96fcfe..52a9de4 100644 --- a/include/defines.h +++ b/include/defines.h @@ -93,6 +93,8 @@ #define BIS_SYSTEM_PARTITION_MOUNT_NAME "sys:" +#define DOWNLOAD_TASK_INTERVAL 100 /* 100 milliseconds. */ + #define HTTP_USER_AGENT APP_TITLE "/" APP_VERSION " (Nintendo Switch)" #define HTTP_CONNECT_TIMEOUT 10L /* 10 seconds. */ #define HTTP_BUFFER_SIZE 131072L /* 128 KiB. */ diff --git a/include/download_task.hpp b/include/download_task.hpp new file mode 100644 index 0000000..9e0682e --- /dev/null +++ b/include/download_task.hpp @@ -0,0 +1,277 @@ +/* + * download_task.hpp + * + * Copyright (c) 2020-2021, DarkMatterCore . + * + * 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 . + */ + +#pragma once + +#ifndef __DOWNLOAD_TASK_HPP__ +#define __DOWNLOAD_TASK_HPP__ + +#include + +#include "defines.h" +#include "async_task.hpp" + +namespace nxdt::tasks +{ + /* Used to hold download progress info. */ + typedef struct { + /// Fields set by DownloadTask::HttpProgressCallback(). + size_t size; ///< Total download size. + size_t current; ///< Number of bytes downloaded thus far. + int percentage; ///< Progress percentage. + + /// Fields set by DownloadTask::onProgressUpdate(). + double speed; ///< Download speed expressed in KiB/s. + std::string eta; ///< Formatted ETA string. + } DownloadTaskProgress; + + /* Custom event type used to push download progress updates. */ + typedef brls::Event DownloadProgressEvent; + + /* Used to hold a buffer + size pair with downloaded data. */ + typedef std::pair 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. */ + template + class DownloadTask: public AsyncTask + { + public: + /* Handles task progress updates on the calling thread. */ + class DownloadTaskHandler: public brls::RepeatingTask + { + private: + bool finished = false; + DownloadTask* task = nullptr; + + protected: + void run(retro_time_t current_time) override final; + + public: + DownloadTaskHandler(retro_time_t interval, DownloadTask* task); + + ALWAYS_INLINE bool isFinished(void) + { + return this->finished; + } + }; + + private: + DownloadProgressEvent progress_event; + DownloadTaskHandler *task_handler = nullptr; + std::chrono::time_point 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 DownloadTaskProgress& progress) override final; + + public: + DownloadTask(void); + ~DownloadTask(void); + + /* 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 + DownloadTask::DownloadTaskHandler::DownloadTaskHandler(retro_time_t interval, DownloadTask* task) : brls::RepeatingTask(interval), task(task) + { + /* Do nothing. */ + } + + template + void DownloadTask::DownloadTaskHandler::run(retro_time_t current_time) + { + brls::RepeatingTask::run(current_time); + if (this->task && !this->finished) this->finished = this->task->loopCallback(); + } + + template + DownloadTask::DownloadTask(void) + { + /* Create task handler. */ + this->task_handler = new DownloadTaskHandler(DOWNLOAD_TASK_INTERVAL, this); + } + + template + DownloadTask::~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 + int DownloadTask::HttpProgressCallback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) + { + (void)ultotal; + (void)ulnow; + + DownloadTaskProgress progress = {0}; + DownloadTask* task = static_cast*>(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(dltotal); + progress.current = static_cast(dlnow); + progress.percentage = static_cast((progress.current * 100) / progress.size); + + /* Push progress onto the class. */ + task->publishProgress(progress); + + return 0; + } + + template + void DownloadTask::onCancelled(const Result& result) + { + (void)result; + + /* Pause task handler. */ + this->task_handler->pause(); + } + + template + void DownloadTask::onPostExecute(const Result& result) + { + (void)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()); + } + + template + void DownloadTask::onPreExecute(void) + { + /* Start task handler. */ + this->task_handler->start(); + + /* Set start time. */ + this->start_time = this->prev_time = std::chrono::steady_clock::now(); + } + + template + void DownloadTask::onProgressUpdate(const DownloadTaskProgress& progress) + { + /* 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 cur_time = std::chrono::steady_clock::now(); + std::chrono::duration diff_time = (cur_time - this->prev_time); + + double diff_time_conv = diff_time.count(); + if (diff_time_conv < 1.0 && ((progress.size && progress.current < progress.size) || this->getStatus() == AsyncTaskStatus::RUNNING)) return; + + /* Calculate transferred data size difference between the last progress update and the current one. */ + double diff_current = static_cast(progress.current - prev_current); + + /* Calculate download speed in kibibytes per second (KiB/s). */ + double speed = ((diff_current / diff_time_conv) / 1024.0); + + /* Calculate remaining data size in kibibytes (KiB) and ETA if we know the download size. */ + double eta = 0.0; + + if (progress.size) + { + double remaining = (static_cast(progress.size - progress.current) / 1024.0); + eta = (remaining / speed); + } + + /* Fill struct. */ + DownloadTaskProgress new_progress = progress; + new_progress.speed = speed; + new_progress.eta = (progress.size ? fmt::format("{:02}H{:02}M{:02}S", std::fmod(eta, 86400.0) / 3600.0, std::fmod(eta, 3600.0) / 60.0, std::fmod(eta, 60.0)) : ""); + + /* 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 + { + protected: + /* 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); + } + }; + + /* Asynchronous task to store downloaded data into a dynamically allocated buffer using a URL. */ + class DownloadDataTask: public DownloadTask + { + protected: + DownloadDataResult doInBackground(const std::string& url, const bool& force_https) override final + { + char *buf = NULL; + 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. */ + buf = httpDownloadData(&buf_size, url.c_str(), force_https, DownloadDataTask::HttpProgressCallback, this); + + return std::make_pair(buf, buf_size); + } + }; +} + +#endif /* __DOWNLOAD_TASK_HPP__ */ diff --git a/include/tasks.hpp b/include/tasks.hpp index 17a2d70..f04af59 100644 --- a/include/tasks.hpp +++ b/include/tasks.hpp @@ -26,13 +26,12 @@ #include +#include "defines.h" #include "core/gamecard.h" #include "core/title.h" #include "core/ums.h" #include "core/usb.h" -#include "async_task.hpp" - namespace nxdt::tasks { /* Used to hold status info data. */ @@ -198,220 +197,6 @@ namespace nxdt::tasks this->usb_host_event.unsubscribe(subscription); } }; - - - - - - - - - typedef struct { - /// Fields set by DownloadTask::HttpProgressCallback(). - size_t size; ///< Total download size. - size_t current; ///< Number of bytes downloaded thus far. - int percentage; ///< Progress percentage. - - /// Fields set by DownloadTask::onProgressUpdate(). - double speed; ///< Download speed expressed in KiB/s. - std::string eta; ///< Formatted ETA string. - } DownloadTaskProgress; - - typedef brls::Event DownloadProgressEvent; - - typedef std::pair 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. */ - template - class DownloadTask: public nxdt::utils::AsyncTask - { - public: - /* Handles task progress updates on the calling thread. */ - class DownloadTaskHandler: public brls::RepeatingTask - { - private: - DownloadTask* task = nullptr; - - protected: - void run(retro_time_t current_time) override final - { - brls::RepeatingTask::run(current_time); - if (this->task) this->task->loopCallback(); - } - - public: - DownloadTaskHandler(retro_time_t interval, DownloadTask* task) : brls::RepeatingTask(interval), task(task) { } - }; - - private: - DownloadProgressEvent progress_event; - DownloadTaskHandler *task_handler = nullptr; - std::chrono::time_point start_time{}, prev_time{}; - size_t prev_current = 0; - - protected: - /* Runs on the calling thread. */ - void onCancelled(const Result& result) override final - { - (void)result; - - /* Pause task handler. */ - this->task_handler->pause(); - } - - /* Runs on the calling thread. */ - void onPostExecute(const Result& result) override final - { - (void)result; - - /* Pause task handler. */ - this->task_handler->pause(); - - /* Update progress one last time. */ - this->onProgressUpdate(this->getProgress()); - } - - /* Runs on the calling thread. */ - void onPreExecute(void) override final - { - /* Start task handler. */ - this->task_handler->start(); - - /* Set start time. */ - this->start_time = this->prev_time = std::chrono::steady_clock::now(); - } - - /* Runs on the calling thread. */ - void onProgressUpdate(const DownloadTaskProgress& progress) override final - { - /* 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 cur_time = std::chrono::steady_clock::now(); - std::chrono::duration diff_time = (cur_time - this->prev_time); - - double diff_time_conv = diff_time.count(); - if (diff_time_conv < 1.0 && ((progress.size && progress.current < progress.size) || this->getStatus() == nxdt::utils::AsyncTaskStatus::RUNNING)) return; - - /* Calculate transferred data size difference between the last progress update and the current one. */ - double diff_current = static_cast(progress.current - prev_current); - - /* Calculate download speed in kibibytes per second (KiB/s). */ - double speed = ((diff_current / diff_time_conv) / 1024.0); - - /* Calculate remaining data size in kibibytes (KiB) and ETA if we know the download size. */ - double eta = 0.0; - - if (progress.size) - { - double remaining = (static_cast(progress.size - progress.current) / 1024.0); - eta = (remaining / speed); - } - - /* Fill struct. */ - DownloadTaskProgress new_progress = progress; - new_progress.speed = speed; - new_progress.eta = (progress.size ? fmt::format("{:02}H{:02}M{:02}S", std::fmod(eta, 86400.0) / 3600.0, std::fmod(eta, 3600.0) / 60.0, std::fmod(eta, 60.0)) : ""); - - /* Update class variables. */ - this->prev_time = cur_time; - this->prev_current = progress.current; - - /* Send updated progress to all subscribers. */ - this->progress_event.fire(new_progress); - } - - public: - /* Runs on the calling thread. */ - DownloadTask(retro_time_t interval) - { - /* Create task handler. */ - this->task_handler = new DownloadTaskHandler(interval, this); - } - - /* Runs on the calling thread. */ - ~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(); - } - - /* 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) - { - (void)ultotal; - (void)ulnow; - - DownloadTaskProgress progress = {0}; - DownloadTask* task = static_cast*>(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(dltotal); - progress.current = static_cast(dlnow); - progress.percentage = static_cast((progress.current * 100) / progress.size); - - /* Push progress onto the class. */ - task->publishProgress(progress); - - return 0; - } - - 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); - } - }; - - /* Asynchronous task to download a file using an output path and a URL. */ - class DownloadFileTask: public DownloadTask - { - protected: - 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(retro_time_t interval) : DownloadTask(interval) { } - }; - - /* Asynchronous task to store downloaded data into a dynamically allocated buffer using a URL. */ - class DownloadDataTask: public DownloadTask - { - protected: - DownloadDataResult doInBackground(const std::string& url, const bool& force_https) - { - char *buf = NULL; - 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. */ - buf = httpDownloadData(&buf_size, url.c_str(), force_https, DownloadDataTask::HttpProgressCallback, this); - - return std::make_pair(buf, buf_size); - } - - public: - DownloadDataTask(retro_time_t interval) : DownloadTask(interval) { } - }; } #endif /* __TASKS_HPP__ */