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

OptionsTab: fully implement application update feature.

Other changes include:

* Codebase: move JSON parsing logic from config.c/h to nxdt_json.c/h.
* Codebase: replace all calls to localtime() with localtime_r() to guarantee thread-safety.
* Codebase: updated todo.txt.
* utils: implement utilsParseGitHubReleaseJsonData(), utilsFreeGitHubReleaseJsonData(), utilsGetApplicationUpdatedState() and utilsGetApplicationUpdatedState().
* utils: add extra logic to move the application's NRO to its proper path if the launch path isn't the right one (commented out at this moment).
* utils: add extra logic to replace the application's NRO at exit (commented out at this moment).
* defines: add DEVOPTAB_SDMC_DEVICE and tweak GitHub URL macros.
* DownloadTask: set percentage to 0 if the download size is unknown.
* DownloadTask: fix ETA string formatting.
* OptionsTab: repurpose OptionsTabUpdateFileDialogContent into OptionsTabUpdateProgress.
* OptionsTab: implement OptionsTabUpdateApplicationFrame.
* RootView: move date formatting into the static GetFormattedDateString() method.
* Makefile: use _GNU_SOURCE as part of CFLAGS to use strptime().
This commit is contained in:
Pablo Curiel 2021-08-07 04:42:03 -04:00
parent 3f16e3f835
commit 28cd0ce10f
24 changed files with 881 additions and 272 deletions

View file

@ -81,7 +81,7 @@ CFLAGS := -g -gdwarf-4 -Wall -Werror -O2 -ffunction-sections $(ARCH) $(DEFINES)
CFLAGS += -DVERSION_MAJOR=${VERSION_MAJOR} -DVERSION_MINOR=${VERSION_MINOR} -DVERSION_MICRO=${VERSION_MICRO} CFLAGS += -DVERSION_MAJOR=${VERSION_MAJOR} -DVERSION_MINOR=${VERSION_MINOR} -DVERSION_MICRO=${VERSION_MICRO}
CFLAGS += -DAPP_TITLE=\"${APP_TITLE}\" -DAPP_AUTHOR=\"${APP_AUTHOR}\" -DAPP_VERSION=\"${APP_VERSION}\" 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 += -DGIT_BRANCH=\"${GIT_BRANCH}\" -DGIT_COMMIT=\"${GIT_COMMIT}\" -DGIT_REV=\"${GIT_REV}\"
CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\"" CFLAGS += -DBOREALIS_RESOURCES="\"${BOREALIS_RESOURCES}\"" -D_GNU_SOURCE
CXXFLAGS := $(CFLAGS) -std=c++20 -Wno-volatile -Wno-unused-parameter CXXFLAGS := $(CFLAGS) -std=c++20 -Wno-volatile -Wno-unused-parameter

View file

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

97
include/core/nxdt_json.h Normal file
View file

@ -0,0 +1,97 @@
/*
* nxdt_json.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 __NXDT_JSON_H__
#define __NXDT_JSON_H__
#include <json-c/json.h>
#ifdef __cplusplus
extern "C" {
#endif
/// Parses a JSON object using the provided string.
/// If 'size' is zero, strlen() is used to retrieve the input string length.
/// json_object_put() must be used to free the returned JSON object.
/// Returns NULL if an error occurs.
struct json_object *jsonParseFromString(const char *str, size_t size);
/// Retrieves a JSON object from another object using a path.
/// Path elements must be separated using forward slashes.
/// If 'out_last_element' is provided, the parent JSON object from the last path element will be returned instead.
/// Furthermore, a string duplication of the last path element will be stored in 'out_last_element', which must be freed by the user.
/// Returns NULL if an error occurs.
struct json_object *jsonGetObjectByPath(const struct json_object *obj, const char *path, char **out_last_element);
/// Logs the last JSON error, if available.
void jsonLogLastError(void);
/// Getters and setters for various data types.
/// Path elements must be separated using forward slashes.
bool jsonGetBoolean(const struct json_object *obj, const char *path);
bool jsonSetBoolean(const struct json_object *obj, const char *path, bool value);
int jsonGetInteger(const struct json_object *obj, const char *path);
bool jsonSetInteger(const struct json_object *obj, const char *path, int value);
const char *jsonGetString(const struct json_object *obj, const char *path);
bool jsonSetString(const struct json_object *obj, const char *path, const char *value);
struct json_object *jsonGetArray(const struct json_object *obj, const char *path);
bool jsonSetArray(const struct json_object *obj, const char *path, struct json_object *value);
/// Helper functions to validate specific JSON object types.
NX_INLINE bool jsonValidateBoolean(const struct json_object *obj)
{
return (obj != NULL && json_object_is_type(obj, json_type_boolean));
}
NX_INLINE bool jsonValidateInteger(const struct json_object *obj, int lower_boundary, int upper_boundary)
{
if (!obj || !json_object_is_type(obj, json_type_int) || lower_boundary > upper_boundary) return false;
int val = json_object_get_int(obj);
return (val >= lower_boundary && val <= upper_boundary);
}
NX_INLINE bool jsonValidateString(const struct json_object *obj)
{
return (obj != NULL && json_object_is_type(obj, json_type_string) && json_object_get_string_len(obj) > 0);
}
NX_INLINE bool jsonValidateArray(const struct json_object *obj)
{
return (obj != NULL && json_object_is_type(obj, json_type_array) && json_object_array_length(obj) > 0);
}
NX_INLINE bool jsonValidateObject(const struct json_object *obj)
{
return (obj != NULL && json_object_is_type(obj, json_type_object) && json_object_object_length(obj) > 0);
}
#ifdef __cplusplus
}
#endif
#endif /* __NXDT_JSON_H__ */

View file

