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.
This commit is contained in:
RocketGod
2025-12-21 22:44:04 -08:00
parent aa645867e4
commit d520db8d81
7 changed files with 181 additions and 82 deletions
+1 -1
View File
@@ -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",
BIN
View File
Binary file not shown.
+129 -78
View File
@@ -4,6 +4,13 @@
#include <toolbox/dir_walk.h>
#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;
}
+2 -1
View File
@@ -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);
FlipperFormat *protopirate_storage_load_file(const char *file_path);
void protopirate_storage_free_file_list(void);
+4
View File
@@ -5,6 +5,7 @@
#include <furi_hal.h>
#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;
+8
View File
@@ -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)
{
+37 -2
View File
@@ -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;