瀏覽代碼

FlipStore - v0.4

- Added an option to delete apps
- Edit by Willy-JL
jblanked 1 年之前
父節點
當前提交
54b7323d5e

+ 2 - 0
README.md

@@ -51,3 +51,5 @@ This is a big task, and I welcome all contributors, especially developers intere
    - This issue has been addressed, but it may still occur. If it does, restart the app.
    - This issue has been addressed, but it may still occur. If it does, restart the app.
 2. The app file is corrupted.
 2. The app file is corrupted.
    - This is likely due to an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view. If this happens more than three times, the current version of FlipStore may not be able to download that app successfully.
    - This is likely due to an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view. If this happens more than three times, the current version of FlipStore may not be able to download that app successfully.
+3. The app is frozen on the "Installing" or "Receiving data" screen. 
+   - If it there LED is not on and it's been more than 5 seconds, restart your Flipper Zero with the devboard plugged in.

+ 1 - 1
alloc/flip_store_alloc.c

@@ -105,7 +105,7 @@ FlipStoreApp *flip_store_app_alloc()
     app->variable_item_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
     app->variable_item_pass = variable_item_list_add(app->variable_item_list, "Password", 0, NULL, NULL);
 
 
     // Submenu
     // Submenu
-    if (!easy_flipper_set_submenu(&app->submenu, FlipStoreViewSubmenu, "FlipStore v0.3", callback_exit_app, &app->view_dispatcher))
+    if (!easy_flipper_set_submenu(&app->submenu, FlipStoreViewSubmenu, "FlipStore v0.4", callback_exit_app, &app->view_dispatcher))
     {
     {
         return NULL;
         return NULL;
     }
     }

+ 4 - 0
assets/CHANGELOG.md

@@ -1,3 +1,7 @@
+## v0.4
+- Added an option to delete apps
+- Edit by Willy-JL
+
 ## v0.3
 ## v0.3
 - Edits by Willy-JL
 - Edits by Willy-JL
 - Improved memory allocation
 - Improved memory allocation

+ 2 - 0
assets/README.md

@@ -51,3 +51,5 @@ This is a big task, and I welcome all contributors, especially developers intere
    - This issue has been addressed, but it may still occur. If it does, restart the app.
    - This issue has been addressed, but it may still occur. If it does, restart the app.
 2. The app file is corrupted.
 2. The app file is corrupted.
    - This is likely due to an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view. If this happens more than three times, the current version of FlipStore may not be able to download that app successfully.
    - This is likely due to an error parsing the data. Restart the app and wait until the green LED light turns off after downloading the app before exiting the view. If this happens more than three times, the current version of FlipStore may not be able to download that app successfully.
+3. The app is frozen on the "Installing" or "Receiving data" screen. 
+   - If it there LED is not on and it's been more than 5 seconds, restart your Flipper Zero with the devboard plugged in.

+ 52 - 24
callback/flip_store_callback.c

@@ -1,5 +1,7 @@
 #include <callback/flip_store_callback.h>
 #include <callback/flip_store_callback.h>
 
 
+bool flip_store_app_does_exist = false;
+
 // Callback for drawing the main screen
 // Callback for drawing the main screen
 void flip_store_view_draw_callback_main(Canvas *canvas, void *model)
 void flip_store_view_draw_callback_main(Canvas *canvas, void *model)
 {
 {
@@ -60,12 +62,19 @@ void flip_store_view_draw_callback_app_list(Canvas *canvas, void *model)
     UNUSED(model);
     UNUSED(model);
     canvas_clear(canvas);
     canvas_clear(canvas);
     canvas_set_font(canvas, FontPrimary);
     canvas_set_font(canvas, FontPrimary);
-    // Adjusted to access flip_catalog as an array of structures
     canvas_draw_str(canvas, 0, 10, flip_catalog[app_selected_index].app_name);
     canvas_draw_str(canvas, 0, 10, flip_catalog[app_selected_index].app_name);
-    // canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7); (future implementation)
-    //  canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");  (future implementation)
-    canvas_draw_icon(canvas, 0, 53, &I_ButtonBACK_10x8);
-    canvas_draw_str_aligned(canvas, 12, 54, AlignLeft, AlignTop, "Back");
+    if (flip_store_app_does_exist)
+    {
+        canvas_draw_icon(canvas, 0, 53, &I_ButtonLeft_4x7);
+        canvas_draw_str_aligned(canvas, 7, 54, AlignLeft, AlignTop, "Delete");
+        canvas_draw_icon(canvas, 45, 53, &I_ButtonBACK_10x8);
+        canvas_draw_str_aligned(canvas, 57, 54, AlignLeft, AlignTop, "Back");
+    }
+    else
+    {
+        canvas_draw_icon(canvas, 0, 53, &I_ButtonBACK_10x8);
+        canvas_draw_str_aligned(canvas, 12, 54, AlignLeft, AlignTop, "Back");
+    }
     canvas_draw_icon(canvas, 90, 53, &I_ButtonRight_4x7);
     canvas_draw_icon(canvas, 90, 53, &I_ButtonRight_4x7);
     canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
     canvas_draw_str_aligned(canvas, 97, 54, AlignLeft, AlignTop, "Install");
 }
 }