@ -52,6 +52,17 @@ typedef enum {
UtilsCustomFirmwareType_ReiNX = 3 UtilsCustomFirmwareType_ReiNX = 3
} UtilsCustomFirmwareType; } UtilsCustomFirmwareType;
/// Used to handle parsed data from a GitHub release JSON.
/// All strings are dynamically allocated.
typedef struct {
struct json_object *obj; ///< JSON object. Must be freed using json_object_put().
const char *version; ///< Pointer to the version string, referenced by obj.
const char *commit_hash; ///< Pointer to the commit hash string, referenced by obj.
struct tm date; ///< Release date.
const char *changelog; ///< Pointer to the changelog string, referenced by obj.
const char *download_url; ///< Pointer to the download URL string, referenced by obj.
} UtilsGitHubReleaseJsonData;
/// Resource initialization. /// Resource initialization.
/// Called at program startup. /// Called at program startup.
bool utilsInitializeResources(const int program_argc, const char **program_argv); bool utilsInitializeResources(const int program_argc, const char **program_argv);
@ -140,6 +151,25 @@ void utilsCreateDirectoryTree(const char *path, bool create_last_element);
/// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned. /// Furthermore, if the full length for the generated path is >= FS_MAX_PATH, NULL will be returned.
char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension); char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension);
/// Parses the provided GitHub release JSON data buffer.
/// The data from the output buffer must be freed using utilsFreeGitHubReleaseJsonData().
bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out);
/// Frees previously allocated data from a UtilsGitHubReleaseJsonData element.
NX_INLINE void utilsFreeGitHubReleaseJsonData(UtilsGitHubReleaseJsonData *data)
{
if (!data) return;
if (data->obj) json_object_put(data->obj);
memset(data, 0, sizeof(UtilsGitHubReleaseJsonData));
}
/// Returns the current application updated state.
bool utilsGetApplicationUpdatedState(void);
/// Sets the application updated state to true, which makes utilsCloseResources() replace the application NRO.
/// Use carefully.
void utilsSetApplicationUpdatedState(void);
/// Simple wrapper to sleep the current thread for a specific number of full seconds. /// Simple wrapper to sleep the current thread for a specific number of full seconds.
NX_INLINE void utilsSleep(u64 seconds) NX_INLINE void utilsSleep(u64 seconds)
{ {

View file

@ -72,6 +72,8 @@
#define UTF8_BOM "\xEF\xBB\xBF" #define UTF8_BOM "\xEF\xBB\xBF"
#define CRLF "\r\n" #define CRLF "\r\n"
#define DEVOPTAB_SDMC_DEVICE "sdmc:"
#define HBMENU_BASE_PATH "/switch/" #define HBMENU_BASE_PATH "/switch/"
#define APP_BASE_PATH HBMENU_BASE_PATH APP_TITLE "/" #define APP_BASE_PATH HBMENU_BASE_PATH APP_TITLE "/"
@ -83,13 +85,14 @@
#define NCA_PATH APP_BASE_PATH "NCA/" #define NCA_PATH APP_BASE_PATH "NCA/"
#define NCA_FS_PATH APP_BASE_PATH "NCA FS/" #define NCA_FS_PATH APP_BASE_PATH "NCA FS/"
#define CONFIG_PATH "sdmc:" APP_BASE_PATH "config.json" #define CONFIG_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH "config.json"
#define DEFAULT_CONFIG_PATH "romfs:/default_config.json" #define DEFAULT_CONFIG_PATH "romfs:/default_config.json"
#define NRO_NAME APP_TITLE ".nro" #define NRO_NAME APP_TITLE ".nro"
#define NRO_PATH APP_BASE_PATH NRO_NAME #define NRO_PATH DEVOPTAB_SDMC_DEVICE APP_BASE_PATH NRO_NAME
#define NRO_TMP_PATH NRO_PATH ".tmp"
#define KEYS_FILE_PATH "sdmc:" HBMENU_BASE_PATH "prod.keys" /* Location used by Lockpick_RCM. */ #define KEYS_FILE_PATH DEVOPTAB_SDMC_DEVICE HBMENU_BASE_PATH "prod.keys" /* Location used by Lockpick_RCM. */
#define LOG_FILE_NAME APP_TITLE ".log" #define LOG_FILE_NAME APP_TITLE ".log"
#define LOG_BUF_SIZE 0x400000 /* 4 MiB. */ #define LOG_BUF_SIZE 0x400000 /* 4 MiB. */
@ -103,9 +106,15 @@
#define HTTP_CONNECT_TIMEOUT 10L /* 10 seconds. */ #define HTTP_CONNECT_TIMEOUT 10L /* 10 seconds. */
#define HTTP_BUFFER_SIZE 131072L /* 128 KiB. */ #define HTTP_BUFFER_SIZE 131072L /* 128 KiB. */
#define GITHUB_REPOSITORY_URL "https://github.com/DarkMatterCore/nxdumptool" #define GITHUB_URL "https://github.com"
#define GITHUB_API_URL "https://api.github.com"
#define GITHUB_REPOSITORY APP_AUTHOR "/" APP_TITLE
#define GITHUB_REPOSITORY_URL GITHUB_URL "/" GITHUB_REPOSITORY
#define GITHUB_NEW_ISSUE_URL GITHUB_REPOSITORY_URL "/issues/new/choose" #define GITHUB_NEW_ISSUE_URL GITHUB_REPOSITORY_URL "/issues/new/choose"
#define GITHUB_API_RELEASE_URL GITHUB_API_URL "/repos/" GITHUB_REPOSITORY "/releases/latest"
#define NSWDB_XML_URL "http://nswdb.com/xml.php" #define NSWDB_XML_URL "http://nswdb.com/xml.php"
#define NSWDB_XML_PATH APP_BASE_PATH "NSWreleases.xml" #define NSWDB_XML_PATH APP_BASE_PATH "NSWreleases.xml"

View file

@ -162,7 +162,7 @@ namespace nxdt::tasks
/* Fill struct. */ /* Fill struct. */
progress.size = static_cast<size_t>(dltotal); progress.size = static_cast<size_t>(dltotal);
progress.current = static_cast<size_t>(dlnow); progress.current = static_cast<size_t>(dlnow);
progress.percentage = static_cast<int>((progress.current * 100) / progress.size); progress.percentage = (progress.size ? static_cast<int>((progress.current * 100) / progress.size) : 0);
/* Push progress onto the class. */ /* Push progress onto the class. */
task->publishProgress(progress); task->publishProgress(progress);
@ -243,7 +243,7 @@ namespace nxdt::tasks
/* Calculate remaining data size and ETA if we know the download size. */ /* Calculate remaining data size and ETA if we know the download size. */
double remaining = static_cast<double>(progress.size - progress.current); double remaining = static_cast<double>(progress.size - progress.current);
double eta = (remaining / speed); double eta = (remaining / speed);
new_progress.eta = fmt::format("{:02}H{:02}M{:02}S", std::fmod(eta, 86400.0) / 3600.0, std::fmod(eta, 3600.0) / 60.0, std::fmod(eta, 60.0)); 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 { } else {
/* No download size means no ETA calculation, sadly. */ /* No download size means no ETA calculation, sadly. */
new_progress.eta = ""; new_progress.eta = "";

View file

@ -30,14 +30,13 @@
namespace nxdt::views namespace nxdt::views
{ {
/* Used as the content view for OptionsTabUpdateFileDialog. */ /* Used in OptionsTabUpdateFileDialog and OptionsTabUpdateApplicationFrame to display the update progress. */
class OptionsTabUpdateFileDialogContent: public brls::View class OptionsTabUpdateProgress: public brls::View
{ {
private: private:
brls::ProgressDisplay *progress_display = nullptr; brls::ProgressDisplay *progress_display = nullptr;
brls::Label *size_label = nullptr, *speed_eta_label = nullptr; brls::Label *size_lbl = nullptr, *speed_eta_lbl = nullptr;
std::string GetFormattedSizeString(size_t size);
std::string GetFormattedSizeString(double size); std::string GetFormattedSizeString(double size);
protected: protected:
@ -45,8 +44,8 @@ namespace nxdt::views
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override; void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
public: public:
OptionsTabUpdateFileDialogContent(void); OptionsTabUpdateProgress(void);
~OptionsTabUpdateFileDialogContent(void); ~OptionsTabUpdateProgress(void);
void SetProgress(const nxdt::tasks::DownloadTaskProgress& progress); void SetProgress(const nxdt::tasks::DownloadTaskProgress& progress);
@ -63,8 +62,35 @@ namespace nxdt::views
public: public:
OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str); OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str);
};
/* Update application frame. */
class OptionsTabUpdateApplicationFrame: public brls::StagedAppletFrame
{
private:
nxdt::tasks::DownloadDataTask json_task;
char *json_buf = NULL;
size_t json_buf_size = 0;
UtilsGitHubReleaseJsonData json_data = {0};
brls::Label *wait_lbl = nullptr; /// First stage.
brls::List *changelog_list = nullptr; /// Second stage.
OptionsTabUpdateProgress *update_progress = nullptr; /// Third stage.
nxdt::tasks::DownloadFileTask nro_task;
brls::GenericEvent::Subscription focus_event_sub;
void DisplayChangelog(void);
void DisplayUpdateProgress(void);
protected:
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
bool onCancel(void) override; bool onCancel(void) override;
public:
OptionsTabUpdateApplicationFrame(void);
~OptionsTabUpdateApplicationFrame(void);
}; };
class OptionsTab: public brls::List class OptionsTab: public brls::List

View file

@ -56,6 +56,8 @@ namespace nxdt::views
public: public:
RootView(void); RootView(void);
~RootView(void); ~RootView(void);
static std::string GetFormattedDateString(const struct tm& timeinfo);
}; };
} }

View file

