mirror of
https://protopirate.net/ProtoPirate/ProtoPirate.git
synced 2026-06-06 19:11:38 +00:00
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:
+1
-1
@@ -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",
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+129
-78
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user