jblanked 9 месяцев назад
Родитель
Сommit
3f6689a246
3 измененных файлов с 352 добавлено и 56 удалено
  1. 0 55
      callback/game.c
  2. 351 1
      flip_world.c
  3. 1 0
      flip_world.h

+ 0 - 55
callback/game.c

@@ -219,61 +219,6 @@ static bool game_fetch_player_stats(FlipperHTTP *fhttp)
     return flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
     return flipper_http_request(fhttp, GET, url, "{\"Content-Type\":\"application/json\"}", NULL);
 }
 }
 
 
-// static bool fetch_app_update(FlipperHTTP *fhttp)
-// {
-//     if (!fhttp)
-//     {
-//         FURI_LOG_E(TAG, "fhttp is NULL");
-//         easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
-//         return false;
-//     }
-
-//     return flipper_http_get_request_with_headers(fhttp, "https://www.jblanked.com/flipper/api/app/last-updated/flip_world/", "{\"Content-Type\":\"application/json\"}");
-// }
-
-// static bool parse_app_update(FlipperHTTP *fhttp)
-// {
-//     if (!fhttp)
-//     {
-//         FURI_LOG_E(TAG, "fhttp is NULL");
-//         easy_flipper_dialog("Error", "fhttp is NULL. Press BACK to return.");
-//         return false;
-//     }
-//     if (fhttp->last_response == NULL || strlen(fhttp->last_response) == 0)
-//     {
-//         FURI_LOG_E(TAG, "fhttp->last_response is NULL or empty");
-//         easy_flipper_dialog("Error", "fhttp->last_response is NULL or empty. Press BACK to return.");
-//         return false;
-//     }
-//     bool last_update_available = false;
-//     char last_updated_old[32];
-//     // load the previous last_updated
-//     if (!load_char("last_updated", last_updated_old, sizeof(last_updated_old)))
-//     {
-//         FURI_LOG_E(TAG, "Failed to load last_updated");
-//         // it's okay, we'll just update it
-//     }
-//     // save the new last_updated
-//     save_char("last_updated", fhttp->last_response);
-
-//     // compare the two
-//     if (strlen(last_updated_old) == 0 || !is_str(last_updated_old, fhttp->last_response))
-//     {
-//         last_update_available = true;
-//     }
-
-//     if (last_update_available)
-//     {
-//         easy_flipper_dialog("Update Available", "An update is available. Press OK to update.");
-//         return true;
-//     }
-//     else
-//     {
-//         easy_flipper_dialog("No Update Available", "No update is available. Press OK to continue.");
-//         return false;
-//     }
-// }
-
 static bool game_thread_start(void *context)
 static bool game_thread_start(void *context)
 {
 {
     FlipWorldApp *app = (FlipWorldApp *)context;
     FlipWorldApp *app = (FlipWorldApp *)context;

+ 351 - 1
flip_world.c

@@ -1,4 +1,5 @@
 #include <flip_world.h>
 #include <flip_world.h>
+#include <flip_storage/storage.h>
 char *fps_choices_str[] = {"30", "60", "120", "240"};
 char *fps_choices_str[] = {"30", "60", "120", "240"};
 uint8_t fps_index = 0;
 uint8_t fps_index = 0;
 char *yes_or_no_choices[] = {"No", "Yes"};
 char *yes_or_no_choices[] = {"No", "Yes"};
@@ -33,4 +34,353 @@ bool is_enough_heap(size_t heap_size, bool check_blocks)
         }
         }
     }
     }
     return true;
     return true;