@@ -80,13 +89,12 @@ bool flip_store_input_callback(InputEvent *event, void *context)
     }
     }
     if (event->type == InputTypeShort)
     if (event->type == InputTypeShort)
     {
     {
-        // Future implementation
-        // if (event->key == InputKeyLeft)
-        //{
-        // Left button clicked, delete the app with DialogEx confirmation
-        // view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppDelete);
-        //    return true;
-        //}
+        if (event->key == InputKeyLeft && flip_store_app_does_exist)
+        {
+            // Left button clicked, delete the app
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppDelete);
+            return true;
+        }
         if (event->key == InputKeyRight)
         if (event->key == InputKeyRight)
         {
         {
             // Right button clicked, download the app
             // Right button clicked, download the app
@@ -205,6 +213,7 @@ uint32_t callback_to_app_list(void *context)
     flip_store_success = false;
     flip_store_success = false;
     flip_store_saved_data = false;
     flip_store_saved_data = false;
     flip_store_saved_success = false;
     flip_store_saved_success = false;
+    flip_store_app_does_exist = false;
     return FlipStoreViewAppList;
     return FlipStoreViewAppList;
 }
 }
 
 
@@ -241,13 +250,24 @@ void dialog_callback(DialogExResult result, void *context)
     else if (result == DialogExResultRight)
     else if (result == DialogExResultRight)
     {
     {
         // delete the app then return to the app list
         // delete the app then return to the app list
-
-        // pop up a message
-        popup_set_header(app->popup, "Success", 0, 0, AlignLeft, AlignTop);
-        popup_set_text(app->popup, "App deleted successfully.", 0, 60, AlignLeft, AlignTop);
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
-        furi_delay_ms(2000); // delay for 2 seconds
-        view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
+        if (!delete_app(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]))
+        {
+            // pop up a message
+            popup_set_header(app->popup, "[ERROR]", 0, 0, AlignLeft, AlignTop);
+            popup_set_text(app->popup, "Issue deleting app.", 0, 50, AlignLeft, AlignTop);
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
+            furi_delay_ms(2000); // delay for 2 seconds
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
+        }
+        else
+        {
+            // pop up a message
+            popup_set_header(app->popup, "[SUCCESS]", 0, 0, AlignLeft, AlignTop);
+            popup_set_text(app->popup, "App deleted successfully.", 0, 50, AlignLeft, AlignTop);
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewPopup);
+            furi_delay_ms(2000); // delay for 2 seconds
+            view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
+        }
     }
     }
 }
 }
 
 
@@ -262,11 +282,6 @@ void popup_callback(void *context)
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewSubmenu);
 }
 }
 
 
-/**
- * @brief Navigation callback for exiting the application
- * @param context The context - unused
- * @return next view id (VIEW_NONE to exit the app)
- */
 uint32_t callback_exit_app(void *context)
 uint32_t callback_exit_app(void *context)
 {
 {
     // Exit the application
     // Exit the application
@@ -300,50 +315,62 @@ void callback_submenu_choices(void *context, uint32_t index)
         break;
         break;
     case FlipStoreSubmenuIndexAppList:
     case FlipStoreSubmenuIndexAppList:
         flip_store_category_index = 0;
         flip_store_category_index = 0;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
         view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppList);
         break;
         break;
     case FlipStoreSubmenuIndexAppListBluetooth:
     case FlipStoreSubmenuIndexAppListBluetooth:
         flip_store_category_index = 0;
         flip_store_category_index = 0;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListBluetooth, "Bluetooth", &app->submenu_app_list_bluetooth));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListBluetooth, "Bluetooth", &app->submenu_app_list_bluetooth));
         break;
         break;
     case FlipStoreSubmenuIndexAppListGames:
     case FlipStoreSubmenuIndexAppListGames:
         flip_store_category_index = 1;
         flip_store_category_index = 1;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListGames, "Games", &app->submenu_app_list_games));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListGames, "Games", &app->submenu_app_list_games));
         break;
         break;
     case FlipStoreSubmenuIndexAppListGPIO:
     case FlipStoreSubmenuIndexAppListGPIO:
         flip_store_category_index = 2;
         flip_store_category_index = 2;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListGPIO, "GPIO", &app->submenu_app_list_gpio));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListGPIO, "GPIO", &app->submenu_app_list_gpio));
         break;
         break;
     case FlipStoreSubmenuIndexAppListInfrared:
     case FlipStoreSubmenuIndexAppListInfrared:
         flip_store_category_index = 3;
         flip_store_category_index = 3;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListInfrared, "Infrared", &app->submenu_app_list_infrared));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListInfrared, "Infrared", &app->submenu_app_list_infrared));
         break;
         break;
     case FlipStoreSubmenuIndexAppListiButton:
     case FlipStoreSubmenuIndexAppListiButton:
         flip_store_category_index = 4;
         flip_store_category_index = 4;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListiButton, "iButton", &app->submenu_app_list_ibutton));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListiButton, "iButton", &app->submenu_app_list_ibutton));
         break;
         break;
     case FlipStoreSubmenuIndexAppListMedia:
     case FlipStoreSubmenuIndexAppListMedia:
         flip_store_category_index = 5;
         flip_store_category_index = 5;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListMedia, "Media", &app->submenu_app_list_media));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListMedia, "Media", &app->submenu_app_list_media));
         break;
         break;
     case FlipStoreSubmenuIndexAppListNFC:
     case FlipStoreSubmenuIndexAppListNFC:
         flip_store_category_index = 6;
         flip_store_category_index = 6;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListNFC, "NFC", &app->submenu_app_list_nfc));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListNFC, "NFC", &app->submenu_app_list_nfc));
         break;
         break;
     case FlipStoreSubmenuIndexAppListRFID:
     case FlipStoreSubmenuIndexAppListRFID:
         flip_store_category_index = 7;
         flip_store_category_index = 7;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListRFID, "RFID", &app->submenu_app_list_rfid));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListRFID, "RFID", &app->submenu_app_list_rfid));
         break;
         break;
     case FlipStoreSubmenuIndexAppListSubGHz:
     case FlipStoreSubmenuIndexAppListSubGHz:
         flip_store_category_index = 8;
         flip_store_category_index = 8;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListSubGHz, "Sub-GHz", &app->submenu_app_list_subghz));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListSubGHz, "Sub-GHz", &app->submenu_app_list_subghz));
         break;
         break;
     case FlipStoreSubmenuIndexAppListTools:
     case FlipStoreSubmenuIndexAppListTools:
         flip_store_category_index = 9;
         flip_store_category_index = 9;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListTools, "Tools", &app->submenu_app_list_tools));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListTools, "Tools", &app->submenu_app_list_tools));
         break;
         break;
     case FlipStoreSubmenuIndexAppListUSB:
     case FlipStoreSubmenuIndexAppListUSB:
         flip_store_category_index = 10;
         flip_store_category_index = 10;
+        flip_store_app_does_exist = false;
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListUSB, "USB", &app->submenu_app_list_usb));
         view_dispatcher_switch_to_view(app->view_dispatcher, flip_store_handle_app_list(app, FlipStoreViewAppListUSB, "USB", &app->submenu_app_list_usb));
         break;
         break;
     default:
     default:
@@ -363,6 +390,7 @@ void callback_submenu_choices(void *context, uint32_t index)
                 if (app_name != NULL && strlen(app_name) > 0)
                 if (app_name != NULL && strlen(app_name) > 0)
                 {
                 {
                     app_selected_index = app_index;
                     app_selected_index = app_index;
+                    flip_store_app_does_exist = app_exists(flip_catalog[app_selected_index].app_id, categories[flip_store_category_index]);
                     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppInfo);
                     view_dispatcher_switch_to_view(app->view_dispatcher, FlipStoreViewAppInfo);
                 }
                 }
                 else
                 else

+ 3 - 5
callback/flip_store_callback.h

@@ -9,6 +9,8 @@
 #include <apps/flip_store_apps.h>
 #include <apps/flip_store_apps.h>
 #include <flip_storage/flip_store_storage.h>
 #include <flip_storage/flip_store_storage.h>
 
 
+extern bool flip_store_app_does_exist;
+
 // Callback for drawing the main screen
 // Callback for drawing the main screen
 void flip_store_view_draw_callback_main(Canvas *canvas, void *model);
 void flip_store_view_draw_callback_main(Canvas *canvas, void *model);
 
 
@@ -29,11 +31,7 @@ void settings_item_selected(void *context, uint32_t index);
 void dialog_callback(DialogExResult result, void *context);
 void dialog_callback(DialogExResult result, void *context);
 
 
 void popup_callback(void *context);
 void popup_callback(void *context);
-/**
- * @brief Navigation callback for exiting the application
- * @param context The context - unused
- * @return next view id (VIEW_NONE to exit the app)
- */
+
 uint32_t callback_exit_app(void *context);
 uint32_t callback_exit_app(void *context);
 void callback_submenu_choices(void *context, uint32_t index);
 void callback_submenu_choices(void *context, uint32_t index);
 
 

+ 13 - 0
flip_storage/flip_store_storage.c

@@ -113,6 +113,19 @@ bool delete_app(const char *app_id, const char *app_category)
     return true;
     return true;
 }
 }
 
 
+bool app_exists(const char *app_id, const char *app_category)
+{
+    // Check if the app exists
+    char directory_path[128];
+    snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", app_category, app_id);
+
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    bool exists = storage_common_exists(storage, directory_path);
+    furi_record_close(RECORD_STORAGE);
+
+    return exists;
+}
+
 // Function to parse JSON incrementally from a file
 // Function to parse JSON incrementally from a file
 bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size)
 bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size)
 {
 {

+ 2 - 1
flip_storage/flip_store_storage.h

@@ -20,9 +20,10 @@ bool load_settings(
     char *password,
     char *password,
     size_t password_size);
     size_t password_size);
 
 
-// future implenetation because we need the app category
 bool delete_app(const char *app_id, const char *app_category);
 bool delete_app(const char *app_id, const char *app_category);
 
 
+bool app_exists(const char *app_id, const char *app_category);
+
 // Function to parse JSON incrementally from a file
 // Function to parse JSON incrementally from a file
 bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size);
 bool parse_json_incrementally(const char *file_path, const char *target_key, char *value_buffer, size_t value_buffer_size);
 
 

+ 70 - 42
flipper_http/flipper_http.c

@@ -2,6 +2,7 @@
 FlipperHTTP fhttp;
 FlipperHTTP fhttp;
 char rx_line_buffer[RX_LINE_BUFFER_SIZE];
 char rx_line_buffer[RX_LINE_BUFFER_SIZE];
 uint8_t file_buffer[FILE_BUFFER_SIZE];
 uint8_t file_buffer[FILE_BUFFER_SIZE];
+size_t file_buffer_len = 0;
 // Function to append received data to file
 // Function to append received data to file
 // make sure to initialize the file path before calling this function
 // make sure to initialize the file path before calling this function
 bool flipper_http_append_to_file(
 bool flipper_http_append_to_file(
@@ -147,14 +148,15 @@ int32_t flipper_http_worker(void *context)
 {
 {
     UNUSED(context);
     UNUSED(context);
     size_t rx_line_pos = 0;
     size_t rx_line_pos = 0;
-    static size_t file_buffer_len = 0;
 
 
     while (1)
     while (1)
     {
     {
         uint32_t events = furi_thread_flags_wait(
         uint32_t events = furi_thread_flags_wait(
             WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
             WorkerEvtStop | WorkerEvtRxDone, FuriFlagWaitAny, FuriWaitForever);
         if (events & WorkerEvtStop)
         if (events & WorkerEvtStop)
+        {
             break;
             break;
+        }
         if (events & WorkerEvtRxDone)
         if (events & WorkerEvtRxDone)
         {
         {
             // Continuously read from the stream buffer until it's empty
             // Continuously read from the stream buffer until it's empty
@@ -178,6 +180,7 @@ int32_t flipper_http_worker(void *context)
                     // Write to file if buffer is full
                     // Write to file if buffer is full
                     if (file_buffer_len >= FILE_BUFFER_SIZE)
                     if (file_buffer_len >= FILE_BUFFER_SIZE)
                     {
                     {
+                        // remove [POST/END] and/or [GET/END] from the file
                         if (!flipper_http_append_to_file(
                         if (!flipper_http_append_to_file(
                                 file_buffer, file_buffer_len, false, fhttp.file_path))
                                 file_buffer, file_buffer_len, false, fhttp.file_path))
                         {
                         {
@@ -210,45 +213,6 @@ int32_t flipper_http_worker(void *context)
         }
         }
     }
     }
 
 
-    if (fhttp.save_bytes)
-    {
-        // Write the remaining data to the file
-        if (file_buffer_len > 0)
-        {
-            if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
-            {
-                FURI_LOG_E(HTTP_TAG, "Failed to append remaining data to file");
-            }
-        }
-    }
-
-    // remove [POST/END] and/or [GET/END] from the file
-    if (fhttp.save_bytes)
-    {
-        char *end = NULL;
-        if ((end = strstr(fhttp.file_path, "[POST/END]")) != NULL)
-        {
-            *end = '\0';
-        }
-        else if ((end = strstr(fhttp.file_path, "[GET/END]")) != NULL)
-        {
-            *end = '\0';
-        }
-    }
-
-    // remove newline from the from the end of the file
-    if (fhttp.save_bytes)
-    {
-        char *end = NULL;
-        if ((end = strstr(fhttp.file_path, "\n")) != NULL)
-        {
-            *end = '\0';
-        }
-    }
-
-    // Reset the file buffer length
-    file_buffer_len = 0;
-
     return 0;
     return 0;
 }
 }
 // Timer callback function
 // Timer callback function
@@ -1128,8 +1092,39 @@ void flipper_http_rx_callback(const char *line, void *context)
             fhttp.just_started_get = false;
             fhttp.just_started_get = false;
             fhttp.state = IDLE;
             fhttp.state = IDLE;
             fhttp.save_bytes = false;
             fhttp.save_bytes = false;
-            fhttp.is_bytes_request = false;
             fhttp.save_received_data = false;
             fhttp.save_received_data = false;
+
+            if (fhttp.is_bytes_request)
+            {
+                // Search for the binary marker `[GET/END]` in the file buffer
+                const char marker[] = "[GET/END]";
+                const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
+
+                for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+                {
+                    // Check if the marker is found
+                    if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+                    {
+                        // Remove the marker by shifting the remaining data left
+                        size_t remaining_len = file_buffer_len - (i + marker_len);
+                        memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
+                        file_buffer_len -= marker_len;
+                        break;
+                    }
+                }
+
+                // If there is data left in the buffer, append it to the file
+                if (file_buffer_len > 0)
+                {
+                    if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+                    {
+                        FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
+                    }
+                    file_buffer_len = 0;
+                }
+            }
+
+            fhttp.is_bytes_request = false;
             return;
             return;
         }
         }
 
 
@@ -1167,8 +1162,39 @@ void flipper_http_rx_callback(const char *line, void *context)
             fhttp.just_started_post = false;
             fhttp.just_started_post = false;
             fhttp.state = IDLE;
             fhttp.state = IDLE;
             fhttp.save_bytes = false;
             fhttp.save_bytes = false;
-            fhttp.is_bytes_request = false;
             fhttp.save_received_data = false;
             fhttp.save_received_data = false;
+
+            if (fhttp.is_bytes_request)
+            {
+                // Search for the binary marker `[POST/END]` in the file buffer
+                const char marker[] = "[POST/END]";
+                const size_t marker_len = sizeof(marker) - 1; // Exclude null terminator
+
+                for (size_t i = 0; i <= file_buffer_len - marker_len; i++)
+                {
+                    // Check if the marker is found
+                    if (memcmp(&file_buffer[i], marker, marker_len) == 0)
+                    {
+                        // Remove the marker by shifting the remaining data left
+                        size_t remaining_len = file_buffer_len - (i + marker_len);
+                        memmove(&file_buffer[i], &file_buffer[i + marker_len], remaining_len);
+                        file_buffer_len -= marker_len;
+                        break;
+                    }
+                }
+
+                // If there is data left in the buffer, append it to the file
+                if (file_buffer_len > 0)
+                {
+                    if (!flipper_http_append_to_file(file_buffer, file_buffer_len, false, fhttp.file_path))
+                    {
+                        FURI_LOG_E(HTTP_TAG, "Failed to append data to file.");
+                    }
+                    file_buffer_len = 0;
+                }
+            }
+
+            fhttp.is_bytes_request = false;
             return;
             return;
         }
         }
 
 
@@ -1291,6 +1317,7 @@ void flipper_http_rx_callback(const char *line, void *context)
         fhttp.state = RECEIVING;
         fhttp.state = RECEIVING;
         // for GET request, save data only if it's a bytes request
         // for GET request, save data only if it's a bytes request
         fhttp.save_bytes = fhttp.is_bytes_request;
         fhttp.save_bytes = fhttp.is_bytes_request;
+        file_buffer_len = 0;
         return;
         return;
     }
     }
     else if (strstr(line, "[POST/SUCCESS]") != NULL)
     else if (strstr(line, "[POST/SUCCESS]") != NULL)
@@ -1301,6 +1328,7 @@ void flipper_http_rx_callback(const char *line, void *context)
         fhttp.state = RECEIVING;
         fhttp.state = RECEIVING;
         // for POST request, save data only if it's a bytes request
         // for POST request, save data only if it's a bytes request
         fhttp.save_bytes = fhttp.is_bytes_request;
         fhttp.save_bytes = fhttp.is_bytes_request;
+        file_buffer_len = 0;
         return;
         return;
     }
     }
     else if (strstr(line, "[PUT/SUCCESS]") != NULL)
     else if (strstr(line, "[PUT/SUCCESS]") != NULL)

+ 2 - 1
flipper_http/flipper_http.h

@@ -15,7 +15,7 @@
 #define UART_CH (FuriHalSerialIdUsart)    // UART channel
 #define UART_CH (FuriHalSerialIdUsart)    // UART channel
 #define TIMEOUT_DURATION_TICKS (5 * 1000) // 5 seconds
 #define TIMEOUT_DURATION_TICKS (5 * 1000) // 5 seconds
 #define BAUDRATE (115200)                 // UART baudrate
 #define BAUDRATE (115200)                 // UART baudrate
-#define RX_BUF_SIZE 1024                  // UART RX buffer size
+#define RX_BUF_SIZE 2048                  // UART RX buffer size
 #define RX_LINE_BUFFER_SIZE 8192          // UART RX line buffer size (increase for large responses)
 #define RX_LINE_BUFFER_SIZE 8192          // UART RX line buffer size (increase for large responses)
 #define MAX_FILE_SHOW 8192                // Maximum data from file to show
 #define MAX_FILE_SHOW 8192                // Maximum data from file to show
 #define FILE_BUFFER_SIZE 512              // File buffer size
 #define FILE_BUFFER_SIZE 512              // File buffer size
@@ -82,6 +82,7 @@ extern FlipperHTTP fhttp;
 // Global static array for the line buffer
 // Global static array for the line buffer
 extern char rx_line_buffer[RX_LINE_BUFFER_SIZE];
 extern char rx_line_buffer[RX_LINE_BUFFER_SIZE];
 extern uint8_t file_buffer[FILE_BUFFER_SIZE];
 extern uint8_t file_buffer[FILE_BUFFER_SIZE];
+extern size_t file_buffer_len;
 
 
 // fhttp.last_response holds the last received data from the UART
 // fhttp.last_response holds the last received data from the UART