From 79920a3522092f0a7ad0bb043ba096119ef8057d Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 27 May 2022 14:19:21 +0300 Subject: [PATCH] [FL-2491] File browser GUI module (#1237) * File browser module and test app * nfc: Add support for saved files in subdirectories * nfc: Use helper function to get shadow path when loading data * File browser dialog integration pt.1 * File browser dialog integration pt.2 * Gui,Dialogs: drop file select * Correct use of dynamic string_t(string_ptr) Co-authored-by: Yukai Li Co-authored-by: Aleksandr Kutuzov --- applications/applications.c | 9 + applications/applications.mk | 6 + applications/bad_usb/bad_usb_app.c | 37 +- applications/bad_usb/bad_usb_app_i.h | 3 +- .../scenes/bad_usb_scene_file_select.c | 14 +- .../bad_usb/scenes/bad_usb_scene_work.c | 9 +- applications/bad_usb/views/bad_usb_view.c | 8 +- applications/bad_usb/views/bad_usb_view.h | 2 +- .../file_browser_test/file_browser_app.c | 99 ++++ .../file_browser_test/file_browser_app_i.h | 32 ++ .../scenes/file_browser_scene.c | 30 + .../scenes/file_browser_scene.h | 29 + .../scenes/file_browser_scene_browser.c | 43 ++ .../scenes/file_browser_scene_config.h | 3 + .../scenes/file_browser_scene_result.c | 36 ++ .../scenes/file_browser_scene_start.c | 44 ++ applications/dialogs/dialogs.c | 7 +- applications/dialogs/dialogs.h | 25 +- applications/dialogs/dialogs_api.c | 29 +- applications/dialogs/dialogs_message.h | 16 +- .../dialogs/dialogs_module_file_browser.c | 59 ++ ...select.h => dialogs_module_file_browser.h} | 2 +- .../dialogs/dialogs_module_file_select.c | 59 -- applications/gui/modules/file_browser.c | 534 ++++++++++++++++++ applications/gui/modules/file_browser.h | 39 ++ .../gui/modules/file_browser_worker.c | 408 +++++++++++++ .../gui/modules/file_browser_worker.h | 55 ++ applications/gui/modules/file_select.c | 475 ---------------- applications/gui/modules/file_select.h | 31 - applications/gui/modules/validators.c | 5 +- applications/ibutton/ibutton.c | 91 ++- applications/ibutton/ibutton_i.h | 3 +- .../ibutton/scenes/ibutton_scene_add_type.c | 3 +- .../scenes/ibutton_scene_delete_confirm.c | 9 +- .../ibutton/scenes/ibutton_scene_emulate.c | 14 +- .../ibutton/scenes/ibutton_scene_info.c | 9 +- .../ibutton/scenes/ibutton_scene_read.c | 2 +- .../ibutton/scenes/ibutton_scene_save_name.c | 24 +- .../ibutton/scenes/ibutton_scene_start.c | 1 + .../ibutton/scenes/ibutton_scene_write.c | 15 +- applications/infrared/infrared_app.cpp | 17 +- applications/infrared/infrared_app.h | 2 + .../infrared/infrared_app_remote_manager.cpp | 130 +++-- .../infrared/infrared_app_remote_manager.h | 27 +- .../scene/infrared_app_scene_edit_rename.cpp | 13 +- .../scene/infrared_app_scene_remote_list.cpp | 19 +- .../scene/infrared_app_scene_start.cpp | 2 + applications/lfrfid/lfrfid_app.cpp | 64 +-- applications/lfrfid/lfrfid_app.h | 7 +- .../scene/lfrfid_app_scene_save_name.cpp | 12 +- applications/music_player/music_player.c | 20 +- applications/nfc/nfc_device.c | 116 ++-- applications/nfc/nfc_device.h | 7 +- applications/nfc/scenes/nfc_scene_delete.c | 2 +- applications/nfc/scenes/nfc_scene_save_name.c | 19 +- .../nfc/scenes/nfc_scene_save_success.c | 3 - .../nfc/scenes/nfc_scene_saved_menu.c | 2 +- applications/nfc/scenes/nfc_scene_set_type.c | 2 + .../subghz/scenes/subghz_scene_delete.c | 2 +- .../subghz/scenes/subghz_scene_delete_raw.c | 4 +- .../subghz/scenes/subghz_scene_more_raw.c | 2 +- .../subghz/scenes/subghz_scene_read_raw.c | 8 +- .../subghz/scenes/subghz_scene_save_name.c | 52 +- .../subghz/scenes/subghz_scene_set_type.c | 6 +- .../subghz/scenes/subghz_scene_transmitter.c | 2 +- applications/subghz/subghz.c | 16 +- applications/subghz/subghz_cli.c | 2 +- applications/subghz/subghz_history.c | 4 +- applications/subghz/subghz_i.c | 52 +- applications/subghz/subghz_i.h | 10 +- applications/subghz/views/receiver.c | 6 +- applications/subghz/views/subghz_read_raw.c | 16 +- applications/subghz/views/transmitter.c | 6 +- assets/compiled/assets_icons.c | 12 + assets/compiled/assets_icons.h | 3 + assets/icons/Archive/back_10px.png | Bin 0 -> 154 bytes assets/icons/Archive/loading_10px.png | Bin 0 -> 173 bytes assets/icons/Archive/music_10px.png | Bin 0 -> 142 bytes lib/one_wire/ibutton/ibutton_key.c | 9 - lib/one_wire/ibutton/ibutton_key.h | 14 - lib/toolbox/path.c | 14 + lib/toolbox/path.h | 9 + 82 files changed, 2025 insertions(+), 1007 deletions(-) create mode 100644 applications/debug_tools/file_browser_test/file_browser_app.c create mode 100644 applications/debug_tools/file_browser_test/file_browser_app_i.h create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene.c create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene.h create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c create mode 100644 applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c create mode 100644 applications/dialogs/dialogs_module_file_browser.c rename applications/dialogs/{dialogs_module_file_select.h => dialogs_module_file_browser.h} (54%) delete mode 100644 applications/dialogs/dialogs_module_file_select.c create mode 100644 applications/gui/modules/file_browser.c create mode 100644 applications/gui/modules/file_browser.h create mode 100644 applications/gui/modules/file_browser_worker.c create mode 100644 applications/gui/modules/file_browser_worker.h delete mode 100644 applications/gui/modules/file_select.c delete mode 100644 applications/gui/modules/file_select.h mode change 100755 => 100644 applications/nfc/scenes/nfc_scene_save_name.c create mode 100644 assets/icons/Archive/back_10px.png create mode 100644 assets/icons/Archive/loading_10px.png create mode 100644 assets/icons/Archive/music_10px.png diff --git a/applications/applications.c b/applications/applications.c index 4deae885..2ed1b961 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -44,6 +44,7 @@ extern int32_t vibro_test_app(void* p); extern int32_t bt_hid_app(void* p); extern int32_t battery_test_app(void* p); extern int32_t text_box_test_app(void* p); +extern int32_t file_browser_app(void* p); // Plugins extern int32_t music_player_app(void* p); @@ -459,6 +460,14 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { .flags = FlipperApplicationFlagDefault}, #endif +#ifdef APP_FILE_BROWSER_TEST + {.app = file_browser_app, + .name = "File Browser test", + .stack_size = 2048, + .icon = &A_BadUsb_14, + .flags = FlipperApplicationFlagDefault}, +#endif + #ifdef APP_BATTERY_TEST {.app = battery_test_app, .name = "Battery Test", diff --git a/applications/applications.mk b/applications/applications.mk index efe6aa42..e7a9ebfc 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -62,6 +62,7 @@ APP_USB_MOUSE = 1 APP_BAD_USB = 1 APP_U2F = 1 APP_UART_ECHO = 1 +APP_FILE_BROWSER_TEST = 1 endif @@ -207,6 +208,11 @@ CFLAGS += -DAPP_KEYPAD_TEST SRV_GUI = 1 endif +APP_FILE_BROWSER_TEST ?= 0 +ifeq ($(APP_FILE_BROWSER_TEST), 1) +CFLAGS += -DAPP_FILE_BROWSER_TEST +SRV_GUI = 1 +endif APP_ACCESSOR ?= 0 ifeq ($(APP_ACCESSOR), 1) diff --git a/applications/bad_usb/bad_usb_app.c b/applications/bad_usb/bad_usb_app.c index eb647e00..65ccc575 100644 --- a/applications/bad_usb/bad_usb_app.c +++ b/applications/bad_usb/bad_usb_app.c @@ -1,4 +1,5 @@ #include "bad_usb_app_i.h" +#include "m-string.h" #include #include #include @@ -22,33 +23,13 @@ static void bad_usb_app_tick_event_callback(void* context) { scene_manager_handle_tick_event(app->scene_manager); } -static bool bad_usb_check_assets() { - Storage* fs_api = furi_record_open("storage"); - - File* dir = storage_file_alloc(fs_api); - bool ret = false; - - if(storage_dir_open(dir, BAD_USB_APP_PATH_FOLDER)) { - ret = true; - } - - storage_dir_close(dir); - storage_file_free(dir); - - furi_record_close("storage"); - - return ret; -} - BadUsbApp* bad_usb_app_alloc(char* arg) { BadUsbApp* app = malloc(sizeof(BadUsbApp)); + string_init(app->file_path); + if(arg != NULL) { - string_t filename; - string_init(filename); - path_extract_filename_no_ext(arg, filename); - strncpy(app->file_name, string_get_cstr(filename), BAD_USB_FILE_NAME_LEN); - string_clear(filename); + string_set_str(app->file_path, arg); } app->gui = furi_record_open("gui"); @@ -83,13 +64,11 @@ BadUsbApp* bad_usb_app_alloc(char* arg) { app->error = BadUsbAppErrorCloseRpc; scene_manager_next_scene(app->scene_manager, BadUsbSceneError); } else { - if(*app->file_name != '\0') { + if(!string_empty_p(app->file_path)) { scene_manager_next_scene(app->scene_manager, BadUsbSceneWork); - } else if(bad_usb_check_assets()) { - scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } else { - app->error = BadUsbAppErrorNoFiles; - scene_manager_next_scene(app->scene_manager, BadUsbSceneError); + string_set_str(app->file_path, BAD_USB_APP_PATH_FOLDER); + scene_manager_next_scene(app->scene_manager, BadUsbSceneFileSelect); } } @@ -117,6 +96,8 @@ void bad_usb_app_free(BadUsbApp* app) { furi_record_close("notification"); furi_record_close("dialogs"); + string_clear(app->file_path); + free(app); } diff --git a/applications/bad_usb/bad_usb_app_i.h b/applications/bad_usb/bad_usb_app_i.h index 67f5816b..c82419e0 100644 --- a/applications/bad_usb/bad_usb_app_i.h +++ b/applications/bad_usb/bad_usb_app_i.h @@ -16,7 +16,6 @@ #define BAD_USB_APP_PATH_FOLDER "/any/badusb" #define BAD_USB_APP_EXTENSION ".txt" -#define BAD_USB_FILE_NAME_LEN 40 typedef enum { BadUsbAppErrorNoFiles, @@ -32,7 +31,7 @@ struct BadUsbApp { Widget* widget; BadUsbAppError error; - char file_name[BAD_USB_FILE_NAME_LEN + 1]; + string_t file_path; BadUsb* bad_usb_view; BadUsbScript* bad_usb_script; }; diff --git a/applications/bad_usb/scenes/bad_usb_scene_file_select.c b/applications/bad_usb/scenes/bad_usb_scene_file_select.c index 82f03bab..1e6ba895 100644 --- a/applications/bad_usb/scenes/bad_usb_scene_file_select.c +++ b/applications/bad_usb/scenes/bad_usb_scene_file_select.c @@ -5,14 +5,16 @@ static bool bad_usb_file_select(BadUsbApp* bad_usb) { furi_assert(bad_usb); - // Input events and views are managed by file_select - bool res = dialog_file_select_show( + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( bad_usb->dialogs, - BAD_USB_APP_PATH_FOLDER, + bad_usb->file_path, + bad_usb->file_path, BAD_USB_APP_EXTENSION, - bad_usb->file_name, - sizeof(bad_usb->file_name), - NULL); + true, + &I_badusb_10px, + true); + return res; } diff --git a/applications/bad_usb/scenes/bad_usb_scene_work.c b/applications/bad_usb/scenes/bad_usb_scene_work.c index a3a46803..516cbde3 100644 --- a/applications/bad_usb/scenes/bad_usb_scene_work.c +++ b/applications/bad_usb/scenes/bad_usb_scene_work.c @@ -2,6 +2,8 @@ #include "../bad_usb_app_i.h" #include "../views/bad_usb_view.h" #include "furi_hal.h" +#include "m-string.h" +#include "toolbox/path.h" void bad_usb_scene_work_ok_callback(InputType type, void* context) { furi_assert(context); @@ -28,10 +30,9 @@ void bad_usb_scene_work_on_enter(void* context) { string_t file_name; string_init(file_name); - bad_usb_set_file_name(app->bad_usb_view, app->file_name); - string_printf( - file_name, "%s/%s%s", BAD_USB_APP_PATH_FOLDER, app->file_name, BAD_USB_APP_EXTENSION); - app->bad_usb_script = bad_usb_script_open(file_name); + path_extract_filename(app->file_path, file_name, true); + bad_usb_set_file_name(app->bad_usb_view, string_get_cstr(file_name)); + app->bad_usb_script = bad_usb_script_open(app->file_path); string_clear(file_name); diff --git a/applications/bad_usb/views/bad_usb_view.c b/applications/bad_usb/views/bad_usb_view.c index 5b6fe6e8..430885df 100644 --- a/applications/bad_usb/views/bad_usb_view.c +++ b/applications/bad_usb/views/bad_usb_view.c @@ -2,6 +2,8 @@ #include "../bad_usb_script.h" #include +#define MAX_NAME_LEN 64 + struct BadUsb { View* view; BadUsbOkCallback callback; @@ -9,7 +11,7 @@ struct BadUsb { }; typedef struct { - char* file_name; + char file_name[MAX_NAME_LEN]; BadUsbState state; uint8_t anim_frame; } BadUsbModel; @@ -149,11 +151,11 @@ void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* c }); } -void bad_usb_set_file_name(BadUsb* bad_usb, char* name) { +void bad_usb_set_file_name(BadUsb* bad_usb, const char* name) { furi_assert(name); with_view_model( bad_usb->view, (BadUsbModel * model) { - model->file_name = name; + strncpy(model->file_name, name, MAX_NAME_LEN); return true; }); } diff --git a/applications/bad_usb/views/bad_usb_view.h b/applications/bad_usb/views/bad_usb_view.h index f5a8a0fa..80a47e2c 100755 --- a/applications/bad_usb/views/bad_usb_view.h +++ b/applications/bad_usb/views/bad_usb_view.h @@ -14,6 +14,6 @@ View* bad_usb_get_view(BadUsb* bad_usb); void bad_usb_set_ok_callback(BadUsb* bad_usb, BadUsbOkCallback callback, void* context); -void bad_usb_set_file_name(BadUsb* bad_usb, char* name); +void bad_usb_set_file_name(BadUsb* bad_usb, const char* name); void bad_usb_set_state(BadUsb* bad_usb, BadUsbState* st); diff --git a/applications/debug_tools/file_browser_test/file_browser_app.c b/applications/debug_tools/file_browser_test/file_browser_app.c new file mode 100644 index 00000000..c9b63ecb --- /dev/null +++ b/applications/debug_tools/file_browser_test/file_browser_app.c @@ -0,0 +1,99 @@ +#include "assets_icons.h" +#include "file_browser_app_i.h" +#include "gui/modules/file_browser.h" +#include "m-string.h" +#include +#include +#include +#include + +static bool file_browser_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + FileBrowserApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool file_browser_app_back_event_callback(void* context) { + furi_assert(context); + FileBrowserApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void file_browser_app_tick_event_callback(void* context) { + furi_assert(context); + FileBrowserApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +FileBrowserApp* file_browser_app_alloc(char* arg) { + UNUSED(arg); + FileBrowserApp* app = malloc(sizeof(FileBrowserApp)); + + app->gui = furi_record_open("gui"); + app->dialogs = furi_record_open("dialogs"); + + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->scene_manager = scene_manager_alloc(&file_browser_scene_handlers, app); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, file_browser_app_tick_event_callback, 500); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, file_browser_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, file_browser_app_back_event_callback); + + app->widget = widget_alloc(); + + string_init(app->file_path); + app->file_browser = file_browser_alloc(app->file_path); + file_browser_configure(app->file_browser, "*", true, &I_badusb_10px, true); + + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewStart, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewResult, widget_get_view(app->widget)); + view_dispatcher_add_view( + app->view_dispatcher, FileBrowserAppViewBrowser, file_browser_get_view(app->file_browser)); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + scene_manager_next_scene(app->scene_manager, FileBrowserSceneStart); + + return app; +} + +void file_browser_app_free(FileBrowserApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewStart); + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewResult); + view_dispatcher_remove_view(app->view_dispatcher, FileBrowserAppViewBrowser); + widget_free(app->widget); + file_browser_free(app->file_browser); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close("gui"); + furi_record_close("notification"); + furi_record_close("dialogs"); + + string_clear(app->file_path); + + free(app); +} + +int32_t file_browser_app(void* p) { + FileBrowserApp* file_browser_app = file_browser_app_alloc((char*)p); + + view_dispatcher_run(file_browser_app->view_dispatcher); + + file_browser_app_free(file_browser_app); + return 0; +} diff --git a/applications/debug_tools/file_browser_test/file_browser_app_i.h b/applications/debug_tools/file_browser_test/file_browser_app_i.h new file mode 100644 index 00000000..6e8412c9 --- /dev/null +++ b/applications/debug_tools/file_browser_test/file_browser_app_i.h @@ -0,0 +1,32 @@ +#pragma once + +#include "scenes/file_browser_scene.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct FileBrowserApp FileBrowserApp; + +struct FileBrowserApp { + Gui* gui; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + DialogsApp* dialogs; + Widget* widget; + FileBrowser* file_browser; + + string_t file_path; +}; + +typedef enum { + FileBrowserAppViewStart, + FileBrowserAppViewBrowser, + FileBrowserAppViewResult, +} FileBrowserAppView; diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c new file mode 100644 index 00000000..72a6e84d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.c @@ -0,0 +1,30 @@ +#include "file_browser_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const file_browser_scene_on_enter_handlers[])(void*) = { +#include "file_browser_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const file_browser_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "file_browser_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const file_browser_scene_on_exit_handlers[])(void* context) = { +#include "file_browser_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers file_browser_scene_handlers = { + .on_enter_handlers = file_browser_scene_on_enter_handlers, + .on_event_handlers = file_browser_scene_on_event_handlers, + .on_exit_handlers = file_browser_scene_on_exit_handlers, + .scene_num = FileBrowserSceneNum, +}; diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h new file mode 100644 index 00000000..d690fca9 --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) FileBrowserScene##id, +typedef enum { +#include "file_browser_scene_config.h" + FileBrowserSceneNum, +} FileBrowserScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers file_browser_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "file_browser_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "file_browser_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "file_browser_scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c new file mode 100644 index 00000000..ca16ad0d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_browser.c @@ -0,0 +1,43 @@ +#include "../file_browser_app_i.h" +#include "furi/check.h" +#include "furi/log.h" +#include "furi_hal.h" +#include "m-string.h" + +#define DEFAULT_PATH "/" +#define EXTENSION "*" + +bool file_browser_scene_browser_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_next_scene(app->scene_manager, FileBrowserSceneResult); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +static void file_browser_callback(void* context) { + FileBrowserApp* app = context; + furi_assert(app); + view_dispatcher_send_custom_event(app->view_dispatcher, SceneManagerEventTypeCustom); +} + +void file_browser_scene_browser_on_enter(void* context) { + FileBrowserApp* app = context; + + file_browser_set_callback(app->file_browser, file_browser_callback, app); + + file_browser_start(app->file_browser, app->file_path); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewBrowser); +} + +void file_browser_scene_browser_on_exit(void* context) { + FileBrowserApp* app = context; + + file_browser_stop(app->file_browser); +} diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h new file mode 100644 index 00000000..6597df3a --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(file_browser, start, Start) +ADD_SCENE(file_browser, browser, Browser) +ADD_SCENE(file_browser, result, Result) diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c new file mode 100644 index 00000000..53576cef --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_result.c @@ -0,0 +1,36 @@ +#include "../file_browser_app_i.h" +#include "furi_hal.h" +#include "m-string.h" + +void file_browser_scene_result_ok_callback(InputType type, void* context) { + furi_assert(context); + FileBrowserApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, type); +} + +bool file_browser_scene_result_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + //FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void file_browser_scene_result_on_enter(void* context) { + FileBrowserApp* app = context; + + widget_add_string_multiline_element( + app->widget, 64, 10, AlignCenter, AlignTop, FontSecondary, string_get_cstr(app->file_path)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewResult); +} + +void file_browser_scene_result_on_exit(void* context) { + UNUSED(context); + FileBrowserApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c new file mode 100644 index 00000000..bb71e83d --- /dev/null +++ b/applications/debug_tools/file_browser_test/scenes/file_browser_scene_start.c @@ -0,0 +1,44 @@ +#include "../file_browser_app_i.h" +#include "furi_hal.h" +#include "gui/modules/widget_elements/widget_element_i.h" + +static void + file_browser_scene_start_ok_callback(GuiButtonType result, InputType type, void* context) { + UNUSED(result); + furi_assert(context); + FileBrowserApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(app->view_dispatcher, type); + } +} + +bool file_browser_scene_start_on_event(void* context, SceneManagerEvent event) { + FileBrowserApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + string_set_str(app->file_path, "/any/badusb/demo_windows.txt"); + scene_manager_next_scene(app->scene_manager, FileBrowserSceneBrowser); + consumed = true; + } else if(event.type == SceneManagerEventTypeTick) { + } + return consumed; +} + +void file_browser_scene_start_on_enter(void* context) { + FileBrowserApp* app = context; + + widget_add_string_multiline_element( + app->widget, 64, 20, AlignCenter, AlignTop, FontSecondary, "Press OK to start"); + + widget_add_button_element( + app->widget, GuiButtonTypeCenter, "Ok", file_browser_scene_start_ok_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, FileBrowserAppViewStart); +} + +void file_browser_scene_start_on_exit(void* context) { + UNUSED(context); + FileBrowserApp* app = context; + widget_reset(app->widget); +} diff --git a/applications/dialogs/dialogs.c b/applications/dialogs/dialogs.c index cf4a2ad6..8929dc11 100644 --- a/applications/dialogs/dialogs.c +++ b/applications/dialogs/dialogs.c @@ -1,6 +1,7 @@ +#include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" -#include "dialogs_module_file_select.h" +#include "dialogs_module_file_browser.h" #include "dialogs_module_message.h" static DialogsApp* dialogs_app_alloc() { @@ -13,9 +14,9 @@ static DialogsApp* dialogs_app_alloc() { static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { UNUSED(app); switch(message->command) { - case DialogsAppCommandFileOpen: + case DialogsAppCommandFileBrowser: message->return_data->bool_value = - dialogs_app_process_module_file_select(&message->data->file_select); + dialogs_app_process_module_file_browser(&message->data->file_browser); break; case DialogsAppCommandDialog: message->return_data->dialog_value = diff --git a/applications/dialogs/dialogs.h b/applications/dialogs/dialogs.h index 9c71c098..53606056 100644 --- a/applications/dialogs/dialogs.h +++ b/applications/dialogs/dialogs.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "m-string.h" #ifdef __cplusplus extern "C" { @@ -10,25 +11,27 @@ extern "C" { typedef struct DialogsApp DialogsApp; -/****************** FILE SELECT ******************/ +/****************** FILE BROWSER ******************/ /** - * Shows and processes the file selection dialog + * Shows and processes the file browser dialog * @param context api pointer - * @param path path to directory + * @param result_path selected file path string pointer + * @param path preselected file path string pointer * @param extension file extension to be offered for selection - * @param selected_filename buffer where the selected filename will be saved - * @param selected_filename_size and the size of this buffer - * @param preselected_filename filename to be preselected + * @param skip_assets true - do not show assets folders + * @param icon file icon pointer, NULL for default icon + * @param hide_ext true - hide extensions for files * @return bool whether a file was selected */ -bool dialog_file_select_show( +bool dialog_file_browser_show( DialogsApp* context, - const char* path, + string_ptr result_path, + string_ptr path, const char* extension, - char* result, - uint8_t result_size, - const char* preselected_filename); + bool skip_assets, + const Icon* icon, + bool hide_ext); /****************** MESSAGE ******************/ diff --git a/applications/dialogs/dialogs_api.c b/applications/dialogs/dialogs_api.c index c4efb287..fab3a5ea 100644 --- a/applications/dialogs/dialogs_api.c +++ b/applications/dialogs/dialogs_api.c @@ -1,31 +1,36 @@ +#include "dialogs/dialogs_message.h" #include "dialogs_i.h" #include "dialogs_api_lock.h" +#include "m-string.h" -/****************** File select ******************/ +/****************** File browser ******************/ -bool dialog_file_select_show( +bool dialog_file_browser_show( DialogsApp* context, - const char* path, + string_ptr result_path, + string_ptr path, const char* extension, - char* result, - uint8_t result_size, - const char* preselected_filename) { + bool skip_assets, + const Icon* icon, + bool hide_ext) { FuriApiLock lock = API_LOCK_INIT_LOCKED(); furi_check(lock != NULL); DialogsAppData data = { - .file_select = { - .path = path, + .file_browser = { .extension = extension, - .result = result, - .result_size = result_size, - .preselected_filename = preselected_filename, + .result_path = result_path, + .file_icon = icon, + .hide_ext = hide_ext, + .skip_assets = skip_assets, + .preselected_filename = path, + }}; DialogsAppReturn return_data; DialogsAppMessage message = { .lock = lock, - .command = DialogsAppCommandFileOpen, + .command = DialogsAppCommandFileBrowser, .data = &data, .return_data = &return_data, }; diff --git a/applications/dialogs/dialogs_message.h b/applications/dialogs/dialogs_message.h index d7b5fabf..ccfbdece 100644 --- a/applications/dialogs/dialogs_message.h +++ b/applications/dialogs/dialogs_message.h @@ -2,25 +2,27 @@ #include #include "dialogs_i.h" #include "dialogs_api_lock.h" +#include "m-string.h" #ifdef __cplusplus extern "C" { #endif typedef struct { - const char* path; const char* extension; - char* result; - uint8_t result_size; - const char* preselected_filename; -} DialogsAppMessageDataFileSelect; + bool skip_assets; + bool hide_ext; + const Icon* file_icon; + string_ptr result_path; + string_ptr preselected_filename; +} DialogsAppMessageDataFileBrowser; typedef struct { const DialogMessage* message; } DialogsAppMessageDataDialog; typedef union { - DialogsAppMessageDataFileSelect file_select; + DialogsAppMessageDataFileBrowser file_browser; DialogsAppMessageDataDialog dialog; } DialogsAppData; @@ -30,7 +32,7 @@ typedef union { } DialogsAppReturn; typedef enum { - DialogsAppCommandFileOpen, + DialogsAppCommandFileBrowser, DialogsAppCommandDialog, } DialogsAppCommand; diff --git a/applications/dialogs/dialogs_module_file_browser.c b/applications/dialogs/dialogs_module_file_browser.c new file mode 100644 index 00000000..ecd0ca79 --- /dev/null +++ b/applications/dialogs/dialogs_module_file_browser.c @@ -0,0 +1,59 @@ +#include "dialogs_i.h" +#include "dialogs_api_lock.h" +#include "gui/modules/file_browser.h" + +typedef struct { + FuriApiLock lock; + bool result; +} DialogsAppFileBrowserContext; + +static void dialogs_app_file_browser_back_callback(void* context) { + furi_assert(context); + DialogsAppFileBrowserContext* file_browser_context = context; + file_browser_context->result = false; + API_LOCK_UNLOCK(file_browser_context->lock); +} + +static void dialogs_app_file_browser_callback(void* context) { + furi_assert(context); + DialogsAppFileBrowserContext* file_browser_context = context; + file_browser_context->result = true; + API_LOCK_UNLOCK(file_browser_context->lock); +} + +bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data) { + bool ret = false; + Gui* gui = furi_record_open("gui"); + + DialogsAppFileBrowserContext* file_browser_context = + malloc(sizeof(DialogsAppFileBrowserContext)); + file_browser_context->lock = API_LOCK_INIT_LOCKED(); + + ViewHolder* view_holder = view_holder_alloc(); + view_holder_attach_to_gui(view_holder, gui); + view_holder_set_back_callback( + view_holder, dialogs_app_file_browser_back_callback, file_browser_context); + + FileBrowser* file_browser = file_browser_alloc(data->result_path); + file_browser_set_callback( + file_browser, dialogs_app_file_browser_callback, file_browser_context); + file_browser_configure( + file_browser, data->extension, data->skip_assets, data->file_icon, data->hide_ext); + file_browser_start(file_browser, data->preselected_filename); + + view_holder_set_view(view_holder, file_browser_get_view(file_browser)); + view_holder_start(view_holder); + API_LOCK_WAIT_UNTIL_UNLOCK(file_browser_context->lock); + + ret = file_browser_context->result; + + view_holder_stop(view_holder); + view_holder_free(view_holder); + file_browser_stop(file_browser); + file_browser_free(file_browser); + API_LOCK_FREE(file_browser_context->lock); + free(file_browser_context); + furi_record_close("gui"); + + return ret; +} diff --git a/applications/dialogs/dialogs_module_file_select.h b/applications/dialogs/dialogs_module_file_browser.h similarity index 54% rename from applications/dialogs/dialogs_module_file_select.h rename to applications/dialogs/dialogs_module_file_browser.h index 749fe9c1..b6cbdf6a 100644 --- a/applications/dialogs/dialogs_module_file_select.h +++ b/applications/dialogs/dialogs_module_file_browser.h @@ -5,7 +5,7 @@ extern "C" { #endif -bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data); +bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrowser* data); #ifdef __cplusplus } diff --git a/applications/dialogs/dialogs_module_file_select.c b/applications/dialogs/dialogs_module_file_select.c deleted file mode 100644 index f133c00a..00000000 --- a/applications/dialogs/dialogs_module_file_select.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "dialogs_i.h" -#include "dialogs_api_lock.h" -#include - -typedef struct { - FuriApiLock lock; - bool result; -} DialogsAppFileSelectContext; - -static void dialogs_app_file_select_back_callback(void* context) { - furi_assert(context); - DialogsAppFileSelectContext* file_select_context = context; - file_select_context->result = false; - API_LOCK_UNLOCK(file_select_context->lock); -} - -static void dialogs_app_file_select_callback(bool result, void* context) { - furi_assert(context); - DialogsAppFileSelectContext* file_select_context = context; - file_select_context->result = result; - API_LOCK_UNLOCK(file_select_context->lock); -} - -bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { - bool ret = false; - Gui* gui = furi_record_open("gui"); - - DialogsAppFileSelectContext* file_select_context = malloc(sizeof(DialogsAppFileSelectContext)); - file_select_context->lock = API_LOCK_INIT_LOCKED(); - - ViewHolder* view_holder = view_holder_alloc(); - view_holder_attach_to_gui(view_holder, gui); - view_holder_set_back_callback( - view_holder, dialogs_app_file_select_back_callback, file_select_context); - - FileSelect* file_select = file_select_alloc(); - file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context); - file_select_set_filter(file_select, data->path, data->extension); - file_select_set_result_buffer(file_select, data->result, data->result_size); - file_select_init(file_select); - if(data->preselected_filename != NULL) { - file_select_set_selected_file(file_select, data->preselected_filename); - } - - view_holder_set_view(view_holder, file_select_get_view(file_select)); - view_holder_start(view_holder); - API_LOCK_WAIT_UNTIL_UNLOCK(file_select_context->lock); - - ret = file_select_context->result; - - view_holder_stop(view_holder); - view_holder_free(view_holder); - file_select_free(file_select); - API_LOCK_FREE(file_select_context->lock); - free(file_select_context); - furi_record_close("gui"); - - return ret; -} diff --git a/applications/gui/modules/file_browser.c b/applications/gui/modules/file_browser.c new file mode 100644 index 00000000..1cef1d07 --- /dev/null +++ b/applications/gui/modules/file_browser.c @@ -0,0 +1,534 @@ +#include "file_browser.h" +#include "assets_icons.h" +#include "cmsis_os2.h" +#include "file_browser_worker.h" +#include "furi/check.h" +#include "furi/common_defines.h" +#include "furi/log.h" +#include "furi_hal_resources.h" +#include "m-string.h" +#include +#include +#include +#include "toolbox/path.h" + +#define LIST_ITEMS 5u +#define MAX_LEN_PX 110 +#define FRAME_HEIGHT 12 +#define Y_OFFSET 3 + +#define ITEM_LIST_LEN_MAX 100 + +typedef enum { + BrowserItemTypeLoading, + BrowserItemTypeBack, + BrowserItemTypeFolder, + BrowserItemTypeFile, +} BrowserItemType; + +typedef struct { + string_t path; + BrowserItemType type; +} BrowserItem_t; + +static void BrowserItem_t_init(BrowserItem_t* obj) { + obj->type = BrowserItemTypeLoading; + string_init(obj->path); +} + +static void BrowserItem_t_init_set(BrowserItem_t* obj, const BrowserItem_t* src) { + obj->type = src->type; + string_init_set(obj->path, src->path); +} + +static void BrowserItem_t_set(BrowserItem_t* obj, const BrowserItem_t* src) { + obj->type = src->type; + string_set(obj->path, src->path); +} + +static void BrowserItem_t_clear(BrowserItem_t* obj) { + string_clear(obj->path); +} + +ARRAY_DEF( + items_array, + BrowserItem_t, + (INIT(API_2(BrowserItem_t_init)), + SET(API_6(BrowserItem_t_set)), + INIT_SET(API_6(BrowserItem_t_init_set)), + CLEAR(API_2(BrowserItem_t_clear)))) + +struct FileBrowser { + View* view; + BrowserWorker* worker; + const char* ext_filter; + bool skip_assets; + + FileBrowserCallback callback; + void* context; + + string_ptr result_path; +}; + +typedef struct { + items_array_t items; + + bool is_root; + bool folder_loading; + bool list_loading; + uint32_t item_cnt; + int32_t item_idx; + int32_t array_offset; + int32_t list_offset; + + const Icon* file_icon; + bool hide_ext; +} FileBrowserModel; + +static const Icon* BrowserItemIcons[] = { + [BrowserItemTypeLoading] = &I_loading_10px, + [BrowserItemTypeBack] = &I_back_10px, + [BrowserItemTypeFolder] = &I_dir_10px, + [BrowserItemTypeFile] = &I_unknown_10px, +}; + +static void file_browser_view_draw_callback(Canvas* canvas, void* _model); +static bool file_browser_view_input_callback(InputEvent* event, void* context); + +static void + browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root); +static void browser_list_load_cb(void* context, uint32_t list_load_offset); +static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last); +static void browser_long_load_cb(void* context); + +FileBrowser* file_browser_alloc(string_ptr result_path) { + furi_assert(result_path); + FileBrowser* browser = malloc(sizeof(FileBrowser)); + browser->view = view_alloc(); + view_allocate_model(browser->view, ViewModelTypeLocking, sizeof(FileBrowserModel)); + view_set_context(browser->view, browser); + view_set_draw_callback(browser->view, file_browser_view_draw_callback); + view_set_input_callback(browser->view, file_browser_view_input_callback); + + browser->result_path = result_path; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_init(model->items); + return false; + }); + + return browser; +} + +void file_browser_free(FileBrowser* browser) { + furi_assert(browser); + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_clear(model->items); + return false; + }); + + view_free(browser->view); + free(browser); +} + +View* file_browser_get_view(FileBrowser* browser) { + furi_assert(browser); + return browser->view; +} + +void file_browser_configure( + FileBrowser* browser, + const char* extension, + bool skip_assets, + const Icon* file_icon, + bool hide_ext) { + furi_assert(browser); + + browser->ext_filter = extension; + browser->skip_assets = skip_assets; + + with_view_model( + browser->view, (FileBrowserModel * model) { + model->file_icon = file_icon; + model->hide_ext = hide_ext; + return false; + }); +} + +void file_browser_start(FileBrowser* browser, string_t path) { + furi_assert(browser); + browser->worker = file_browser_worker_alloc(path, browser->ext_filter, browser->skip_assets); + file_browser_worker_set_callback_context(browser->worker, browser); + file_browser_worker_set_folder_callback(browser->worker, browser_folder_open_cb); + file_browser_worker_set_list_callback(browser->worker, browser_list_load_cb); + file_browser_worker_set_item_callback(browser->worker, browser_list_item_cb); + file_browser_worker_set_long_load_callback(browser->worker, browser_long_load_cb); +} + +void file_browser_stop(FileBrowser* browser) { + furi_assert(browser); + file_browser_worker_free(browser->worker); + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + model->item_cnt = 0; + model->item_idx = 0; + model->array_offset = 0; + model->list_offset = 0; + return false; + }); +} + +void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context) { + browser->context = context; + browser->callback = callback; +} + +static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { + size_t array_size = items_array_size(model->items); + + if((idx >= (uint32_t)model->array_offset + array_size) || + (idx < (uint32_t)model->array_offset)) { + return false; + } + return true; +} + +static bool browser_is_list_load_required(FileBrowserModel* model) { + size_t array_size = items_array_size(model->items); + uint32_t item_cnt = (model->is_root) ? model->item_cnt : model->item_cnt - 1; + + if((model->list_loading) || (array_size >= item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + ITEM_LIST_LEN_MAX / 4))) { + return true; + } + + if(((model->array_offset + array_size) < item_cnt) && + (model->item_idx > (int32_t)(model->array_offset + array_size - ITEM_LIST_LEN_MAX / 4))) { + return true; + } + + return false; +} + +static void browser_update_offset(FileBrowser* browser) { + furi_assert(browser); + + with_view_model( + browser->view, (FileBrowserModel * model) { + uint16_t bounds = model->item_cnt > (LIST_ITEMS - 1) ? 2 : model->item_cnt; + + if((model->item_cnt > (LIST_ITEMS - 1)) && + (model->item_idx >= ((int32_t)model->item_cnt - 1))) { + model->list_offset = model->item_idx - (LIST_ITEMS - 1); + } else if(model->list_offset < model->item_idx - bounds) { + model->list_offset = CLAMP( + model->item_idx - (int32_t)(LIST_ITEMS - 2), + (int32_t)model->item_cnt - bounds, + 0); + } else if(model->list_offset > model->item_idx - bounds) { + model->list_offset = + CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0); + } + + return false; + }); +} + +static void + browser_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + int32_t load_offset = 0; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + if(is_root) { + model->item_cnt = item_cnt; + model->item_idx = (file_idx > 0) ? file_idx : 0; + load_offset = + CLAMP(model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0); + } else { + model->item_cnt = item_cnt + 1; + model->item_idx = file_idx + 1; + load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 2 - 1, (int32_t)model->item_cnt - 1, 0); + } + model->array_offset = 0; + model->list_offset = 0; + model->is_root = is_root; + model->list_loading = true; + model->folder_loading = false; + return true; + }); + browser_update_offset(browser); + + file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX); +} + +static void browser_list_load_cb(void* context, uint32_t list_load_offset) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + BrowserItem_t back_item; + BrowserItem_t_init(&back_item); + back_item.type = BrowserItemTypeBack; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_reset(model->items); + model->array_offset = list_load_offset; + if(!model->is_root) { + if(list_load_offset == 0) { + items_array_push_back(model->items, back_item); + } else { + model->array_offset += 1; + } + } + return false; + }); + + BrowserItem_t_clear(&back_item); +} + +static void browser_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + BrowserItem_t item; + + if(!is_last) { + BrowserItem_t_init(&item); + string_set(item.path, item_path); + item.type = (is_folder) ? BrowserItemTypeFolder : BrowserItemTypeFile; + + with_view_model( + browser->view, (FileBrowserModel * model) { + items_array_push_back(model->items, item); + return false; + }); + BrowserItem_t_clear(&item); + } else { + with_view_model( + browser->view, (FileBrowserModel * model) { + model->list_loading = false; + return true; + }); + } +} + +static void browser_long_load_cb(void* context) { + furi_assert(context); + FileBrowser* browser = (FileBrowser*)context; + + with_view_model( + browser->view, (FileBrowserModel * model) { + model->folder_loading = true; + return true; + }); +} + +static void browser_draw_frame(Canvas* canvas, uint16_t idx, bool scrollbar) { + canvas_set_color(canvas, ColorBlack); + canvas_draw_box( + canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT, (scrollbar ? 122 : 127), FRAME_HEIGHT); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_dot(canvas, 0, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 1, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + 1); + + canvas_draw_dot(canvas, 0, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); + canvas_draw_dot(canvas, scrollbar ? 121 : 126, Y_OFFSET + idx * FRAME_HEIGHT); + canvas_draw_dot( + canvas, scrollbar ? 121 : 126, (Y_OFFSET + idx * FRAME_HEIGHT) + (FRAME_HEIGHT - 1)); +} + +static void browser_draw_loading(Canvas* canvas, FileBrowserModel* model) { + uint8_t width = 49; + uint8_t height = 47; + uint8_t x = 128 / 2 - width / 2; + uint8_t y = 64 / 2 - height / 2; + + UNUSED(model); + + elements_bold_rounded_frame(canvas, x, y, width, height); + + canvas_set_font(canvas, FontSecondary); + elements_multiline_text(canvas, x + 7, y + 13, "Loading..."); + + canvas_draw_icon(canvas, x + 13, y + 19, &A_Loading_24); +} + +static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) { + uint32_t array_size = items_array_size(model->items); + bool show_scrollbar = model->item_cnt > LIST_ITEMS; + + string_t filename; + string_init(filename); + + for(uint32_t i = 0; i < MIN(model->item_cnt, LIST_ITEMS); i++) { + int32_t idx = CLAMP((uint32_t)(i + model->list_offset), model->item_cnt, 0u); + + BrowserItemType item_type = BrowserItemTypeLoading; + + if(browser_is_item_in_array(model, idx)) { + BrowserItem_t* item = items_array_get( + model->items, CLAMP(idx - model->array_offset, (int32_t)(array_size - 1), 0)); + item_type = item->type; + path_extract_filename( + item->path, filename, (model->hide_ext) && (item_type == BrowserItemTypeFile)); + } else { + string_set_str(filename, "---"); + } + + if(item_type == BrowserItemTypeBack) { + string_set_str(filename, ". ."); + } + + elements_string_fit_width( + canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX)); + + if(model->item_idx == idx) { + browser_draw_frame(canvas, i, show_scrollbar); + } else { + canvas_set_color(canvas, ColorBlack); + } + + if((item_type == BrowserItemTypeFile) && (model->file_icon)) { + canvas_draw_icon(canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, model->file_icon); + } else if(BrowserItemIcons[item_type] != NULL) { + canvas_draw_icon( + canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]); + } + canvas_draw_str(canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, string_get_cstr(filename)); + } + + if(show_scrollbar) { + elements_scrollbar_pos( + canvas, + 126, + Y_OFFSET, + canvas_height(canvas) - Y_OFFSET, + model->item_idx, + model->item_cnt); + } + + string_clear(filename); +} + +static void file_browser_view_draw_callback(Canvas* canvas, void* _model) { + FileBrowserModel* model = _model; + + if(model->folder_loading) { + browser_draw_loading(canvas, model); + } else { + browser_draw_list(canvas, model); + } +} + +static bool file_browser_view_input_callback(InputEvent* event, void* context) { + FileBrowser* browser = context; + furi_assert(browser); + bool consumed = false; + bool is_loading = false; + + with_view_model( + browser->view, (FileBrowserModel * model) { + is_loading = model->folder_loading; + return false; + }); + + if(is_loading) { + return false; + } else if(event->key == InputKeyUp || event->key == InputKeyDown) { + if(event->type == InputTypeShort || event->type == InputTypeRepeat) { + with_view_model( + browser->view, (FileBrowserModel * model) { + if(event->key == InputKeyUp) { + model->item_idx = + ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 4 * 3, + (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, + 0); + file_browser_worker_load( + browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + } else if(event->key == InputKeyDown) { + model->item_idx = (model->item_idx + 1) % model->item_cnt; + if(browser_is_list_load_required(model)) { + model->list_loading = true; + int32_t load_offset = CLAMP( + model->item_idx - ITEM_LIST_LEN_MAX / 4 * 1, + (int32_t)model->item_cnt - ITEM_LIST_LEN_MAX, + 0); + file_browser_worker_load( + browser->worker, load_offset, ITEM_LIST_LEN_MAX); + } + } + return true; + }); + browser_update_offset(browser); + consumed = true; + } + } else if(event->key == InputKeyOk) { + if(event->type == InputTypeShort) { + BrowserItem_t* selected_item = NULL; + int32_t select_index = 0; + with_view_model( + browser->view, (FileBrowserModel * model) { + if(browser_is_item_in_array(model, model->item_idx)) { + selected_item = + items_array_get(model->items, model->item_idx - model->array_offset); + select_index = model->item_idx; + if((!model->is_root) && (select_index > 0)) { + select_index -= 1; + } + } + return false; + }); + + if(selected_item) { + if(selected_item->type == BrowserItemTypeBack) { + file_browser_worker_folder_exit(browser->worker); + } else if(selected_item->type == BrowserItemTypeFolder) { + file_browser_worker_folder_enter( + browser->worker, selected_item->path, select_index); + } else if(selected_item->type == BrowserItemTypeFile) { + string_set(browser->result_path, selected_item->path); + if(browser->callback) { + browser->callback(browser->context); + } + } + } + consumed = true; + } + } else if(event->key == InputKeyLeft) { + if(event->type == InputTypeShort) { + bool is_root = false; + with_view_model( + browser->view, (FileBrowserModel * model) { + is_root = model->is_root; + return false; + }); + if(!is_root) { + file_browser_worker_folder_exit(browser->worker); + } + consumed = true; + } + } + + return consumed; +} diff --git a/applications/gui/modules/file_browser.h b/applications/gui/modules/file_browser.h new file mode 100644 index 00000000..ebc64509 --- /dev/null +++ b/applications/gui/modules/file_browser.h @@ -0,0 +1,39 @@ +/** + * @file file_browser.h + * GUI: FileBrowser view module API + */ + +#pragma once + +#include "m-string.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FileBrowser FileBrowser; +typedef void (*FileBrowserCallback)(void* context); + +FileBrowser* file_browser_alloc(string_ptr result_path); + +void file_browser_free(FileBrowser* browser); + +View* file_browser_get_view(FileBrowser* browser); + +void file_browser_configure( + FileBrowser* browser, + const char* extension, + bool skip_assets, + const Icon* file_icon, + bool hide_ext); + +void file_browser_start(FileBrowser* browser, string_t path); + +void file_browser_stop(FileBrowser* browser); + +void file_browser_set_callback(FileBrowser* browser, FileBrowserCallback callback, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/applications/gui/modules/file_browser_worker.c b/applications/gui/modules/file_browser_worker.c new file mode 100644 index 00000000..93baba00 --- /dev/null +++ b/applications/gui/modules/file_browser_worker.c @@ -0,0 +1,408 @@ +#include "file_browser_worker.h" +#include "furi/check.h" +#include "furi/common_defines.h" +#include "m-string.h" +#include "storage/filesystem_api_defines.h" +#include +#include +#include +#include +#include +#include "toolbox/path.h" + +#define TAG "BrowserWorker" + +#define ASSETS_DIR "assets" +#define BROWSER_ROOT "/any" +#define FILE_NAME_LEN_MAX 256 +#define LONG_LOAD_THRESHOLD 100 + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtLoad = (1 << 1), + WorkerEvtFolderEnter = (1 << 2), + WorkerEvtFolderExit = (1 << 3), +} WorkerEvtFlags; + +#define WORKER_FLAGS_ALL \ + (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit) + +ARRAY_DEF(idx_last_array, int32_t) + +struct BrowserWorker { + FuriThread* thread; + + string_t filter_extension; + string_t path_next; + int32_t item_sel_idx; + uint32_t load_offset; + uint32_t load_count; + bool skip_assets; + idx_last_array_t idx_last; + + void* cb_ctx; + BrowserWorkerFolderOpenCallback folder_cb; + BrowserWorkerListLoadCallback list_load_cb; + BrowserWorkerListItemCallback list_item_cb; + BrowserWorkerLongLoadCallback long_load_cb; +}; + +static bool browser_path_is_file(string_t path) { + bool state = false; + FileInfo file_info; + Storage* storage = furi_record_open("storage"); + if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if((file_info.flags & FSF_DIRECTORY) == 0) { + state = true; + } + } + furi_record_close("storage"); + return state; +} + +static bool browser_path_trim(string_t path) { + bool is_root = false; + size_t filename_start = string_search_rchar(path, '/'); + string_left(path, filename_start); + if((string_empty_p(path)) || (filename_start == STRING_FAILURE)) { + string_set_str(path, BROWSER_ROOT); + is_root = true; + } + return is_root; +} + +static bool browser_filter_by_name(BrowserWorker* browser, string_t name, bool is_folder) { + if(is_folder) { + // Skip assets folders (if enabled) + if(browser->skip_assets) { + return ((string_cmp_str(name, ASSETS_DIR) == 0) ? (false) : (true)); + } else { + return true; + } + } else { + // Filter files by extension + if((string_empty_p(browser->filter_extension)) || + (string_cmp_str(browser->filter_extension, "*") == 0)) { + return true; + } + if(string_end_with_string_p(name, browser->filter_extension)) { + return true; + } + } + return false; +} + +static bool browser_folder_check_and_switch(string_t path) { + FileInfo file_info; + Storage* storage = furi_record_open("storage"); + bool is_root = false; + while(1) { + // Check if folder is existing and navigate back if not + if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) { + if(file_info.flags & FSF_DIRECTORY) { + break; + } + } + if(is_root) { + break; + } + is_root = browser_path_trim(path); + } + furi_record_close("storage"); + return is_root; +} + +static bool browser_folder_init( + BrowserWorker* browser, + string_t path, + string_t filename, + uint32_t* item_cnt, + int32_t* file_idx) { + bool state = false; + FileInfo file_info; + uint32_t total_files_cnt = 0; + + Storage* storage = furi_record_open("storage"); + File* directory = storage_file_alloc(storage); + + char name_temp[FILE_NAME_LEN_MAX]; + string_t name_str; + string_init(name_str); + + *item_cnt = 0; + *file_idx = -1; + + if(storage_dir_open(directory, string_get_cstr(path))) { + state = true; + while(1) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { + total_files_cnt++; + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(!string_empty_p(filename)) { + if(string_cmp(name_str, filename) == 0) { + *file_idx = *item_cnt; + } + } + (*item_cnt)++; + } + if(total_files_cnt == LONG_LOAD_THRESHOLD) { + // There are too many files in folder and counting them will take some time - send callback to app + if(browser->long_load_cb) { + browser->long_load_cb(browser->cb_ctx); + } + } + } + } + } + + string_clear(name_str); + + storage_dir_close(directory); + storage_file_free(directory); + + furi_record_close("storage"); + + return state; +} + +static bool + browser_folder_load(BrowserWorker* browser, string_t path, uint32_t offset, uint32_t count) { + FileInfo file_info; + + Storage* storage = furi_record_open("storage"); + File* directory = storage_file_alloc(storage); + + char name_temp[FILE_NAME_LEN_MAX]; + string_t name_str; + string_init(name_str); + + uint32_t items_cnt = 0; + + do { + if(!storage_dir_open(directory, string_get_cstr(path))) { + break; + } + + items_cnt = 0; + while(items_cnt < offset) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if(storage_file_get_error(directory) == FSE_OK) { + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + items_cnt++; + } + } else { + break; + } + } + if(items_cnt != offset) { + break; + } + + if(browser->list_load_cb) { + browser->list_load_cb(browser->cb_ctx, offset); + } + + items_cnt = 0; + while(items_cnt < count) { + if(!storage_dir_read(directory, &file_info, name_temp, FILE_NAME_LEN_MAX)) { + break; + } + if(storage_file_get_error(directory) == FSE_OK) { + string_set_str(name_str, name_temp); + if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + string_printf(name_str, "%s/%s", string_get_cstr(path), name_temp); + if(browser->list_item_cb) { + browser->list_item_cb( + browser->cb_ctx, name_str, (file_info.flags & FSF_DIRECTORY), false); + } + items_cnt++; + } + } else { + break; + } + } + if(browser->list_item_cb) { + browser->list_item_cb(browser->cb_ctx, NULL, false, true); + } + } while(0); + + string_clear(name_str); + + storage_dir_close(directory); + storage_file_free(directory); + + furi_record_close("storage"); + + return (items_cnt == count); +} + +static int32_t browser_worker(void* context) { + BrowserWorker* browser = (BrowserWorker*)context; + furi_assert(browser); + FURI_LOG_D(TAG, "Start"); + + uint32_t items_cnt = 0; + string_t path; + string_init_set_str(path, BROWSER_ROOT); + browser->item_sel_idx = -1; + + // If start path is a path to the file - try finding index of this file in a folder + string_t filename; + string_init(filename); + if(browser_path_is_file(browser->path_next)) { + path_extract_filename(browser->path_next, filename, false); + } + + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); + + while(1) { + uint32_t flags = osThreadFlagsWait(WORKER_FLAGS_ALL, osFlagsWaitAny, osWaitForever); + furi_assert((flags & osFlagsError) == 0); + + if(flags & WorkerEvtFolderEnter) { + string_set(path, browser->path_next); + bool is_root = browser_folder_check_and_switch(path); + + // Push previous selected item index to history array + idx_last_array_push_back(browser->idx_last, browser->item_sel_idx); + + int32_t file_idx = 0; + browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + FURI_LOG_D( + TAG, + "Enter folder: %s items: %u idx: %d", + string_get_cstr(path), + items_cnt, + file_idx); + if(browser->folder_cb) { + browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); + } + string_reset(filename); + } + + if(flags & WorkerEvtFolderExit) { + browser_path_trim(path); + bool is_root = browser_folder_check_and_switch(path); + + int32_t file_idx = 0; + browser_folder_init(browser, path, filename, &items_cnt, &file_idx); + if(idx_last_array_size(browser->idx_last) > 0) { + // Pop previous selected item index from history array + idx_last_array_pop_back(&file_idx, browser->idx_last); + } + FURI_LOG_D( + TAG, "Exit to: %s items: %u idx: %d", string_get_cstr(path), items_cnt, file_idx); + if(browser->folder_cb) { + browser->folder_cb(browser->cb_ctx, items_cnt, file_idx, is_root); + } + } + + if(flags & WorkerEvtLoad) { + FURI_LOG_D(TAG, "Load offset: %u cnt: %u", browser->load_offset, browser->load_count); + browser_folder_load(browser, path, browser->load_offset, browser->load_count); + } + + if(flags & WorkerEvtStop) { + break; + } + } + + string_clear(filename); + string_clear(path); + + FURI_LOG_D(TAG, "End"); + return 0; +} + +BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets) { + BrowserWorker* browser = malloc(sizeof(BrowserWorker)); + + idx_last_array_init(browser->idx_last); + + string_init_set_str(browser->filter_extension, filter_ext); + browser->skip_assets = skip_assets; + string_init_set(browser->path_next, path); + + browser->thread = furi_thread_alloc(); + furi_thread_set_name(browser->thread, "BrowserWorker"); + furi_thread_set_stack_size(browser->thread, 2048); + furi_thread_set_context(browser->thread, browser); + furi_thread_set_callback(browser->thread, browser_worker); + furi_thread_start(browser->thread); + + return browser; +} + +void file_browser_worker_free(BrowserWorker* browser) { + furi_assert(browser); + + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtStop); + furi_thread_join(browser->thread); + furi_thread_free(browser->thread); + + string_clear(browser->filter_extension); + string_clear(browser->path_next); + + idx_last_array_clear(browser->idx_last); + + free(browser); +} + +void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context) { + furi_assert(browser); + browser->cb_ctx = context; +} + +void file_browser_worker_set_folder_callback( + BrowserWorker* browser, + BrowserWorkerFolderOpenCallback cb) { + furi_assert(browser); + browser->folder_cb = cb; +} + +void file_browser_worker_set_list_callback( + BrowserWorker* browser, + BrowserWorkerListLoadCallback cb) { + furi_assert(browser); + browser->list_load_cb = cb; +} + +void file_browser_worker_set_item_callback( + BrowserWorker* browser, + BrowserWorkerListItemCallback cb) { + furi_assert(browser); + browser->list_item_cb = cb; +} + +void file_browser_worker_set_long_load_callback( + BrowserWorker* browser, + BrowserWorkerLongLoadCallback cb) { + furi_assert(browser); + browser->long_load_cb = cb; +} + +void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx) { + furi_assert(browser); + string_set(browser->path_next, path); + browser->item_sel_idx = item_idx; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderEnter); +} + +void file_browser_worker_folder_exit(BrowserWorker* browser) { + furi_assert(browser); + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtFolderExit); +} + +void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count) { + furi_assert(browser); + browser->load_offset = offset; + browser->load_count = count; + osThreadFlagsSet(furi_thread_get_thread_id(browser->thread), WorkerEvtLoad); +} diff --git a/applications/gui/modules/file_browser_worker.h b/applications/gui/modules/file_browser_worker.h new file mode 100644 index 00000000..b0d360a3 --- /dev/null +++ b/applications/gui/modules/file_browser_worker.h @@ -0,0 +1,55 @@ +#pragma once + +#include "m-string.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BrowserWorker BrowserWorker; +typedef void (*BrowserWorkerFolderOpenCallback)( + void* context, + uint32_t item_cnt, + int32_t file_idx, + bool is_root); +typedef void (*BrowserWorkerListLoadCallback)(void* context, uint32_t list_load_offset); +typedef void (*BrowserWorkerListItemCallback)( + void* context, + string_t item_path, + bool is_folder, + bool is_last); +typedef void (*BrowserWorkerLongLoadCallback)(void* context); + +BrowserWorker* file_browser_worker_alloc(string_t path, const char* filter_ext, bool skip_assets); + +void file_browser_worker_free(BrowserWorker* browser); + +void file_browser_worker_set_callback_context(BrowserWorker* browser, void* context); + +void file_browser_worker_set_folder_callback( + BrowserWorker* browser, + BrowserWorkerFolderOpenCallback cb); + +void file_browser_worker_set_list_callback( + BrowserWorker* browser, + BrowserWorkerListLoadCallback cb); + +void file_browser_worker_set_item_callback( + BrowserWorker* browser, + BrowserWorkerListItemCallback cb); + +void file_browser_worker_set_long_load_callback( + BrowserWorker* browser, + BrowserWorkerLongLoadCallback cb); + +void file_browser_worker_folder_enter(BrowserWorker* browser, string_t path, int32_t item_idx); + +void file_browser_worker_folder_exit(BrowserWorker* browser); + +void file_browser_worker_load(BrowserWorker* browser, uint32_t offset, uint32_t count); + +#ifdef __cplusplus +} +#endif diff --git a/applications/gui/modules/file_select.c b/applications/gui/modules/file_select.c deleted file mode 100644 index 14541196..00000000 --- a/applications/gui/modules/file_select.c +++ /dev/null @@ -1,475 +0,0 @@ -#include "file_select.h" -#include -#include -#include - -#define FILENAME_COUNT 4 - -struct FileSelect { - // public - View* view; - Storage* fs_api; - const char* path; - const char* extension; - - bool init_completed; - - FileSelectCallback callback; - void* context; - - char* buffer; - uint8_t buffer_size; -}; - -typedef struct { - string_t filename[FILENAME_COUNT]; - uint8_t position; - - uint16_t first_file_index; - uint16_t file_count; - -} FileSelectModel; - -bool file_select_fill_strings(FileSelect* file_select); -bool file_select_fill_count(FileSelect* file_select); -static bool file_select_init_inner(FileSelect* file_select); - -static void file_select_draw_callback(Canvas* canvas, void* _model) { - FileSelectModel* model = _model; - - string_t string_buff; - const uint8_t item_height = 16; - const uint8_t item_width = 123; - const uint8_t text_max_width = 115; - - canvas_clear(canvas); - canvas_set_font(canvas, FontSecondary); - - if(model->file_count) { - for(uint8_t i = 0; i < MIN(FILENAME_COUNT, model->file_count); i++) { - if(i == model->position) { - canvas_set_color(canvas, ColorBlack); - canvas_draw_box(canvas, 0, (i * item_height) + 1, item_width, item_height - 2); - - canvas_set_color(canvas, ColorWhite); - canvas_draw_dot(canvas, 0, (i * item_height) + 1); - canvas_draw_dot(canvas, 0, (i * item_height) + item_height - 2); - canvas_draw_dot(canvas, item_width - 1, (i * item_height) + 1); - canvas_draw_dot(canvas, item_width - 1, (i * item_height) + item_height - 2); - } else { - canvas_set_color(canvas, ColorBlack); - } - - string_init_set(string_buff, model->filename[i]); - elements_string_fit_width(canvas, string_buff, text_max_width); - canvas_draw_str( - canvas, 6, (i * item_height) + item_height - 4, string_get_cstr(string_buff)); - - string_clear(string_buff); - } - } else { - canvas_draw_str(canvas, 6, item_height, "Empty folder"); - } - elements_scrollbar(canvas, model->first_file_index + model->position, model->file_count); -} - -static bool file_select_input_callback(InputEvent* event, void* context) { - FileSelect* file_select = (FileSelect*)context; - bool consumed = false; - - if((event->type == InputTypeShort) | (event->type == InputTypeRepeat)) { - if(!file_select->init_completed) { - if(!file_select_init_inner(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyUp) { - with_view_model( - file_select->view, (FileSelectModel * model) { - if(model->position == 0) { - if(model->first_file_index == 0) { - // wrap - int16_t max_first_file_index = model->file_count - FILENAME_COUNT; - model->position = MIN(FILENAME_COUNT - 1, model->file_count - 1); - model->first_file_index = - max_first_file_index < 0 ? 0 : max_first_file_index; - } else { - model->first_file_index--; - } - } else if(model->position == 1) { - if(model->first_file_index == 0) { - model->position--; - } else { - model->first_file_index--; - } - } else { - model->position--; - } - return true; - }); - consumed = true; - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyDown) { - with_view_model( - file_select->view, (FileSelectModel * model) { - uint16_t max_first_file_index = model->file_count > FILENAME_COUNT ? - model->file_count - FILENAME_COUNT : - 0; - - if(model->position >= MIN(FILENAME_COUNT - 1, model->file_count - 1)) { - if(model->first_file_index >= max_first_file_index) { - // wrap - model->position = 0; - model->first_file_index = 0; - } else { - model->first_file_index++; - } - } else if(model->position >= (FILENAME_COUNT - 2)) { - if(model->first_file_index >= max_first_file_index) { - model->position++; - } else { - model->first_file_index++; - } - } else { - model->position++; - } - return true; - }); - consumed = true; - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } - } else if(event->key == InputKeyOk) { - if(file_select->callback != NULL) { - size_t files = 0; - if(file_select->buffer) { - with_view_model( - file_select->view, (FileSelectModel * model) { - files = model->file_count; - strlcpy( - file_select->buffer, - string_get_cstr(model->filename[model->position]), - file_select->buffer_size); - - return false; - }); - }; - if(files > 0) { - file_select->callback(true, file_select->context); - } - } - consumed = true; - } - } - - return consumed; -} - -static bool file_select_init_inner(FileSelect* file_select) { - bool result = false; - if(file_select->path && file_select->extension && file_select->fs_api) { - if(file_select_fill_count(file_select)) { - if(file_select_fill_strings(file_select)) { - file_select->init_completed = true; - result = true; - } - } - } - - return result; -} - -FileSelect* file_select_alloc() { - FileSelect* file_select = malloc(sizeof(FileSelect)); - file_select->view = view_alloc(); - file_select->fs_api = furi_record_open("storage"); - - view_set_context(file_select->view, file_select); - view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); - view_set_draw_callback(file_select->view, file_select_draw_callback); - view_set_input_callback(file_select->view, file_select_input_callback); - - with_view_model( - file_select->view, (FileSelectModel * model) { - for(uint8_t i = 0; i < FILENAME_COUNT; i++) { - string_init(model->filename[i]); - } - - model->first_file_index = 0; - model->file_count = 0; - return false; - }); - - return file_select; -} - -void file_select_free(FileSelect* file_select) { - furi_assert(file_select); - with_view_model( - file_select->view, (FileSelectModel * model) { - for(uint8_t i = 0; i < FILENAME_COUNT; i++) { - string_clear(model->filename[i]); - } - return false; - }); - view_free(file_select->view); - free(file_select); - furi_record_close("storage"); -} - -View* file_select_get_view(FileSelect* file_select) { - furi_assert(file_select); - return file_select->view; -} - -void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { - file_select->context = context; - file_select->callback = callback; -} - -void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) { - furi_assert(file_select); - file_select->path = path; - file_select->extension = extension; -} - -void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) { - file_select->buffer = buffer; - file_select->buffer_size = buffer_size; - - if(file_select->buffer) { - strlcpy(file_select->buffer, "", file_select->buffer_size); - } -} - -bool file_select_init(FileSelect* file_select) { - if(!file_select_init_inner(file_select)) { - file_select->callback(false, file_select->context); - return false; - } else { - return true; - } -} - -static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) { - bool result = false; - - if(!(file_info->flags & FSF_DIRECTORY)) { - if(strcmp(file_select->extension, "*") == 0) { - result = true; - } else if(strstr(name, file_select->extension) != NULL) { - result = true; - } - } - - return result; -} - -bool file_select_fill_strings(FileSelect* file_select) { - furi_assert(file_select); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - uint8_t string_counter = 0; - uint16_t file_counter = 0; - const uint8_t name_length = 100; - char* name = malloc(name_length); - uint16_t first_file_index = 0; - - with_view_model( - file_select->view, (FileSelectModel * model) { - first_file_index = model->first_file_index; - return false; - }); - - if(!storage_dir_open(directory, file_select->path)) { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - if(file_counter >= first_file_index) { - with_view_model( - file_select->view, (FileSelectModel * model) { - string_set_str(model->filename[string_counter], name); - - if(strcmp(file_select->extension, "*") != 0) { - string_replace_all_str( - model->filename[string_counter], file_select->extension, ""); - } - - return true; - }); - string_counter++; - - if(string_counter >= FILENAME_COUNT) { - break; - } - } - file_counter++; - } - } else { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return false; - } - } - - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; -} - -bool file_select_fill_count(FileSelect* file_select) { - furi_assert(file_select); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - uint16_t file_counter = 0; - const uint8_t name_length = 100; - char* name = malloc(name_length); - - if(!storage_dir_open(directory, file_select->path)) { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - file_counter++; - } - } else { - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return false; - } - } - - with_view_model( - file_select->view, (FileSelectModel * model) { - model->file_count = file_counter; - return false; - }); - - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return true; -} - -void file_select_set_selected_file_internal(FileSelect* file_select, const char* filename) { - furi_assert(file_select); - furi_assert(filename); - furi_assert(file_select->fs_api); - furi_assert(file_select->path); - furi_assert(file_select->extension); - - if(strlen(filename) == 0) return; - - FileInfo file_info; - File* directory = storage_file_alloc(file_select->fs_api); - - const uint8_t name_length = 100; - char* name = malloc(name_length); - uint16_t file_position = 0; - bool file_found = false; - - string_t filename_str; - string_init_set_str(filename_str, filename); - if(strcmp(file_select->extension, "*") != 0) { - string_cat_str(filename_str, file_select->extension); - } - - if(!storage_dir_open(directory, file_select->path)) { - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return; - } - - while(1) { - if(!storage_dir_read(directory, &file_info, name, name_length)) { - break; - } - - if(storage_file_get_error(directory) == FSE_OK) { - if(filter_file(file_select, &file_info, name)) { - if(strcmp(string_get_cstr(filename_str), name) == 0) { - file_found = true; - break; - } - - file_position++; - } - } else { - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); - return; - } - } - - if(file_found) { - with_view_model( - file_select->view, (FileSelectModel * model) { - uint16_t max_first_file_index = - model->file_count > FILENAME_COUNT ? model->file_count - FILENAME_COUNT : 0; - - model->first_file_index = file_position; - - if(model->first_file_index > 0) { - model->first_file_index -= 1; - } - - if(model->first_file_index >= max_first_file_index) { - model->first_file_index = max_first_file_index; - } - - model->position = file_position - model->first_file_index; - - return true; - }); - } - - string_clear(filename_str); - storage_dir_close(directory); - storage_file_free(directory); - free(name); -} - -void file_select_set_selected_file(FileSelect* file_select, const char* filename) { - file_select_set_selected_file_internal(file_select, filename); - - if(!file_select_fill_strings(file_select)) { - file_select->callback(false, file_select->context); - } -} diff --git a/applications/gui/modules/file_select.h b/applications/gui/modules/file_select.h deleted file mode 100644 index ed3d5b60..00000000 --- a/applications/gui/modules/file_select.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file file_select.h - * GUI: FileSelect view module API - */ - -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct FileSelect FileSelect; - -typedef void (*FileSelectCallback)(bool result, void* context); - -FileSelect* file_select_alloc(); - -void file_select_free(FileSelect* file_select); -View* file_select_get_view(FileSelect* file_select); - -void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); -void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); -void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); -bool file_select_init(FileSelect* file_select); -void file_select_set_selected_file(FileSelect* file_select, const char* filename); - -#ifdef __cplusplus -} -#endif diff --git a/applications/gui/modules/validators.c b/applications/gui/modules/validators.c index 242dbe68..546423d0 100644 --- a/applications/gui/modules/validators.c +++ b/applications/gui/modules/validators.c @@ -3,7 +3,7 @@ #include "applications/storage/storage.h" struct ValidatorIsFile { - const char* app_path_folder; + char* app_path_folder; const char* app_extension; char* current_name; }; @@ -40,7 +40,7 @@ ValidatorIsFile* validator_is_file_alloc_init( const char* current_name) { ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); - instance->app_path_folder = app_path_folder; + instance->app_path_folder = strdup(app_path_folder); instance->app_extension = app_extension; instance->current_name = strdup(current_name); @@ -49,6 +49,7 @@ ValidatorIsFile* validator_is_file_alloc_init( void validator_is_file_free(ValidatorIsFile* instance) { furi_assert(instance); + free(instance->app_path_folder); free(instance->current_name); free(instance); } diff --git a/applications/ibutton/ibutton.c b/applications/ibutton/ibutton.c index a38f077f..b1b5bf62 100644 --- a/applications/ibutton/ibutton.c +++ b/applications/ibutton/ibutton.c @@ -1,7 +1,8 @@ #include "ibutton.h" +#include "assets_icons.h" #include "ibutton_i.h" #include "ibutton/scenes/ibutton_scene.h" - +#include "m-string.h" #include #include @@ -85,6 +86,8 @@ void ibutton_tick_event_callback(void* context) { iButton* ibutton_alloc() { iButton* ibutton = malloc(sizeof(iButton)); + string_init(ibutton->file_path); + ibutton->scene_manager = scene_manager_alloc(&ibutton_scene_handlers, ibutton); ibutton->view_dispatcher = view_dispatcher_alloc(); @@ -176,49 +179,28 @@ void ibutton_free(iButton* ibutton) { ibutton_worker_free(ibutton->key_worker); ibutton_key_free(ibutton->key); + string_clear(ibutton->file_path); + free(ibutton); } bool ibutton_file_select(iButton* ibutton) { - bool success = dialog_file_select_show( + bool success = dialog_file_browser_show( ibutton->dialogs, - IBUTTON_APP_FOLDER, + ibutton->file_path, + ibutton->file_path, IBUTTON_APP_EXTENSION, - ibutton->file_name, - IBUTTON_FILE_NAME_SIZE, - ibutton_key_get_name_p(ibutton->key)); + true, + &I_ibutt_10px, + true); if(success) { - string_t key_str; - string_init_printf( - key_str, "%s/%s%s", IBUTTON_APP_FOLDER, ibutton->file_name, IBUTTON_APP_EXTENSION); - success = ibutton_load_key_data(ibutton, key_str); - - if(success) { - ibutton_key_set_name(ibutton->key, ibutton->file_name); - } - - string_clear(key_str); + success = ibutton_load_key_data(ibutton, ibutton->file_path); } return success; } -bool ibutton_load_key(iButton* ibutton, const char* key_name) { - string_t key_path; - string_init_set_str(key_path, key_name); - - const bool success = ibutton_load_key_data(ibutton, key_path); - - if(success) { - path_extract_filename_no_ext(key_name, key_path); - ibutton_key_set_name(ibutton->key, string_get_cstr(key_path)); - } - - string_clear(key_path); - return success; -} - bool ibutton_save_key(iButton* ibutton, const char* key_name) { // Create ibutton directory if necessary ibutton_make_app_folder(ibutton); @@ -226,27 +208,22 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { FlipperFormat* file = flipper_format_file_alloc(ibutton->storage); iButtonKey* key = ibutton->key; - string_t key_file_name; bool result = false; - string_init(key_file_name); do { // First remove key if it was saved (we rename the key) - if(!ibutton_delete_key(ibutton)) break; - - // Save the key - ibutton_key_set_name(key, key_name); + ibutton_delete_key(ibutton); // Set full file name, for new key - string_printf( - key_file_name, - "%s/%s%s", - IBUTTON_APP_FOLDER, - ibutton_key_get_name_p(key), - IBUTTON_APP_EXTENSION); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + size_t filename_start = string_search_rchar(ibutton->file_path, '/'); + string_left(ibutton->file_path, filename_start); + } + + string_cat_printf(ibutton->file_path, "/%s%s", key_name, IBUTTON_APP_EXTENSION); // Open file for write - if(!flipper_format_file_open_always(file, string_get_cstr(key_file_name))) break; + if(!flipper_format_file_open_always(file, string_get_cstr(ibutton->file_path))) break; // Write header if(!flipper_format_write_header_cstr(file, IBUTTON_APP_FILE_TYPE, 1)) break; @@ -271,8 +248,6 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { flipper_format_free(file); - string_clear(key_file_name); - if(!result) { dialog_message_show_storage_error(ibutton->dialogs, "Cannot save\nkey file"); } @@ -281,17 +256,8 @@ bool ibutton_save_key(iButton* ibutton, const char* key_name) { } bool ibutton_delete_key(iButton* ibutton) { - string_t file_name; bool result = false; - - string_init_printf( - file_name, - "%s/%s%s", - IBUTTON_APP_FOLDER, - ibutton_key_get_name_p(ibutton->key), - IBUTTON_APP_EXTENSION); - result = storage_simply_remove(ibutton->storage, string_get_cstr(file_name)); - string_clear(file_name); + result = storage_simply_remove(ibutton->storage, string_get_cstr(ibutton->file_path)); return result; } @@ -335,8 +301,17 @@ int32_t ibutton_app(void* p) { ibutton_make_app_folder(ibutton); - if(p && ibutton_load_key(ibutton, (const char*)p)) { - // TODO: Display an error if the key from p could not be loaded + bool key_loaded = false; + + if(p) { + string_set_str(ibutton->file_path, (const char*)p); + if(ibutton_load_key_data(ibutton, ibutton->file_path)) { + key_loaded = true; + // TODO: Display an error if the key from p could not be loaded + } + } + + if(key_loaded) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneEmulate); } else { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneStart); diff --git a/applications/ibutton/ibutton_i.h b/applications/ibutton/ibutton_i.h index 36857fd6..e66712be 100644 --- a/applications/ibutton/ibutton_i.h +++ b/applications/ibutton/ibutton_i.h @@ -41,7 +41,7 @@ struct iButton { iButtonWorker* key_worker; iButtonKey* key; - char file_name[IBUTTON_FILE_NAME_SIZE]; + string_t file_path; char text_store[IBUTTON_TEXT_STORE_SIZE + 1]; Submenu* submenu; @@ -74,7 +74,6 @@ typedef enum { } iButtonNotificationMessage; bool ibutton_file_select(iButton* ibutton); -bool ibutton_load_key(iButton* ibutton, const char* key_name); bool ibutton_save_key(iButton* ibutton, const char* key_name); bool ibutton_delete_key(iButton* ibutton); void ibutton_text_store_set(iButton* ibutton, const char* text, ...); diff --git a/applications/ibutton/scenes/ibutton_scene_add_type.c b/applications/ibutton/scenes/ibutton_scene_add_type.c index db129295..273330e7 100644 --- a/applications/ibutton/scenes/ibutton_scene_add_type.c +++ b/applications/ibutton/scenes/ibutton_scene_add_type.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include "m-string.h" enum SubmenuIndex { SubmenuIndexCyfral, @@ -44,7 +45,7 @@ bool ibutton_scene_add_type_on_event(void* context, SceneManagerEvent event) { furi_crash("Unknown key type"); } - ibutton_key_set_name(key, ""); + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_key_clear_data(key); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddValue); } diff --git a/applications/ibutton/scenes/ibutton_scene_delete_confirm.c b/applications/ibutton/scenes/ibutton_scene_delete_confirm.c index 73ea97cc..51f1f279 100644 --- a/applications/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include static void ibutton_scene_delete_confirm_widget_callback( GuiButtonType result, @@ -16,7 +17,11 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { iButtonKey* key = ibutton->key; const uint8_t* key_data = ibutton_key_get_data_p(key); - ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", ibutton_key_get_name_p(key)); + string_t key_name; + string_init(key_name); + path_extract_filename(ibutton->file_path, key_name, true); + + ibutton_text_store_set(ibutton, "\e#Delete %s?\e#", string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 27, AlignCenter, AlignCenter, ibutton->text_store, false); widget_add_button_element( @@ -62,6 +67,8 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { widget, 64, 33, AlignCenter, AlignBottom, FontSecondary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + string_clear(key_name); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_emulate.c b/applications/ibutton/scenes/ibutton_scene_emulate.c index 8ffe73b6..59022397 100644 --- a/applications/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/ibutton/scenes/ibutton_scene_emulate.c @@ -1,5 +1,6 @@ #include "../ibutton_i.h" #include +#include static void ibutton_scene_emulate_callback(void* context, bool emulated) { iButton* ibutton = context; @@ -15,14 +16,19 @@ void ibutton_scene_emulate_on_enter(void* context) { iButtonKey* key = ibutton->key; const uint8_t* key_data = ibutton_key_get_data_p(key); - const char* key_name = ibutton_key_get_name_p(key); + + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } uint8_t line_count = 2; DOLPHIN_DEED(DolphinDeedIbuttonEmulate); // check that stored key has name - if(strcmp(key_name, "") != 0) { - ibutton_text_store_set(ibutton, "emulating\n%s", key_name); + if(!string_empty_p(key_name)) { + ibutton_text_store_set(ibutton, "emulating\n%s", string_get_cstr(key_name)); line_count = 2; } else { // if not, show key data @@ -77,6 +83,8 @@ void ibutton_scene_emulate_on_enter(void* context) { ibutton_worker_emulate_set_callback( ibutton->key_worker, ibutton_scene_emulate_callback, ibutton); ibutton_worker_emulate_start(ibutton->key_worker, key); + + string_clear(key_name); } bool ibutton_scene_emulate_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_info.c b/applications/ibutton/scenes/ibutton_scene_info.c index 5b0af1d8..bd364ada 100644 --- a/applications/ibutton/scenes/ibutton_scene_info.c +++ b/applications/ibutton/scenes/ibutton_scene_info.c @@ -1,4 +1,5 @@ #include "../ibutton_i.h" +#include void ibutton_scene_info_on_enter(void* context) { iButton* ibutton = context; @@ -7,7 +8,11 @@ void ibutton_scene_info_on_enter(void* context) { const uint8_t* key_data = ibutton_key_get_data_p(key); - ibutton_text_store_set(ibutton, "%s", ibutton_key_get_name_p(key)); + string_t key_name; + string_init(key_name); + path_extract_filename(ibutton->file_path, key_name, true); + + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); widget_add_text_box_element( widget, 0, 0, 128, 28, AlignCenter, AlignCenter, ibutton->text_store, false); @@ -46,6 +51,8 @@ void ibutton_scene_info_on_enter(void* context) { widget, 64, 35, AlignCenter, AlignBottom, FontPrimary, ibutton->text_store); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); + + string_clear(key_name); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_read.c b/applications/ibutton/scenes/ibutton_scene_read.c index a25f27e6..0cc0a8df 100644 --- a/applications/ibutton/scenes/ibutton_scene_read.c +++ b/applications/ibutton/scenes/ibutton_scene_read.c @@ -18,7 +18,7 @@ void ibutton_scene_read_on_enter(void* context) { popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); - ibutton_key_set_name(key, ""); + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); ibutton_worker_read_set_callback(worker, ibutton_scene_read_callback, ibutton); ibutton_worker_read_start(worker, key); diff --git a/applications/ibutton/scenes/ibutton_scene_save_name.c b/applications/ibutton/scenes/ibutton_scene_save_name.c index b1baf6af..6caf5d2d 100644 --- a/applications/ibutton/scenes/ibutton_scene_save_name.c +++ b/applications/ibutton/scenes/ibutton_scene_save_name.c @@ -1,5 +1,7 @@ #include "../ibutton_i.h" +#include "m-string.h" #include +#include static void ibutton_scene_save_name_text_input_callback(void* context) { iButton* ibutton = context; @@ -10,13 +12,17 @@ void ibutton_scene_save_name_on_enter(void* context) { iButton* ibutton = context; TextInput* text_input = ibutton->text_input; - const char* key_name = ibutton_key_get_name_p(ibutton->key); - const bool key_name_is_empty = !strcmp(key_name, ""); + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } + const bool key_name_is_empty = string_empty_p(key_name); if(key_name_is_empty) { set_random_name(ibutton->text_store, IBUTTON_TEXT_STORE_SIZE); } else { - ibutton_text_store_set(ibutton, "%s", key_name); + ibutton_text_store_set(ibutton, "%s", string_get_cstr(key_name)); } text_input_set_header_text(text_input, "Name the key"); @@ -28,11 +34,19 @@ void ibutton_scene_save_name_on_enter(void* context) { IBUTTON_KEY_NAME_SIZE, key_name_is_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(IBUTTON_APP_FOLDER, IBUTTON_APP_EXTENSION, key_name); + string_t folder_path; + string_init(folder_path); + + path_extract_dirname(string_get_cstr(ibutton->file_path), folder_path); + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), IBUTTON_APP_EXTENSION, string_get_cstr(key_name)); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewTextInput); + + string_clear(key_name); + string_clear(folder_path); } bool ibutton_scene_save_name_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/ibutton/scenes/ibutton_scene_start.c b/applications/ibutton/scenes/ibutton_scene_start.c index a3141f5a..cc8af983 100644 --- a/applications/ibutton/scenes/ibutton_scene_start.c +++ b/applications/ibutton/scenes/ibutton_scene_start.c @@ -36,6 +36,7 @@ bool ibutton_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexRead) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneRead); } else if(event.event == SubmenuIndexSaved) { + string_set_str(ibutton->file_path, IBUTTON_APP_FOLDER); scene_manager_next_scene(ibutton->scene_manager, iButtonSceneSelectKey); } else if(event.event == SubmenuIndexAdd) { scene_manager_next_scene(ibutton->scene_manager, iButtonSceneAddType); diff --git a/applications/ibutton/scenes/ibutton_scene_write.c b/applications/ibutton/scenes/ibutton_scene_write.c index 35e45d83..4ce19408 100644 --- a/applications/ibutton/scenes/ibutton_scene_write.c +++ b/applications/ibutton/scenes/ibutton_scene_write.c @@ -1,4 +1,6 @@ #include "../ibutton_i.h" +#include "m-string.h" +#include "toolbox/path.h" typedef enum { iButtonSceneWriteStateDefault, @@ -17,13 +19,18 @@ void ibutton_scene_write_on_enter(void* context) { iButtonWorker* worker = ibutton->key_worker; const uint8_t* key_data = ibutton_key_get_data_p(key); - const char* key_name = ibutton_key_get_name_p(key); + + string_t key_name; + string_init(key_name); + if(string_end_with_str_p(ibutton->file_path, IBUTTON_APP_EXTENSION)) { + path_extract_filename(ibutton->file_path, key_name, true); + } uint8_t line_count = 2; // check that stored key has name - if(strcmp(key_name, "") != 0) { - ibutton_text_store_set(ibutton, "writing\n%s", key_name); + if(!string_empty_p(key_name)) { + ibutton_text_store_set(ibutton, "writing\n%s", string_get_cstr(key_name)); line_count = 2; } else { // if not, show key data @@ -79,6 +86,8 @@ void ibutton_scene_write_on_enter(void* context) { ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); ibutton_worker_write_start(worker, key); + + string_clear(key_name); } bool ibutton_scene_write_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/infrared/infrared_app.cpp b/applications/infrared/infrared_app.cpp index 5dc3b311..1ac859d1 100644 --- a/applications/infrared/infrared_app.cpp +++ b/applications/infrared/infrared_app.cpp @@ -1,4 +1,5 @@ #include "infrared_app.h" +#include "m-string.h" #include #include #include @@ -12,20 +13,18 @@ int32_t InfraredApp::run(void* args) { bool exit = false; if(args) { - std::string path = static_cast(args); - std::string remote_name(path, path.find_last_of('/') + 1, path.size()); - auto last_dot = remote_name.find_last_of('.'); - if(last_dot != std::string::npos) { - remote_name.erase(last_dot); - path.erase(path.find_last_of('/')); - bool result = remote_manager.load(path, remote_name); + string_t path; + string_init_set_str(path, (char*)args); + if(string_end_with_str_p(path, InfraredApp::infrared_extension)) { + bool result = remote_manager.load(path); if(result) { current_scene = InfraredApp::Scene::Remote; } else { - printf("Failed to load remote \'%s\'\r\n", remote_name.c_str()); + printf("Failed to load remote \'%s\'\r\n", string_get_cstr(path)); return -1; } } + string_clear(path); } scenes[current_scene]->on_enter(this); @@ -51,6 +50,7 @@ int32_t InfraredApp::run(void* args) { InfraredApp::InfraredApp() { furi_check(InfraredAppRemoteManager::max_button_name_length < get_text_store_size()); + string_init_set_str(file_path, InfraredApp::infrared_directory); notification = static_cast(furi_record_open("notification")); dialogs = static_cast(furi_record_open("dialogs")); infrared_worker = infrared_worker_alloc(); @@ -60,6 +60,7 @@ InfraredApp::~InfraredApp() { infrared_worker_free(infrared_worker); furi_record_close("notification"); furi_record_close("dialogs"); + string_clear(file_path); for(auto& [key, scene] : scenes) delete scene; } diff --git a/applications/infrared/infrared_app.h b/applications/infrared/infrared_app.h index e0db7c51..1cb8b661 100644 --- a/applications/infrared/infrared_app.h +++ b/applications/infrared/infrared_app.h @@ -251,6 +251,8 @@ public: /** Main class destructor, deinitializes all critical objects */ ~InfraredApp(); + string_t file_path; + /** Path to Infrared directory */ static constexpr const char* infrared_directory = "/any/infrared"; /** Infrared files extension (remote files and universal databases) */ diff --git a/applications/infrared/infrared_app_remote_manager.cpp b/applications/infrared/infrared_app_remote_manager.cpp index 4fd64553..faeccb39 100644 --- a/applications/infrared/infrared_app_remote_manager.cpp +++ b/applications/infrared/infrared_app_remote_manager.cpp @@ -1,3 +1,5 @@ +#include "m-string.h" +#include "storage/filesystem_api_defines.h" #include #include "infrared_app_remote_manager.h" #include "infrared/helpers/infrared_parser.h" @@ -11,44 +13,58 @@ #include #include #include "infrared_app.h" +#include static const char* default_remote_name = "remote"; -std::string InfraredAppRemoteManager::make_full_name( - const std::string& path, - const std::string& remote_name) const { - return std::string("") + path + "/" + remote_name + InfraredApp::infrared_extension; -} - -std::string InfraredAppRemoteManager::find_vacant_remote_name(const std::string& name) { - std::string result_name; +void InfraredAppRemoteManager::find_vacant_remote_name(string_t name, string_t path) { Storage* storage = static_cast(furi_record_open("storage")); - FS_Error error = storage_common_stat( - storage, make_full_name(InfraredApp::infrared_directory, name).c_str(), NULL); + string_t base_path; + string_init_set(base_path, path); - if(error == FSE_NOT_EXIST) { - result_name = name; - } else if(error != FSE_OK) { - result_name = std::string(); - } else { + if(string_end_with_str_p(base_path, InfraredApp::infrared_extension)) { + size_t filename_start = string_search_rchar(base_path, '/'); + string_left(base_path, filename_start); + } + + string_printf( + base_path, + "%s/%s%s", + string_get_cstr(path), + string_get_cstr(name), + InfraredApp::infrared_extension); + + FS_Error error = storage_common_stat(storage, string_get_cstr(base_path), NULL); + + if(error == FSE_OK) { /* if suggested name is occupied, try another one (name2, name3, etc) */ + size_t dot = string_search_rchar(base_path, '.'); + string_left(base_path, dot); + + string_t path_temp; + string_init(path_temp); + uint32_t i = 1; - std::string new_name; do { - new_name = make_full_name(InfraredApp::infrared_directory, name + std::to_string(++i)); - error = storage_common_stat(storage, new_name.c_str(), NULL); + string_printf( + path_temp, + "%s%u%s", + string_get_cstr(base_path), + ++i, + InfraredApp::infrared_extension); + error = storage_common_stat(storage, string_get_cstr(path_temp), NULL); } while(error == FSE_OK); + string_clear(path_temp); + if(error == FSE_NOT_EXIST) { - result_name = name + std::to_string(i); - } else { - result_name = std::string(); + string_cat_printf(name, "%u", i); } } + string_clear(base_path); furi_record_close("storage"); - return result_name; } bool InfraredAppRemoteManager::add_button(const char* button_name, const InfraredAppSignal& signal) { @@ -61,12 +77,23 @@ bool InfraredAppRemoteManager::add_remote_with_button( const InfraredAppSignal& signal) { furi_check(button_name != nullptr); - auto new_name = find_vacant_remote_name(default_remote_name); - if(new_name.empty()) { - return false; - } + string_t new_name; + string_init_set_str(new_name, default_remote_name); + + string_t new_path; + string_init_set_str(new_path, InfraredApp::infrared_directory); + + find_vacant_remote_name(new_name, new_path); + + string_cat_printf( + new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); + + remote = std::make_unique(new_path); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_path); + string_clear(new_name); - remote = std::make_unique(InfraredApp::infrared_directory, new_name); return add_button(button_name, signal); } @@ -93,8 +120,7 @@ const InfraredAppSignal& InfraredAppRemoteManager::get_button_data(size_t index) bool InfraredAppRemoteManager::delete_remote() { Storage* storage = static_cast(furi_record_open("storage")); - FS_Error error = - storage_common_remove(storage, make_full_name(remote->path, remote->name).c_str()); + FS_Error error = storage_common_remove(storage, string_get_cstr(remote->path)); reset_remote(); furi_record_close("storage"); @@ -128,22 +154,33 @@ std::string InfraredAppRemoteManager::get_remote_name() { bool InfraredAppRemoteManager::rename_remote(const char* str) { furi_check(str != nullptr); furi_check(remote.get() != nullptr); + furi_check(!string_empty_p(remote->path)); if(!remote->name.compare(str)) { return true; } - auto new_name = find_vacant_remote_name(str); - if(new_name.empty()) { - return false; + string_t new_name; + string_init_set_str(new_name, str); + find_vacant_remote_name(new_name, remote->path); + + string_t new_path; + string_init_set(new_path, remote->path); + if(string_end_with_str_p(new_path, InfraredApp::infrared_extension)) { + size_t filename_start = string_search_rchar(new_path, '/'); + string_left(new_path, filename_start); } + string_cat_printf( + new_path, "/%s%s", string_get_cstr(new_name), InfraredApp::infrared_extension); Storage* storage = static_cast(furi_record_open("storage")); - std::string old_filename = make_full_name(remote->path, remote->name); - std::string new_filename = make_full_name(remote->path, new_name); - FS_Error error = storage_common_rename(storage, old_filename.c_str(), new_filename.c_str()); - remote->name = new_name; + FS_Error error = + storage_common_rename(storage, string_get_cstr(remote->path), string_get_cstr(new_path)); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_name); + string_clear(new_path); furi_record_close("storage"); return (error == FSE_OK || error == FSE_EXIST); @@ -171,10 +208,8 @@ bool InfraredAppRemoteManager::store(void) { FlipperFormat* ff = flipper_format_file_alloc(storage); - FURI_LOG_I( - "RemoteManager", "store file: \'%s\'", make_full_name(remote->path, remote->name).c_str()); - result = - flipper_format_file_open_always(ff, make_full_name(remote->path, remote->name).c_str()); + FURI_LOG_I("RemoteManager", "store file: \'%s\'", string_get_cstr(remote->path)); + result = flipper_format_file_open_always(ff, string_get_cstr(remote->path)); if(result) { result = flipper_format_write_header_cstr(ff, "IR signals file", 1); } @@ -192,13 +227,13 @@ bool InfraredAppRemoteManager::store(void) { return result; } -bool InfraredAppRemoteManager::load(const std::string& path, const std::string& remote_name) { +bool InfraredAppRemoteManager::load(string_t path) { bool result = false; Storage* storage = static_cast(furi_record_open("storage")); FlipperFormat* ff = flipper_format_file_alloc(storage); - FURI_LOG_I("RemoteManager", "load file: \'%s\'", make_full_name(path, remote_name).c_str()); - result = flipper_format_file_open_existing(ff, make_full_name(path, remote_name).c_str()); + FURI_LOG_I("RemoteManager", "load file: \'%s\'", string_get_cstr(path)); + result = flipper_format_file_open_existing(ff, string_get_cstr(path)); if(result) { string_t header; string_init(header); @@ -210,7 +245,14 @@ bool InfraredAppRemoteManager::load(const std::string& path, const std::string& string_clear(header); } if(result) { - remote = std::make_unique(path, remote_name); + string_t new_name; + string_init(new_name); + + remote = std::make_unique(path); + path_extract_filename(path, new_name, true); + remote->name = std::string(string_get_cstr(new_name)); + + string_clear(new_name); InfraredAppSignal signal; std::string signal_name; while(infrared_parser_read_signal(ff, signal, signal_name)) { diff --git a/applications/infrared/infrared_app_remote_manager.h b/applications/infrared/infrared_app_remote_manager.h index 31557d54..b6f0b170 100644 --- a/applications/infrared/infrared_app_remote_manager.h +++ b/applications/infrared/infrared_app_remote_manager.h @@ -8,6 +8,7 @@ #include "infrared_app_signal.h" +#include "m-string.h" #include #include @@ -60,17 +61,19 @@ class InfraredAppRemote { /** Name of remote */ std::string name; /** Path to remote file */ - std::string path; + string_t path; public: /** Initialize new remote * * @param path - remote file path - * @param name - new remote name */ - InfraredAppRemote(const std::string& path, const std::string& name) - : name(name) - , path(path) { + InfraredAppRemote(string_t file_path) { + string_init_set(path, file_path); + } + + ~InfraredAppRemote() { + string_clear(path); } }; @@ -78,12 +81,6 @@ public: class InfraredAppRemoteManager { /** Remote instance. There can be 1 remote loaded at a time. */ std::unique_ptr remote; - /** Make full name from remote name - * - * @param remote_name name of remote - * @retval full name of remote on disk - */ - std::string make_full_name(const std::string& path, const std::string& remote_name) const; public: /** Restriction to button name length. Buttons larger are ignored. */ @@ -125,9 +122,9 @@ public: * incremented digit(2,3,4,etc) added to name and check repeated. * * @param name - suggested remote name - * @retval garanteed free remote name, prefixed with suggested + * @param path - remote file path */ - std::string find_vacant_remote_name(const std::string& name); + void find_vacant_remote_name(string_t name, string_t path); /** Get button list * @@ -185,8 +182,8 @@ public: /** Load data from disk into current remote * - * @param name - name of remote to load + * @param path - path to remote file * @retval true if success, false otherwise */ - bool load(const std::string& path, const std::string& name); + bool load(string_t path); }; diff --git a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp index dc63c64b..761da49f 100644 --- a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp +++ b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp @@ -1,4 +1,6 @@ #include "../infrared_app.h" +#include "m-string.h" +#include "toolbox/path.h" void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { InfraredAppViewManager* view_manager = app->get_view_manager(); @@ -21,9 +23,18 @@ void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { enter_name_length = InfraredAppRemoteManager::max_remote_name_length; text_input_set_header_text(text_input, "Name the remote"); + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(app->file_path, InfraredApp::infrared_extension)) { + path_extract_dirname(string_get_cstr(app->file_path), folder_path); + } + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( - app->infrared_directory, app->infrared_extension, remote_name.c_str()); + string_get_cstr(folder_path), app->infrared_extension, remote_name.c_str()); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); + + string_clear(folder_path); } text_input_set_result_callback( diff --git a/applications/infrared/scene/infrared_app_scene_remote_list.cpp b/applications/infrared/scene/infrared_app_scene_remote_list.cpp index f59ff3e9..c72acb6e 100644 --- a/applications/infrared/scene/infrared_app_scene_remote_list.cpp +++ b/applications/infrared/scene/infrared_app_scene_remote_list.cpp @@ -1,4 +1,5 @@ #include "../infrared_app.h" +#include "assets_icons.h" #include "infrared/infrared_app_event.h" #include @@ -8,11 +9,6 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { bool result = false; bool file_select_result; auto remote_manager = app->get_remote_manager(); - auto last_selected_remote = remote_manager->get_remote_name(); - const char* last_selected_remote_name = - last_selected_remote.size() ? last_selected_remote.c_str() : nullptr; - auto filename_ts = - std::make_unique(InfraredAppRemoteManager::max_remote_name_length); DialogsApp* dialogs = app->get_dialogs(); InfraredAppViewManager* view_manager = app->get_view_manager(); @@ -20,16 +16,17 @@ void InfraredAppSceneRemoteList::on_enter(InfraredApp* app) { button_menu_reset(button_menu); view_manager->switch_to(InfraredAppViewManager::ViewId::ButtonMenu); - file_select_result = dialog_file_select_show( + file_select_result = dialog_file_browser_show( dialogs, - InfraredApp::infrared_directory, + app->file_path, + app->file_path, InfraredApp::infrared_extension, - filename_ts->text, - filename_ts->text_size, - last_selected_remote_name); + true, + &I_ir_10px, + true); if(file_select_result) { - if(remote_manager->load(InfraredApp::infrared_directory, std::string(filename_ts->text))) { + if(remote_manager->load(app->file_path)) { app->switch_to_next_scene(InfraredApp::Scene::Remote); result = true; } diff --git a/applications/infrared/scene/infrared_app_scene_start.cpp b/applications/infrared/scene/infrared_app_scene_start.cpp index c42ab9fe..5efdce7a 100644 --- a/applications/infrared/scene/infrared_app_scene_start.cpp +++ b/applications/infrared/scene/infrared_app_scene_start.cpp @@ -26,6 +26,8 @@ void InfraredAppSceneStart::on_enter(InfraredApp* app) { submenu, "Learn New Remote", SubmenuIndexLearnNewRemote, submenu_callback, app); submenu_add_item(submenu, "Saved Remotes", SubmenuIndexSavedRemotes, submenu_callback, app); submenu_set_selected_item(submenu, submenu_item_selected); + + string_set_str(app->file_path, InfraredApp::infrared_directory); submenu_item_selected = 0; view_manager->switch_to(InfraredAppViewManager::ViewId::Submenu); diff --git a/applications/lfrfid/lfrfid_app.cpp b/applications/lfrfid/lfrfid_app.cpp index c2274234..4027a07e 100644 --- a/applications/lfrfid/lfrfid_app.cpp +++ b/applications/lfrfid/lfrfid_app.cpp @@ -1,4 +1,7 @@ #include "lfrfid_app.h" +#include "assets_icons.h" +#include "furi/common_defines.h" +#include "m-string.h" #include "scene/lfrfid_app_scene_start.h" #include "scene/lfrfid_app_scene_read.h" #include "scene/lfrfid_app_scene_read_success.h" @@ -31,9 +34,11 @@ LfRfidApp::LfRfidApp() , storage{"storage"} , dialogs{"dialogs"} , text_store(40) { + string_init_set_str(file_path, app_folder); } LfRfidApp::~LfRfidApp() { + string_clear(file_path); } void LfRfidApp::run(void* _args) { @@ -42,7 +47,8 @@ void LfRfidApp::run(void* _args) { make_app_folder(); if(strlen(args)) { - load_key_data(args, &worker.key); + string_set_str(file_path, args); + load_key_data(file_path, &worker.key); scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); scene_controller.process(100, SceneType::Emulate); } else { @@ -69,65 +75,49 @@ void LfRfidApp::run(void* _args) { } bool LfRfidApp::save_key(RfidKey* key) { - string_t file_name; bool result = false; make_app_folder(); - string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); - result = save_key_data(string_get_cstr(file_name), key); - string_clear(file_name); + if(string_end_with_str_p(file_path, app_extension)) { + size_t filename_start = string_search_rchar(file_path, '/'); + string_left(file_path, filename_start); + } + string_cat_printf(file_path, "/%s%s", key->get_name(), app_extension); + + result = save_key_data(file_path, key); return result; } bool LfRfidApp::load_key_from_file_select(bool need_restore) { - TextStore* filename_ts = new TextStore(64); - bool result = false; - - if(need_restore) { - result = dialog_file_select_show( - dialogs, - app_folder, - app_extension, - filename_ts->text, - filename_ts->text_size, - worker.key.get_name()); - } else { - result = dialog_file_select_show( - dialogs, app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL); + if(!need_restore) { + string_set_str(file_path, app_folder); } + bool result = dialog_file_browser_show( + dialogs, file_path, file_path, app_extension, true, &I_125_10px, true); + if(result) { - string_t key_str; - string_init_printf(key_str, "%s/%s%s", app_folder, filename_ts->text, app_extension); - result = load_key_data(string_get_cstr(key_str), &worker.key); - string_clear(key_str); + result = load_key_data(file_path, &worker.key); } - delete filename_ts; return result; } bool LfRfidApp::delete_key(RfidKey* key) { - string_t file_name; - bool result = false; - - string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); - result = storage_simply_remove(storage, string_get_cstr(file_name)); - string_clear(file_name); - - return result; + UNUSED(key); + return storage_simply_remove(storage, string_get_cstr(file_path)); } -bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { +bool LfRfidApp::load_key_data(string_t path, RfidKey* key) { FlipperFormat* file = flipper_format_file_alloc(storage); bool result = false; string_t str_result; string_init(str_result); do { - if(!flipper_format_file_open_existing(file, path)) break; + if(!flipper_format_file_open_existing(file, string_get_cstr(path))) break; // header uint32_t version; @@ -149,7 +139,7 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { break; loaded_key.set_data(key_data, loaded_key.get_type_data_count()); - path_extract_filename_no_ext(path, str_result); + path_extract_filename(path, str_result, true); loaded_key.set_name(string_get_cstr(str_result)); *key = loaded_key; @@ -166,12 +156,12 @@ bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { return result; } -bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { +bool LfRfidApp::save_key_data(string_t path, RfidKey* key) { FlipperFormat* file = flipper_format_file_alloc(storage); bool result = false; do { - if(!flipper_format_file_open_always(file, path)) break; + if(!flipper_format_file_open_always(file, string_get_cstr(path))) break; if(!flipper_format_write_header_cstr(file, app_filetype, 1)) break; if(!flipper_format_write_comment_cstr(file, "Key type can be EM4100, H10301 or I40134")) break; diff --git a/applications/lfrfid/lfrfid_app.h b/applications/lfrfid/lfrfid_app.h index dddfb753..3f8209a1 100644 --- a/applications/lfrfid/lfrfid_app.h +++ b/applications/lfrfid/lfrfid_app.h @@ -1,4 +1,5 @@ #pragma once +#include "m-string.h" #include #include @@ -76,6 +77,8 @@ public: TextStore text_store; + string_t file_path; + void run(void* args); static const char* app_folder; @@ -86,8 +89,8 @@ public: bool load_key_from_file_select(bool need_restore); bool delete_key(RfidKey* key); - bool load_key_data(const char* path, RfidKey* key); - bool save_key_data(const char* path, RfidKey* key); + bool load_key_data(string_t path, RfidKey* key); + bool save_key_data(string_t path, RfidKey* key); void make_app_folder(); }; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp index d460724e..d7ba2c9e 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp @@ -1,11 +1,14 @@ #include "lfrfid_app_scene_save_name.h" +#include "m-string.h" #include +#include void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { const char* key_name = app->worker.key.get_name(); bool key_name_empty = !strcmp(key_name, ""); if(key_name_empty) { + string_set_str(app->file_path, app->app_folder); set_random_name(app->text_store.text, app->text_store.text_size); } else { app->text_store.set("%s", key_name); @@ -21,10 +24,17 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool /* need_restore */) { app->worker.key.get_name_length(), key_name_empty); + string_t folder_path; + string_init(folder_path); + + path_extract_dirname(string_get_cstr(app->file_path), folder_path); + ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(app->app_folder, app->app_extension, key_name); + validator_is_file_alloc_init(string_get_cstr(folder_path), app->app_extension, key_name); text_input->set_validator(validator_is_file_callback, validator_is_file); + string_clear(folder_path); + app->view_controller.switch_to(); } diff --git a/applications/music_player/music_player.c b/applications/music_player/music_player.c index 6dfef492..9b5dda0f 100644 --- a/applications/music_player/music_player.c +++ b/applications/music_player/music_player.c @@ -1,3 +1,5 @@ +#include "assets_icons.h" +#include "m-string.h" #include #include @@ -298,23 +300,23 @@ int32_t music_player_app(void* p) { if(p) { string_cat_str(file_path, p); } else { - char file_name[256] = {0}; + string_set_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + DialogsApp* dialogs = furi_record_open("dialogs"); - bool res = dialog_file_select_show( + bool res = dialog_file_browser_show( dialogs, - MUSIC_PLAYER_APP_PATH_FOLDER, + file_path, + file_path, MUSIC_PLAYER_APP_EXTENSION, - file_name, - 255, - NULL); + true, + &I_music_10px, + false); + furi_record_close("dialogs"); if(!res) { FURI_LOG_E(TAG, "No file selected"); break; } - string_cat_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); - string_cat_str(file_path, "/"); - string_cat_str(file_path, file_name); } if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { diff --git a/applications/nfc/nfc_device.c b/applications/nfc/nfc_device.c index 63f0c3cc..ca3fc45a 100644 --- a/applications/nfc/nfc_device.c +++ b/applications/nfc/nfc_device.c @@ -1,4 +1,6 @@ #include "nfc_device.h" +#include "assets_icons.h" +#include "m-string.h" #include "nfc_types.h" #include @@ -14,6 +16,7 @@ NfcDevice* nfc_device_alloc() { NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); nfc_dev->storage = furi_record_open("storage"); nfc_dev->dialogs = furi_record_open("dialogs"); + string_init(nfc_dev->load_path); return nfc_dev; } @@ -22,6 +25,7 @@ void nfc_device_free(NfcDevice* nfc_dev) { nfc_device_clear(nfc_dev); furi_record_close("storage"); furi_record_close("dialogs"); + string_clear(nfc_dev->load_path); free(nfc_dev); } @@ -730,11 +734,24 @@ void nfc_device_set_name(NfcDevice* dev, const char* name) { strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); } +static void nfc_device_get_path_without_ext(string_t orig_path, string_t shadow_path) { + // TODO: this won't work if there is ".nfc" anywhere in the path other than + // at the end + size_t ext_start = string_search_str(orig_path, NFC_APP_EXTENSION); + string_set_n(shadow_path, orig_path, 0, ext_start); +} + +static void nfc_device_get_shadow_path(string_t orig_path, string_t shadow_path) { + nfc_device_get_path_without_ext(orig_path, shadow_path); + string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); +} + static bool nfc_device_save_file( NfcDevice* dev, const char* dev_name, const char* folder, - const char* extension) { + const char* extension, + bool use_load_path) { furi_assert(dev); bool saved = false; @@ -744,10 +761,19 @@ static bool nfc_device_save_file( string_init(temp_str); do { - // Create nfc directory if necessary - if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; - // First remove nfc device file if it was saved - string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + if(use_load_path && !string_empty_p(dev->load_path)) { + // Get directory name + path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; + // Make path to file to save + string_cat_printf(temp_str, "/%s%s", dev_name, extension); + } else { + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // First remove nfc device file if it was saved + string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + } // Open file if(!flipper_format_file_open_always(file, string_get_cstr(temp_str))) break; // Write header @@ -786,12 +812,12 @@ static bool nfc_device_save_file( } bool nfc_device_save(NfcDevice* dev, const char* dev_name) { - return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION); + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true); } bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { dev->shadow_file_exist = true; - return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION); + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); } static bool nfc_device_load_data(NfcDevice* dev, string_t path) { @@ -805,9 +831,7 @@ static bool nfc_device_load_data(NfcDevice* dev, string_t path) { do { // Check existance of shadow file - size_t ext_start = string_search_str(path, NFC_APP_EXTENSION); - string_set_n(temp_str, path, 0, ext_start); - string_cat_printf(temp_str, "%s", NFC_APP_SHADOW_EXTENSION); + nfc_device_get_shadow_path(path, temp_str); dev->shadow_file_exist = storage_common_stat(dev->storage, string_get_cstr(temp_str), NULL) == FSE_OK; // Open shadow file if it exists. If not - open original @@ -864,15 +888,16 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { furi_assert(file_path); // Load device data - string_t path; - string_init_set_str(path, file_path); - bool dev_load = nfc_device_load_data(dev, path); + string_set_str(dev->load_path, file_path); + bool dev_load = nfc_device_load_data(dev, dev->load_path); if(dev_load) { // Set device name - path_extract_filename_no_ext(file_path, path); - nfc_device_set_name(dev, string_get_cstr(path)); + string_t filename; + string_init(filename); + path_extract_filename_no_ext(file_path, filename); + nfc_device_set_name(dev, string_get_cstr(filename)); + string_clear(filename); } - string_clear(path); return dev_load; } @@ -880,23 +905,19 @@ bool nfc_device_load(NfcDevice* dev, const char* file_path) { bool nfc_file_select(NfcDevice* dev) { furi_assert(dev); - // Input events and views are managed by file_select - bool res = dialog_file_select_show( - dev->dialogs, - NFC_APP_FOLDER, - NFC_APP_EXTENSION, - dev->file_name, - sizeof(dev->file_name), - dev->dev_name); + // Input events and views are managed by file_browser + bool res = dialog_file_browser_show( + dev->dialogs, dev->load_path, dev->load_path, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); if(res) { - string_t dev_str; - // Get key file path - string_init_printf(dev_str, "%s/%s%s", NFC_APP_FOLDER, dev->file_name, NFC_APP_EXTENSION); - res = nfc_device_load_data(dev, dev_str); + string_t filename; + string_init(filename); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); + res = nfc_device_load_data(dev, dev->load_path); if(res) { - nfc_device_set_name(dev, dev->file_name); + nfc_device_set_name(dev, dev->dev_name); } - string_clear(dev_str); + string_clear(filename); } return res; @@ -914,9 +935,10 @@ void nfc_device_clear(NfcDevice* dev) { nfc_device_data_clear(&dev->dev_data); memset(&dev->dev_data, 0, sizeof(dev->dev_data)); dev->format = NfcDeviceSaveFormatUid; + string_set_str(dev->load_path, NFC_APP_FOLDER); } -bool nfc_device_delete(NfcDevice* dev) { +bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { furi_assert(dev); bool deleted = false; @@ -925,12 +947,20 @@ bool nfc_device_delete(NfcDevice* dev) { do { // Delete original file - string_init_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(file_path, dev->load_path); + } else { + string_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; // Delete shadow file if it exists if(dev->shadow_file_exist) { - string_printf( - file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, file_path); + } else { + string_printf( + file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; } deleted = true; @@ -944,19 +974,29 @@ bool nfc_device_delete(NfcDevice* dev) { return deleted; } -bool nfc_device_restore(NfcDevice* dev) { +bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { furi_assert(dev); furi_assert(dev->shadow_file_exist); bool restored = false; string_t path; + string_init(path); + do { - string_init_printf( - path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, path); + } else { + string_printf( + path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; dev->shadow_file_exist = false; - string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(path, dev->load_path); + } else { + string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } if(!nfc_device_load_data(dev, path)) break; restored = true; } while(0); diff --git a/applications/nfc/nfc_device.h b/applications/nfc/nfc_device.h index 215f637f..400f6c36 100644 --- a/applications/nfc/nfc_device.h +++ b/applications/nfc/nfc_device.h @@ -12,7 +12,6 @@ #include #define NFC_DEV_NAME_MAX_LEN 22 -#define NFC_FILE_NAME_MAX_LEN 120 #define NFC_READER_DATA_MAX_SIZE 64 #define NFC_APP_FOLDER "/any/nfc" @@ -57,7 +56,7 @@ typedef struct { DialogsApp* dialogs; NfcDeviceData dev_data; char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; - char file_name[NFC_FILE_NAME_MAX_LEN]; + string_t load_path; NfcDeviceSaveFormat format; bool shadow_file_exist; } NfcDevice; @@ -80,6 +79,6 @@ void nfc_device_data_clear(NfcDeviceData* dev); void nfc_device_clear(NfcDevice* dev); -bool nfc_device_delete(NfcDevice* dev); +bool nfc_device_delete(NfcDevice* dev, bool use_load_path); -bool nfc_device_restore(NfcDevice* dev); +bool nfc_device_restore(NfcDevice* dev, bool use_load_path); diff --git a/applications/nfc/scenes/nfc_scene_delete.c b/applications/nfc/scenes/nfc_scene_delete.c index e8ba3e44..1946b929 100755 --- a/applications/nfc/scenes/nfc_scene_delete.c +++ b/applications/nfc/scenes/nfc_scene_delete.c @@ -73,7 +73,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { if(event.event == GuiButtonTypeLeft) { return scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == GuiButtonTypeRight) { - if(nfc_device_delete(nfc->dev)) { + if(nfc_device_delete(nfc->dev, true)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/nfc/scenes/nfc_scene_save_name.c b/applications/nfc/scenes/nfc_scene_save_name.c old mode 100755 new mode 100644 index e95c97eb..d5e05472 --- a/applications/nfc/scenes/nfc_scene_save_name.c +++ b/applications/nfc/scenes/nfc_scene_save_name.c @@ -1,6 +1,8 @@ #include "../nfc_i.h" +#include "m-string.h" #include #include +#include void nfc_scene_save_name_text_input_callback(void* context) { Nfc* nfc = context; @@ -29,11 +31,22 @@ void nfc_scene_save_name_on_enter(void* context) { NFC_DEV_NAME_MAX_LEN, dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, nfc->dev->dev_name); + string_t folder_path; + string_init(folder_path); + + if(string_end_with_str_p(nfc->dev->load_path, NFC_APP_EXTENSION)) { + path_extract_dirname(string_get_cstr(nfc->dev->load_path), folder_path); + } else { + string_set_str(folder_path, NFC_APP_FOLDER); + } + + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(folder_path), NFC_APP_EXTENSION, nfc->dev->dev_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput); + + string_clear(folder_path); } bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { @@ -43,7 +56,7 @@ bool nfc_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventTextInputDone) { if(strcmp(nfc->dev->dev_name, "")) { - nfc_device_delete(nfc->dev); + nfc_device_delete(nfc->dev, true); } if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetUid)) { nfc->dev->dev_data.nfc_data = nfc->dev_edit_data; diff --git a/applications/nfc/scenes/nfc_scene_save_success.c b/applications/nfc/scenes/nfc_scene_save_success.c index 985897a6..5c15a509 100644 --- a/applications/nfc/scenes/nfc_scene_save_success.c +++ b/applications/nfc/scenes/nfc_scene_save_success.c @@ -30,9 +30,6 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneCardMenu)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneCardMenu); - } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { - consumed = scene_manager_search_and_switch_to_another_scene( - nfc->scene_manager, NfcSceneFileSelect); } else if(scene_manager_has_previous_scene( nfc->scene_manager, NfcSceneMifareDesfireMenu)) { consumed = scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/nfc/scenes/nfc_scene_saved_menu.c b/applications/nfc/scenes/nfc_scene_saved_menu.c index 1b435ccd..f2b2dea3 100644 --- a/applications/nfc/scenes/nfc_scene_saved_menu.c +++ b/applications/nfc/scenes/nfc_scene_saved_menu.c @@ -78,7 +78,7 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo); consumed = true; } else if(event.event == SubmenuIndexRestoreOriginal) { - if(!nfc_device_restore(nfc->dev)) { + if(!nfc_device_restore(nfc->dev, true)) { scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); } else { diff --git a/applications/nfc/scenes/nfc_scene_set_type.c b/applications/nfc/scenes/nfc_scene_set_type.c index 0dbb4f7e..0fe63424 100755 --- a/applications/nfc/scenes/nfc_scene_set_type.c +++ b/applications/nfc/scenes/nfc_scene_set_type.c @@ -1,4 +1,5 @@ #include "../nfc_i.h" +#include "m-string.h" enum SubmenuIndex { SubmenuIndexNFCA4, @@ -16,6 +17,7 @@ void nfc_scene_set_type_on_enter(void* context) { Submenu* submenu = nfc->submenu; // Clear device name nfc_device_set_name(nfc->dev, ""); + string_set_str(nfc->dev->load_path, ""); submenu_add_item( submenu, "NFC-A 7-bytes UID", SubmenuIndexNFCA7, nfc_scene_set_type_submenu_callback, nfc); submenu_add_item( diff --git a/applications/subghz/scenes/subghz_scene_delete.c b/applications/subghz/scenes/subghz_scene_delete.c index 83d6d475..43151de2 100644 --- a/applications/subghz/scenes/subghz_scene_delete.c +++ b/applications/subghz/scenes/subghz_scene_delete.c @@ -49,7 +49,7 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDelete) { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path_tmp, subghz->file_path); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_delete_raw.c b/applications/subghz/scenes/subghz_scene_delete_raw.c index 03f0eb81..a20968d5 100644 --- a/applications/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/subghz/scenes/subghz_scene_delete_raw.c @@ -24,7 +24,7 @@ void subghz_scene_delete_raw_on_enter(void* context) { char delete_str[SUBGHZ_MAX_LEN_NAME + 16]; string_t file_name; string_init(file_name); - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); snprintf(delete_str, sizeof(delete_str), "\e#Delete %s?\e#", string_get_cstr(file_name)); string_clear(file_name); @@ -61,7 +61,7 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDeleteRAW) { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path_tmp, subghz->file_path); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_more_raw.c b/applications/subghz/scenes/subghz_scene_more_raw.c index 54bd0815..a5bade92 100644 --- a/applications/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/subghz/scenes/subghz_scene_more_raw.c @@ -45,7 +45,7 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW); return true; } else if(event.event == SubmenuIndexEdit) { - memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); + string_reset(subghz->file_path_tmp); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 767f0924..191a7285 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -23,7 +23,7 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { break; } - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, temp_str); ret = true; } while(false); @@ -73,13 +73,13 @@ void subghz_scene_read_raw_on_enter(void* context) { subghz_read_raw_set_status(subghz->subghz_read_raw, SubGhzReadRAWStatusIDLE, ""); break; case SubGhzRxKeyStateRAWLoad: - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, SubGhzReadRAWStatusLoadKeyTX, string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; break; case SubGhzRxKeyStateRAWSave: - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); subghz_read_raw_set_status( subghz->subghz_read_raw, SubGhzReadRAWStatusSaveKey, string_get_cstr(file_name)); subghz->txrx->rx_key_state = SubGhzRxKeyStateIDLE; @@ -273,7 +273,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { subghz->state_notifications = SubGhzNotificationStateRx; subghz->txrx->rx_key_state = SubGhzRxKeyStateAddKey; } else { - string_set(subghz->error_str, "Function requires\nan SD card."); + string_set_str(subghz->error_str, "Function requires\nan SD card."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } } diff --git a/applications/subghz/scenes/subghz_scene_save_name.c b/applications/subghz/scenes/subghz_scene_save_name.c index 617709cc..95fc1938 100644 --- a/applications/subghz/scenes/subghz_scene_save_name.c +++ b/applications/subghz/scenes/subghz_scene_save_name.c @@ -1,4 +1,6 @@ #include "../subghz_i.h" +#include "m-string.h" +#include "subghz/types.h" #include #include "../helpers/subghz_custom_event.h" #include @@ -20,45 +22,49 @@ void subghz_scene_save_name_on_enter(void* context) { bool dev_name_empty = false; string_t file_name; + string_t dir_name; string_init(file_name); + string_init(dir_name); - if(!strcmp(subghz->file_path, "")) { + if(!subghz_path_is_file(subghz->file_path)) { char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0}; set_random_name(file_name_buf, SUBGHZ_MAX_LEN_NAME); - string_set(file_name, file_name_buf); - strncpy(subghz->file_dir, SUBGHZ_APP_FOLDER, SUBGHZ_MAX_LEN_NAME); + string_set_str(file_name, file_name_buf); + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); //highlighting the entire filename by default dev_name_empty = true; } else { - strncpy(subghz->file_path_tmp, subghz->file_path, SUBGHZ_MAX_LEN_NAME); - path_extract_dirname(subghz->file_path, file_name); - strncpy(subghz->file_dir, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); - path_extract_filename_no_ext(subghz->file_path, file_name); + string_set(subghz->file_path_tmp, subghz->file_path); + path_extract_dirname(string_get_cstr(subghz->file_path), dir_name); + path_extract_filename(subghz->file_path, file_name, true); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME); - path_extract_filename_no_ext(subghz->file_path, file_name); + path_extract_filename(subghz->file_path, file_name, true); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == SubGhzCustomEventManagerSetRAW) { dev_name_empty = true; } } + string_set(subghz->file_path, dir_name); } - strncpy(subghz->file_path, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); + + strncpy(subghz->file_name_tmp, string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME); text_input_set_header_text(text_input, "Name signal"); text_input_set_result_callback( text_input, subghz_scene_save_name_text_input_callback, subghz, - subghz->file_path, + subghz->file_name_tmp, MAX_TEXT_INPUT_LEN, // buffer size dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(subghz->file_dir, SUBGHZ_APP_EXTENSION, NULL); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + string_get_cstr(subghz->file_path), SUBGHZ_APP_EXTENSION, NULL); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); string_clear(file_name); + string_clear(dir_name); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); } @@ -66,18 +72,15 @@ void subghz_scene_save_name_on_enter(void* context) { bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeBack) { - strncpy(subghz->file_path, subghz->file_path_tmp, SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, subghz->file_path_tmp); scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneSaveName) { - if(strcmp(subghz->file_path, "")) { - string_t temp_str; - string_init_printf( - temp_str, "%s/%s%s", subghz->file_dir, subghz->file_path, SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); - string_clear(temp_str); - if(strcmp(subghz->file_path_tmp, "")) { + if(strcmp(subghz->file_name_tmp, "")) { + string_cat_printf( + subghz->file_path, "/%s%s", subghz->file_name_tmp, SUBGHZ_APP_EXTENSION); + if(subghz_path_is_file(subghz->file_path_tmp)) { if(!subghz_rename_file(subghz)) { return false; } @@ -85,7 +88,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) != SubGhzCustomEventManagerNoSet) { subghz_save_protocol_to_file( - subghz, subghz->txrx->fff_data, subghz->file_path); + subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneSetType, @@ -95,13 +98,14 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { subghz, subghz_history_get_raw_data( subghz->txrx->history, subghz->txrx->idx_menu_chosen), - subghz->file_path); + string_get_cstr(subghz->file_path)); } } if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { - subghz_protocol_raw_gen_fff_data(subghz->txrx->fff_data, subghz->file_path); + subghz_protocol_raw_gen_fff_data( + subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet); } else { @@ -111,7 +115,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess); return true; } else { - string_set(subghz->error_str, "No name file"); + string_set_str(subghz->error_str, "No name file"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return true; } diff --git a/applications/subghz/scenes/subghz_scene_set_type.c b/applications/subghz/scenes/subghz_scene_set_type.c index 9dad8cd9..cb56ff74 100644 --- a/applications/subghz/scenes/subghz_scene_set_type.c +++ b/applications/subghz/scenes/subghz_scene_set_type.c @@ -39,7 +39,7 @@ bool subghz_scene_set_type_submenu_gen_data_protocol( subghz_receiver_search_decoder_base_by_name(subghz->txrx->receiver, protocol_name); if(subghz->txrx->decoder_result == NULL) { - string_set(subghz->error_str, "Protocol not found"); + string_set_str(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); return false; } @@ -282,7 +282,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set( + string_set_str( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } @@ -306,7 +306,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { } subghz_transmitter_free(subghz->txrx->transmitter); if(!generated_protocol) { - string_set( + string_set_str( subghz->error_str, "Function requires\nan SD card with\nfresh databases."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); } diff --git a/applications/subghz/scenes/subghz_scene_transmitter.c b/applications/subghz/scenes/subghz_scene_transmitter.c index a7244424..552a36e9 100644 --- a/applications/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/subghz/scenes/subghz_scene_transmitter.c @@ -94,7 +94,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { subghz->scene_manager, SubGhzSceneStart); return true; } else if(event.event == SubGhzCustomEventViewTransmitterError) { - string_set(subghz->error_str, "Protocol not found"); + string_set_str(subghz->error_str, "Protocol not found"); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); } } else if(event.type == SceneManagerEventTypeTick) { diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index f597adde..d9dc19f4 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -1,5 +1,7 @@ /* Abandon hope, all ye who enter here. */ +#include "m-string.h" +#include "subghz/types.h" #include "subghz_i.h" #include @@ -24,6 +26,9 @@ void subghz_tick_event_callback(void* context) { SubGhz* subghz_alloc() { SubGhz* subghz = malloc(sizeof(SubGhz)); + string_init(subghz->file_path); + string_init(subghz->file_path_tmp); + // GUI subghz->gui = furi_record_open("gui"); @@ -241,9 +246,9 @@ void subghz_free(SubGhz* subghz) { furi_record_close("notification"); subghz->notifications = NULL; - // About birds - furi_assert(subghz->file_path[SUBGHZ_MAX_LEN_NAME] == 0); - furi_assert(subghz->file_path_tmp[SUBGHZ_MAX_LEN_NAME] == 0); + // Path strings + string_clear(subghz->file_path); + string_clear(subghz->file_path_tmp); // The rest free(subghz); @@ -260,7 +265,7 @@ int32_t subghz_app(void* p) { // Check argument and run corresponding scene if(p) { if(subghz_key_load(subghz, p)) { - strncpy(subghz->file_path, p, SUBGHZ_MAX_LEN_NAME); + string_set_str(subghz->file_path, p); if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { //Load Raw TX @@ -276,12 +281,13 @@ int32_t subghz_app(void* p) { view_dispatcher_stop(subghz->view_dispatcher); } } else { + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); if(load_database) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart); } else { scene_manager_set_scene_state( subghz->scene_manager, SubGhzSceneShowError, SubGhzCustomEventManagerSet); - string_set( + string_set_str( subghz->error_str, "No SD card or\ndatabase found.\nSome app function\nmay be reduced."); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowError); diff --git a/applications/subghz/subghz_cli.c b/applications/subghz/subghz_cli.c index f919e1cf..6c92a5d6 100644 --- a/applications/subghz/subghz_cli.c +++ b/applications/subghz/subghz_cli.c @@ -303,7 +303,7 @@ void subghz_cli_command_decode_raw(Cli* cli, string_t args, void* context) { UNUSED(context); string_t file_name; string_init(file_name); - string_set(file_name, "/any/subghz/test.sub"); + string_set_str(file_name, "/any/subghz/test.sub"); Storage* storage = furi_record_open("storage"); FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); diff --git a/applications/subghz/subghz_history.c b/applications/subghz/subghz_history.c index cb30726b..a8f86eec 100644 --- a/applications/subghz/subghz_history.c +++ b/applications/subghz/subghz_history.c @@ -169,14 +169,14 @@ bool subghz_history_add_to_history( break; } if(!strcmp(string_get_cstr(instance->tmp_string), "KeeLoq")) { - string_set(instance->tmp_string, "KL "); + string_set_str(instance->tmp_string, "KL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); break; } string_cat(instance->tmp_string, text); } else if(!strcmp(string_get_cstr(instance->tmp_string), "Star Line")) { - string_set(instance->tmp_string, "SL "); + string_set_str(instance->tmp_string, "SL "); if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) { FURI_LOG_E(TAG, "Missing Protocol"); break; diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c index ff22b429..81017a8b 100644 --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -1,5 +1,8 @@ #include "subghz_i.h" +#include "assets_icons.h" +#include "m-string.h" +#include "subghz/types.h" #include #include #include @@ -45,11 +48,11 @@ void subghz_get_frequency_modulation(SubGhz* subghz, string_t frequency, string_ if(modulation != NULL) { if(subghz->txrx->preset == FuriHalSubGhzPresetOok650Async || subghz->txrx->preset == FuriHalSubGhzPresetOok270Async) { - string_set(modulation, "AM"); + string_set_str(modulation, "AM"); } else if( subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev238Async || subghz->txrx->preset == FuriHalSubGhzPreset2FSKDev476Async) { - string_set(modulation, "FM"); + string_set_str(modulation, "FM"); } else { furi_crash("SugGhz: Modulation is incorrect."); } @@ -189,8 +192,9 @@ void subghz_tx_stop(SubGhz* subghz) { //if protocol dynamic then we save the last upload if((subghz->txrx->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) && - (strcmp(subghz->file_path, ""))) { - subghz_save_protocol_to_file(subghz, subghz->txrx->fff_data, subghz->file_path); + (subghz_path_is_file(subghz->file_path))) { + subghz_save_protocol_to_file( + subghz, subghz->txrx->fff_data, string_get_cstr(subghz->file_path)); } subghz_idle(subghz); notification_message(subghz->notifications, &sequence_reset_red); @@ -332,10 +336,10 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { bool res = false; - if(strcmp(subghz->file_path, "")) { + if(subghz_path_is_file(subghz->file_path)) { //get the name of the next free file - path_extract_filename_no_ext(subghz->file_path, file_name); - path_extract_dirname(subghz->file_path, file_path); + path_extract_filename(subghz->file_path, file_name, true); + path_extract_dirname(string_get_cstr(subghz->file_path), file_path); storage_get_next_filename( storage, @@ -351,7 +355,7 @@ bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { string_get_cstr(file_path), string_get_cstr(file_name), SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); + string_set(subghz->file_path, temp_str); res = true; } @@ -411,19 +415,17 @@ bool subghz_load_protocol_from_file(SubGhz* subghz) { string_init(file_path); // Input events and views are managed by file_select - bool res = dialog_file_select_show( + bool res = dialog_file_browser_show( subghz->dialogs, - SUBGHZ_APP_FOLDER, - SUBGHZ_APP_EXTENSION, subghz->file_path, - sizeof(subghz->file_path), - NULL); + subghz->file_path, + SUBGHZ_APP_EXTENSION, + true, + &I_sub1_10px, + true); if(res) { - string_printf( - file_path, "%s/%s%s", SUBGHZ_APP_FOLDER, subghz->file_path, SUBGHZ_APP_EXTENSION); - strncpy(subghz->file_path, string_get_cstr(file_path), SUBGHZ_MAX_LEN_NAME); - res = subghz_key_load(subghz, subghz->file_path); + res = subghz_key_load(subghz, string_get_cstr(subghz->file_path)); } string_clear(file_path); @@ -437,9 +439,9 @@ bool subghz_rename_file(SubGhz* subghz) { Storage* storage = furi_record_open("storage"); - if(strcmp(subghz->file_path_tmp, subghz->file_path)) { - FS_Error fs_result = - storage_common_rename(storage, subghz->file_path_tmp, subghz->file_path); + if(string_cmp(subghz->file_path_tmp, subghz->file_path)) { + FS_Error fs_result = storage_common_rename( + storage, string_get_cstr(subghz->file_path_tmp), string_get_cstr(subghz->file_path)); if(fs_result != FSE_OK) { dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); @@ -455,7 +457,7 @@ bool subghz_delete_file(SubGhz* subghz) { furi_assert(subghz); Storage* storage = furi_record_open("storage"); - bool result = storage_simply_remove(storage, subghz->file_path_tmp); + bool result = storage_simply_remove(storage, string_get_cstr(subghz->file_path_tmp)); furi_record_close("storage"); subghz_file_name_clear(subghz); @@ -465,8 +467,12 @@ bool subghz_delete_file(SubGhz* subghz) { void subghz_file_name_clear(SubGhz* subghz) { furi_assert(subghz); - memset(subghz->file_path, 0, sizeof(subghz->file_path)); - memset(subghz->file_path_tmp, 0, sizeof(subghz->file_path_tmp)); + string_set_str(subghz->file_path, SUBGHZ_APP_FOLDER); + string_reset(subghz->file_path_tmp); +} + +bool subghz_path_is_file(string_t path) { + return string_end_with_str_p(path, SUBGHZ_APP_EXTENSION); } uint32_t subghz_random_serial(void) { diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index 1e917af5..5d1b0699 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -35,7 +35,7 @@ #include #include -#define SUBGHZ_MAX_LEN_NAME 250 +#define SUBGHZ_MAX_LEN_NAME 64 /** SubGhzNotification state */ typedef enum { @@ -119,10 +119,9 @@ struct SubGhz { TextInput* text_input; Widget* widget; DialogsApp* dialogs; - char file_path[SUBGHZ_MAX_LEN_NAME + 1]; - char file_path_tmp[SUBGHZ_MAX_LEN_NAME + 1]; - //ToDo you can get rid of it, you need to refactor text input to return the path to the folder - char file_dir[SUBGHZ_MAX_LEN_NAME + 1]; + string_t file_path; + string_t file_path_tmp; + char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; SubGhzNotificationState state_notifications; SubGhzViewReceiver* subghz_receiver; @@ -173,5 +172,6 @@ bool subghz_load_protocol_from_file(SubGhz* subghz); bool subghz_rename_file(SubGhz* subghz); bool subghz_delete_file(SubGhz* subghz); void subghz_file_name_clear(SubGhz* subghz); +bool subghz_path_is_file(string_t path); uint32_t subghz_random_serial(void); void subghz_hopper_update(SubGhz* subghz); diff --git a/applications/subghz/views/receiver.c b/applications/subghz/views/receiver.c index 7b19cbcf..866d8272 100644 --- a/applications/subghz/views/receiver.c +++ b/applications/subghz/views/receiver.c @@ -111,9 +111,9 @@ void subghz_view_receiver_add_data_statusbar( furi_assert(subghz_receiver); with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); - string_set(model->history_stat_str, history_stat_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); + string_set_str(model->history_stat_str, history_stat_str); return true; }); } diff --git a/applications/subghz/views/subghz_read_raw.c b/applications/subghz/views/subghz_read_raw.c index ff3ba45a..a4807d77 100644 --- a/applications/subghz/views/subghz_read_raw.c +++ b/applications/subghz/views/subghz_read_raw.c @@ -46,8 +46,8 @@ void subghz_read_raw_add_data_statusbar( furi_assert(instance); with_view_model( instance->view, (SubGhzReadRAWModel * model) { - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); return true; }); } @@ -372,7 +372,7 @@ bool subghz_read_raw_input(InputEvent* event, void* context) { model->satus = SubGhzReadRAWStatusStart; model->rssi_history_end = false; model->ind_write = 0; - string_set(model->sample_write, "0 spl."); + string_set_str(model->sample_write, "0 spl."); string_reset(model->file_name); instance->callback(SubGhzCustomEventViewReadRAWErase, instance->context); } @@ -424,7 +424,7 @@ void subghz_read_raw_set_status( model->rssi_history_end = false; model->ind_write = 0; string_reset(model->file_name); - string_set(model->sample_write, "0 spl."); + string_set_str(model->sample_write, "0 spl."); return true; }); break; @@ -441,8 +441,8 @@ void subghz_read_raw_set_status( model->satus = SubGhzReadRAWStatusLoadKeyIDLE; model->rssi_history_end = false; model->ind_write = 0; - string_set(model->file_name, file_name); - string_set(model->sample_write, "RAW"); + string_set_str(model->file_name, file_name); + string_set_str(model->sample_write, "RAW"); return true; }); break; @@ -451,8 +451,8 @@ void subghz_read_raw_set_status( instance->view, (SubGhzReadRAWModel * model) { model->satus = SubGhzReadRAWStatusLoadKeyIDLE; if(!model->ind_write) { - string_set(model->file_name, file_name); - string_set(model->sample_write, "RAW"); + string_set_str(model->file_name, file_name); + string_set_str(model->sample_write, "RAW"); } else { string_reset(model->file_name); } diff --git a/applications/subghz/views/transmitter.c b/applications/subghz/views/transmitter.c index 3113e31f..3cbcf098 100644 --- a/applications/subghz/views/transmitter.c +++ b/applications/subghz/views/transmitter.c @@ -36,9 +36,9 @@ void subghz_view_transmitter_add_data_to_show( furi_assert(subghz_transmitter); with_view_model( subghz_transmitter->view, (SubGhzViewTransmitterModel * model) { - string_set(model->key_str, key_str); - string_set(model->frequency_str, frequency_str); - string_set(model->preset_str, preset_str); + string_set_str(model->key_str, key_str); + string_set_str(model->frequency_str, frequency_str); + string_set_str(model->preset_str, preset_str); model->show_button = show_button; return true; }); diff --git a/assets/compiled/assets_icons.c b/assets/compiled/assets_icons.c index 9046dddd..fb125095 100644 --- a/assets/compiled/assets_icons.c +++ b/assets/compiled/assets_icons.c @@ -40,6 +40,9 @@ const uint8_t* const _I_125_10px[] = {_I_125_10px_0}; const uint8_t _I_Nfc_10px_0[] = {0x00,0x80,0x00,0x00,0x01,0x22,0x02,0x43,0x02,0x45,0x02,0x49,0x02,0x31,0x02,0x22,0x02,0x00,0x01,0x80,0x00,}; const uint8_t* const _I_Nfc_10px[] = {_I_Nfc_10px_0}; +const uint8_t _I_back_10px_0[] = {0x00,0x00,0x00,0x10,0x00,0x38,0x00,0x7C,0x00,0xFE,0x00,0x38,0x00,0x38,0x00,0xF8,0x01,0xF8,0x01,0x00,0x00,}; +const uint8_t* const _I_back_10px[] = {_I_back_10px_0}; + const uint8_t _I_badusb_10px_0[] = {0x01,0x00,0x11,0x00,0x00,0x0f,0xe2,0x01,0xfc,0x80,0xdd,0x20,0x32,0x48,0x08,0x14,0x40,0x23,0xa8,0x08,0xa0,}; const uint8_t* const _I_badusb_10px[] = {_I_badusb_10px_0}; @@ -55,6 +58,12 @@ const uint8_t* const _I_ibutt_10px[] = {_I_ibutt_10px_0}; const uint8_t _I_ir_10px_0[] = {0x00,0xFC,0x00,0x02,0x01,0x79,0x02,0x84,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x58,0x00,0x78,0x00,0xFF,0x03,}; const uint8_t* const _I_ir_10px[] = {_I_ir_10px_0}; +const uint8_t _I_loading_10px_0[] = {0x00,0xFE,0x00,0x82,0x00,0xBA,0x00,0x54,0x00,0x28,0x00,0x28,0x00,0x44,0x00,0x92,0x00,0xBA,0x00,0xFE,0x00,}; +const uint8_t* const _I_loading_10px[] = {_I_loading_10px_0}; + +const uint8_t _I_music_10px_0[] = {0x01,0x00,0x10,0x00,0xf0,0x00,0x46,0x03,0x20,0x80,0x00,0x4e,0x7d,0x00,0x9f,0x80,0x4a,0x3c,0x13,0x20,}; +const uint8_t* const _I_music_10px[] = {_I_music_10px_0}; + const uint8_t _I_sub1_10px_0[] = {0x01,0x00,0x12,0x00,0x81,0x40,0x69,0x30,0x2c,0x2c,0x0b,0x6a,0x01,0x28,0x0c,0x0a,0x65,0x01,0x98,0x40,0x00,0x26,}; const uint8_t* const _I_sub1_10px[] = {_I_sub1_10px_0}; @@ -648,11 +657,14 @@ const Icon A_Levelup1_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rat const Icon A_Levelup2_128x64 = {.width=128,.height=64,.frame_count=11,.frame_rate=2,.frames=_A_Levelup2_128x64}; const Icon I_125_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_125_10px}; const Icon I_Nfc_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_Nfc_10px}; +const Icon I_back_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_back_10px}; const Icon I_badusb_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_badusb_10px}; const Icon I_ble_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ble_10px}; const Icon I_dir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_dir_10px}; const Icon I_ibutt_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ibutt_10px}; const Icon I_ir_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_ir_10px}; +const Icon I_loading_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_loading_10px}; +const Icon I_music_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_music_10px}; const Icon I_sub1_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_sub1_10px}; const Icon I_u2f_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_u2f_10px}; const Icon I_unknown_10px = {.width=10,.height=10,.frame_count=1,.frame_rate=0,.frames=_I_unknown_10px}; diff --git a/assets/compiled/assets_icons.h b/assets/compiled/assets_icons.h index 64cd3d36..45947880 100644 --- a/assets/compiled/assets_icons.h +++ b/assets/compiled/assets_icons.h @@ -7,11 +7,14 @@ extern const Icon A_Levelup1_128x64; extern const Icon A_Levelup2_128x64; extern const Icon I_125_10px; extern const Icon I_Nfc_10px; +extern const Icon I_back_10px; extern const Icon I_badusb_10px; extern const Icon I_ble_10px; extern const Icon I_dir_10px; extern const Icon I_ibutt_10px; extern const Icon I_ir_10px; +extern const Icon I_loading_10px; +extern const Icon I_music_10px; extern const Icon I_sub1_10px; extern const Icon I_u2f_10px; extern const Icon I_unknown_10px; diff --git a/assets/icons/Archive/back_10px.png b/assets/icons/Archive/back_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..008efe82fa129f55c8efa77688dd507f31398896 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih`sfGge++G4X{LmR$r2S$euShDcma{&W9q1G|A@v4nS_xAT(=AqI)BoYRaGO@;Li wag`sfGge++QC**pItze8KAtX)ArhBs`vdt77;ren`<{QJxM$1n6@^=3kBM_gs5bkj zOgNacP$Dkno^tQ4ONX;p$w=sbzP$Pyzt8HuHJfiox2e6^5wrUCr{B&G7(ZT+pZ{3J RO%!M@gQu&X%Q~loCIH;wIT`=} literal 0 HcmV?d00001 diff --git a/assets/icons/Archive/music_10px.png b/assets/icons/Archive/music_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..d41eb0db8c822c60be6c097393b3682680b81a6c GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasA%~WHIdey2}7aaTa() z7Bet#3xhBt!>l_x9Asc&(0a_=e)W&n8K5!-Pgg&ebxsLQ0Ao%f>i_@% literal 0 HcmV?d00001 diff --git a/lib/one_wire/ibutton/ibutton_key.c b/lib/one_wire/ibutton/ibutton_key.c index c6d4466f..2c0f7fa2 100644 --- a/lib/one_wire/ibutton/ibutton_key.c +++ b/lib/one_wire/ibutton/ibutton_key.c @@ -4,7 +4,6 @@ struct iButtonKey { uint8_t data[IBUTTON_KEY_DATA_SIZE]; - char name[IBUTTON_KEY_NAME_SIZE]; iButtonKeyType type; }; @@ -42,14 +41,6 @@ uint8_t ibutton_key_get_data_size(iButtonKey* key) { return ibutton_key_get_size_by_type(key->type); } -void ibutton_key_set_name(iButtonKey* key, const char* name) { - strlcpy(key->name, name, IBUTTON_KEY_NAME_SIZE); -} - -const char* ibutton_key_get_name_p(iButtonKey* key) { - return key->name; -} - void ibutton_key_set_type(iButtonKey* key, iButtonKeyType key_type) { key->type = key_type; } diff --git a/lib/one_wire/ibutton/ibutton_key.h b/lib/one_wire/ibutton/ibutton_key.h index 8d6732bc..f66537d7 100644 --- a/lib/one_wire/ibutton/ibutton_key.h +++ b/lib/one_wire/ibutton/ibutton_key.h @@ -68,20 +68,6 @@ const uint8_t* ibutton_key_get_data_p(iButtonKey* key); */ uint8_t ibutton_key_get_data_size(iButtonKey* key); -/** - * Set key name - * @param key - * @param name - */ -void ibutton_key_set_name(iButtonKey* key, const char* name); - -/** - * Get pointer to key name - * @param key - * @return const char* - */ -const char* ibutton_key_get_name_p(iButtonKey* key); - /** * Set key type * @param key diff --git a/lib/toolbox/path.c b/lib/toolbox/path.c index 4fd042e4..a99e57d1 100644 --- a/lib/toolbox/path.c +++ b/lib/toolbox/path.c @@ -19,6 +19,20 @@ void path_extract_filename_no_ext(const char* path, string_t filename) { string_mid(filename, start_position, end_position - start_position); } +void path_extract_filename(string_t path, string_t name, bool trim_ext) { + size_t filename_start = string_search_rchar(path, '/'); + if(filename_start > 0) { + filename_start++; + string_set_n(name, path, filename_start, string_size(path) - filename_start); + } + if(trim_ext) { + size_t dot = string_search_rchar(name, '.'); + if(dot > 0) { + string_left(name, dot); + } + } +} + static inline void path_cleanup(string_t path) { string_strim(path); while(string_end_with_str_p(path, "/")) { diff --git a/lib/toolbox/path.h b/lib/toolbox/path.h index 0de63bb2..76e501cc 100644 --- a/lib/toolbox/path.h +++ b/lib/toolbox/path.h @@ -14,6 +14,15 @@ extern "C" { */ void path_extract_filename_no_ext(const char* path, string_t filename); +/** + * @brief Extract filename string from path. + * + * @param path path string + * @param filename output filename string. Must be initialized before. + * @param trim_ext true - get filename without extension + */ +void path_extract_filename(string_t path, string_t filename, bool trim_ext); + /** * @brief Extract last path component *