-}
+}
+
+static DateTime *flip_world_get_rtc_time()
+{
+    DateTime *rtc_time = malloc(sizeof(DateTime));
+    if (!rtc_time)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for DateTime");
+        return NULL;
+    }
+    furi_hal_rtc_get_datetime(rtc_time);
+    return rtc_time;
+}
+
+static DateTime *flip_world_json_to_datetime(const char *str)
+{
+    DateTime *rtc_time = malloc(sizeof(DateTime));
+    if (!rtc_time)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for DateTime");
+        return NULL;
+    }
+    char *hour = get_json_value("hour", str);
+    char *minute = get_json_value("minute", str);
+    char *second = get_json_value("second", str);
+    char *day = get_json_value("day", str);
+    char *month = get_json_value("month", str);
+    char *year = get_json_value("year", str);
+    char *weekday = get_json_value("weekday", str);
+
+    if (!hour || !minute || !second || !day || !month || !year || !weekday)
+    {
+        FURI_LOG_E(TAG, "Failed to parse JSON");
+        free(rtc_time);
+        return NULL;
+    }
+
+    rtc_time->hour = atoi(hour);
+    rtc_time->minute = atoi(minute);
+    rtc_time->second = atoi(second);
+    rtc_time->day = atoi(day);
+    rtc_time->month = atoi(month);
+    rtc_time->year = atoi(year);
+    rtc_time->weekday = atoi(weekday);
+
+    free(hour);
+    free(minute);
+    free(second);
+    free(day);
+    free(month);
+    free(year);
+    free(weekday);
+
+    return rtc_time;
+}
+
+static FuriString *flip_world_datetime_to_json(DateTime *rtc_time)
+{
+    if (!rtc_time)
+    {
+        FURI_LOG_E(TAG, "rtc_time is NULL");
+        return NULL;
+    }
+    FuriString *json = furi_string_alloc();
+    if (!json)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate memory for JSON");
+        return NULL;
+    }
+    furi_string_printf(
+        json,
+        "{\"hour\":%d,\"minute\":%d,\"second\":%d,\"day\":%d,\"month\":%d,\"year\":%d,\"weekday\":%d}",
+        rtc_time->hour,
+        rtc_time->minute,
+        rtc_time->second,
+        rtc_time->day,
+        rtc_time->month,
+        rtc_time->year,
+        rtc_time->weekday);
+    return json;
+}
+
+static bool flip_world_save_rtc_time(DateTime *rtc_time)
+{
+    if (!rtc_time)
+    {
+        FURI_LOG_E(TAG, "rtc_time is NULL");
+        return false;
+    }
+    FuriString *json = flip_world_datetime_to_json(rtc_time);
+    if (!json)
+    {
+        FURI_LOG_E(TAG, "Failed to convert DateTime to JSON");
+        return false;
+    }
+    save_char("last_checked", furi_string_get_cstr(json));
+    furi_string_free(json);
+    return true;
+}
+
+//
+// Returns true if time_current is one hour (or more) later than the stored last_updated time
+//
+static bool flip_world_is_update_time(DateTime *time_current)
+{
+    if (!time_current)
+    {
+        FURI_LOG_E(TAG, "time_current is NULL");
+        return false;
+    }
+    char last_updated_old[32];
+    if (!load_char("last_updated", last_updated_old, sizeof(last_updated_old)))
+    {
+        FURI_LOG_E(TAG, "Failed to load last_updated");
+        FuriString *json = flip_world_datetime_to_json(time_current);
+        if (json)
+        {
+            save_char("last_updated", furi_string_get_cstr(json));
+            furi_string_free(json);
+        }
+        return false;
+    }
+    DateTime *last_updated_time = flip_world_json_to_datetime(last_updated_old);
+    if (!last_updated_time)
+    {
+        FURI_LOG_E(TAG, "Failed to convert JSON to DateTime");
+        return false;
+    }
+
+    bool time_diff = false;
+    // If the date is different assume more than one hour has passed.
+    if (time_current->year != last_updated_time->year ||
+        time_current->month != last_updated_time->month ||
+        time_current->day != last_updated_time->day)
+    {
+        time_diff = true;
+    }
+    else
+    {
+        // For the same day, compute seconds from midnight.
+        int seconds_current = time_current->hour * 3600 + time_current->minute * 60 + time_current->second;
+        int seconds_last = last_updated_time->hour * 3600 + last_updated_time->minute * 60 + last_updated_time->second;
+        if ((seconds_current - seconds_last) >= 3600)
+        {
+            time_diff = true;
+        }
+    }
+
+    if (time_diff)
+    {
+        FuriString *json = flip_world_datetime_to_json(time_current);
+        if (json)
+        {
+            save_char("last_updated", furi_string_get_cstr(json));
+            furi_string_free(json);
+        }
+    }
+    free(last_updated_time);
+    return time_diff;
+}
+
+// Sends a request to fetch the last updated date of the app.
+static bool flip_world_last_app_update(FlipperHTTP *fhttp, bool flipper_server)
+{
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "fhttp is NULL");
+        return false;
+    }
+    return flipper_http_request(
+        fhttp,
+        GET,
+        !flipper_server ? "https://www.jblanked.com/flipper/api/app/last-updated/flip_world/"
+                        : "//--TODO--//",
+        "{\"Content-Type\":\"application/json\"}",
+        NULL);
+}
+
+// Parses the server response and returns true if an update is available.
+static bool flip_world_parse_last_app_update(FlipperHTTP *fhttp, DateTime *time_current)
+{
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "fhttp is NULL");
+        return false;
+    }
+    if (fhttp->state == ISSUE)
+    {
+        FURI_LOG_E(TAG, "Failed to fetch last app update");
+        return false;
+    }
+    if (fhttp->last_response == NULL || strlen(fhttp->last_response) == 0)
+    {
+        FURI_LOG_E(TAG, "fhttp->last_response is NULL or empty");
+        return false;
+    }
+
+    // Save the server app version.
+    save_char("server_app_version", fhttp->last_response);
+
+    // Only check for an update if an hour or more has passed.
+    if (flip_world_is_update_time(time_current))
+    {
+        char app_version[32];
+        if (!load_char("app_version", app_version, sizeof(app_version)))
+        {
+            FURI_LOG_E(TAG, "Failed to load app version");
+            return false;
+        }
+        if (strcmp(fhttp->last_response, app_version) != 0)
+        {
+            FURI_LOG_I(TAG, "Update available");
+            return true;
+        }
+        else
+        {
+            FURI_LOG_I(TAG, "No update available");
+            return false;
+        }
+    }
+    return false; // Not yet time to update.
+}
+
+static bool flip_world_get_fap_file(FlipperHTTP *fhttp, bool flipper_server)
+{
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL.");
+        return false;
+    }
+    char url[256];
+    fhttp->save_received_data = false;
+    fhttp->is_bytes_request = true;
+#ifndef FW_ORIGIN_Momentum
+    snprintf(
+        fhttp->file_path,
+        sizeof(fhttp->file_path),
+        STORAGE_EXT_PATH_PREFIX "/apps/GPIO/flip_world.fap");
+#else
+    snprintf(
+        fhttp->file_path,
+        sizeof(fhttp->file_path),
+        STORAGE_EXT_PATH_PREFIX "/apps/GPIO/FlipperHTTP/flip_world.fap");
+#endif
+    if (flipper_server)
+    {
+        char build_id[32];
+        snprintf(build_id, sizeof(build_id), "%s", "//--TODO--//");
+        uint8_t target;
+        target = furi_hal_version_get_hw_target();
+        uint16_t api_major, api_minor;
+        furi_hal_info_get_api_version(&api_major, &api_minor);
+        snprintf(
+            url,
+            sizeof(url),
+            "https://catalog.flipperzero.one/api/v0/application/version/%s/build/compatible?target=f%d&api=%d.%d",
+            build_id,
+            target,
+            api_major,
+            api_minor);
+    }
+    else
+    {
+        snprintf(url, sizeof(url), "https://www.jblanked.com/flipper/api/app/download/flip_world");
+    }
+    return flipper_http_request(fhttp, BYTES, url, "{\"Content-Type\": \"application/octet-stream\"}", NULL);
+}
+
+// Updates the app. Uses the supplied current time for validating if update check should proceed.
+static void flip_world_update_app(FlipperHTTP *fhttp, DateTime *time_current)
+{
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "fhttp is NULL");
+        return;
+    }
+    if (!flip_world_last_app_update(fhttp, false))
+    {
+        FURI_LOG_E(TAG, "Failed to fetch last app update");
+        return;
+    }
+    while (fhttp->state == RECEIVING && furi_timer_is_running(fhttp->get_timeout_timer) > 0)
+    {
+        // Wait for the request to finish.
+        furi_delay_ms(100);
+    }
+    furi_timer_stop(fhttp->get_timeout_timer);
+    if (flip_world_parse_last_app_update(fhttp, time_current))
+    {
+        easy_flipper_dialog("App Update", "FlipWorld update is available.\nPress BACK to install.");
+        if (!flip_world_get_fap_file(fhttp, false))
+        {
+            FURI_LOG_E(TAG, "Failed to fetch fap file");
+            return;
+        }
+        while (fhttp->state == RECEIVING && furi_timer_is_running(fhttp->get_timeout_timer) > 0)
+        {
+            furi_delay_ms(100);
+        }
+        furi_timer_stop(fhttp->get_timeout_timer);
+        if (fhttp->state == ISSUE)
+        {
+            FURI_LOG_E(TAG, "Failed to fetch fap file");
+            return;
+        }
+        easy_flipper_dialog("Update Done", "FlipWorld update is done.\nPress BACK to restart.");
+    }
+    else
+    {
+        FURI_LOG_I(TAG, "No update available");
+    }
+}
+
+// Handles the app update routine. This function obtains the current RTC time,
+// checks the "last_checked" value, and if it is more than one hour old, calls for an update.
+bool flip_world_handle_app_update(FlipperHTTP *fhttp)
+{
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "fhttp is NULL");
+        return false;
+    }
+    DateTime *rtc_time = flip_world_get_rtc_time();
+    if (!rtc_time)
+    {
+        FURI_LOG_E(TAG, "Failed to get RTC time");
+        return false;
+    }
+    char last_checked[32];
+    if (!load_char("last_checked", last_checked, sizeof(last_checked)))
+    {
+        // First time – save the current time and check for an update.
+        flip_world_save_rtc_time(rtc_time);
+        flip_world_update_app(fhttp, rtc_time);
+        free(rtc_time);
+        return true;
+    }
+    else
+    {
+        // Check if the current RTC time is at least one hour past the stored time.
+        if (flip_world_is_update_time(rtc_time))
+        {
+            flip_world_update_app(fhttp, rtc_time);
+            free(rtc_time);
+            return true;
+        }
+        free(rtc_time);
+        return false; // No update necessary.
+    }
+}

+ 1 - 0
flip_world.h

@@ -107,3 +107,4 @@ float atof_(const char *nptr);
 float atof_furi(const FuriString *nptr);
 float atof_furi(const FuriString *nptr);
 bool is_str(const char *src, const char *dst);
 bool is_str(const char *src, const char *dst);
 bool is_enough_heap(size_t heap_size, bool check_blocks);
 bool is_enough_heap(size_t heap_size, bool check_blocks);
+bool flip_world_handle_app_update(FlipperHTTP *fhttp);