@ -37,7 +37,7 @@ namespace nxdt::tasks
{ {
/* Used to hold status info data. */ /* Used to hold status info data. */
typedef struct { typedef struct {
struct tm *timeinfo; struct tm timeinfo;
u32 charge_percentage; u32 charge_percentage;
PsmChargerType charger_type; PsmChargerType charger_type;
NifmInternetConnectionType connection_type; NifmInternetConnectionType connection_type;

@ -1 +1 @@
Subproject commit cf58d0115c358faa39c71867c6e49f0aa5756e59 Subproject commit fb8891e4d75474d1a1ecbde1c930b0a308b1c3e0

@ -1 +1 @@
Subproject commit 782aa51e0aa149427664cc3a9c2e520937576fcc Subproject commit 85a802a38a0abc655284b9d77a0e452f0ee9763b

View file

@ -1,5 +1,13 @@
{ {
"__comment__": "Comments about how specific fields work use keys that follow the '__{field}_comment__' format. These don't have to be replicated in your translation files.",
"unknown": "Unknown", "unknown": "Unknown",
"applet_mode_warning": "\uE8B2 Warning: the application is running under Applet Mode! \uE8B2\nThis mode severely limits the amount of usable RAM. If you consistently reproduce any crashes, please consider running the application via title override (hold R while launching a game)." "applet_mode_warning": "\uE8B2 Warning: the application is running under Applet Mode! \uE8B2\nThis mode severely limits the amount of usable RAM. If you consistently reproduce any crashes, please consider running the application via title override (hold R while launching a game).",
"time_format": "12",
"__time_format_comment__": "Use 12 for a 12-hour clock, or 24 for a 24-hour clock",
"date": "{1:02d}/{2:02d}/{0} {3:02d}:{4:02d}:{5:02d} {6}",
"__date_comment__": "{0} = Year, {1} = Month, {2} = Day, {3} = Hour, {4} = Minute, {5} = Second, {6} = AM/PM (if time_format is set to 12)"
} }

View file

@ -22,7 +22,13 @@
"update_app": { "update_app": {
"label": "Update application", "label": "Update application",
"description": "Checks if an update is available in nxdumptool's GitHub repository. Requires Internet connectivity." "description": "Checks if an update is available in nxdumptool's GitHub repository. Requires Internet connectivity.",
"frame": {
"please_wait": "Please wait…",
"release_details": "Commit hash: {0:.7}\nRelease date: {1} UTC+0",
"changelog_header": "Changelog",
"update_action": "Update"
}
}, },
"update_dialog": { "update_dialog": {
@ -32,9 +38,11 @@
"notifications": { "notifications": {
"no_internet_connection": "Internet connection unavailable. Unable to update.", "no_internet_connection": "Internet connection unavailable. Unable to update.",
"update_failed": "Update failed! For more information, please check the logfile.", "update_failed": "Update failed! Check the logfile for more info.",
"nswdb_xml_updated": "NSWDB XML successfully updated!", "nswdb_xml_updated": "NSWDB XML successfully updated!",
"is_nso": "The application is running as an NSO. Unable to update.", "is_nso": "The application is running as an NSO. Unable to update.",
"already_updated": "The application has already been updated. Please reload." "already_updated": "The application has already been updated. Please reload.",
"github_json_failed": "Failed to download or parse GitHub release JSON!",
"app_updated": "Application successfully updated! Please reload for the changes to take effect."
} }
} }

View file

@ -1,14 +1,6 @@
{ {
"__comment__": "Comments about how specific fields work use keys that follow the '__{field}_comment__' format. These don't have to be replicated in your translation files.",
"applet_mode": "\uE8B2 Applet Mode \uE8B2", "applet_mode": "\uE8B2 Applet Mode \uE8B2",
"time_format": "12",
"__time_format_comment__": "Use 12 for a 12-hour clock, or 24 for a 24-hour clock",
"date": "{1:02d}/{2:02d}/{0} {3:02d}:{4:02d}:{5:02d} {6}",
"__date_comment__": "{0} = Year, {1} = Month, {2} = Day, {3} = Hour, {4} = Minute, {5} = Second, {6} = AM/PM (if time_format is set to 12)",
"not_connected": "Not connected", "not_connected": "Not connected",
"tabs": { "tabs": {

View file

@ -322,7 +322,7 @@ static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst
snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5); snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5);
pch = strtok_r(issuer_copy, "-", &state); pch = strtok_r(issuer_copy, "-", &state);
while(pch != NULL) while(pch)
{ {
if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch)) if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch))
{ {
@ -352,7 +352,7 @@ static u32 certGetCertificateCountInSignatureIssuer(const char *issuer)
snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5); snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5);
pch = strtok_r(issuer_copy, "-", &state); pch = strtok_r(issuer_copy, "-", &state);
while(pch != NULL) while(pch)
{ {
count++; count++;
pch = strtok_r(NULL, "-", &state); pch = strtok_r(NULL, "-", &state);

View file

@ -23,43 +23,35 @@
#include "config.h" #include "config.h"
#include "title.h" #include "title.h"
#define JSON_VALIDATE_FIELD(type, name, ...) \ #define CONFIG_VALIDATE_FIELD(type, name, ...) \
if (!strcmp(key, #name)) { \ if (!strcmp(key, #name)) { \
if (name##_found || !configValidateJson##type(val, ##__VA_ARGS__)) goto end; \ if (name##_found || !jsonValidate##type(val, ##__VA_ARGS__)) goto end; \
name##_found = true; \ name##_found = true; \
continue; \ continue; \
} }
#define JSON_VALIDATE_OBJECT(type, name) \ #define CONFIG_VALIDATE_OBJECT(type, name) \
if (!strcmp(key, #name)) { \ if (!strcmp(key, #name)) { \
if (name##_found || !configValidateJson##type##Object(val)) goto end; \ if (name##_found || !configValidateJson##type##Object(val)) goto end; \
name##_found = true; \ name##_found = true; \
continue; \ continue; \
} }
#define JSON_GETTER(functype, vartype, jsontype, ...) \ #define CONFIG_GETTER(functype, vartype, ...) \
vartype configGet##functype(const char *path) { \ vartype configGet##functype(const char *path) { \
vartype ret = (vartype)0; \ vartype ret = (vartype)0; \
SCOPED_LOCK(&g_configMutex) { \ SCOPED_LOCK(&g_configMutex) { \
if (!g_configInterfaceInit) break; \ if (!g_configInterfaceInit) break; \
struct json_object *obj = configGetJsonObjectByPath(g_configJson, path); \ ret = jsonGet##functype(g_configJson, path); \
if (!obj || !configValidateJson##functype(obj, ##__VA_ARGS__)) break; \
ret = (vartype)json_object_get_##jsontype(obj); \
} \ } \
return ret; \ return ret; \
} }
#define JSON_SETTER(functype, vartype, jsontype, ...) \ #define CONFIG_SETTER(functype, vartype, ...) \
void configSet##functype(const char *path, vartype value) { \ void configSet##functype(const char *path, vartype value) { \
SCOPED_LOCK(&g_configMutex) { \ SCOPED_LOCK(&g_configMutex) { \
if (!g_configInterfaceInit) break; \ if (!g_configInterfaceInit) break; \
struct json_object *obj = configGetJsonObjectByPath(g_configJson, path); \ if (jsonSet##functype(g_configJson, path, value)) configWriteConfigJson(); \
if (!obj || !configValidateJson##functype(obj, ##__VA_ARGS__)) break; \
if (json_object_set_##jsontype(obj, value)) { \
configWriteConfigJson(); \
} else { \
LOG_MSG("Failed to update \"%s\"!", path); \
} \
} \ } \
} }
@ -76,20 +68,12 @@ static bool configParseConfigJson(void);
static void configWriteConfigJson(void); static void configWriteConfigJson(void);
static void configFreeConfigJson(void); static void configFreeConfigJson(void);
static struct json_object *configGetJsonObjectByPath(const struct json_object *obj, const char *path);
static bool configValidateJsonRootObject(const struct json_object *obj); static bool configValidateJsonRootObject(const struct json_object *obj);
static bool configValidateJsonGameCardObject(const struct json_object *obj); static bool configValidateJsonGameCardObject(const struct json_object *obj);
static bool configValidateJsonNspObject(const struct json_object *obj); static bool configValidateJsonNspObject(const struct json_object *obj);
static bool configValidateJsonTicketObject(const struct json_object *obj); static bool configValidateJsonTicketObject(const struct json_object *obj);
static bool configValidateJsonNcaFsObject(const struct json_object *obj); static bool configValidateJsonNcaFsObject(const struct json_object *obj);
NX_INLINE bool configValidateJsonBoolean(const struct json_object *obj);
NX_INLINE bool configValidateJsonInteger(const struct json_object *obj, int lower_boundary, int upper_boundary);
NX_INLINE bool configValidateJsonObject(const struct json_object *obj);
static void configLogJsonError(void);
bool configInitialize(void) bool configInitialize(void)
{ {
bool ret = false; bool ret = false;
@ -126,11 +110,11 @@ void configExit(void)
} }
} }
JSON_GETTER(Boolean, bool, boolean); CONFIG_GETTER(Boolean, bool);
JSON_SETTER(Boolean, bool, boolean); CONFIG_SETTER(Boolean, bool);
JSON_GETTER(Integer, int, int, INT32_MIN, INT32_MAX); CONFIG_GETTER(Integer, int);
JSON_SETTER(Integer, int, int, INT32_MIN, INT32_MAX); CONFIG_SETTER(Integer, int);
static bool configParseConfigJson(void) static bool configParseConfigJson(void)
{ {
@ -140,7 +124,7 @@ static bool configParseConfigJson(void)
g_configJson = json_object_from_file(CONFIG_PATH); g_configJson = json_object_from_file(CONFIG_PATH);
if (!g_configJson) if (!g_configJson)
{ {
configLogJsonError(); jsonLogLastError();
goto end; goto end;
} }
@ -163,7 +147,7 @@ end:
configWriteConfigJson(); configWriteConfigJson();
ret = true; ret = true;
} else { } else {
configLogJsonError(); jsonLogLastError();
} }
} }
@ -173,7 +157,7 @@ end:
static void configWriteConfigJson(void) static void configWriteConfigJson(void)
{ {
if (!g_configJson) return; if (!g_configJson) return;
if (json_object_to_file_ext(CONFIG_PATH, g_configJson, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) != 0) configLogJsonError(); if (json_object_to_file_ext(CONFIG_PATH, g_configJson, JSON_C_TO_STRING_SPACED | JSON_C_TO_STRING_PRETTY) != 0) jsonLogLastError();
} }
static void configFreeConfigJson(void) static void configFreeConfigJson(void)
@ -183,70 +167,22 @@ static void configFreeConfigJson(void)
g_configJson = NULL; g_configJson = NULL;
} }
static struct json_object *configGetJsonObjectByPath(const struct json_object *obj, const char *path)
{
const struct json_object *parent_obj = obj;
struct json_object *child_obj = NULL;
char *path_dup = NULL, *pch = NULL, *state = NULL;
if (!configValidateJsonObject(obj) || !path || !*path)
{
LOG_MSG("Invalid parameters!");
return NULL;
}
/* Duplicate path to avoid problems with strtok_r(). */
if (!(path_dup = strdup(path)))
{
LOG_MSG("Unable to duplicate input path! (\"%s\").", path);
return NULL;
}
pch = strtok_r(path_dup, "/", &state);
if (!pch)
{
LOG_MSG("Failed to tokenize input path! (\"%s\").", path);
goto end;
}
while(pch)
{
if (!json_object_object_get_ex(parent_obj, pch, &child_obj))
{
LOG_MSG("Failed to retrieve JSON object by key for \"%s\"! (\"%s\").", pch, path);
break;
}
pch = strtok_r(NULL, "/", &state);
if (pch)
{
parent_obj = child_obj;
child_obj = NULL;
}
}
end:
if (path_dup) free(path_dup);
return child_obj;
}
static bool configValidateJsonRootObject(const struct json_object *obj) static bool configValidateJsonRootObject(const struct json_object *obj)
{ {
bool ret = false, overclock_found = false, naming_convention_found = false, dump_destination_found = false, gamecard_found = false; bool ret = false, overclock_found = false, naming_convention_found = false, dump_destination_found = false, gamecard_found = false;
bool nsp_found = false, ticket_found = false, nca_fs_found = false; bool nsp_found = false, ticket_found = false, nca_fs_found = false;
if (!configValidateJsonObject(obj)) goto end; if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val) json_object_object_foreach(obj, key, val)
{ {
JSON_VALIDATE_FIELD(Boolean, overclock); CONFIG_VALIDATE_FIELD(Boolean, overclock);
JSON_VALIDATE_FIELD(Integer, naming_convention, TitleNamingConvention_Full, TitleNamingConvention_Count - 1); CONFIG_VALIDATE_FIELD(Integer, naming_convention, TitleNamingConvention_Full, TitleNamingConvention_Count - 1);
JSON_VALIDATE_FIELD(Integer, dump_destination, ConfigDumpDestination_SdCard, ConfigDumpDestination_Count - 1); CONFIG_VALIDATE_FIELD(Integer, dump_destination, ConfigDumpDestination_SdCard, ConfigDumpDestination_Count - 1);
JSON_VALIDATE_OBJECT(GameCard, gamecard); CONFIG_VALIDATE_OBJECT(GameCard, gamecard);
JSON_VALIDATE_OBJECT(Nsp, nsp); CONFIG_VALIDATE_OBJECT(Nsp, nsp);
JSON_VALIDATE_OBJECT(Ticket, ticket); CONFIG_VALIDATE_OBJECT(Ticket, ticket);
JSON_VALIDATE_OBJECT(NcaFs, nca_fs); CONFIG_VALIDATE_OBJECT(NcaFs, nca_fs);
goto end; goto end;
} }
@ -260,15 +196,15 @@ static bool configValidateJsonGameCardObject(const struct json_object *obj)
{ {
bool ret = false, append_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false, checksum_lookup_method_found = false; bool ret = false, append_key_area_found = false, keep_certificate_found = false, trim_dump_found = false, calculate_checksum_found = false, checksum_lookup_method_found = false;
if (!configValidateJsonObject(obj)) goto end; if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val) json_object_object_foreach(obj, key, val)
{ {
JSON_VALIDATE_FIELD(Boolean, append_key_area); CONFIG_VALIDATE_FIELD(Boolean, append_key_area);
JSON_VALIDATE_FIELD(Boolean, keep_certificate); CONFIG_VALIDATE_FIELD(Boolean, keep_certificate);
JSON_VALIDATE_FIELD(Boolean, trim_dump); CONFIG_VALIDATE_FIELD(Boolean, trim_dump);
JSON_VALIDATE_FIELD(Boolean, calculate_checksum); CONFIG_VALIDATE_FIELD(Boolean, calculate_checksum);
JSON_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_Count - 1); CONFIG_VALIDATE_FIELD(Integer, checksum_lookup_method, ConfigChecksumLookupMethod_None, ConfigChecksumLookupMethod_Count - 1);
goto end; goto end;
} }
@ -283,20 +219,20 @@ static bool configValidateJsonNspObject(const struct json_object *obj)
bool ret = false, set_download_distribution_found = false, remove_console_data_found = false, remove_titlekey_crypto_found = false, replace_acid_key_sig_found = false; bool ret = false, set_download_distribution_found = false, remove_console_data_found = false, remove_titlekey_crypto_found = false, replace_acid_key_sig_found = false;
bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false, append_authoringtool_data_found = false, lookup_checksum_found = false; bool disable_linked_account_requirement_found = false, enable_screenshots_found = false, enable_video_capture_found = false, disable_hdcp_found = false, append_authoringtool_data_found = false, lookup_checksum_found = false;
if (!configValidateJsonObject(obj)) goto end; if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val) json_object_object_foreach(obj, key, val)
{ {
JSON_VALIDATE_FIELD(Boolean, set_download_distribution); CONFIG_VALIDATE_FIELD(Boolean, set_download_distribution);
JSON_VALIDATE_FIELD(Boolean, remove_console_data); CONFIG_VALIDATE_FIELD(Boolean, remove_console_data);
JSON_VALIDATE_FIELD(Boolean, remove_titlekey_crypto); CONFIG_VALIDATE_FIELD(Boolean, remove_titlekey_crypto);
JSON_VALIDATE_FIELD(Boolean, replace_acid_key_sig); CONFIG_VALIDATE_FIELD(Boolean, replace_acid_key_sig);
JSON_VALIDATE_FIELD(Boolean, disable_linked_account_requirement); CONFIG_VALIDATE_FIELD(Boolean, disable_linked_account_requirement);
JSON_VALIDATE_FIELD(Boolean, enable_screenshots); CONFIG_VALIDATE_FIELD(Boolean, enable_screenshots);
JSON_VALIDATE_FIELD(Boolean, enable_video_capture); CONFIG_VALIDATE_FIELD(Boolean, enable_video_capture);
JSON_VALIDATE_FIELD(Boolean, disable_hdcp); CONFIG_VALIDATE_FIELD(Boolean, disable_hdcp);
JSON_VALIDATE_FIELD(Boolean, lookup_checksum); CONFIG_VALIDATE_FIELD(Boolean, lookup_checksum);
JSON_VALIDATE_FIELD(Boolean, append_authoringtool_data); CONFIG_VALIDATE_FIELD(Boolean, append_authoringtool_data);
goto end; goto end;
} }
@ -311,11 +247,11 @@ static bool configValidateJsonTicketObject(const struct json_object *obj)
{ {
bool ret = false, remove_console_data_found = false; bool ret = false, remove_console_data_found = false;
if (!configValidateJsonObject(obj)) goto end; if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val) json_object_object_foreach(obj, key, val)
{ {
JSON_VALIDATE_FIELD(Boolean, remove_console_data); CONFIG_VALIDATE_FIELD(Boolean, remove_console_data);
goto end; goto end;
} }
@ -329,11 +265,11 @@ static bool configValidateJsonNcaFsObject(const struct json_object *obj)
{ {
bool ret = false, use_layeredfs_dir_found = false; bool ret = false, use_layeredfs_dir_found = false;
if (!configValidateJsonObject(obj)) goto end; if (!jsonValidateObject(obj)) goto end;
json_object_object_foreach(obj, key, val) json_object_object_foreach(obj, key, val)
{ {
JSON_VALIDATE_FIELD(Boolean, use_layeredfs_dir); CONFIG_VALIDATE_FIELD(Boolean, use_layeredfs_dir);
goto end; goto end;
} }
@ -342,34 +278,3 @@ static bool configValidateJsonNcaFsObject(const struct json_object *obj)
end: end:
return ret; return ret;
} }
NX_INLINE bool configValidateJsonBoolean(const struct json_object *obj)
{
return (obj != NULL && json_object_is_type(obj, json_type_boolean));
}
NX_INLINE bool configValidateJsonInteger(const struct json_object *obj, int lower_boundary, int upper_boundary)
{
if (!obj || !json_object_is_type(obj, json_type_int) || lower_boundary > upper_boundary) return false;
int val = json_object_get_int(obj);
return (val >= lower_boundary && val <= upper_boundary);
}
NX_INLINE bool configValidateJsonObject(const struct json_object *obj)
{
return (obj != NULL && json_object_is_type(obj, json_type_object) && json_object_object_length(obj) > 0);
}
static void configLogJsonError(void)
{
size_t str_len = 0;
char *str = (char*)json_util_get_last_err(); /* Drop the const. */
if (!str || !(str_len = strlen(str))) return;
/* Remove line breaks. */
if (str[str_len - 1] == '\n') str[--str_len] = '\0';
if (str[str_len - 1] == '\r') str[--str_len] = '\0';
/* Log error message. */
LOG_MSG("%s", str);
}

220
source/core/nxdt_json.c Normal file
View file

@ -0,0 +1,220 @@
/*
* nxdt_json.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 "nxdt_json.h"
#define JSON_GETTER(functype, vartype, jsontype, ...) \
vartype jsonGet##functype(const struct json_object *obj, const char *path) { \
vartype ret = (vartype)0; \
struct json_object *child = jsonGetObjectByPath(obj, path, NULL); \
if (child && jsonValidate##functype(child, ##__VA_ARGS__)) ret = (vartype)json_object_get_##jsontype(child); \
return ret; \
}
#define JSON_SETTER(functype, vartype, jsontype, ...) \
bool jsonSet##functype(const struct json_object *obj, const char *path, vartype value) { \
bool ret = false; \
struct json_object *child = jsonGetObjectByPath(obj, path, NULL); \
if (child && jsonValidate##functype(child, ##__VA_ARGS__)) { \
ret = (json_object_set_##jsontype(child, value) == 1); \
if (!ret) LOG_MSG("Failed to update \"%s\"!", path); \
} \
return ret; \
}
struct json_object *jsonParseFromString(const char *str, size_t size)
{
if (!str || !*str)
{
LOG_MSG("Invalid parameters!");
return false;
}
/* Calculate string size if it wasn't provided. */
if (!size) size = (strlen(str) + 1);
struct json_tokener *tok = NULL;
struct json_object *obj = NULL;
enum json_tokener_error jerr = json_tokener_success;
/* Allocate tokener. */
tok = json_tokener_new();
if (!tok)
{
LOG_MSG("json_tokener_new failed!");
goto end;
}
/* Parse JSON buffer. */
obj = json_tokener_parse_ex(tok, str, (int)size);
if ((jerr = json_tokener_get_error(tok)) != json_tokener_success)
{
LOG_MSG("json_tokener_parse_ex failed! Reason: \"%s\".", json_tokener_error_desc(jerr));
if (obj)
{
json_object_put(obj);
obj = NULL;
}
}
end:
if (tok) json_tokener_free(tok);
return obj;
}
struct json_object *jsonGetObjectByPath(const struct json_object *obj, const char *path, char **out_last_element)
{
const struct json_object *parent_obj = obj;
struct json_object *child_obj = NULL;
char *path_dup = NULL, *pch = NULL, *state = NULL, *prev_pch = NULL;
if (!jsonValidateObject(obj) || !path || !*path)
{
LOG_MSG("Invalid parameters!");
return NULL;
}
/* Duplicate path to avoid problems with strtok_r(). */
if (!(path_dup = strdup(path)))
{
LOG_MSG("Unable to duplicate input path! (\"%s\").", path);
return NULL;
}
/* Tokenize input path. */
pch = strtok_r(path_dup, "/", &state);
if (!pch)
{
LOG_MSG("Failed to tokenize input path! (\"%s\").", path);
goto end;
}
while(pch)
{
prev_pch = pch;
pch = strtok_r(NULL, "/", &state);
if (pch || !out_last_element)
{
/* Retrieve JSON object using the current path element. */
if (!json_object_object_get_ex(parent_obj, prev_pch, &child_obj))
{
LOG_MSG("Failed to retrieve JSON object by key for \"%s\"! (\"%s\").", prev_pch, path);
break;
}
/* Update parent and child pointers if we can still proceed. */
if (pch)
{
parent_obj = child_obj;
child_obj = NULL;
}
} else {
/* No additional path elements can be found + the user wants the string for the last path element. */
/* Let's start by setting the last parent object as the return value. */
child_obj = (struct json_object*)parent_obj; /* Drop the const. */
/* Duplicate last path element string. */
*out_last_element = strdup(prev_pch);
if (!*out_last_element)
{
LOG_MSG("Failed to duplicate last path element \"%s\"! (\"%s\").", prev_pch, path);
child_obj = NULL;
}
}
}
end:
if (path_dup) free(path_dup);
return child_obj;
}
void jsonLogLastError(void)
{
size_t str_len = 0;
char *str = (char*)json_util_get_last_err(); /* Drop the const. */
if (!str || !(str_len = strlen(str))) return;
/* Remove line breaks. */
if (str[str_len - 1] == '\n') str[--str_len] = '\0';
if (str[str_len - 1] == '\r') str[--str_len] = '\0';
/* Log error message. */
LOG_MSG("%s", str);
}
JSON_GETTER(Boolean, bool, boolean);
JSON_SETTER(Boolean, bool, boolean);
JSON_GETTER(Integer, int, int, INT32_MIN, INT32_MAX);
JSON_SETTER(Integer, int, int, INT32_MIN, INT32_MAX);
JSON_GETTER(String, const char*, string);
JSON_SETTER(String, const char*, string);
/* Special handling for JSON arrays. */
struct json_object *jsonGetArray(const struct json_object *obj, const char *path)
{
struct json_object *ret = NULL, *child = jsonGetObjectByPath(obj, path, NULL);
if (child && jsonValidateArray(child)) ret = child;
return ret;
}
bool jsonSetArray(const struct json_object *obj, const char *path, struct json_object *value)
{
if (!obj || !path || !*path || !jsonValidateArray(value))
{
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
struct json_object *parent_obj = NULL;
char *key = NULL;
/* Get parent JSON object. */
parent_obj = jsonGetObjectByPath(obj, path, &key);
if (!parent_obj)
{
LOG_MSG("Failed to retrieve parent JSON object! (\"%s\").", path);
return false;
}
/* Set new JSON array. */
if (json_object_object_add(parent_obj, key, value) != 0)
{
LOG_MSG("json_object_object_add failed! (\"%s\").", path);
goto end;
}
/* Update return value. */
ret = true;
end:
if (key) free(key);
return ret;
}

View file

@ -70,21 +70,23 @@ __attribute__((format(printf, 4, 5))) void logWriteFormattedStringToBuffer(char
char *dst_ptr = *dst, *tmp_str = NULL; char *dst_ptr = *dst, *tmp_str = NULL;
size_t dst_cur_size = *dst_size, dst_str_len = (dst_ptr ? strlen(dst_ptr) : 0); size_t dst_cur_size = *dst_size, dst_str_len = (dst_ptr ? strlen(dst_ptr) : 0);
struct tm ts = {0};
struct timespec now = {0};
if (dst_str_len >= dst_cur_size) return; if (dst_str_len >= dst_cur_size) return;
va_start(args, fmt); va_start(args, fmt);
/* Get current time with nanosecond precision. */ /* Get current time with nanosecond precision. */
struct timespec now = {0};
clock_gettime(CLOCK_REALTIME, &now); clock_gettime(CLOCK_REALTIME, &now);
/* Get local time. */ /* Get local time. */
struct tm *ts = localtime(&(now.tv_sec)); localtime_r(&(now.tv_sec), &ts);
ts->tm_year += 1900; ts.tm_year += 1900;
ts->tm_mon++; ts.tm_mon++;
/* Get formatted string length. */ /* Get formatted string length. */
str1_len = snprintf(NULL, 0, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); str1_len = snprintf(NULL, 0, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
if (str1_len <= 0) goto end; if (str1_len <= 0) goto end;
str2_len = vsnprintf(NULL, 0, fmt, args); str2_len = vsnprintf(NULL, 0, fmt, args);
@ -113,7 +115,7 @@ __attribute__((format(printf, 4, 5))) void logWriteFormattedStringToBuffer(char
} }
/* Generate formatted string. */ /* Generate formatted string. */
sprintf(dst_ptr + dst_str_len, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); sprintf(dst_ptr + dst_str_len, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
vsprintf(dst_ptr + dst_str_len + (size_t)str1_len, fmt, args); vsprintf(dst_ptr + dst_str_len + (size_t)str1_len, fmt, args);
strcat(dst_ptr, CRLF); strcat(dst_ptr, CRLF);
@ -272,17 +274,19 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
char *tmp_str = NULL; char *tmp_str = NULL;
size_t tmp_len = 0; size_t tmp_len = 0;
/* Get current time with nanosecond precision. */ struct tm ts = {0};
struct timespec now = {0}; struct timespec now = {0};
/* Get current time with nanosecond precision. */
clock_gettime(CLOCK_REALTIME, &now); clock_gettime(CLOCK_REALTIME, &now);
/* Get local time. */ /* Get local time. */
struct tm *ts = localtime(&(now.tv_sec)); localtime_r(&(now.tv_sec), &ts);
ts->tm_year += 1900; ts.tm_year += 1900;
ts->tm_mon++; ts.tm_mon++;
/* Get formatted string length. */ /* Get formatted string length. */
str1_len = snprintf(NULL, 0, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); str1_len = snprintf(NULL, 0, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
if (str1_len <= 0) return; if (str1_len <= 0) return;
str2_len = vsnprintf(NULL, 0, fmt, args); str2_len = vsnprintf(NULL, 0, fmt, args);
@ -314,7 +318,7 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
} }
/* Nice and easy string formatting using the log buffer. */ /* Nice and easy string formatting using the log buffer. */
sprintf(g_logBuffer + g_logBufferLength, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); sprintf(g_logBuffer + g_logBufferLength, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
vsprintf(g_logBuffer + g_logBufferLength + (size_t)str1_len, fmt, args); vsprintf(g_logBuffer + g_logBufferLength + (size_t)str1_len, fmt, args);
strcat(g_logBuffer, CRLF); strcat(g_logBuffer, CRLF);
g_logBufferLength += log_str_len; g_logBufferLength += log_str_len;
@ -328,7 +332,7 @@ static void _logWriteFormattedStringToLogFile(bool save, const char *func_name,
if (!tmp_str) return; if (!tmp_str) return;
/* Generate formatted string. */ /* Generate formatted string. */
sprintf(tmp_str, g_logStrFormat, ts->tm_year, ts->tm_mon, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, now.tv_nsec, func_name); sprintf(tmp_str, g_logStrFormat, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, now.tv_nsec, func_name);
vsprintf(tmp_str + (size_t)str1_len, fmt, args); vsprintf(tmp_str + (size_t)str1_len, fmt, args);
strcat(tmp_str, CRLF); strcat(tmp_str, CRLF);

View file

@ -79,6 +79,8 @@ static const char *g_outputDirs[] = {
static const size_t g_outputDirsCount = MAX_ELEMENTS(g_outputDirs); static const size_t g_outputDirsCount = MAX_ELEMENTS(g_outputDirs);
static bool g_appUpdated = false;
/* Function prototypes. */ /* Function prototypes. */
static void _utilsGetLaunchPath(int program_argc, const char **program_argv); static void _utilsGetLaunchPath(int program_argc, const char **program_argv);
@ -114,7 +116,7 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
_utilsGetLaunchPath(program_argc, program_argv); _utilsGetLaunchPath(program_argc, program_argv);
/* Retrieve pointer to the SD card FsFileSystem element. */ /* Retrieve pointer to the SD card FsFileSystem element. */
if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem("sdmc:"))) if (!(g_sdCardFileSystem = fsdevGetDeviceFileSystem(DEVOPTAB_SDMC_DEVICE)))
{ {
LOG_MSG("Failed to retrieve FsFileSystem object for the SD card!"); LOG_MSG("Failed to retrieve FsFileSystem object for the SD card!");
break; break;
@ -123,7 +125,6 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
/* Create logfile. */ /* Create logfile. */
logWriteStringToLogFile("________________________________________________________________\r\n"); logWriteStringToLogFile("________________________________________________________________\r\n");
LOG_MSG(APP_TITLE " v%u.%u.%u starting (" GIT_REV "). Built on " __DATE__ " - " __TIME__ ".", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); LOG_MSG(APP_TITLE " v%u.%u.%u starting (" GIT_REV "). Built on " __DATE__ " - " __TIME__ ".", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);
if (g_appLaunchPath) LOG_MSG("Launch path: \"%s\".", g_appLaunchPath);
/* Log Horizon OS version. */ /* Log Horizon OS version. */
u32 hos_version = hosversionGet(); u32 hos_version = hosversionGet();
@ -148,6 +149,22 @@ bool utilsInitializeResources(const int program_argc, const char **program_argv)
/* Create output directories (SD card only). */ /* Create output directories (SD card only). */
utilsCreateOutputDirectories(NULL); utilsCreateOutputDirectories(NULL);
if (g_appLaunchPath)
{
LOG_MSG("Launch path: \"%s\".", g_appLaunchPath);
/* Move NRO if the launch path isn't the right one, then return. */
/* TODO: uncomment this block whenever we are ready for a release. */
/*if (strcmp(g_appLaunchPath, NRO_PATH) != 0)
{
remove(NRO_PATH);
rename(g_appLaunchPath, NRO_PATH);
LOG_MSG("Moved NRO to \"%s\". Please reload the application.", NRO_PATH);
break;
}*/
}
/* Initialize HTTP interface. */ /* Initialize HTTP interface. */
/* CURL must be initialized before starting any other threads. */ /* CURL must be initialized before starting any other threads. */
if (!httpInitialize()) break; if (!httpInitialize()) break;
@ -280,6 +297,14 @@ void utilsCloseResources(void)
/* Close initialized services. */ /* Close initialized services. */
servicesClose(); servicesClose();
/* Replace application NRO (if needed). */
/* TODO: uncomment this block whenever we're ready for a release. */
/*if (g_appUpdated)
{
remove(NRO_PATH);
rename(NRO_TMP_PATH, NRO_PATH);
}*/
/* Close logfile. */ /* Close logfile. */
logCloseLogFile(); logCloseLogFile();
@ -622,7 +647,7 @@ void utilsCreateOutputDirectories(const char *device)
for(size_t i = 0; i < g_outputDirsCount; i++) for(size_t i = 0; i < g_outputDirsCount; i++)
{ {
sprintf(path, "%s%s", (device ? device : "sdmc:"), g_outputDirs[i]); sprintf(path, "%s%s", (device ? device : DEVOPTAB_SDMC_DEVICE), g_outputDirs[i]);
mkdir(path, 0744); mkdir(path, 0744);
} }
} }
@ -803,13 +828,103 @@ end:
return path; return path;
} }
bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out)
{
if (!json_buf || !json_buf_size || !out)
{
LOG_MSG("Invalid parameters!");
return false;
}
bool ret = false;
const char *published_at = NULL;
struct json_object *assets = NULL;
/* Free output buffer beforehand. */
utilsFreeGitHubReleaseJsonData(out);
/* Parse JSON object. */
out->obj = jsonParseFromString(json_buf, json_buf_size);
if (!out->obj)
{
LOG_MSG("Failed to parse JSON object!");
return false;
}
/* Get required JSON elements. */
out->version = jsonGetString(out->obj, "tag_name");
out->commit_hash = jsonGetString(out->obj, "target_commitish");
published_at = jsonGetString(out->obj, "published_at");
out->changelog = jsonGetString(out->obj, "body");
assets = jsonGetArray(out->obj, "assets");
if (!out->version || !out->commit_hash || !published_at || !out->changelog || !assets)
{
LOG_MSG("Failed to retrieve required elements from the provided JSON!");
goto end;
}
/* Parse release date. */
if (!strptime(published_at, "%Y-%m-%dT%H:%M:%SZ", &(out->date)))
{
LOG_MSG("Failed to parse release date \"%s\"!", published_at);
goto end;
}
/* Loop through the assets array until we find the NRO. */
size_t assets_len = json_object_array_length(assets);
for(size_t i = 0; i < assets_len; i++)
{
struct json_object *cur_asset = NULL;
const char *asset_name = NULL;
/* Get current asset object. */
cur_asset = json_object_array_get_idx(assets, i);
if (!cur_asset) continue;
/* Get current asset name. */
asset_name = jsonGetString(cur_asset, "name");
if (!asset_name || strcmp(asset_name, NRO_NAME) != 0) continue;
/* Jackpot. Get the download URL. */
out->download_url = jsonGetString(cur_asset, "browser_download_url");
break;
}
if (!out->download_url)
{
LOG_MSG("Failed to retrieve required elements from the provided JSON!");
goto end;
}
/* Update return value. */
ret = true;
end:
if (!ret) utilsFreeGitHubReleaseJsonData(out);
return ret;
}
bool utilsGetApplicationUpdatedState(void)
{
bool ret = false;
SCOPED_LOCK(&g_resourcesMutex) ret = g_appUpdated;
return ret;
}
void utilsSetApplicationUpdatedState(void)
{
SCOPED_LOCK(&g_resourcesMutex) g_appUpdated = true;
}
static void _utilsGetLaunchPath(int program_argc, const char **program_argv) static void _utilsGetLaunchPath(int program_argc, const char **program_argv)
{ {
if (program_argc <= 0 || !program_argv) return; if (program_argc <= 0 || !program_argv) return;
for(int i = 0; i < program_argc; i++) for(int i = 0; i < program_argc; i++)
{ {
if (program_argv[i] && !strncmp(program_argv[i], "sdmc:/", 6)) if (program_argv[i] && !strncmp(program_argv[i], DEVOPTAB_SDMC_DEVICE "/", 6))
{ {
g_appLaunchPath = program_argv[i]; g_appLaunchPath = program_argv[i];
break; break;

View file

@ -1749,7 +1749,7 @@ save_ctx_t *save_open_savefile(const char *path, u32 action)
/* Code to dump the requested file in its entirety. Useful to retrieve protected system savefiles without exiting HOS. */ /* Code to dump the requested file in its entirety. Useful to retrieve protected system savefiles without exiting HOS. */
/*char sd_path[FS_MAX_PATH] = {0}; /*char sd_path[FS_MAX_PATH] = {0};
sprintf(sd_path, "sdmc:/%s", strrchr(path, '/') + 1); sprintf(sd_path, DEVOPTAB_SDMC_DEVICE "/%s", strrchr(path, '/') + 1);
UINT blksize = 0x100000; UINT blksize = 0x100000;
u8 *buf = malloc(blksize); u8 *buf = malloc(blksize);

View file

@ -21,75 +21,80 @@
#include <nxdt_utils.h> #include <nxdt_utils.h>
#include <options_tab.hpp> #include <options_tab.hpp>
#include <root_view.hpp>
#include <focusable_item.hpp>
#include <title.h> #include <title.h>
#include <sstream>
using namespace brls::i18n::literals; /* For _i18n. */ namespace i18n = brls::i18n; /* For getStr(). */
using namespace i18n::literals; /* For _i18n. */
namespace nxdt::views namespace nxdt::views
{ {
OptionsTabUpdateFileDialogContent::OptionsTabUpdateFileDialogContent(void) OptionsTabUpdateProgress::OptionsTabUpdateProgress(void)
{ {
this->progress_display = new brls::ProgressDisplay(); this->progress_display = new brls::ProgressDisplay();
this->progress_display->setParent(this); this->progress_display->setParent(this);
this->size_label = new brls::Label(brls::LabelStyle::MEDIUM, "", false); this->size_lbl = new brls::Label(brls::LabelStyle::MEDIUM, "", false);
this->size_label->setVerticalAlign(NVG_ALIGN_BOTTOM); this->size_lbl->setVerticalAlign(NVG_ALIGN_BOTTOM);
this->size_label->setParent(this); this->size_lbl->setParent(this);
this->speed_eta_label = new brls::Label(brls::LabelStyle::MEDIUM, "", false); this->speed_eta_lbl = new brls::Label(brls::LabelStyle::MEDIUM, "", false);
this->speed_eta_label->setVerticalAlign(NVG_ALIGN_TOP); this->speed_eta_lbl->setVerticalAlign(NVG_ALIGN_TOP);
this->speed_eta_label->setParent(this); this->speed_eta_lbl->setParent(this);
} }
OptionsTabUpdateFileDialogContent::~OptionsTabUpdateFileDialogContent(void) OptionsTabUpdateProgress::~OptionsTabUpdateProgress(void)
{ {
delete this->progress_display; delete this->progress_display;
delete this->size_label; delete this->size_lbl;
delete this->speed_eta_label; delete this->speed_eta_lbl;
} }
void OptionsTabUpdateFileDialogContent::SetProgress(const nxdt::tasks::DownloadTaskProgress& progress) void OptionsTabUpdateProgress::SetProgress(const nxdt::tasks::DownloadTaskProgress& progress)
{ {
/* Update progress percentage. */ /* Update progress percentage. */
this->progress_display->setProgress(progress.size ? progress.percentage : 0, 100); this->progress_display->setProgress(progress.percentage, 100);
/* Update size string. */ /* Update size string. */
this->size_label->setText(fmt::format("{} / {}", this->GetFormattedSizeString(progress.current), progress.size ? this->GetFormattedSizeString(progress.size) : "?")); this->size_lbl->setText(fmt::format("{} / {}", this->GetFormattedSizeString(static_cast<double>(progress.current)), \
progress.size ? this->GetFormattedSizeString(static_cast<double>(progress.size)) : "?"));
/* Update speed / ETA string. */ /* Update speed / ETA string. */
if (progress.eta != "") if (progress.eta.length())
{ {
this->speed_eta_label->setText(fmt::format("{}/s - ETA: {}", this->GetFormattedSizeString(progress.speed), progress.eta)); this->speed_eta_lbl->setText(fmt::format("{}/s - ETA: {}", this->GetFormattedSizeString(progress.speed), progress.eta));
} else { } else {
this->speed_eta_label->setText(fmt::format("{}/s", this->GetFormattedSizeString(progress.speed))); this->speed_eta_lbl->setText(fmt::format("{}/s", this->GetFormattedSizeString(progress.speed)));
} }
this->invalidate(); this->invalidate();
} }
void OptionsTabUpdateFileDialogContent::willAppear(bool resetState) void OptionsTabUpdateProgress::willAppear(bool resetState)
{ {
this->progress_display->willAppear(resetState); this->progress_display->willAppear(resetState);
} }
void OptionsTabUpdateFileDialogContent::willDisappear(bool resetState) void OptionsTabUpdateProgress::willDisappear(bool resetState)
{ {
this->progress_display->willDisappear(resetState); this->progress_display->willDisappear(resetState);
} }
void OptionsTabUpdateFileDialogContent::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) void OptionsTabUpdateProgress::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{ {
/* Progress display. */ /* Progress display. */
this->progress_display->frame(ctx); this->progress_display->frame(ctx);
/* Size label. */ /* Size label. */
this->size_label->frame(ctx); this->size_lbl->frame(ctx);
/* Speed / ETA label. */ /* Speed / ETA label. */
this->speed_eta_label->frame(ctx); this->speed_eta_lbl->frame(ctx);
} }
void OptionsTabUpdateFileDialogContent::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) void OptionsTabUpdateProgress::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{ {
unsigned elem_width = roundf(static_cast<float>(this->width) * 0.90f); unsigned elem_width = roundf(static_cast<float>(this->width) * 0.90f);
@ -103,34 +108,27 @@ namespace nxdt::views
this->progress_display->invalidate(true); this->progress_display->invalidate(true);
/* Size label. */ /* Size label. */
this->size_label->setWidth(elem_width); this->size_lbl->setWidth(elem_width);
this->size_label->invalidate(true); this->size_lbl->invalidate(true);
this->size_label->setBoundaries( this->size_lbl->setBoundaries(
this->x + (this->width - this->size_label->getWidth()) / 2, this->x + (this->width - this->size_lbl->getWidth()) / 2,
this->progress_display->getY() - this->progress_display->getHeight() / 8, this->progress_display->getY() - this->progress_display->getHeight() / 8,
this->size_label->getWidth(), this->size_lbl->getWidth(),
this->size_label->getHeight()); this->size_lbl->getHeight());
/* Speed / ETA label. */ /* Speed / ETA label. */
this->speed_eta_label->setWidth(elem_width); this->speed_eta_lbl->setWidth(elem_width);
this->speed_eta_label->invalidate(true); this->speed_eta_lbl->invalidate(true);
this->speed_eta_label->setBoundaries( this->speed_eta_lbl->setBoundaries(
this->x + (this->width - this->speed_eta_label->getWidth()) / 2, this->x + (this->width - this->speed_eta_lbl->getWidth()) / 2,
this->progress_display->getY() + this->progress_display->getHeight() + this->progress_display->getHeight() / 8, this->progress_display->getY() + this->progress_display->getHeight() + this->progress_display->getHeight() / 8,
this->speed_eta_label->getWidth(), this->speed_eta_lbl->getWidth(),
this->speed_eta_label->getHeight()); this->speed_eta_lbl->getHeight());
} }
std::string OptionsTabUpdateFileDialogContent::GetFormattedSizeString(size_t size) std::string OptionsTabUpdateProgress::GetFormattedSizeString(double size)
{
char strbuf[0x40] = {0};
utilsGenerateFormattedSizeString(static_cast<double>(size), strbuf, sizeof(strbuf));
return std::string(strbuf);
}
std::string OptionsTabUpdateFileDialogContent::GetFormattedSizeString(double size)
{ {
char strbuf[0x40] = {0}; char strbuf[0x40] = {0};
utilsGenerateFormattedSizeString(size, strbuf, sizeof(strbuf)); utilsGenerateFormattedSizeString(size, strbuf, sizeof(strbuf));
@ -140,27 +138,31 @@ namespace nxdt::views
OptionsTabUpdateFileDialog::OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str) : brls::Dialog(), success_str(success_str) OptionsTabUpdateFileDialog::OptionsTabUpdateFileDialog(std::string path, std::string url, bool force_https, std::string success_str) : brls::Dialog(), success_str(success_str)
{ {
/* Set content view. */ /* Set content view. */
OptionsTabUpdateFileDialogContent *content = new OptionsTabUpdateFileDialogContent(); OptionsTabUpdateProgress *update_progress = new OptionsTabUpdateProgress();
this->setContentView(content); this->setContentView(update_progress);
/* Add cancel button. */ /* Add cancel button. */
this->addButton("options_tab/update_dialog/cancel"_i18n, [this](brls::View* view) { this->addButton("options_tab/update_dialog/cancel"_i18n, [this](brls::View* view) {
this->onCancel(); /* Cancel download task. */
this->download_task.cancel();
/* Close dialog. */
this->close();
}); });
/* Disable cancelling with B button. */ /* Disable cancelling with B button. */
this->setCancelable(false); this->setCancelable(false);
/* Subscribe to the download task. */ /* Subscribe to the download task. */
this->download_task.RegisterListener([this, content](const nxdt::tasks::DownloadTaskProgress& progress) { this->download_task.RegisterListener([this, update_progress](const nxdt::tasks::DownloadTaskProgress& progress) {
/* Update progress. */ /* Update progress. */
content->SetProgress(progress); update_progress->SetProgress(progress);
/* Check if the download task has finished. */ /* Check if the download task has finished. */
if (this->download_task.isFinished()) if (this->download_task.isFinished())
{ {
/* Stop spinner. */ /* Stop spinner. */
content->willDisappear(); update_progress->willDisappear();
/* Update button label. */ /* Update button label. */
this->setButtonText(0, "options_tab/update_dialog/close"_i18n); this->setButtonText(0, "options_tab/update_dialog/close"_i18n);
@ -174,17 +176,205 @@ namespace nxdt::views
this->download_task.execute(path, url, force_https); this->download_task.execute(path, url, force_https);
} }
bool OptionsTabUpdateFileDialog::onCancel(void) OptionsTabUpdateApplicationFrame::OptionsTabUpdateApplicationFrame(void) : brls::StagedAppletFrame(false)
{ {
/* Cancel download task. */ /* Set UI properties. */
this->download_task.cancel(); this->setTitle("options_tab/update_app/label"_i18n);
this->setIcon(BOREALIS_ASSET("icon/" APP_TITLE ".jpg"));
this->setFooterText("v" APP_VERSION " (" GIT_REV ")");
/* Close dialog. */ /* Add first stage. */
this->close(); this->wait_lbl = new brls::Label(brls::LabelStyle::DIALOG, "options_tab/update_app/frame/please_wait"_i18n, false);
this->wait_lbl->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addStage(this->wait_lbl);
/* Add second stage. */
this->changelog_list = new brls::List();
this->changelog_list->setSpacing(this->changelog_list->getSpacing() / 2);
this->changelog_list->setMarginBottom(20);
this->addStage(this->changelog_list);
/* Add third stage. */
this->update_progress = new OptionsTabUpdateProgress();
this->addStage(this->update_progress);
/* Register cancel action. */
this->registerAction("brls/hints/back"_i18n, brls::Key::B, [this](void){
return this->onCancel();
});
/* Subscribe to the global focus change event so we can rebuild hints as soon as this frame is pushed to the view stack. */
this->focus_event_sub = brls::Application::getGlobalFocusChangeEvent()->subscribe([this](brls::View* view) {
this->rebuildHints();
});
/* Subscribe to the JSON task. */
this->json_task.RegisterListener([this](const nxdt::tasks::DownloadTaskProgress& progress) {
/* Return immediately if the JSON task hasn't finished. */
if (!this->json_task.isFinished()) return;
/* Retrieve task result. */
nxdt::tasks::DownloadDataResult json_task_result = this->json_task.get();
this->json_buf = json_task_result.first;
this->json_buf_size = json_task_result.second;
/* Parse downloaded JSON object. */
if (utilsParseGitHubReleaseJsonData(this->json_buf, this->json_buf_size, &(this->json_data)))
{
/* Display changelog. */
this->DisplayChangelog();
} else {
/* Log downloaded data if we failed to parse it. */
LOG_DATA(this->json_buf, this->json_buf_size, "Failed to parse GitHub release JSON. Downloaded data:");
/* Display notification. */
brls::Application::notify("options_tab/notifications/github_json_failed"_i18n);
/* Pop view */
this->onCancel();
}
});
/* Start JSON task. */
this->json_task.execute(GITHUB_API_RELEASE_URL, true);
}
OptionsTabUpdateApplicationFrame::~OptionsTabUpdateApplicationFrame(void)
{
/* Free parsed JSON data. */
utilsFreeGitHubReleaseJsonData(&(this->json_data));
/* Free JSON buffer. */
if (this->json_buf) free(this->json_buf);
/* Unsubscribe focus event listener. */
brls::Application::getGlobalFocusChangeEvent()->unsubscribe(this->focus_event_sub);
}
void OptionsTabUpdateApplicationFrame::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
brls::StagedAppletFrame::layout(vg, style, stash);
if (this->getCurrentStage() == 0)
{
/* Center wait label. */
this->wait_lbl->setBoundaries(this->x + (this->width / 2), this->y, this->width, this->height);
this->wait_lbl->invalidate();
}
}
bool OptionsTabUpdateApplicationFrame::onCancel(void)
{
/* Cancel NRO task. */
this->nro_task.cancel();
/* Cancel JSON task. */
this->json_task.cancel();
/* Pop view. */
brls::Application::popView();
return true; return true;
} }
void OptionsTabUpdateApplicationFrame::DisplayChangelog(void)
{
std::string item;
std::stringstream ss(std::string(this->json_data.changelog));
/* Display version string at the top. */
FocusableLabel *version_lbl = new FocusableLabel(false, brls::LabelStyle::CRASH, std::string(this->json_data.version), true);
version_lbl->setHorizontalAlign(NVG_ALIGN_CENTER);
this->changelog_list->addView(version_lbl);
/* Display release date and commit hash. */
brls::Label *release_details_lbl = new brls::Label(brls::LabelStyle::DESCRIPTION, i18n::getStr("options_tab/update_app/frame/release_details"_i18n, \
this->json_data.commit_hash, RootView::GetFormattedDateString(this->json_data.date)), true);
release_details_lbl->setHorizontalAlign(NVG_ALIGN_CENTER);
this->changelog_list->addView(release_details_lbl);
/* Add changelog header. */
this->changelog_list->addView(new brls::Header("options_tab/update_app/frame/changelog_header"_i18n));
/* Split changelog string and fill list. */
while(std::getline(ss, item))
{
/* Don't proceed if this is an empty line. */
/* Make sure to remove any possible carriage returns. */
size_t item_len = item.length();
if (!item_len) continue;
if (item.back() == '\r')
{
if (item_len > 1)
{
item.pop_back();
} else {
continue;
}
}
/* Add line to the changelog view. */
this->changelog_list->addView(new FocusableLabel(false, brls::LabelStyle::SMALL, item, true));
}
/* Register update action. */
this->registerAction("options_tab/update_app/frame/update_action"_i18n, brls::Key::PLUS, [this](void){
/* Display update progress. */
this->DisplayUpdateProgress();
return true;
});
/* Rebuild action hints. */
this->rebuildHints();
/* Go to the next stage. */
this->nextStage();
}
void OptionsTabUpdateApplicationFrame::DisplayUpdateProgress(void)
{
/* Remove update action. */
this->registerAction("options_tab/update_app/frame/update_action"_i18n, brls::Key::PLUS, [](void){
return true;
}, true);
/* Register cancel action once more, using a different label. */
this->registerAction("options_tab/update_dialog/cancel"_i18n, brls::Key::B, [this](void){
return this->onCancel();
});
/* Rebuild action hints. */
this->rebuildHints();
/* Subscribe to the NRO task. */
this->nro_task.RegisterListener([this](const nxdt::tasks::DownloadTaskProgress& progress) {
/* Update progress. */
this->update_progress->SetProgress(progress);
/* Check if the download task has finished. */
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();
if (ret) utilsSetApplicationUpdatedState();
/* Display notification. */
brls::Application::notify(ret ? "options_tab/notifications/app_updated"_i18n : "options_tab/notifications/update_failed"_i18n);
/* Pop view */
this->onCancel();
}
});
/* Start NRO task. */
this->nro_task.execute(NRO_TMP_PATH, std::string(this->json_data.download_url), true);
/* Go to the next stage. */
this->nextStage();
}
OptionsTab::OptionsTab(nxdt::tasks::StatusInfoTask *status_info_task) : brls::List(), status_info_task(status_info_task) OptionsTab::OptionsTab(nxdt::tasks::StatusInfoTask *status_info_task) : brls::List(), status_info_task(status_info_task)
{ {
/* Set custom spacing. */ /* Set custom spacing. */
@ -271,17 +461,15 @@ namespace nxdt::views
this->DisplayNotification("options_tab/notifications/no_internet_connection"_i18n); this->DisplayNotification("options_tab/notifications/no_internet_connection"_i18n);
return; return;
} else } else
if (false) /// TODO: add a proper check here if (utilsGetApplicationUpdatedState())
{ {
/* Display a notification if the application has already been updated. */ /* Display a notification if the application has already been updated. */
this->DisplayNotification("options_tab/notifications/already_updated"_i18n); this->DisplayNotification("options_tab/notifications/already_updated"_i18n);
return; return;
} }
/*brls::StagedAppletFrame *staged_frame = new brls::StagedAppletFrame(); /* Display update frame. */
staged_frame->setTitle("options_tab/update_app/label"_i18n); brls::Application::pushView(new OptionsTabUpdateApplicationFrame(), brls::ViewAnimation::FADE, false);
brls::Application::pushView(staged_frame);*/
}); });
this->addView(update_app); this->addView(update_app);

View file

@ -119,9 +119,6 @@ namespace nxdt::views
/* Subscribe to status info event. */ /* Subscribe to status info event. */
this->status_info_task_sub = this->status_info_task->RegisterListener([this](const nxdt::tasks::StatusInfoData *status_info_data) { this->status_info_task_sub = this->status_info_task->RegisterListener([this](const nxdt::tasks::StatusInfoData *status_info_data) {
bool is_am = true;
struct tm *timeinfo = status_info_data->timeinfo;
u32 charge_percentage = status_info_data->charge_percentage; u32 charge_percentage = status_info_data->charge_percentage;
PsmChargerType charger_type = status_info_data->charger_type; PsmChargerType charger_type = status_info_data->charger_type;
@ -129,25 +126,7 @@ namespace nxdt::views
char *ip_addr = status_info_data->ip_addr; char *ip_addr = status_info_data->ip_addr;
/* Update time label. */ /* Update time label. */
timeinfo->tm_mon++; this->time_lbl->setText(this->GetFormattedDateString(status_info_data->timeinfo));
timeinfo->tm_year += 1900;
if ("root_view/time_format"_i18n.compare("12") == 0)
{
/* Adjust time for 12-hour clock. */
if (timeinfo->tm_hour > 12)
{
timeinfo->tm_hour -= 12;
is_am = false;
} else
if (!timeinfo->tm_hour)
{
timeinfo->tm_hour = 12;
}
}
this->time_lbl->setText(i18n::getStr("root_view/date"_i18n, timeinfo->tm_year, timeinfo->tm_mon, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, \
is_am ? "AM" : "PM"));
/* Update battery labels. */ /* Update battery labels. */
this->battery_icon->setText(charger_type != PsmChargerType_Unconnected ? "\uE1A3" : (charge_percentage <= 15 ? "\uE19C" : "\uE1A4")); this->battery_icon->setText(charger_type != PsmChargerType_Unconnected ? "\uE1A3" : (charge_percentage <= 15 ? "\uE19C" : "\uE1A4"));
@ -192,6 +171,32 @@ namespace nxdt::views
delete this->usb_host_speed_lbl; delete this->usb_host_speed_lbl;
} }
std::string RootView::GetFormattedDateString(const struct tm& timeinfo)
{
bool is_am = true;
struct tm ts = timeinfo;
/* Update time label. */
ts.tm_mon++;
ts.tm_year += 1900;
if ("generic/time_format"_i18n.compare("12") == 0)
{
/* Adjust time for 12-hour clock. */
if (ts.tm_hour > 12)
{
ts.tm_hour -= 12;
is_am = false;
} else
if (!ts.tm_hour)
{
ts.tm_hour = 12;
}
}
return i18n::getStr("generic/date"_i18n, ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, is_am ? "AM" : "PM");
}
void RootView::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) void RootView::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{ {
brls::AppletFrame::draw(vg, x, y, width, height, style, ctx); brls::AppletFrame::draw(vg, x, y, width, height, style, ctx);

View file

@ -55,7 +55,7 @@ namespace nxdt::tasks
/* Get current time. */ /* Get current time. */
time_t unix_time = time(NULL); time_t unix_time = time(NULL);
status_info_data->timeinfo = localtime(&unix_time); localtime_r(&unix_time, &(status_info_data->timeinfo));
/* Get battery stats. */ /* Get battery stats. */
psmGetBatteryChargePercentage(&(status_info_data->charge_percentage)); psmGetBatteryChargePercentage(&(status_info_data->charge_percentage));

View file

@ -23,10 +23,10 @@ todo:
usb: improve abi (make it rest-like?) usb: improve abi (make it rest-like?)
usb: improve cancel mechanism usb: improve cancel mechanism
others: move curl (de)initialization to http.c others: fix shrinking bar
others: use hardcoded directories, move data to hardcoded directory if the launch path isn't the right one others: add version / commit hash check before updating app
others: check todo with grep
others: dump verification via nswdb / no-intro others: dump verification via nswdb / no-intro
others: update application feature
others: fatfs browser for emmc partitions others: fatfs browser for emmc partitions
reminder: reminder: