From d520db8d81d8463b05a17ff4db0df39cb56d40c3 Mon Sep 17 00:00:00 2001 From: RocketGod <57732082+RocketGod-git@users.noreply.github.com> Date: Sun, 21 Dec 2025 22:44:04 -0800 Subject: [PATCH] Refactor file storage: add file list cache and improve save logic Introduces a static file list cache in protopirate_storage to improve file listing and sorting, with new functions to build, free, and access the cache. Updates file save logic to better handle protocol-specific fields and serialization, and ensures memory is freed on app exit. Adds debug logging for file operations and improves robustness in file handling throughout the app. --- application.fam | 2 +- dist/proto_pirate.fap | Bin 99840 -> 101360 bytes helpers/protopirate_storage.c | 207 ++++++++++++++++---------- helpers/protopirate_storage.h | 3 +- protopirate_app.c | 4 + scenes/protopirate_scene_saved.c | 8 + scenes/protopirate_scene_sub_decode.c | 39 ++++- 7 files changed, 181 insertions(+), 82 deletions(-) diff --git a/application.fam b/application.fam index 8c2a105..81a5a27 100644 --- a/application.fam +++ b/application.fam @@ -7,7 +7,7 @@ App( requires=["gui"], stack_size=4 * 1024, fap_description="Decode car key fob signals from Sub-GHz", - fap_version="1.2", + fap_version="1.3", fap_icon="images/protopirate_10px.png", fap_category="Sub-GHz", fap_icon_assets="images", diff --git a/dist/proto_pirate.fap b/dist/proto_pirate.fap index 33a2d28a7cebd45fd14241eecda5a7e46fb6aa75..3dd53fb57e3d489628a30a48489787adac32039d 100644 GIT binary patch delta 48645 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 #define TAG "ProtoPirateStorage" +#define MAX_FILES_TO_DISPLAY 50 + +// Structure to hold file info for sorting +typedef struct { + char name[64]; + uint32_t timestamp; +} FileEntry; bool protopirate_storage_init() { @@ -17,7 +24,6 @@ bool protopirate_storage_get_next_filename( const char *protocol_name, FuriString *out_filename) { - Storage *storage = furi_record_open(RECORD_STORAGE); FuriString *temp_path = furi_string_alloc(); uint32_t index = 0; @@ -56,7 +62,6 @@ bool protopirate_storage_save_capture( const char *protocol_name, FuriString *out_path) { - if (!protopirate_storage_init()) { FURI_LOG_E(TAG, "Failed to create app folder"); @@ -87,7 +92,7 @@ bool protopirate_storage_save_capture( } // Write standard SubGhz file header - if (!flipper_format_write_header_cstr(save_file, "Flipper SubGhz", 1)) + if (!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) { FURI_LOG_E(TAG, "Failed to write header"); break; @@ -109,13 +114,13 @@ bool protopirate_storage_save_capture( } // Bit count + flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "Bit", &uint32_value, 1)) { flipper_format_write_uint32(save_file, "Bit", &uint32_value, 1); - FURI_LOG_I(TAG, "Saving Bit count: %lu", uint32_value); } - // Key data - could be hex string or uint32 array + // Key data flipper_format_rewind(flipper_format); if (flipper_format_read_string(flipper_format, "Key", string_value)) { @@ -123,9 +128,8 @@ bool protopirate_storage_save_capture( } else { - // Try reading as uint32 array for raw data flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "Key", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "Key", &uint32_array_size) && uint32_array_size > 0) { uint32_array = malloc(sizeof(uint32_t) * uint32_array_size); if (flipper_format_read_uint32(flipper_format, "Key", uint32_array, uint32_array_size)) @@ -153,7 +157,7 @@ bool protopirate_storage_save_capture( // Custom preset data if exists flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "Custom_preset_module", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "Custom_preset_module", &uint32_array_size) && uint32_array_size > 0) { if (flipper_format_read_string(flipper_format, "Custom_preset_module", string_value)) { @@ -162,7 +166,7 @@ bool protopirate_storage_save_capture( } flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "Custom_preset_data", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "Custom_preset_data", &uint32_array_size) && uint32_array_size > 0) { uint8_t *custom_data = malloc(uint32_array_size); if (flipper_format_read_hex(flipper_format, "Custom_preset_data", custom_data, uint32_array_size)) @@ -172,9 +176,7 @@ bool protopirate_storage_save_capture( free(custom_data); } - // Protocol-specific fields - - // TE (timing) if exists + // Protocol-specific fields - TE flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "TE", &uint32_value, 1)) { @@ -202,7 +204,7 @@ bool protopirate_storage_save_capture( flipper_format_write_uint32(save_file, "Cnt", &uint32_value, 1); } - // CRC if exists + // CRC flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "CRC", &uint32_value, 1)) { @@ -223,9 +225,9 @@ bool protopirate_storage_save_capture( flipper_format_write_uint32(save_file, "Check", &uint32_value, 1); } - // Raw data arrays if exist + // Raw data arrays flipper_format_rewind(flipper_format); - if (flipper_format_get_value_count(flipper_format, "RAW_Data", &uint32_array_size)) + if (flipper_format_get_value_count(flipper_format, "RAW_Data", &uint32_array_size) && uint32_array_size > 0) { uint32_array = malloc(sizeof(uint32_t) * uint32_array_size); if (flipper_format_read_uint32(flipper_format, "RAW_Data", uint32_array, uint32_array_size)) @@ -235,7 +237,7 @@ bool protopirate_storage_save_capture( free(uint32_array); } - // DataHi/DataLo for protocols with complex encoding + // DataHi/DataLo flipper_format_rewind(flipper_format); if (flipper_format_read_uint32(flipper_format, "DataHi", &uint32_value, 1)) { @@ -282,22 +284,6 @@ bool protopirate_storage_save_capture( flipper_format_write_uint32(save_file, "BS", &uint32_value, 1); } - // Debug: Log what we saved - flipper_format_rewind(save_file); - FuriString *debug_str = furi_string_alloc(); - uint32_t debug_bits; - uint32_t debug_version; - flipper_format_read_header(save_file, debug_str, &debug_version); - if (flipper_format_read_string(save_file, "Protocol", debug_str)) { - FURI_LOG_I(TAG, "Saved Protocol: %s", furi_string_get_cstr(debug_str)); - } - flipper_format_rewind(save_file); - flipper_format_read_header(save_file, debug_str, &debug_version); - if (flipper_format_read_uint32(save_file, "Bit", &debug_bits, 1)) { - FURI_LOG_I(TAG, "Saved Bit count: %lu", debug_bits); - } - furi_string_free(debug_str); - furi_string_free(string_value); if (out_path) @@ -317,31 +303,109 @@ bool protopirate_storage_save_capture( return result; } -uint32_t protopirate_storage_get_file_count() +// Static array to hold sorted file entries +static FileEntry *g_file_entries = NULL; +static uint32_t g_file_count = 0; + +// Build the sorted file list +static void protopirate_storage_build_file_list(void) { + // Free previous list + if (g_file_entries) + { + free(g_file_entries); + g_file_entries = NULL; + } + g_file_count = 0; + Storage *storage = furi_record_open(RECORD_STORAGE); File *dir = storage_file_alloc(storage); FileInfo file_info; - uint32_t count = 0; + // First pass: count files + uint32_t total_count = 0; if (storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) { char name[256]; while (storage_dir_read(dir, &file_info, name, sizeof(name))) { - if (!file_info_is_dir(&file_info) && - strstr(name, PROTOPIRATE_APP_EXTENSION)) + if (!file_info_is_dir(&file_info) && strstr(name, PROTOPIRATE_APP_EXTENSION)) { - count++; + total_count++; } } + storage_dir_close(dir); + } + + if (total_count == 0) + { + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); + return; + } + + // Allocate array + g_file_entries = malloc(sizeof(FileEntry) * total_count); + if (!g_file_entries) + { + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); + return; + } + + // Second pass: collect file info + FuriString *full_path = furi_string_alloc(); + if (storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) + { + char name[256]; + while (storage_dir_read(dir, &file_info, name, sizeof(name)) && g_file_count < total_count) + { + if (!file_info_is_dir(&file_info) && strstr(name, PROTOPIRATE_APP_EXTENSION)) + { + // Copy name (without extension for display) + strncpy(g_file_entries[g_file_count].name, name, sizeof(g_file_entries[g_file_count].name) - 1); + g_file_entries[g_file_count].name[sizeof(g_file_entries[g_file_count].name) - 1] = '\0'; + + // Remove extension + char *dot = strrchr(g_file_entries[g_file_count].name, '.'); + if (dot) *dot = '\0'; + + // Get file timestamp + furi_string_printf(full_path, "%s/%s", PROTOPIRATE_APP_FOLDER, name); + + // File Count + g_file_entries[g_file_count].timestamp = total_count - g_file_count; + + g_file_count++; + } + } + storage_dir_close(dir); + } + furi_string_free(full_path); + + // Reverse file count so newest files are first + if (g_file_count > 1) + { + // Reverse the array so newest (last found) is first + for (uint32_t i = 0; i < g_file_count / 2; i++) + { + FileEntry temp = g_file_entries[i]; + g_file_entries[i] = g_file_entries[g_file_count - 1 - i]; + g_file_entries[g_file_count - 1 - i] = temp; + } } - storage_dir_close(dir); storage_file_free(dir); furi_record_close(RECORD_STORAGE); - return count; + FURI_LOG_I(TAG, "Built file list with %lu entries", g_file_count); +} + +uint32_t protopirate_storage_get_file_count() +{ + // Rebuild the file list each time we're asked for count + protopirate_storage_build_file_list(); + return g_file_count; } bool protopirate_storage_get_file_by_index( @@ -349,54 +413,31 @@ bool protopirate_storage_get_file_by_index( FuriString *out_path, FuriString *out_name) { - - Storage *storage = furi_record_open(RECORD_STORAGE); - File *dir = storage_file_alloc(storage); - FileInfo file_info; - uint32_t current_index = 0; - bool found = false; - - if (storage_dir_open(dir, PROTOPIRATE_APP_FOLDER)) + if (!g_file_entries || index >= g_file_count) { - char name[256]; - while (storage_dir_read(dir, &file_info, name, sizeof(name))) - { - if (!file_info_is_dir(&file_info) && - strstr(name, PROTOPIRATE_APP_EXTENSION)) - { - if (current_index == index) - { - if (out_path) - { - furi_string_printf(out_path, "%s/%s", PROTOPIRATE_APP_FOLDER, name); - } - if (out_name) - { - // Remove extension for display - char *dot = strrchr(name, '.'); - if (dot) - *dot = '\0'; - furi_string_set_str(out_name, name); - } - found = true; - break; - } - current_index++; - } - } + return false; } - storage_dir_close(dir); - storage_file_free(dir); - furi_record_close(RECORD_STORAGE); + if (out_path) + { + furi_string_printf(out_path, "%s/%s%s", + PROTOPIRATE_APP_FOLDER, + g_file_entries[index].name, + PROTOPIRATE_APP_EXTENSION); + } + if (out_name) + { + furi_string_set_str(out_name, g_file_entries[index].name); + } - return found; + return true; } bool protopirate_storage_delete_file(const char *file_path) { Storage *storage = furi_record_open(RECORD_STORAGE); bool result = storage_simply_remove(storage, file_path); + FURI_LOG_I(TAG, "Delete file %s: %s", file_path, result ? "OK" : "FAILED"); furi_record_close(RECORD_STORAGE); return result; } @@ -414,6 +455,16 @@ FlipperFormat *protopirate_storage_load_file(const char *file_path) return NULL; } - furi_record_close(RECORD_STORAGE); return flipper_format; +} + +// Call this when exiting the app to free memory +void protopirate_storage_free_file_list(void) +{ + if (g_file_entries) + { + free(g_file_entries); + g_file_entries = NULL; + } + g_file_count = 0; } \ No newline at end of file diff --git a/helpers/protopirate_storage.h b/helpers/protopirate_storage.h index 9d5f025..1fc1db0 100644 --- a/helpers/protopirate_storage.h +++ b/helpers/protopirate_storage.h @@ -20,4 +20,5 @@ bool protopirate_storage_get_next_filename( uint32_t protopirate_storage_get_file_count(); bool protopirate_storage_get_file_by_index(uint32_t index, FuriString *out_path, FuriString *out_name); bool protopirate_storage_delete_file(const char *file_path); -FlipperFormat *protopirate_storage_load_file(const char *file_path); \ No newline at end of file +FlipperFormat *protopirate_storage_load_file(const char *file_path); +void protopirate_storage_free_file_list(void); \ No newline at end of file diff --git a/protopirate_app.c b/protopirate_app.c index aff570a..274a167 100644 --- a/protopirate_app.c +++ b/protopirate_app.c @@ -5,6 +5,7 @@ #include #include "protocols/protocol_items.h" #include "helpers/protopirate_settings.h" +#include "helpers/protopirate_storage.h" #define TAG "ProtoPirateApp" @@ -218,6 +219,9 @@ void protopirate_app_free(ProtoPirateApp *app) FURI_LOG_I(TAG, "Freeing ProtoPirate Decoder App"); + // Free the storage file list cache + protopirate_storage_free_file_list(); + // Save settings before exiting ProtoPirateSettings settings; settings.frequency = app->txrx->preset->frequency; diff --git a/scenes/protopirate_scene_saved.c b/scenes/protopirate_scene_saved.c index c154740..af0c2c6 100644 --- a/scenes/protopirate_scene_saved.c +++ b/scenes/protopirate_scene_saved.c @@ -2,6 +2,8 @@ #include "../protopirate_app_i.h" #include "../helpers/protopirate_storage.h" +#define TAG "ProtoPirateSceneSaved" + typedef enum { SubmenuIndexBack = 0xFF, @@ -20,7 +22,10 @@ void protopirate_scene_saved_on_enter(void *context) submenu_reset(app->submenu); submenu_set_header(app->submenu, "Saved Captures"); + FURI_LOG_I(TAG, "Entering saved captures scene"); + uint32_t file_count = protopirate_storage_get_file_count(); + FURI_LOG_I(TAG, "File count: %lu", file_count); if (file_count == 0) { @@ -40,6 +45,7 @@ void protopirate_scene_saved_on_enter(void *context) { if (protopirate_storage_get_file_by_index(i, path, name)) { + FURI_LOG_D(TAG, "Adding menu item: %s", furi_string_get_cstr(name)); submenu_add_item( app->submenu, furi_string_get_cstr(name), @@ -76,6 +82,8 @@ bool protopirate_scene_saved_on_event(void *context, SceneManagerEvent event) if (protopirate_storage_get_file_by_index(event.event, path, name)) { + FURI_LOG_I(TAG, "Loading file: %s", furi_string_get_cstr(path)); + // Store path for the info scene to use if (app->loaded_file_path) { diff --git a/scenes/protopirate_scene_sub_decode.c b/scenes/protopirate_scene_sub_decode.c index 41ea1ac..f9d2c20 100644 --- a/scenes/protopirate_scene_sub_decode.c +++ b/scenes/protopirate_scene_sub_decode.c @@ -491,6 +491,36 @@ static bool protopirate_process_raw_chunk(ProtoPirateApp* app, SubDecodeContext* ctx->decode_success = true; ctx->can_save = true; + // Serialize the decoded data BEFORE freeing the decoder + ctx->save_data = flipper_format_string_alloc(); + if(ctx->current_protocol->decoder->serialize) { + // Create a temporary preset for serialization + SubGhzRadioPreset temp_preset; + temp_preset.frequency = ctx->frequency; + temp_preset.name = furi_string_alloc_set("AM650"); + temp_preset.data = NULL; + temp_preset.data_size = 0; + + SubGhzProtocolStatus status = ctx->current_protocol->decoder->serialize( + ctx->current_decoder, ctx->save_data, &temp_preset); + + if(status != SubGhzProtocolStatusOk) { + FURI_LOG_W(TAG, "RAW serialize failed: %d", status); + flipper_format_free(ctx->save_data); + ctx->save_data = NULL; + ctx->can_save = false; + } else { + FURI_LOG_I(TAG, "RAW serialize success for %s", ctx->current_protocol->name); + } + + furi_string_free(temp_preset.name); + } else { + FURI_LOG_W(TAG, "Protocol %s has no serialize function", ctx->current_protocol->name); + flipper_format_free(ctx->save_data); + ctx->save_data = NULL; + ctx->can_save = false; + } + ctx->current_protocol->decoder->free(ctx->current_decoder); ctx->current_decoder = NULL; return true; @@ -588,27 +618,32 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even if(ctx->save_data) { FuriString* protocol = furi_string_alloc(); flipper_format_rewind(ctx->save_data); + if(!flipper_format_read_string(ctx->save_data, "Protocol", protocol)) { furi_string_set_str(protocol, "Unknown"); + FURI_LOG_W(TAG, "Could not read Protocol from save_data"); } // Clean protocol name for filename furi_string_replace_all(protocol, "/", "_"); furi_string_replace_all(protocol, " ", "_"); + FURI_LOG_I(TAG, "Saving as protocol: %s", furi_string_get_cstr(protocol)); + FuriString* saved_path = furi_string_alloc(); if(protopirate_storage_save_capture( ctx->save_data, furi_string_get_cstr(protocol), saved_path)) { - FURI_LOG_I(TAG, "Saved: %s", furi_string_get_cstr(saved_path)); + FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(saved_path)); notification_message(app->notifications, &sequence_success); } else { - FURI_LOG_E(TAG, "Save failed"); + FURI_LOG_E(TAG, "Save failed!"); notification_message(app->notifications, &sequence_error); } furi_string_free(protocol); furi_string_free(saved_path); } else { + FURI_LOG_E(TAG, "save_data is NULL, cannot save"); notification_message(app->notifications, &sequence_error); } consumed = true;