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

Implemented AsyncTask class.

Other changes include:

* Updated borealis.
* Updated Makefile.
* Began implementation of a (very) simple, CURL-based HTTP handler.
* OptionsTab: added a small disclaimer about dump options.
* OptionsTab:  added notifications for the update application item (running as NSO, app already updated).
* config: improved boundary handling while validating integer entries.
* utils: (de)initialize CURL (this will be moved to http.c eventually).
This commit is contained in:
Pablo Curiel 2021-07-25 01:37:13 -04:00
parent ff0a2a385c
commit 05dec93795
14 changed files with 474 additions and 22 deletions

View file

@ -82,9 +82,6 @@ CFLAGS += -DVERSION_MAJOR=${VERSION_MAJOR} -DVERSION_MINOR=${VERSION_MINOR} -DV
CFLAGS += -DAPP_TITLE=\"${APP_TITLE}\" -DAPP_AUTHOR=\"${APP_AUTHOR}\" -DAPP_VERSION=\"${APP_VERSION}\"
CFLAGS += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\" -DGIT_REV=\"${GIT_REV}\"
CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\""
CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags`
CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags`
CXXFLAGS := $(CFLAGS) -std=c++20 -Wno-volatile -Wno-unused-parameter

23
http.c Normal file
View file

@ -0,0 +1,23 @@
/*
* http.c
*
* Copyright (c) 2020-2021, 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/>.
*/
#include "nxdt_utils.h"
#include "http.h"

307
include/async_task.hpp Normal file
View file

@ -0,0 +1,307 @@
/*
* async_task.hpp
*
* Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
*
* Based on attcs' C++ implementation at:
* https://github.com/attcs/AsyncTask/blob/master/asynctask.h.
*
* 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 __ASYNC_TASK_HPP__
#define __ASYNC_TASK_HPP__
#include <exception>
#include <future>
#include <atomic>
namespace nxdt::utils
{
/* Used by AsyncTask to throw exceptions whenever required. */
class AsyncTaskException : std::exception
{
public:
enum class eEx : int
{
TaskIsAlreadyRunning, ///< Task is already running.
TaskIsAlreadyFinished, ///< Task is already finished.
TaskIsPending, ///< Task hasn't been executed.
TaskIsCancelled, ///< Task has been cancelled.
TaskWaitTimeout ///< Timed out while waiting for the task to finish.
};
eEx e;
AsyncTaskException() = default;
AsyncTaskException(eEx e) : e(e) { }
};
/* Used by AsyncTask to indicate the current status of the asynchronous task. */
enum class AsyncTaskStatus : int
{
PENDING, ///< The task hasn't been executed yet.
RUNNING, ///< The task is currently running.
FINISHED ///< The task is finished.
};
/* Asynchronous task handler class. */
template<typename Progress, typename Result, typename... Params>
class AsyncTask
{
private:
AsyncTaskStatus m_status = AsyncTaskStatus::PENDING;
Result m_result{};
std::future<Result> m_future{};
std::atomic<Progress> m_progress{};
std::atomic_bool m_cancelled = false;
std::atomic_bool m_rethrowException = false;
std::exception_ptr m_exceptionPtr{};
/* Runs on the calling thread after doInBackground() finishes execution. */
void finish(Result&& result)
{
/* Copy result. */
this->m_result = result;
/* Call appropiate post-execution function. */
if (this->isCancelled())
{
this->onCancelled(this->m_result);
} else {
this->onPostExecute(this->m_result);
}
/* Update status. */
this->m_status = AsyncTaskStatus::FINISHED;
/* Rethrow asynchronous task exception (if available). */
if (this->m_rethrowException.load() && this->m_exceptionPtr) std::rethrow_exception(this->m_exceptionPtr);
}
protected:
/* Set class as non-copyable. */
NON_COPYABLE(AsyncTask);
virtual ~AsyncTask(void) noexcept
{
/* Return right away if the task isn't running. */
if (this->getStatus() != AsyncTaskStatus::RUNNING) return;
/* Cancel task. This won't do anything if it has already been cancelled. */
this->cancel();
/* Return right away if the result was already retrieved. */
if (!this->m_future.valid()) return;
/* Wait until a result is provided by the task thread. */
/* Avoid rethrowing any exceptions here - program execution could end if another exception has already been rethrown. */
m_future.wait();
}
/* Asynchronous task function. */
/* This function should periodically call isCancelled() to determine if it should end prematurely. */
virtual Result doInBackground(const Params&... params) = 0;
/* Posts asynchronous task result. Runs on the asynchronous task thread. */
virtual Result postResult(Result&& result)
{
return result;
}
/* Cleanup function called if the task is cancelled. Runs on the calling thread. */
virtual void onCancelled(const Result& result) { }
/* Post-execution function called right after the task finishes. Runs on the calling thread. */
virtual void onPostExecute(const Result& result) { }
/* Pre-execution function called right before the task starts. Runs on the calling thread. */
virtual void onPreExecute(void) { }
/* Progress update function. Runs on the calling thread. */
virtual void onProgressUpdate(const Progress& progress) { }
/* Stores the current progress inside the class. Runs on the asynchronous task thread. */
virtual void publishProgress(const Progress& progress)
{
/* Don't proceed if the task isn't running. */
if (this->getStatus() != AsyncTaskStatus::RUNNING || this->isCancelled()) return;
/* Update progress. */
this->m_progress.store(progress);
}
public:
AsyncTask(void) = default;
/* Cancels the task. Runs on the calling thread. */
void cancel(void) noexcept
{
/* Return right away if the task has already completed, or if it has already been cancelled. */
if (this->getStatus() == AsyncTaskStatus::FINISHED || this->isCancelled()) return;
/* Update cancel flag. */
this->m_cancelled.store(true);
}
/* Starts the asynchronous task. Runs on the calling thread. */
AsyncTask<Progress, Result, Params...>& execute(const Params&... params)
{
/* Return right away if the task was cancelled before starting. */
if (this->isCancelled()) return *this;
/* Verify task status. */
switch(this->getStatus())
{
case AsyncTaskStatus::RUNNING:
throw AsyncTaskException(AsyncTaskException::eEx::TaskIsAlreadyRunning);
case AsyncTaskStatus::FINISHED:
throw AsyncTaskException(AsyncTaskException::eEx::TaskIsAlreadyFinished);
default:
break;
}
/* Update task status. */
this->m_status = AsyncTaskStatus::RUNNING;
/* Run onPreExecute() callback. */
this->onPreExecute();
/* Start asynchronous task on a new thread. */
this->m_future = std::async(std::launch::async, [this](const Params&... params) -> Result {
/* Catch any exceptions thrown by the asynchronous task. */
try {
return this->postResult(this->doInBackground(params...));
} catch(...) {
this->cancel();
this->m_rethrowException.store(true);
this->m_exceptionPtr = std::current_exception();
}
return {};
}, params...);
return *this;
}
/* Waits for the asynchronous task to complete, then returns its result. Runs on the calling thread. */
/* If an exception is thrown by the asynchronous task, it will be rethrown by this function. */
Result get(void)
{
auto status = this->getStatus();
/* Throw an exception if the asynchronous task hasn't been executed. */
if (status == AsyncTaskStatus::PENDING) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsPending);
/* If the task is still running, wait until it finishes. */
/* get() calls wait() on its own if the result hasn't been retrieved. */
/* finish() takes care of rethrowing any exceptions thrown by the asynchronous task. */
if (status == AsyncTaskStatus::RUNNING) this->finish(this->m_future.get());
/* Throw an exception if the asynchronous task was cancelled. */
if (this->isCancelled()) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsCancelled);
/* Return result. */
return this->m_result;
}
/* Waits for at most the given time for the asynchronous task to complete, then returns its result. Runs on the calling thread. */
/* If an exception is thrown by the asynchronous task, it will be rethrown by this function. */
template<typename Rep, typename Period>
Result get(const std::chrono::duration<Rep, Period>& timeout)
{
auto status = this->getStatus();
/* Throw an exception if the asynchronous task hasn't been executed. */
if (status == AsyncTaskStatus::PENDING) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsPending);
/* Check if the task is still running. */
if (status == AsyncTaskStatus::RUNNING)
{
/* Wait for at most the given time for the asynchronous task to complete. */
auto thread_status = this->m_future.wait_for(timeout);
switch(thread_status)
{
case std::future_status::timeout:
/* Throw an exception if we timed out while waiting for the task to finish. */
throw AsyncTaskException(AsyncTaskException::eEx::TaskWaitTimeout);
case std::future_status::ready:
/* Retrieve the task result. */
/* finish() takes care of rethrowing any exceptions thrown by the asynchronous task. */
this->finish(this->m_future.get());
/* Throw an exception if the asynchronous task was cancelled. */
if (this->isCancelled()) throw AsyncTaskException(AsyncTaskException::eEx::TaskIsCancelled);
break;
default:
break;
}
}
/* Return result. */
return this->m_result;
}
/* Returns the current task status. Runs on both threads. */
AsyncTaskStatus getStatus(void) noexcept
{
return this->m_status;
}
/* Returns true if the task was cancelled before it completed normally. Runs on both threads. */
/* Can be used by the asynchronous task to return prematurely. */
bool isCancelled(void) noexcept
{
return this->m_cancelled.load();
}
/* Used by the calling thread to refresh the task progress, preferrably inside a loop. Returns true if the task finished. */
/* If an exception is thrown by the asynchronous task, it will be rethrown by this function. */
bool loopCallback(void)
{
auto status = this->getStatus();
/* Return immediately if the task already finished. */
if (status == AsyncTaskStatus::FINISHED) return true;
/* Return immediately if the task hasn't started, or if its result was already retrieved. */
if (status == AsyncTaskStatus::PENDING || !this->m_future.valid()) return false;
/* Get task thread status without waiting. */
auto thread_status = this->m_future.wait_for(std::chrono::seconds(0));
switch(thread_status)
{
case std::future_status::timeout:
/* Update progress. */
this->onProgressUpdate(this->m_progress.load());
return false;
case std::future_status::ready:
/* Finish task. */
this->finish(this->m_future.get());
return true;
default:
break;
}
return false;
}
};
}
#endif /* __ASYNC_TASK_HPP__ */

View file

@ -24,6 +24,8 @@
#ifndef __CONFIG_H__
#define __CONFIG_H__
#include <json-c/json.h>
#ifdef __cplusplus
extern "C" {
#endif

39
include/core/http.h Normal file
View file

@ -0,0 +1,39 @@
/*
* http.h
*
* Copyright (c) 2020-2021, 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 __HTTP_H__
#define __HTTP_H__
#include <curl/curl.h>
/*
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
*/
#endif /* __HTTP_H__ */

View file

@ -59,6 +59,9 @@
/* Configuration handler. */
#include "config.h"
/* HTTP requests handler. */
#include "http.h"
/* USB Mass Storage support. */
#include "ums.h"

View file

@ -30,8 +30,15 @@ namespace nxdt::views
{
class OptionsTab: public brls::List
{
private:
bool display_notification = true;
brls::menu_timer_t notification_timer = 0.0f;
brls::menu_timer_ctx_entry_t notification_timer_ctx = {0};
void DisplayNotification(std::string str);
public:
OptionsTab(void);
~OptionsTab(void);
};
}

@ -1 +1 @@
Subproject commit bfc32dba9394409691a940896d2a10dfc65c10df
Subproject commit 097693eb5264941f8697902ea88a89e2efd25c11

View file

@ -1,4 +1,6 @@
{
"dump_options_info": "Dump options are displayed in their respective menus for convenience.",
"overclock": {
"label": "Overclock",
"description": "Overclocks both CPU and MEM to 1785 MHz and 1600 MHz, respectively, in order to speed up dump operations. This is considered a relatively safe action.\n\nIf the application is running under title override mode, and sys-clk is active, and a clock profile has been created for the overriden title, this setting has no effect at all.",
@ -16,5 +18,10 @@
"update_app": {
"label": "Update application",
"description": "Checks if an update is available in nxdumptool's GitHub repository. Requires Internet connectivity."
},
"notifications": {
"is_nso": "The application is running as a NSO. Unable to update.",
"already_updated": "The application has already been updated. Please reload."
}
}

View file

@ -1,10 +1,10 @@
{
"notifications": {
"gamecard_status_updated": "Gamecard status updated",
"gamecard_ejected": "Gamecard ejected",
"user_titles": "User titles updated",
"ums_device": "USB Mass Storage devices updated",
"usb_host_connected": "USB host connected",
"usb_host_disconnected": "USB host disconnected"
"gamecard_status_updated": "Gamecard status updated.",
"gamecard_ejected": "Gamecard ejected.",
"user_titles": "User titles updated.",
"ums_device": "USB Mass Storage devices updated.",
"usb_host_connected": "USB host connected.",
"usb_host_disconnected": "USB host disconnected."
}
}

View file

@ -23,8 +23,6 @@
#include "config.h"
#include "title.h"
#include <json-c/json.h>
#define JSON_VALIDATE_FIELD(type, name, ...) \
if (!strcmp(key, #name)) { \
if (name##_found || !configValidateJson##type(val, ##__VA_ARGS__)) goto end; \
@ -241,8 +239,8 @@ static bool configValidateJsonRootObject(const struct json_object *obj)
json_object_object_foreach(obj, key, val)
{
JSON_VALIDATE_FIELD(Boolean, overclock);
JSON_VALIDATE_FIELD(Integer, naming_convention, TitleNamingConvention_Full, TitleNamingConvention_IdAndVersionOnly);
JSON_VALIDATE_FIELD(Integer, dump_destination, ConfigDumpDestination_SdCard, ConfigDumpDestination_UsbHost);
JSON_VALIDATE_FIELD(Integer, naming_convention, TitleNamingConvention_Full, TitleNamingConvention_Count - 1);
JSON_VALIDATE_FIELD(Integer, dump_destination, ConfigDumpDestination_SdCard, ConfigDumpDestination_Count - 1);
JSON_VALIDATE_OBJECT(GameCard, gamecard);
JSON_VALIDATE_OBJECT(Nsp, nsp);
JSON_VALIDATE_OBJECT(Ticket, ticket);
@ -268,7 +266,7 @@ static bool configValidateJsonGameCardObject(const struct json_object *obj)
JSON_VALIDATE_FIELD(Boolean, keep_certificate);
JSON_VALIDATE_FIELD(Boolean, trim_dump);
JSON_VALIDATE_FIELD(Boolean, calculate_checksum);
JSON_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_NoIntro);
JSON_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_Count - 1);
goto end;
}

View file

@ -210,9 +210,19 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
/* TODO: only use this function while dealing with a dump process - make sure to handle power button presses as well. */
appletSetMediaPlaybackState(true);
/* Redirect stdout and stderr over network to nxlink. */
/* Initialize socket driver. */
rc = socketInitializeDefault();
if (R_SUCCEEDED(rc)) g_nxLinkSocketFd = nxlinkConnectToHost(true, true);
if (R_FAILED(rc))
{
LOG_MSG("socketInitializeDefault failed! (0x%08X).", rc);
break;
}
/* Initialize CURL. */
curl_global_init(CURL_GLOBAL_ALL);
/* Redirect stdout and stderr over network to nxlink. */
g_nxLinkSocketFd = nxlinkConnectToHost(true, true);
/* Update flags. */
ret = g_resourcesInit = true;
@ -227,6 +237,9 @@ void utilsCloseResources(void)
{
SCOPED_LOCK(&g_resourcesMutex)
{
/* Cleanup CURL. */
curl_global_cleanup();
/* Close nxlink socket. */
if (g_nxLinkSocketFd >= 0)
{
@ -234,6 +247,7 @@ void utilsCloseResources(void)
g_nxLinkSocketFd = -1;
}
/* Deinitialize socket driver. */
socketExit();
/* Enable screen dimming and auto sleep. */

View file

@ -33,15 +33,28 @@ namespace nxdt::views
this->setSpacing(this->getSpacing() / 2);
this->setMarginBottom(20);
/* Information about actual dump options. */
brls::Label *dump_options_info = new brls::Label(brls::LabelStyle::DESCRIPTION, "options_tab/dump_options_info"_i18n, true);
dump_options_info->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addView(dump_options_info);
/* Overclock. */
brls::ToggleListItem *overclock = new brls::ToggleListItem("options_tab/overclock/label"_i18n, configGetBoolean("overclock"), \
"options_tab/overclock/description"_i18n, "options_tab/overclock/value_enabled"_i18n, \
"options_tab/overclock/value_disabled"_i18n);
overclock->getClickEvent()->subscribe([](brls::View* view) {
brls::ToggleListItem *item = static_cast<brls::ToggleListItem*>(view);
/* Get current value. */
bool value = item->getToggleState();
/* Change hardware clocks based on the current value. */
utilsOverclockSystem(value);
/* Update configuration. */
configSetBoolean("overclock", value);
brls::Logger::debug("Overclock setting changed by user.");
});
this->addView(overclock);
@ -52,16 +65,57 @@ namespace nxdt::views
}, static_cast<unsigned>(configGetInteger("naming_convention")),
"options_tab/naming_convention/description"_i18n);
naming_convention->getValueSelectedEvent()->subscribe([](int selected){
/* Make sure the current value isn't out of bounds. */
if (selected < 0 || selected > static_cast<int>(TitleNamingConvention_Count)) return;
/* Update configuration. */
configSetInteger("naming_convention", selected);
brls::Logger::debug("Naming convention setting changed by user.");
});
this->addView(naming_convention);
/* Update application. */
if (!envIsNso())
{
brls::ListItem *update_app = new brls::ListItem("options_tab/update_app/label"_i18n, "options_tab/update_app/description"_i18n);
update_app->getClickEvent()->subscribe([this](brls::View* view) {
if (envIsNso())
{
/* Display a notification if we're running as a NSO. */
this->DisplayNotification("options_tab/notifications/is_nso"_i18n);
return;
} else
if (false)
{
/* Display a notification if the application has already been updated. */
this->DisplayNotification("options_tab/notifications/already_updated"_i18n);
return;
}
/*brls::StagedAppletFrame *staged_frame = new brls::StagedAppletFrame();
staged_frame->setTitle("options_tab/update_app/label"_i18n);
brls::Application::pushView(staged_frame);*/
});
this->addView(update_app);
}
OptionsTab::~OptionsTab(void)
{
brls::menu_timer_kill(&(this->notification_timer));
}
void OptionsTab::DisplayNotification(std::string str)
{
if (str == "" || !this->display_notification) return;
brls::Application::notify(str);
this->display_notification = false;
this->notification_timer_ctx.duration = brls::Application::getStyle()->AnimationDuration.notificationTimeout;
this->notification_timer_ctx.cb = [this](void *userdata) { this->display_notification = true; };
this->notification_timer_ctx.tick = [](void*){};
this->notification_timer_ctx.userdata = nullptr;
brls::menu_timer_start(&(this->notification_timer), &(this->notification_timer_ctx));
}
}

View file

@ -23,6 +23,7 @@ todo:
usb: improve abi (make it rest-like?)
usb: improve cancel mechanism
others: move curl (de)initialization to http.c
others: use hardcoded directories, move data to hardcoded directory if the launch path isn't the right one
others: dump verification via nswdb / no-intro
others: update application feature