From ba0c5d9e35404da3fcd68c79c195022e6d3244aa Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sat, 7 Aug 2021 05:44:36 -0400 Subject: [PATCH] utils: implement utilsIsApplicationUpdatable(). Also removed legacy code that has already been reimplemented. --- include/core/nxdt_utils.h | 17 +- legacy/util.c | 546 ------------------------------ legacy/util.h | 4 - romfs/i18n/en-US/options_tab.json | 1 + source/core/nxdt_utils.c | 64 +++- source/options_tab.cpp | 35 +- todo.txt | 1 - 7 files changed, 97 insertions(+), 571 deletions(-) diff --git a/include/core/nxdt_utils.h b/include/core/nxdt_utils.h index 5d75e77..b9cbc20 100644 --- a/include/core/nxdt_utils.h +++ b/include/core/nxdt_utils.h @@ -151,10 +151,20 @@ 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. char *utilsGeneratePath(const char *prefix, const char *filename, const char *extension); +/// Returns the current application updated state. +bool utilsGetApplicationUpdatedState(void); + +/// Sets the application updated state to true, which makes utilsCloseResources() replace the application NRO. +void utilsSetApplicationUpdatedState(void); + /// 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); +/// Parses the provided version string and compares it to the application version. Returns true if the application can be updated. +/// If both versions are equal, the provided commit hash is compared to our commit hash - if they're different, true will be returned. +bool utilsIsApplicationUpdatable(const char *version, const char *commit_hash); + /// Frees previously allocated data from a UtilsGitHubReleaseJsonData element. NX_INLINE void utilsFreeGitHubReleaseJsonData(UtilsGitHubReleaseJsonData *data) { @@ -163,13 +173,6 @@ NX_INLINE void utilsFreeGitHubReleaseJsonData(UtilsGitHubReleaseJsonData *data) 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. NX_INLINE void utilsSleep(u64 seconds) { diff --git a/legacy/util.c b/legacy/util.c index 6f84af0..2acade6 100644 --- a/legacy/util.c +++ b/legacy/util.c @@ -1840,119 +1840,6 @@ void gameCardDumpNSWDBCheck(u32 crc) if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "No match found in NSWDB.COM XML database! This could either be a bad dump or an undumped gamecard."); } -static Result networkInit() -{ - if (initNet) return 0; - - Result result = socketInitializeDefault(); - if (R_SUCCEEDED(result)) - { - curl_global_init(CURL_GLOBAL_ALL); - initNet = true; - } - - return result; -} - -static void networkExit() -{ - if (!initNet) return; - - curl_global_cleanup(); - socketExit(); - initNet = false; -} - -static size_t writeCurlFile(char *buffer, size_t size, size_t number_of_items, void *input_stream) -{ - size_t total_size = (size * number_of_items); - if (fwrite(buffer, 1, total_size, input_stream) != total_size) return 0; - return total_size; -} - -static size_t writeCurlBuffer(char *buffer, size_t size, size_t number_of_items, void *input_stream) -{ - (void) input_stream; - const size_t bsz = (size * number_of_items); - - if (result_sz == 0 || !result_buf) - { - result_sz = 0x1000; - result_buf = malloc(result_sz); - if (!result_buf) return 0; - } - - bool need_realloc = false; - - while((result_written + bsz) > result_sz) - { - result_sz <<= 1; - need_realloc = true; - } - - if (need_realloc) - { - char *new_buf = realloc(result_buf, result_sz); - if (!new_buf) return 0; - result_buf = new_buf; - } - - memcpy(result_buf + result_written, buffer, bsz); - result_written += bsz; - return bsz; -} - -static bool performCurlRequest(CURL *curl, const char *url, FILE *filePtr, bool forceHttps, bool verbose) -{ - if (!curl || !url || !strlen(url)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to perform CURL request!", __func__); - return false; - } - - curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); - curl_easy_setopt(curl, CURLOPT_URL, url); - curl_easy_setopt(curl, CURLOPT_USERAGENT, HTTP_USER_AGENT); - curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); - curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); - curl_easy_setopt(curl, CURLOPT_NOBODY, 0L); - curl_easy_setopt(curl, CURLOPT_HEADER, 0L); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); - if (forceHttps) curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); - - if (filePtr) - { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCurlFile); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, filePtr); - } else { - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCurlBuffer); - } - - CURLcode res; - long http_code = 0; - double size = 0.0; - bool success = false; - - res = curl_easy_perform(curl); - - result_sz = result_written = 0; - - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); - curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD, &size); - - if (res == CURLE_OK && http_code >= 200 && http_code <= 299 && size > 0) - { - if (verbose) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Successfully downloaded %.0lf bytes!", size); - success = true; - } else { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: CURL request failed for \"%s\" endpoint!\nHTTP status code: %ld", __func__, url, http_code); - } - - return success; -} - void noIntroDumpCheck(bool isDigital, u32 crc) { Result result; @@ -2004,436 +1891,3 @@ out: if (R_SUCCEEDED(result)) networkExit(); } - -void updateNSWDBXml() -{ - Result result; - CURL *curl = NULL; - bool success = false; - FILE *nswdbXml = NULL; - - result = networkInit(); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize socket! (%08X)", __func__, result); - goto out; - } - - char xmlPath[256] = {'\0'}; - snprintf(xmlPath, MAX_CHARACTERS(xmlPath), "%s.tmp", NSWDB_XML_PATH); - - curl = curl_easy_init(); - if (!curl) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize CURL context!", __func__); - goto out; - } - - nswdbXml = fopen(xmlPath, "wb"); - if (!nswdbXml) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, NSWDB_XML_URL); - goto out; - } - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Downloading XML database from \"%s\", please wait...", NSWDB_XML_URL); - breaks++; - - appletModeOperationWarning(); - uiRefreshDisplay(); - breaks++; - - changeHomeButtonBlockStatus(true); - - success = performCurlRequest(curl, NSWDB_XML_URL, nswdbXml, false, true); - - changeHomeButtonBlockStatus(false); - -out: - if (nswdbXml) fclose(nswdbXml); - - if (success) - { - remove(NSWDB_XML_PATH); - rename(xmlPath, NSWDB_XML_PATH); - } else { - remove(xmlPath); - } - - if (curl) curl_easy_cleanup(curl); - - if (R_SUCCEEDED(result)) networkExit(); - - breaks += 2; -} - -static int versionNumCmp(char *ver1, char *ver2) -{ - int i, curPart, res; - char *token = NULL; - - // Define a struct for comparison purposes - typedef struct { - int major; - int minor; - int build; - } version_t; - - version_t versionNum1, versionNum2; - memset(&versionNum1, 0, sizeof(version_t)); - memset(&versionNum2, 0, sizeof(version_t)); - - // Create copies of the version strings to avoid modifications by strtok() - char ver1tok[64] = {'\0'}; - snprintf(ver1tok, 63, ver1); - - char ver2tok[64] = {'\0'}; - snprintf(ver2tok, 63, ver2); - - // Parse version string 1 - i = 0; - token = strtok(ver1tok, "."); - while(token != NULL && i < 3) - { - curPart = atoi(token); - - switch(i) - { - case 0: - versionNum1.major = curPart; - break; - case 1: - versionNum1.minor = curPart; - break; - case 2: - versionNum1.build = curPart; - break; - default: - break; - } - - token = strtok(NULL, "."); - - i++; - } - - // Parse version string 2 - i = 0; - token = strtok(ver2tok, "."); - while(token != NULL && i < 3) - { - curPart = atoi(token); - - switch(i) - { - case 0: - versionNum2.major = curPart; - break; - case 1: - versionNum2.minor = curPart; - break; - case 2: - versionNum2.build = curPart; - break; - default: - break; - } - - token = strtok(NULL, "."); - - i++; - } - - // Compare version_t structs - if (versionNum1.major == versionNum2.major) - { - if (versionNum1.minor == versionNum2.minor) - { - if (versionNum1.build == versionNum2.build) - { - res = 0; - } else - if (versionNum1.build < versionNum2.build) - { - res = -1; - } else { - res = 1; - } - } else - if (versionNum1.minor < versionNum2.minor) - { - res = -1; - } else { - res = 1; - } - } else - if (versionNum1.major < versionNum2.major) - { - res = -1; - } else { - res = 1; - } - - return res; -} - -static struct json_object *retrieveJsonObjMemberByNameAndType(struct json_object *jobj, char *memberName, json_type memberType) -{ - if (!jobj || !memberName || !strlen(memberName)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve member by name and type from JSON object!", __func__); - return NULL; - } - - struct json_object *memberObj = NULL; - json_type memberObjType; - - if (!json_object_object_get_ex(jobj, memberName, &memberObj)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve member \"%s\" from JSON object!", __func__, memberName); - return NULL; - } - - memberObjType = json_object_get_type(memberObj); - if (memberObjType != memberType) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid type for member \"%s\" in JSON object! (got \"%s\", expected \"%s\")", __func__, memberName, json_type_to_name(memberObjType), json_type_to_name(memberType)); - return NULL; - } - - return memberObj; -} - -static const char *retrieveJsonObjStrMemberContentsByName(struct json_object *jobj, char *memberName) -{ - if (!jobj || !memberName || !strlen(memberName)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve string member contents by name from JSON object!", __func__); - return NULL; - } - - struct json_object *memberObj = retrieveJsonObjMemberByNameAndType(jobj, memberName, json_type_string); - if (!memberObj) return NULL; - - const char *memberObjStr = json_object_get_string(memberObj); - if (!memberObjStr || !strlen(memberObjStr)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: string member \"%s\" from JSON object is empty!", __func__, memberName); - return NULL; - } - - return memberObjStr; -} - -static struct json_object *retrieveJsonObjArrayMemberByName(struct json_object *jobj, char *memberName, size_t *outputArrayLength) -{ - if (!jobj || !memberName || !strlen(memberName) || !outputArrayLength) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve array member by name from JSON object!", __func__); - return NULL; - } - - struct json_object *memberObj = retrieveJsonObjMemberByNameAndType(jobj, memberName, json_type_array); - if (memberObj) *outputArrayLength = json_object_array_length(memberObj); - - return memberObj; -} - -static struct json_object *retrieveJsonObjArrayElementByIndex(struct json_object *jobj, size_t idx) -{ - if (!jobj || json_object_get_type(jobj) != json_type_array) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve element by index from JSON array object!", __func__); - return NULL; - } - - struct json_object *memberObjArrayElement = json_object_array_get_idx(jobj, idx); - if (!memberObjArrayElement) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve element at index %lu from JSON array object!", __func__, idx); - - return memberObjArrayElement; -} - -bool updateApplication() -{ - if (envIsNso()) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to update application. Not running as a NRO.", __func__); - breaks += 2; - return false; - } - - Result result; - CURL *curl = NULL; - FILE *nxDumpToolNro = NULL; - - char releaseTag[32] = {'\0'}; - bool success = false; - - size_t i, assetsCnt = 0; - struct json_object *jobj = NULL, *assets = NULL; - const char *releaseNameObjStr = NULL, *dlUrlObjStr = NULL; - - char nroPath[NAME_BUF_LEN] = {'\0'}; - snprintf(nroPath, MAX_CHARACTERS(nroPath), "%s.tmp", (appLaunchPath ? appLaunchPath : NRO_PATH)); - - result = networkInit(); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize socket! (%08X)", __func__, result); - goto out; - } - - curl = curl_easy_init(); - if (!curl) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize CURL context!", __func__); - goto out; - } - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Requesting latest release information from \"%s\"...", GITHUB_API_URL); - breaks++; - - uiRefreshDisplay(); - - if (!performCurlRequest(curl, GITHUB_API_URL, NULL, true, false)) goto out; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Parsing response JSON data..."); - breaks++; - - uiRefreshDisplay(); - - jobj = json_tokener_parse(result_buf); - if (!jobj) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to parse JSON response!", __func__); - goto out; - } - - releaseNameObjStr = retrieveJsonObjStrMemberContentsByName(jobj, GITHUB_API_JSON_RELEASE_NAME); - if (!releaseNameObjStr) goto out; - - snprintf(releaseTag, MAX_CHARACTERS(releaseTag), releaseNameObjStr); - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Latest release: %s.", releaseTag); - breaks++; - - uiRefreshDisplay(); - - // Remove the first character from the release name if it's v/V/r/R - if (releaseTag[0] == 'v' || releaseTag[0] == 'V' || releaseTag[0] == 'r' || releaseTag[0] == 'R') - { - u32 releaseTagLen = strlen(releaseTag); - memmove(releaseTag, releaseTag + 1, releaseTagLen - 1); - releaseTag[releaseTagLen - 1] = '\0'; - } - - // Compare versions - if (versionNumCmp(releaseTag, APP_VERSION) <= 0) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "You already have the latest version!"); - breaks += 2; - - // Ask the user if they want to perform a forced update - int cur_breaks = breaks; - - if (yesNoPrompt("Do you want to perform a forced update?")) - { - // Remove the prompt from the screen - breaks = cur_breaks; - uiFill(0, STRING_Y_POS(breaks), FB_WIDTH, FB_HEIGHT - STRING_Y_POS(breaks), BG_COLOR_RGB); - uiRefreshDisplay(); - } else { - breaks -= 2; - goto out; - } - } - - assets = retrieveJsonObjArrayMemberByName(jobj, GITHUB_API_JSON_ASSETS, &assetsCnt); - if (!assets) goto out; - - // Cycle through the assets to find the right download URL - for(i = 0; i < assetsCnt; i++) - { - struct json_object *assetElement = retrieveJsonObjArrayElementByIndex(assets, i); - if (!assetElement) break; - - const char *assetName = retrieveJsonObjStrMemberContentsByName(assetElement, GITHUB_API_JSON_ASSETS_NAME); - if (!assetName) break; - - if (!strncmp(assetName, NRO_NAME, strlen(assetName))) - { - // Found it - dlUrlObjStr = retrieveJsonObjStrMemberContentsByName(assetElement, GITHUB_API_JSON_ASSETS_DL_URL); - break; - } - } - - if (!dlUrlObjStr) - { - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to locate NRO download URL!", __func__); - goto out; - } - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Download URL: \"%s\".", dlUrlObjStr); - breaks++; - - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Please wait..."); - breaks++; - - appletModeOperationWarning(); - uiRefreshDisplay(); - breaks++; - - changeHomeButtonBlockStatus(true); - - nxDumpToolNro = fopen(nroPath, "wb"); - if (!nxDumpToolNro) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open \"%s\" in write mode!", __func__, nroPath); - goto out; - } - - curl_easy_reset(curl); - - success = performCurlRequest(curl, dlUrlObjStr, nxDumpToolNro, true, true); - if (!success) goto out; - - breaks++; - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_SUCCESS_RGB, "Please restart the application to reflect the changes."); - -out: - if (nxDumpToolNro) fclose(nxDumpToolNro); - - if (strlen(nroPath)) - { - if (success) - { - snprintf(strbuf, MAX_CHARACTERS(strbuf), nroPath); - nroPath[strlen(nroPath) - 4] = '\0'; - - remove(nroPath); - rename(strbuf, nroPath); - } else { - remove(nroPath); - } - } - - if (jobj) json_object_put(jobj); - - if (result_buf) - { - free(result_buf); - result_buf = NULL; - } - - if (curl) curl_easy_cleanup(curl); - - if (R_SUCCEEDED(result)) networkExit(); - - breaks += 2; - - changeHomeButtonBlockStatus(false); - - return success; -} diff --git a/legacy/util.h b/legacy/util.h index 66c50b7..2fe4bfa 100644 --- a/legacy/util.h +++ b/legacy/util.h @@ -290,8 +290,4 @@ void gameCardDumpNSWDBCheck(u32 crc); void noIntroDumpCheck(bool isDigital, u32 crc); -void updateNSWDBXml(); - -bool updateApplication(); - #endif diff --git a/romfs/i18n/en-US/options_tab.json b/romfs/i18n/en-US/options_tab.json index 8482fb6..39b2495 100644 --- a/romfs/i18n/en-US/options_tab.json +++ b/romfs/i18n/en-US/options_tab.json @@ -43,6 +43,7 @@ "is_nso": "The application is running as an NSO. Unable to update.", "already_updated": "The application has already been updated. Please reload.", "github_json_failed": "Failed to download or parse GitHub release JSON!", + "up_to_date": "The application is up to date!", "app_updated": "Application successfully updated! Please reload for the changes to take effect." } } diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index f55dbb8..3646f21 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -35,6 +35,14 @@ /* Reference: https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits. */ #define NT_MAX_FILENAME_LENGTH 255 +/* Type definitions. */ + +typedef struct { + u32 major; + u32 minor; + u32 micro; +} UtilsApplicationVersion; + /* Global variables. */ static bool g_resourcesInit = false; @@ -828,6 +836,18 @@ end: return path; } +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; +} + bool utilsParseGitHubReleaseJsonData(const char *json_buf, size_t json_buf_size, UtilsGitHubReleaseJsonData *out) { if (!json_buf || !json_buf_size || !out) @@ -906,18 +926,48 @@ end: return ret; } -bool utilsGetApplicationUpdatedState(void) +bool utilsIsApplicationUpdatable(const char *version, const char *commit_hash) { + if (!version || !*version || *version != 'v' || !commit_hash || !*commit_hash) + { + LOG_MSG("Invalid parameters!"); + return false; + } + bool ret = false; - SCOPED_LOCK(&g_resourcesMutex) ret = g_appUpdated; + UtilsApplicationVersion cur_version = { VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO }, new_version = {0}; + + /* Parse version string. */ + sscanf(version, "v%u.%u.%u", &(new_version.major), &(new_version.minor), &(new_version.micro)); + + /* Compare versions. */ + if (cur_version.major == new_version.major) + { + if (cur_version.minor == new_version.minor) + { + if (cur_version.micro == new_version.micro) + { + /* Versions are equal. Let's compare the commit hashes and return true if they're different. */ + ret = (strncasecmp(commit_hash, GIT_COMMIT, 7) != 0); + } else + if (cur_version.micro < new_version.micro) + { + ret = true; + } + } else + if (cur_version.minor < new_version.minor) + { + ret = true; + } + } else + if (cur_version.major < new_version.major) + { + ret = true; + } + return ret; } -void utilsSetApplicationUpdatedState(void) -{ - SCOPED_LOCK(&g_resourcesMutex) g_appUpdated = true; -} - static void _utilsGetLaunchPath(int program_argc, const char **program_argv) { if (program_argc <= 0 || !program_argv) return; diff --git a/source/options_tab.cpp b/source/options_tab.cpp index a2d19a1..571b862 100644 --- a/source/options_tab.cpp +++ b/source/options_tab.cpp @@ -213,6 +213,9 @@ namespace nxdt::views /* Return immediately if the JSON task hasn't finished. */ if (!this->json_task.isFinished()) return; + bool pop_view = false; + std::string notification = ""; + /* Retrieve task result. */ nxdt::tasks::DownloadDataResult json_task_result = this->json_task.get(); this->json_buf = json_task_result.first; @@ -221,16 +224,36 @@ namespace nxdt::views /* Parse downloaded JSON object. */ if (utilsParseGitHubReleaseJsonData(this->json_buf, this->json_buf_size, &(this->json_data))) { - /* Display changelog. */ - this->DisplayChangelog(); + /* Check if the application can be updated. */ + if (utilsIsApplicationUpdatable(this->json_data.version, this->json_data.commit_hash)) + { + /* Display changelog. */ + this->DisplayChangelog(); + } else { + /* Update flag. */ + pop_view = true; + + /* Set notification string. */ + notification = "options_tab/notifications/up_to_date"_i18n; + } } else { - /* Log downloaded data if we failed to parse it. */ + /* Log downloaded data. */ 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); + /* Update flag. */ + pop_view = true; - /* Pop view */ + /* Set notification string. */ + notification = "options_tab/notifications/github_json_failed"_i18n; + } + + /* Pop view (if needed). */ + if (pop_view) + { + /* Display notification. */ + brls::Application::notify(notification); + + /* Pop view. */ this->onCancel(); } }); diff --git a/todo.txt b/todo.txt index b75fb00..d7c09d0 100644 --- a/todo.txt +++ b/todo.txt @@ -24,7 +24,6 @@ todo: usb: improve cancel mechanism others: fix shrinking bar - others: add version / commit hash check before updating app others: check todo with grep others: dump verification via nswdb / no-intro others: fatfs browser for emmc partitions