From 777a4d109d723fbe2300d0f66788eed6b24ee208 Mon Sep 17 00:00:00 2001 From: Sergey Gavrilov Date: Wed, 1 Mar 2023 20:57:27 +0300 Subject: [PATCH] [FL-3055] Getter for application data path (#2181) * Threads: application id * Unit tests: appsdata getter test * Unit tests: moar test cases for appsdata getter * Unit tests: remove folders after test * Storage: dir_is_exist, migrate, + unit_tests * Plugins: migration * Storage: common_exists, moar unit_tests 4 "common_migrate", "common_migrate" and "common_merge" bugfixes * Storage: use FuriString for path handling * Storage API: send caller thread id with path * Storage: remove StorageType field in storage file list * Storage: simplify processing * Storage API: send caller thread id with path everywhere * Storage: /app alias, unit tests and path creation * Storage, path helper: remove unused * Examples: app data example * App plugins: use new VFS path * Storage: file_info_is_dir * Services: handle alias if the service accepts a path. * App plugins: fixes * Make PVS happy * Storage: fix storage_merge_recursive * Storage: rename process_aliases to resolve_path. Rename APPS_DATA to APP_DATA. * Apps: use predefined macro instead of raw paths. Example Apps Data: README fixes. * Storage: rename storage_common_resolve_path to storage_common_resolve_path_and_ensure_app_directory * Api: fix version * Storage: rename alias message * Storage: do not create app folders in path resolving process in certain cases. --------- Co-authored-by: Astra <93453568+Astrrra@users.noreply.github.com> Co-authored-by: Aleksandr Kutuzov --- .pvsconfig | 3 + applications/debug/unit_tests/rpc/rpc_test.c | 9 +- .../debug/unit_tests/storage/dirwalk_test.c | 8 +- .../debug/unit_tests/storage/storage_test.c | 292 ++++++++++++++- .../examples/example_apps_data/README.md | 18 + .../example_apps_data/application.fam | 9 + .../example_apps_data/example_apps_data.c | 40 ++ .../main/archive/helpers/archive_browser.c | 2 +- .../main/archive/helpers/archive_favorites.c | 2 +- .../main/archive/helpers/archive_files.c | 2 +- applications/main/fap_loader/fap_loader_app.c | 7 + applications/plugins/hid_app/hid.c | 12 +- applications/plugins/hid_app/hid.h | 2 +- .../plugins/music_player/music_player.c | 11 +- .../picopass/helpers/iclass_elite_dict.c | 20 +- applications/plugins/picopass/picopass.c | 8 + .../plugins/picopass/picopass_device.c | 14 +- .../plugins/picopass/picopass_device.h | 2 - .../scenes/picopass_scene_save_name.c | 4 +- .../scenes/spi_mem_scene_start.c | 2 +- .../plugins/spi_mem_manager/spi_mem_app.c | 10 +- .../plugins/spi_mem_manager/spi_mem_app_i.h | 1 - .../plugins/spi_mem_manager/spi_mem_files.c | 8 +- .../plugins/spi_mem_manager/spi_mem_files.h | 1 - applications/services/applications.h | 1 + applications/services/bt/bt_service/bt_api.c | 9 +- applications/services/cli/cli_commands.c | 11 +- applications/services/dialogs/dialogs_api.c | 22 +- .../gui/modules/file_browser_worker.c | 12 +- applications/services/loader/loader.c | 2 + applications/services/rpc/rpc_storage.c | 9 +- .../services/storage/filesystem_api.c | 4 + .../services/storage/filesystem_api_defines.h | 11 +- applications/services/storage/storage.h | 41 +++ applications/services/storage/storage_cli.c | 6 +- .../services/storage/storage_external_api.c | 103 ++++-- applications/services/storage/storage_glue.c | 11 +- applications/services/storage/storage_glue.h | 7 +- applications/services/storage/storage_i.h | 2 + .../services/storage/storage_message.h | 13 + .../services/storage/storage_processing.c | 262 ++++++++------ .../services/storage/storage_test_app.c | 341 ------------------ .../storage_move_to_sd/storage_move_to_sd.c | 2 +- .../music_player/Marble_Machine.fmf | 0 .../picopass/assets/iclass_elite_dict.txt | 0 firmware/targets/f7/api_symbols.csv | 9 +- furi/core/thread.c | 40 +- furi/core/thread.h | 37 ++ furi/flipper.c | 1 + lib/toolbox/dir_walk.c | 2 +- lib/toolbox/tar/tar_archive.c | 2 +- scripts/fbt/appmanifest.py | 1 + 52 files changed, 871 insertions(+), 577 deletions(-) create mode 100644 applications/examples/example_apps_data/README.md create mode 100644 applications/examples/example_apps_data/application.fam create mode 100644 applications/examples/example_apps_data/example_apps_data.c delete mode 100644 applications/services/storage/storage_test_app.c rename assets/resources/{ => apps_data}/music_player/Marble_Machine.fmf (100%) rename assets/resources/{ => apps_data}/picopass/assets/iclass_elite_dict.txt (100%) diff --git a/.pvsconfig b/.pvsconfig index a9ab9c9f..49c63ad7 100644 --- a/.pvsconfig +++ b/.pvsconfig @@ -44,3 +44,6 @@ # Functions that always return the same error code //-V:picopass_device_decrypt:1048 + +# Examples +//V_EXCLUDE_PATH applications/examples/ \ No newline at end of file diff --git a/applications/debug/unit_tests/rpc/rpc_test.c b/applications/debug/unit_tests/rpc/rpc_test.c index 76acf6be..329f3b74 100644 --- a/applications/debug/unit_tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/rpc/rpc_test.c @@ -191,7 +191,7 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) { size_t size = strlen(clean_dir) + strlen(name) + 1 + 1; char* fullname = malloc(size); snprintf(fullname, size, "%s/%s", clean_dir, name); - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { clean_directory(fs_api, fullname); } FS_Error error = storage_common_remove(fs_api, fullname); @@ -608,9 +608,8 @@ static void test_rpc_storage_list_create_expected_list( } if(path_contains_only_ascii(name)) { - list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? - PB_Storage_File_FileType_DIR : - PB_Storage_File_FileType_FILE; + list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : + PB_Storage_File_FileType_FILE; list->file[i].size = fileinfo.size; list->file[i].data = NULL; /* memory free inside rpc_encode_and_send() -> pb_release() */ @@ -873,7 +872,7 @@ static void test_rpc_storage_stat_run(const char* path, uint32_t command_id) { if(error == FSE_OK) { response->which_content = PB_Main_storage_stat_response_tag; response->content.storage_stat_response.has_file = true; - response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ? + response->content.storage_stat_response.file.type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : PB_Storage_File_FileType_FILE; response->content.storage_stat_response.file.size = fileinfo.size; diff --git a/applications/debug/unit_tests/storage/dirwalk_test.c b/applications/debug/unit_tests/storage/dirwalk_test.c index 97aaa358..e0842a7a 100644 --- a/applications/debug/unit_tests/storage/dirwalk_test.c +++ b/applications/debug/unit_tests/storage/dirwalk_test.c @@ -179,7 +179,7 @@ MU_TEST_1(test_dirwalk_full, Storage* storage) { while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); - mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); + mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo))); } dir_walk_free(dir_walk); @@ -204,7 +204,7 @@ MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) { while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); - mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); + mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo))); } dir_walk_free(dir_walk); @@ -219,7 +219,7 @@ static bool test_dirwalk_filter_no_folder_ext(const char* name, FileInfo* filein UNUSED(ctx); // only files - if(!(fileinfo->flags & FSF_DIRECTORY)) { + if(!file_info_is_dir(fileinfo)) { // with ".test" in name if(strstr(name, ".test") != NULL) { return true; @@ -243,7 +243,7 @@ MU_TEST_1(test_dirwalk_filter, Storage* storage) { while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) { furi_string_right(path, strlen(EXT_PATH("dirwalk/"))); - mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY))); + mu_check(storage_test_paths_mark(paths, path, file_info_is_dir(&fileinfo))); } dir_walk_free(dir_walk); diff --git a/applications/debug/unit_tests/storage/storage_test.c b/applications/debug/unit_tests/storage/storage_test.c index 11500970..582be790 100644 --- a/applications/debug/unit_tests/storage/storage_test.c +++ b/applications/debug/unit_tests/storage/storage_test.c @@ -2,9 +2,40 @@ #include #include +// DO NOT USE THIS IN PRODUCTION CODE +// This is a hack to access internal storage functions and definitions +#include + +#define UNIT_TESTS_PATH(path) EXT_PATH("unit_tests/" path) + #define STORAGE_LOCKED_FILE EXT_PATH("locked_file.test") #define STORAGE_LOCKED_DIR STORAGE_INT_PATH_PREFIX +#define STORAGE_TEST_DIR UNIT_TESTS_PATH("test_dir") + +static bool storage_file_create(Storage* storage, const char* path, const char* data) { + File* file = storage_file_alloc(storage); + bool result = false; + do { + if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_NEW)) { + break; + } + + if(storage_file_write(file, data, strlen(data)) != strlen(data)) { + break; + } + + if(!storage_file_close(file)) { + break; + } + + result = true; + } while(0); + + storage_file_free(file); + return result; +} + static void storage_file_open_lock_setup() { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); @@ -115,7 +146,7 @@ static int32_t storage_dir_locker(void* ctx) { File* file = storage_file_alloc(storage); furi_check(storage_dir_open(file, STORAGE_LOCKED_DIR)); furi_semaphore_release(semaphore); - furi_delay_ms(1000); + furi_delay_ms(100); furi_check(storage_dir_close(file)); furi_record_close(RECORD_STORAGE); @@ -152,9 +183,21 @@ MU_TEST(storage_dir_open_lock) { mu_assert(result, "cannot open locked dir"); } +MU_TEST(storage_dir_exists_test) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + mu_check(!storage_dir_exists(storage, STORAGE_TEST_DIR)); + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, STORAGE_TEST_DIR)); + mu_check(storage_dir_exists(storage, STORAGE_TEST_DIR)); + mu_assert_int_eq(FSE_OK, storage_common_remove(storage, STORAGE_TEST_DIR)); + + furi_record_close(RECORD_STORAGE); +} + MU_TEST_SUITE(storage_dir) { MU_RUN_TEST(storage_dir_open_close); MU_RUN_TEST(storage_dir_open_lock); + MU_RUN_TEST(storage_dir_exists_test); } static const char* const storage_copy_test_paths[] = { @@ -303,9 +346,256 @@ MU_TEST_SUITE(storage_rename) { furi_record_close(RECORD_STORAGE); } +#define APPSDATA_APP_PATH(path) APPS_DATA_PATH "/" path + +static const char* storage_test_apps[] = { + "-_twilight_-", + "-_rainbow_-", + "-_pinkie_-", + "-_apple_-", + "-_flutter_-", + "-_rare_-", +}; + +static size_t storage_test_apps_count = COUNT_OF(storage_test_apps); + +static int32_t storage_test_app(void* arg) { + UNUSED(arg); + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_remove(storage, "/app/test"); + int32_t ret = storage_file_create(storage, "/app/test", "test"); + furi_record_close(RECORD_STORAGE); + return ret; +} + +MU_TEST(test_storage_data_path_apps) { + for(size_t i = 0; i < storage_test_apps_count; i++) { + FuriThread* thread = + furi_thread_alloc_ex(storage_test_apps[i], 1024, storage_test_app, NULL); + furi_thread_set_appid(thread, storage_test_apps[i]); + furi_thread_start(thread); + furi_thread_join(thread); + + mu_assert_int_eq(true, furi_thread_get_return_code(thread)); + + // Check if app data dir and file exists + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* expected = furi_string_alloc(); + furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]); + + mu_check(storage_dir_exists(storage, furi_string_get_cstr(expected))); + furi_string_cat(expected, "/test"); + mu_check(storage_file_exists(storage, furi_string_get_cstr(expected))); + + furi_string_printf(expected, APPSDATA_APP_PATH("%s"), storage_test_apps[i]); + storage_simply_remove_recursive(storage, furi_string_get_cstr(expected)); + + furi_record_close(RECORD_STORAGE); + + furi_string_free(expected); + furi_thread_free(thread); + } +} + +MU_TEST(test_storage_data_path) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + File* file = storage_file_alloc(storage); + mu_check(storage_dir_open(file, "/app")); + mu_check(storage_dir_close(file)); + storage_file_free(file); + + // check that appsdata folder exists + mu_check(storage_dir_exists(storage, APPS_DATA_PATH)); + + // check that cli folder exists + mu_check(storage_dir_exists(storage, APPSDATA_APP_PATH("cli"))); + + storage_simply_remove(storage, APPSDATA_APP_PATH("cli")); + + furi_record_close(RECORD_STORAGE); +} + +MU_TEST(test_storage_common_migrate) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Setup test folders + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from non existing + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + // Test migration from existing folder to non existing + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3")); + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext"))); + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + // Test migration from existing folder to existing folder + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file1"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file2.ext"), "test2")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old/file3.ext.ext"), "test3")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file1"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file2.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file11"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file21.ext"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new/file3.ext1.ext"))); + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from empty folder to existing file + // Expected result: FSE_OK, folder removed, file untouched + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test1")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from empty folder to existing folder + // Expected result: FSE_OK, old folder removed, new folder untouched + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new"))); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from existing file to non existing, no extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + + // Test migration from existing file to non existing, with extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file")); + + // Test migration from existing file to existing file, no extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new"), "test2")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1")); + + // Test migration from existing file to existing file, with extension + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old.file"), "test1")); + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_new.file"), "test2")); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old.file"), UNIT_TESTS_PATH("migrate_new.file"))); + + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new.file"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old.file"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1.file"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old.file")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new.file")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1.file")); + + // Test migration from existing file to existing folder + mu_check(storage_file_create(storage, UNIT_TESTS_PATH("migrate_old"), "test1")); + mu_assert_int_eq(FSE_OK, storage_common_mkdir(storage, UNIT_TESTS_PATH("migrate_new"))); + + mu_assert_int_eq( + FSE_OK, + storage_common_migrate( + storage, UNIT_TESTS_PATH("migrate_old"), UNIT_TESTS_PATH("migrate_new"))); + + mu_check(storage_dir_exists(storage, UNIT_TESTS_PATH("migrate_new"))); + mu_check(!storage_file_exists(storage, UNIT_TESTS_PATH("migrate_old"))); + mu_check(storage_file_exists(storage, UNIT_TESTS_PATH("migrate_new1"))); + + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_old")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new")); + storage_simply_remove_recursive(storage, UNIT_TESTS_PATH("migrate_new1")); + + furi_record_close(RECORD_STORAGE); +} + +MU_TEST_SUITE(test_data_path) { + MU_RUN_TEST(test_storage_data_path); + MU_RUN_TEST(test_storage_data_path_apps); +} + +MU_TEST_SUITE(test_storage_common) { + MU_RUN_TEST(test_storage_common_migrate); +} + int run_minunit_test_storage() { MU_RUN_SUITE(storage_file); MU_RUN_SUITE(storage_dir); MU_RUN_SUITE(storage_rename); + MU_RUN_SUITE(test_data_path); + MU_RUN_SUITE(test_storage_common); return MU_EXIT_CODE; } diff --git a/applications/examples/example_apps_data/README.md b/applications/examples/example_apps_data/README.md new file mode 100644 index 00000000..fd866607 --- /dev/null +++ b/applications/examples/example_apps_data/README.md @@ -0,0 +1,18 @@ +# Apps Data folder Example + +This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth. + +## What is the Apps Data Folder? + +The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware. + +The path to the current application folder is related to the `appid` of the app. The `appid` is used to identify the app in the app store and is stored in the `application.fam` file. +The Apps Data folder is located only on the external storage, the SD card. + +For example, if the `appid` of the app is `snake_game`, the path to the Apps Data folder will be `/ext/apps_data/snake_game`. But using raw paths is not recommended, because the path to the Apps Data folder can change in the future. Use the `/app` alias instead. + +## How to get the path to the Apps Data folder? + +You can use `/app` alias to get the path to the current application data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `/app/config.txt`. But this way is not recommended, because even the `/app` alias can change in the future. + +We recommend to use the `APP_DATA_PATH` macro to get the path to the Apps Data folder. For example, if you want to open a file `config.txt` in the Apps Data folder, you can use the next path: `APP_DATA_PATH("config.txt")`. \ No newline at end of file diff --git a/applications/examples/example_apps_data/application.fam b/applications/examples/example_apps_data/application.fam new file mode 100644 index 00000000..f44dca97 --- /dev/null +++ b/applications/examples/example_apps_data/application.fam @@ -0,0 +1,9 @@ +App( + appid="example_apps_data", + name="Example: Apps Data", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_apps_data_main", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Examples", +) diff --git a/applications/examples/example_apps_data/example_apps_data.c b/applications/examples/example_apps_data/example_apps_data.c new file mode 100644 index 00000000..d6104c13 --- /dev/null +++ b/applications/examples/example_apps_data/example_apps_data.c @@ -0,0 +1,40 @@ +#include +#include + +// Define log tag +#define TAG "example_apps_data" + +// Application entry point +int32_t example_apps_data_main(void* p) { + // Mark argument as unused + UNUSED(p); + + // Open storage + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Allocate file + File* file = storage_file_alloc(storage); + + // Get the path to the current application data folder + // That is: /ext/apps_data/ + // And it will create folders in the path if they don't exist + // In this example it will create /ext/apps_data/example_apps_data + // And file will be /ext/apps_data/example_apps_data/test.txt + + // Open file, write data and close it + if(!storage_file_open(file, APP_DATA_PATH("test.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + FURI_LOG_E(TAG, "Failed to open file"); + } + if(!storage_file_write(file, "Hello World!", strlen("Hello World!"))) { + FURI_LOG_E(TAG, "Failed to write to file"); + } + storage_file_close(file); + + // Deallocate file + storage_file_free(file); + + // Close storage + furi_record_close(RECORD_STORAGE); + + return 0; +} diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 1133f6d7..f5efca46 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -436,7 +436,7 @@ static bool archive_is_dir_exists(FuriString* path) { FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { - if(file_info.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&file_info)) { state = true; } } diff --git a/applications/main/archive/helpers/archive_favorites.c b/applications/main/archive/helpers/archive_favorites.c index 8bbcb521..f395ee5a 100644 --- a/applications/main/archive/helpers/archive_favorites.c +++ b/applications/main/archive/helpers/archive_favorites.c @@ -160,7 +160,7 @@ bool archive_favorites_read(void* context) { if(storage_file_exists(storage, furi_string_get_cstr(buffer))) { storage_common_stat(storage, furi_string_get_cstr(buffer), &file_info); archive_add_file_item( - browser, (file_info.flags & FSF_DIRECTORY), furi_string_get_cstr(buffer)); + browser, file_info_is_dir(&file_info), furi_string_get_cstr(buffer)); file_count++; } else { need_refresh = true; diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index 87265a45..a8bd937c 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -91,7 +91,7 @@ void archive_delete_file(void* context, const char* format, ...) { bool res = false; - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { res = storage_simply_remove_recursive(fs_api, furi_string_get_cstr(filename)); } else { res = (storage_common_remove(fs_api, furi_string_get_cstr(filename)) == FSE_OK); diff --git a/applications/main/fap_loader/fap_loader_app.c b/applications/main/fap_loader/fap_loader_app.c index 7911aa06..e81a3ce4 100644 --- a/applications/main/fap_loader/fap_loader_app.c +++ b/applications/main/fap_loader/fap_loader_app.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "elf_cpp/elf_hashtable.h" #include "fap_loader_app.h" @@ -105,6 +106,12 @@ static bool fap_loader_run_selected_app(FapLoader* loader) { FURI_LOG_I(TAG, "FAP Loader is starting app"); FuriThread* thread = flipper_application_spawn(loader->app, NULL); + + FuriString* app_name = furi_string_alloc(); + path_extract_filename_no_ext(furi_string_get_cstr(loader->fap_path), app_name); + furi_thread_set_appid(thread, furi_string_get_cstr(app_name)); + furi_string_free(app_name); + furi_thread_start(thread); furi_thread_join(thread); diff --git a/applications/plugins/hid_app/hid.c b/applications/plugins/hid_app/hid.c index 7f63f0cc..949ff63b 100644 --- a/applications/plugins/hid_app/hid.c +++ b/applications/plugins/hid_app/hid.c @@ -376,7 +376,17 @@ int32_t hid_ble_app(void* p) { // Wait 2nd core to update nvm storage furi_delay_ms(200); - bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH); + // Migrate data from old sd-card folder + Storage* storage = furi_record_open(RECORD_STORAGE); + + storage_common_migrate( + storage, + EXT_PATH("apps/Tools/" HID_BT_KEYS_STORAGE_NAME), + APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + bt_keys_storage_set_storage_path(app->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); + + furi_record_close(RECORD_STORAGE); if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) { FURI_LOG_E(TAG, "Failed to switch to HID profile"); diff --git a/applications/plugins/hid_app/hid.h b/applications/plugins/hid_app/hid.h index fe32a199..8ed1664a 100644 --- a/applications/plugins/hid_app/hid.h +++ b/applications/plugins/hid_app/hid.h @@ -23,7 +23,7 @@ #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" -#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys") +#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" typedef enum { HidTransportUsb, diff --git a/applications/plugins/music_player/music_player.c b/applications/plugins/music_player/music_player.c index 28127a57..2380d7d1 100644 --- a/applications/plugins/music_player/music_player.c +++ b/applications/plugins/music_player/music_player.c @@ -10,7 +10,6 @@ #define TAG "MusicPlayer" -#define MUSIC_PLAYER_APP_PATH_FOLDER ANY_PATH("music_player") #define MUSIC_PLAYER_APP_EXTENSION "*" #define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4 @@ -307,18 +306,24 @@ int32_t music_player_app(void* p) { if(p && strlen(p)) { furi_string_set(file_path, (const char*)p); } else { - furi_string_set(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_migrate( + storage, EXT_PATH("music_player"), STORAGE_APP_DATA_PATH_PREFIX); + furi_record_close(RECORD_STORAGE); + + furi_string_set(file_path, STORAGE_APP_DATA_PATH_PREFIX); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options( &browser_options, MUSIC_PLAYER_APP_EXTENSION, &I_music_10px); browser_options.hide_ext = false; - browser_options.base_path = MUSIC_PLAYER_APP_PATH_FOLDER; + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); furi_record_close(RECORD_DIALOGS); + if(!res) { FURI_LOG_E(TAG, "No file selected"); break; diff --git a/applications/plugins/picopass/helpers/iclass_elite_dict.c b/applications/plugins/picopass/helpers/iclass_elite_dict.c index 455eb23c..e8c13dd1 100644 --- a/applications/plugins/picopass/helpers/iclass_elite_dict.c +++ b/applications/plugins/picopass/helpers/iclass_elite_dict.c @@ -3,8 +3,8 @@ #include #include -#define ICLASS_ELITE_DICT_FLIPPER_PATH EXT_PATH("picopass/assets/iclass_elite_dict.txt") -#define ICLASS_ELITE_DICT_USER_PATH EXT_PATH("picopass/assets/iclass_elite_dict_user.txt") +#define ICLASS_ELITE_DICT_FLIPPER_NAME APP_DATA_PATH("assets/iclass_elite_dict.txt") +#define ICLASS_ELITE_DICT_USER_NAME APP_DATA_PATH("assets/iclass_elite_dict_user.txt") #define TAG "IclassEliteDict" @@ -21,10 +21,10 @@ bool iclass_elite_dict_check_presence(IclassEliteDictType dict_type) { bool dict_present = false; if(dict_type == IclassEliteDictTypeFlipper) { - dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_PATH, NULL) == - FSE_OK; + dict_present = + (storage_common_stat(storage, ICLASS_ELITE_DICT_FLIPPER_NAME, NULL) == FSE_OK); } else if(dict_type == IclassEliteDictTypeUser) { - dict_present = storage_common_stat(storage, ICLASS_ELITE_DICT_USER_PATH, NULL) == FSE_OK; + dict_present = (storage_common_stat(storage, ICLASS_ELITE_DICT_USER_NAME, NULL) == FSE_OK); } furi_record_close(RECORD_STORAGE); @@ -36,27 +36,26 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { IclassEliteDict* dict = malloc(sizeof(IclassEliteDict)); Storage* storage = furi_record_open(RECORD_STORAGE); dict->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); FuriString* next_line = furi_string_alloc(); bool dict_loaded = false; do { if(dict_type == IclassEliteDictTypeFlipper) { if(!buffered_file_stream_open( - dict->stream, ICLASS_ELITE_DICT_FLIPPER_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + dict->stream, ICLASS_ELITE_DICT_FLIPPER_NAME, FSAM_READ, FSOM_OPEN_EXISTING)) { buffered_file_stream_close(dict->stream); break; } } else if(dict_type == IclassEliteDictTypeUser) { if(!buffered_file_stream_open( - dict->stream, ICLASS_ELITE_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { + dict->stream, ICLASS_ELITE_DICT_USER_NAME, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { buffered_file_stream_close(dict->stream); break; } } // Read total amount of keys - while(true) { + while(true) { //-V547 if(!stream_read_line(dict->stream, next_line)) break; if(furi_string_get_char(next_line, 0) == '#') continue; if(furi_string_size(next_line) != ICLASS_ELITE_KEY_LINE_LEN) continue; @@ -69,12 +68,13 @@ IclassEliteDict* iclass_elite_dict_alloc(IclassEliteDictType dict_type) { FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); } while(false); - if(!dict_loaded) { + if(!dict_loaded) { //-V547 buffered_file_stream_close(dict->stream); free(dict); dict = NULL; } + furi_record_close(RECORD_STORAGE); furi_string_free(next_line); return dict; diff --git a/applications/plugins/picopass/picopass.c b/applications/plugins/picopass/picopass.c index 96ea82c3..5d1cee70 100644 --- a/applications/plugins/picopass/picopass.c +++ b/applications/plugins/picopass/picopass.c @@ -171,6 +171,12 @@ void picopass_show_loading_popup(void* context, bool show) { } } +static void picopass_migrate_from_old_folder() { + Storage* storage = furi_record_open(RECORD_STORAGE); + storage_common_migrate(storage, "/ext/picopass", STORAGE_APP_DATA_PATH_PREFIX); + furi_record_close(RECORD_STORAGE); +} + bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) { bool result = size > 0; while(size > 0) { @@ -183,6 +189,8 @@ bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) int32_t picopass_app(void* p) { UNUSED(p); + picopass_migrate_from_old_folder(); + Picopass* picopass = picopass_alloc(); scene_manager_next_scene(picopass->scene_manager, PicopassSceneStart); diff --git a/applications/plugins/picopass/picopass_device.c b/applications/plugins/picopass/picopass_device.c index e3940698..ec0bc5af 100644 --- a/applications/plugins/picopass/picopass_device.c +++ b/applications/plugins/picopass/picopass_device.c @@ -48,13 +48,9 @@ static bool picopass_device_save_file( if(use_load_path && !furi_string_empty(dev->load_path)) { // Get directory name path_extract_dirname(furi_string_get_cstr(dev->load_path), temp_str); - // Create picopass directory if necessary - if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(temp_str))) break; // Make path to file to save furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension); } else { - // Create picopass directory if necessary - if(!storage_simply_mkdir(dev->storage, PICOPASS_APP_FOLDER)) break; // First remove picopass device file if it was saved furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); } @@ -126,10 +122,11 @@ static bool picopass_device_save_file( bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { if(dev->format == PicopassDeviceSaveFormatHF) { return picopass_device_save_file( - dev, dev_name, PICOPASS_APP_FOLDER, PICOPASS_APP_EXTENSION, true); + dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true); } else if(dev->format == PicopassDeviceSaveFormatLF) { return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true); } + return false; } @@ -225,13 +222,12 @@ void picopass_device_free(PicopassDevice* picopass_dev) { bool picopass_file_select(PicopassDevice* dev) { furi_assert(dev); - // Input events and views are managed by file_browser FuriString* picopass_app_folder; - picopass_app_folder = furi_string_alloc_set(PICOPASS_APP_FOLDER); + picopass_app_folder = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, PICOPASS_APP_EXTENSION, &I_Nfc_10px); - browser_options.base_path = PICOPASS_APP_FOLDER; + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; bool res = dialog_file_browser_show( dev->dialogs, dev->load_path, picopass_app_folder, &browser_options); @@ -274,7 +270,7 @@ bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { furi_string_set(file_path, dev->load_path); } else { furi_string_printf( - file_path, "%s/%s%s", PICOPASS_APP_FOLDER, dev->dev_name, PICOPASS_APP_EXTENSION); + file_path, APP_DATA_PATH("%s%s"), dev->dev_name, PICOPASS_APP_EXTENSION); } if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; deleted = true; diff --git a/applications/plugins/picopass/picopass_device.h b/applications/plugins/picopass/picopass_device.h index 99f1ceea..d7d0977d 100644 --- a/applications/plugins/picopass/picopass_device.h +++ b/applications/plugins/picopass/picopass_device.h @@ -24,7 +24,6 @@ #define PICOPASS_AIA_BLOCK_INDEX 5 #define PICOPASS_PACS_CFG_BLOCK_INDEX 6 -#define PICOPASS_APP_FOLDER ANY_PATH("picopass") #define PICOPASS_APP_EXTENSION ".picopass" #define PICOPASS_APP_SHADOW_EXTENSION ".pas" @@ -81,7 +80,6 @@ typedef struct { PicopassDeviceSaveFormat format; PicopassLoadingCallback loading_cb; void* loading_cb_ctx; - } PicopassDevice; PicopassDevice* picopass_device_alloc(); diff --git a/applications/plugins/picopass/scenes/picopass_scene_save_name.c b/applications/plugins/picopass/scenes/picopass_scene_save_name.c index 59f33c79..baf882b8 100644 --- a/applications/plugins/picopass/scenes/picopass_scene_save_name.c +++ b/applications/plugins/picopass/scenes/picopass_scene_save_name.c @@ -31,12 +31,10 @@ void picopass_scene_save_name_on_enter(void* context) { dev_name_empty); FuriString* folder_path; - folder_path = furi_string_alloc(); + folder_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); if(furi_string_end_with(picopass->dev->load_path, PICOPASS_APP_EXTENSION)) { path_extract_dirname(furi_string_get_cstr(picopass->dev->load_path), folder_path); - } else { - furi_string_set(folder_path, PICOPASS_APP_FOLDER); } ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( diff --git a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c index b664df68..38d064a4 100644 --- a/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c +++ b/applications/plugins/spi_mem_manager/scenes/spi_mem_scene_start.c @@ -60,7 +60,7 @@ bool spi_mem_scene_start_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(app->scene_manager, SPIMemSceneChipDetect); success = true; } else if(event.event == SPIMemSceneStartSubmenuIndexSaved) { - furi_string_set(app->file_path, SPI_MEM_FILE_FOLDER); + furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX); scene_manager_next_scene(app->scene_manager, SPIMemSceneSelectFile); success = true; } else if(event.event == SPIMemSceneStartSubmenuIndexErase) { diff --git a/applications/plugins/spi_mem_manager/spi_mem_app.c b/applications/plugins/spi_mem_manager/spi_mem_app.c index 63531b74..96c3632d 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_app.c +++ b/applications/plugins/spi_mem_manager/spi_mem_app.c @@ -16,9 +16,9 @@ static bool spi_mem_back_event_callback(void* context) { } SPIMemApp* spi_mem_alloc(void) { - SPIMemApp* instance = malloc(sizeof(SPIMemApp)); + SPIMemApp* instance = malloc(sizeof(SPIMemApp)); //-V799 - instance->file_path = furi_string_alloc(); + instance->file_path = furi_string_alloc_set(STORAGE_APP_DATA_PATH_PREFIX); instance->gui = furi_record_open(RECORD_GUI); instance->notifications = furi_record_open(RECORD_NOTIFICATION); instance->view_dispatcher = view_dispatcher_alloc(); @@ -37,7 +37,8 @@ SPIMemApp* spi_mem_alloc(void) { instance->text_input = text_input_alloc(); instance->mode = SPIMemModeUnknown; - furi_string_set(instance->file_path, SPI_MEM_FILE_FOLDER); + // Migrate data from old sd-card folder + storage_common_migrate(instance->storage, EXT_PATH("spimem"), STORAGE_APP_DATA_PATH_PREFIX); view_dispatcher_enable_queue(instance->view_dispatcher); view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance); @@ -70,7 +71,7 @@ SPIMemApp* spi_mem_alloc(void) { furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_external); scene_manager_next_scene(instance->scene_manager, SPIMemSceneStart); return instance; -} +} //-V773 void spi_mem_free(SPIMemApp* instance) { view_dispatcher_remove_view(instance->view_dispatcher, SPIMemViewSubmenu); @@ -105,7 +106,6 @@ void spi_mem_free(SPIMemApp* instance) { int32_t spi_mem_app(void* p) { UNUSED(p); SPIMemApp* instance = spi_mem_alloc(); - spi_mem_file_create_folder(instance); view_dispatcher_run(instance->view_dispatcher); spi_mem_free(instance); return 0; diff --git a/applications/plugins/spi_mem_manager/spi_mem_app_i.h b/applications/plugins/spi_mem_manager/spi_mem_app_i.h index 4ce05617..285ca66d 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_app_i.h +++ b/applications/plugins/spi_mem_manager/spi_mem_app_i.h @@ -24,7 +24,6 @@ #define TAG "SPIMem" #define SPI_MEM_FILE_EXTENSION ".bin" -#define SPI_MEM_FILE_FOLDER EXT_PATH("spimem") #define SPI_MEM_FILE_NAME_SIZE 100 #define SPI_MEM_TEXT_BUFFER_SIZE 128 diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.c b/applications/plugins/spi_mem_manager/spi_mem_files.c index a7374da1..9b787bd7 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_files.c +++ b/applications/plugins/spi_mem_manager/spi_mem_files.c @@ -1,11 +1,5 @@ #include "spi_mem_app_i.h" -void spi_mem_file_create_folder(SPIMemApp* app) { - if(!storage_simply_mkdir(app->storage, SPI_MEM_FILE_FOLDER)) { - dialog_message_show_storage_error(app->dialogs, "Cannot create\napp folder"); - } -} - bool spi_mem_file_delete(SPIMemApp* app) { return (storage_simply_remove(app->storage, furi_string_get_cstr(app->file_path))); } @@ -13,7 +7,7 @@ bool spi_mem_file_delete(SPIMemApp* app) { bool spi_mem_file_select(SPIMemApp* app) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, SPI_MEM_FILE_EXTENSION, &I_Dip8_10px); - browser_options.base_path = SPI_MEM_FILE_FOLDER; + browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; bool success = dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options); return success; diff --git a/applications/plugins/spi_mem_manager/spi_mem_files.h b/applications/plugins/spi_mem_manager/spi_mem_files.h index 0e735d95..6a529d32 100644 --- a/applications/plugins/spi_mem_manager/spi_mem_files.h +++ b/applications/plugins/spi_mem_manager/spi_mem_files.h @@ -1,7 +1,6 @@ #pragma once #include "spi_mem_app.h" -void spi_mem_file_create_folder(SPIMemApp* app); bool spi_mem_file_select(SPIMemApp* app); bool spi_mem_file_create(SPIMemApp* app, const char* file_name); bool spi_mem_file_delete(SPIMemApp* app); diff --git a/applications/services/applications.h b/applications/services/applications.h index acbfea31..871e9af5 100644 --- a/applications/services/applications.h +++ b/applications/services/applications.h @@ -11,6 +11,7 @@ typedef enum { typedef struct { const FuriThreadCallback app; const char* name; + const char* appid; const size_t stack_size; const Icon* icon; const FlipperApplicationFlag flags; diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index e3cf78cc..2f56b50a 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -45,7 +45,14 @@ void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path) { furi_assert(bt->keys_storage); furi_assert(keys_storage_path); - bt_keys_storage_set_file_path(bt->keys_storage, keys_storage_path); + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* path = furi_string_alloc_set(keys_storage_path); + storage_common_resolve_path_and_ensure_app_directory(storage, path); + + bt_keys_storage_set_file_path(bt->keys_storage, furi_string_get_cstr(path)); + + furi_string_free(path); + furi_record_close(RECORD_STORAGE); } void bt_keys_storage_set_default_path(Bt* bt) { diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index ca9d8b98..0f042f6c 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -372,11 +372,18 @@ void cli_command_ps(Cli* cli, FuriString* args, void* context) { FuriThreadId threads_ids[threads_num_max]; uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); printf( - "%-20s %-14s %-8s %-8s %s\r\n", "Name", "Stack start", "Heap", "Stack", "Stack min free"); + "%-20s %-20s %-14s %-8s %-8s %s\r\n", + "AppID", + "Name", + "Stack start", + "Heap", + "Stack", + "Stack min free"); for(uint8_t i = 0; i < thread_num; i++) { TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i]; printf( - "%-20s 0x%-12lx %-8zu %-8lu %-8lu\r\n", + "%-20s %-20s 0x%-12lx %-8zu %-8lu %-8lu\r\n", + furi_thread_get_appid(threads_ids[i]), furi_thread_get_name(threads_ids[i]), (uint32_t)tcb->pxStack, memmgr_heap_get_thread_memory(threads_ids[i]), diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index ca2435b9..4723a1f9 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -2,6 +2,7 @@ #include "dialogs_i.h" #include #include +#include /****************** File browser ******************/ @@ -13,6 +14,22 @@ bool dialog_file_browser_show( FuriApiLock lock = api_lock_alloc_locked(); furi_check(lock != NULL); + Storage* storage = furi_record_open(RECORD_STORAGE); + FuriString* base_path = furi_string_alloc(); + + if(options && options->base_path) { + furi_string_set(base_path, options->base_path); + storage_common_resolve_path_and_ensure_app_directory(storage, base_path); + } + + if(result_path) { + storage_common_resolve_path_and_ensure_app_directory(storage, result_path); + } + + if(path) { + storage_common_resolve_path_and_ensure_app_directory(storage, path); + } + DialogsAppData data = { .file_browser = { .extension = options ? options->extension : "", @@ -24,7 +41,7 @@ bool dialog_file_browser_show( .preselected_filename = path, .item_callback = options ? options->item_loader_callback : NULL, .item_callback_context = options ? options->item_loader_context : NULL, - .base_path = options ? options->base_path : NULL, + .base_path = furi_string_get_cstr(base_path), }}; DialogsAppReturn return_data; @@ -39,6 +56,9 @@ bool dialog_file_browser_show( furi_message_queue_put(context->message_queue, &message, FuriWaitForever) == FuriStatusOk); api_lock_wait_unlock_and_free(lock); + furi_record_close(RECORD_STORAGE); + furi_string_free(base_path); + return return_data.bool_value; } diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 80c4f4c4..857acbba 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -60,7 +60,7 @@ static bool browser_path_is_file(FuriString* path) { FileInfo file_info; Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { - if((file_info.flags & FSF_DIRECTORY) == 0) { + if(!file_info_is_dir(&file_info)) { state = true; } } @@ -119,7 +119,7 @@ static bool browser_folder_check_and_switch(FuriString* path) { while(1) { // Check if folder is existing and navigate back if not if(storage_common_stat(storage, furi_string_get_cstr(path), &file_info) == FSE_OK) { - if(file_info.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&file_info)) { break; } } @@ -161,7 +161,7 @@ static bool browser_folder_init( if((storage_file_get_error(directory) == FSE_OK) && (name_temp[0] != '\0')) { total_files_cnt++; furi_string_set(name_str, name_temp); - if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(browser_filter_by_name(browser, name_str, file_info_is_dir(&file_info))) { if(!furi_string_empty(filename)) { if(furi_string_cmp(name_str, filename) == 0) { *file_idx = *item_cnt; @@ -214,7 +214,7 @@ static bool } if(storage_file_get_error(directory) == FSE_OK) { furi_string_set(name_str, name_temp); - if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(browser_filter_by_name(browser, name_str, file_info_is_dir(&file_info))) { items_cnt++; } } else { @@ -236,11 +236,11 @@ static bool } if(storage_file_get_error(directory) == FSE_OK) { furi_string_set(name_str, name_temp); - if(browser_filter_by_name(browser, name_str, (file_info.flags & FSF_DIRECTORY))) { + if(browser_filter_by_name(browser, name_str, file_info_is_dir(&file_info))) { furi_string_printf(name_str, "%s/%s", furi_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); + browser->cb_ctx, name_str, file_info_is_dir(&file_info), false); } items_cnt++; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index caaf9f11..5f2d8a2e 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -29,6 +29,8 @@ static bool } furi_thread_set_name(loader_instance->application_thread, loader_instance->application->name); + furi_thread_set_appid( + loader_instance->application_thread, loader_instance->application->appid); furi_thread_set_stack_size( loader_instance->application_thread, loader_instance->application->stack_size); furi_thread_set_context( diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index c4493cc7..c3a4a047 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -201,7 +201,7 @@ static void rpc_system_storage_stat_process(const PB_Main* request, void* contex if(error == FSE_OK) { response->which_content = PB_Main_storage_stat_response_tag; response->content.storage_stat_response.has_file = true; - response->content.storage_stat_response.file.type = (fileinfo.flags & FSF_DIRECTORY) ? + response->content.storage_stat_response.file.type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : PB_Storage_File_FileType_FILE; response->content.storage_stat_response.file.size = fileinfo.size; @@ -291,9 +291,8 @@ static void rpc_system_storage_list_process(const PB_Main* request, void* contex rpc_send_and_release(session, &response); i = 0; } - list->file[i].type = (fileinfo.flags & FSF_DIRECTORY) ? - PB_Storage_File_FileType_DIR : - PB_Storage_File_FileType_FILE; + list->file[i].type = file_info_is_dir(&fileinfo) ? PB_Storage_File_FileType_DIR : + PB_Storage_File_FileType_FILE; list->file[i].size = fileinfo.size; list->file[i].data = NULL; list->file[i].name = name; @@ -458,7 +457,7 @@ static bool rpc_system_storage_is_dir_is_empty(Storage* fs_api, const char* path FileInfo fileinfo; bool is_dir_is_empty = true; FS_Error error = storage_common_stat(fs_api, path, &fileinfo); - if((error == FSE_OK) && (fileinfo.flags & FSF_DIRECTORY)) { + if((error == FSE_OK) && file_info_is_dir(&fileinfo)) { File* dir = storage_file_alloc(fs_api); if(storage_dir_open(dir, path)) { char* name = malloc(MAX_NAME_LENGTH); diff --git a/applications/services/storage/filesystem_api.c b/applications/services/storage/filesystem_api.c index b979967a..30b20ede 100644 --- a/applications/services/storage/filesystem_api.c +++ b/applications/services/storage/filesystem_api.c @@ -36,3 +36,7 @@ const char* filesystem_api_error_get_desc(FS_Error error_id) { } return result; } + +bool file_info_is_dir(const FileInfo* file_info) { + return (file_info->flags & FSF_DIRECTORY); +} \ No newline at end of file diff --git a/applications/services/storage/filesystem_api_defines.h b/applications/services/storage/filesystem_api_defines.h index b73e6eb3..cd24b882 100644 --- a/applications/services/storage/filesystem_api_defines.h +++ b/applications/services/storage/filesystem_api_defines.h @@ -1,5 +1,6 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -40,10 +41,10 @@ typedef enum { FSF_DIRECTORY = (1 << 0), /**< Directory */ } FS_Flags; -/** Structure that hold file index and returned api errors */ +/** Structure that hold file index and returned api errors */ typedef struct File File; -/** Structure that hold file info */ +/** Structure that hold file info */ typedef struct { uint8_t flags; /**< flags from FS_Flags enum */ uint64_t size; /**< file size */ @@ -55,6 +56,12 @@ typedef struct { */ const char* filesystem_api_error_get_desc(FS_Error error_id); +/** Checks if file info is directory + * @param file_info file info pointer + * @return bool is directory + */ +bool file_info_is_dir(const FileInfo* file_info); + #ifdef __cplusplus } #endif diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index e093cbe0..e35b8164 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -10,10 +10,12 @@ extern "C" { #define STORAGE_INT_PATH_PREFIX "/int" #define STORAGE_EXT_PATH_PREFIX "/ext" #define STORAGE_ANY_PATH_PREFIX "/any" +#define STORAGE_APP_DATA_PATH_PREFIX "/app" #define INT_PATH(path) STORAGE_INT_PATH_PREFIX "/" path #define EXT_PATH(path) STORAGE_EXT_PATH_PREFIX "/" path #define ANY_PATH(path) STORAGE_ANY_PATH_PREFIX "/" path +#define APP_DATA_PATH(path) STORAGE_APP_DATA_PATH_PREFIX "/" path #define RECORD_STORAGE "storage" @@ -175,6 +177,15 @@ bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_ */ bool storage_dir_rewind(File* file); +/** + * @brief Check that dir exists + * + * @param storage + * @param path + * @return bool + */ +bool storage_dir_exists(Storage* storage, const char* path); + /******************* Common Functions *******************/ /** Retrieves unix timestamp of last access @@ -246,6 +257,36 @@ FS_Error storage_common_fs_info( uint64_t* total_space, uint64_t* free_space); +/** + * @brief Parse aliases in path and replace them with real path + * Also will create special folders if they are not exist + * + * @param storage + * @param path + * @return bool + */ +void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path); + +/** + * @brief Move content of one folder to another, with rename of all conflicting files. + * Source folder will be deleted if the migration is successful. + * + * @param storage + * @param source + * @param dest + * @return FS_Error + */ +FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest); + +/** + * @brief Check that file or dir exists + * + * @param storage + * @param path + * @return bool + */ +bool storage_common_exists(Storage* storage, const char* path); + /******************* Error Functions *******************/ /** Retrieves the error text from the error id diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index ff205569..8e2dcdbb 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -131,7 +131,7 @@ static void storage_cli_list(Cli* cli, FuriString* path) { while(storage_dir_read(file, &fileinfo, name, MAX_NAME_LENGTH)) { read_done = true; - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { printf("\t[D] %s\r\n", name); } else { printf("\t[F] %s %lub\r\n", name, (uint32_t)(fileinfo.size)); @@ -169,7 +169,7 @@ static void storage_cli_tree(Cli* cli, FuriString* path) { while(dir_walk_read(dir_walk, name, &fileinfo) == DirWalkOK) { read_done = true; - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { printf("\t[D] %s\r\n", furi_string_get_cstr(name)); } else { printf( @@ -383,7 +383,7 @@ static void storage_cli_stat(Cli* cli, FuriString* path) { FS_Error error = storage_common_stat(api, furi_string_get_cstr(path), &fileinfo); if(error == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { printf("Directory\r\n"); } else { printf("File, size: %lub\r\n", (uint32_t)(fileinfo.size)); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index c5dfd533..8d8220f8 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -39,12 +39,6 @@ .file = file, \ }}; -#define S_API_DATA_PATH \ - SAData data = { \ - .path = { \ - .path = path, \ - }}; - #define S_RETURN_BOOL (return_data.bool_value); #define S_RETURN_UINT16 (return_data.uint16_value); #define S_RETURN_UINT64 (return_data.uint64_value); @@ -70,6 +64,7 @@ static bool storage_file_open_internal( .path = path, .access_mode = access_mode, .open_mode = open_mode, + .thread_id = furi_thread_get_current_id(), }}; file->type = FileTypeOpenFile; @@ -249,7 +244,7 @@ bool storage_file_exists(Storage* storage, const char* path) { FileInfo fileinfo; FS_Error error = storage_common_stat(storage, path, &fileinfo); - if(error == FSE_OK && !(fileinfo.flags & FSF_DIRECTORY)) { + if(error == FSE_OK && !file_info_is_dir(&fileinfo)) { exist = true; } @@ -266,6 +261,7 @@ static bool storage_dir_open_internal(File* file, const char* path) { .dopen = { .file = file, .path = path, + .thread_id = furi_thread_get_current_id(), }}; file->type = FileTypeOpenDir; @@ -349,12 +345,28 @@ bool storage_dir_rewind(File* file) { return S_RETURN_BOOL; } +bool storage_dir_exists(Storage* storage, const char* path) { + bool exist = false; + FileInfo fileinfo; + FS_Error error = storage_common_stat(storage, path, &fileinfo); + + if(error == FSE_OK && file_info_is_dir(&fileinfo)) { + exist = true; + } + + return exist; +} /****************** COMMON ******************/ FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp) { S_API_PROLOGUE; - SAData data = {.ctimestamp = {.path = path, .timestamp = timestamp}}; + SAData data = { + .ctimestamp = { + .path = path, + .timestamp = timestamp, + .thread_id = furi_thread_get_current_id(), + }}; S_API_MESSAGE(StorageCommandCommonTimestamp); S_API_EPILOGUE; @@ -363,8 +375,12 @@ FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) { S_API_PROLOGUE; - - SAData data = {.cstat = {.path = path, .fileinfo = fileinfo}}; + SAData data = { + .cstat = { + .path = path, + .fileinfo = fileinfo, + .thread_id = furi_thread_get_current_id(), + }}; S_API_MESSAGE(StorageCommandCommonStat); S_API_EPILOGUE; @@ -373,7 +389,12 @@ FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* filei FS_Error storage_common_remove(Storage* storage, const char* path) { S_API_PROLOGUE; - S_API_DATA_PATH; + SAData data = { + .path = { + .path = path, + .thread_id = furi_thread_get_current_id(), + }}; + S_API_MESSAGE(StorageCommandCommonRemove); S_API_EPILOGUE; return S_RETURN_ERROR; @@ -423,7 +444,7 @@ static FS_Error furi_string_right(path, strlen(old_path)); furi_string_printf(tmp_new_path, "%s%s", new_path, furi_string_get_cstr(path)); - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path)); } else { error = storage_common_copy( @@ -452,7 +473,7 @@ FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* error = storage_common_stat(storage, old_path, &fileinfo); if(error == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_copy_recursive(storage, old_path, new_path); } else { Stream* stream_from = file_stream_alloc(storage); @@ -479,7 +500,7 @@ FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* static FS_Error storage_merge_recursive(Storage* storage, const char* old_path, const char* new_path) { - FS_Error error = storage_common_mkdir(storage, new_path); + FS_Error error = FSE_OK; DirWalk* dir_walk = dir_walk_alloc(storage); FuriString *path, *file_basename, *tmp_new_path; FileInfo fileinfo; @@ -488,7 +509,7 @@ static FS_Error tmp_new_path = furi_string_alloc(); do { - if((error != FSE_OK) && (error != FSE_EXIST)) break; + if(!storage_simply_mkdir(storage, new_path)) break; dir_walk_set_recursive(dir_walk, false); if(!dir_walk_open(dir_walk, old_path)) { @@ -508,13 +529,13 @@ static FS_Error path_extract_basename(furi_string_get_cstr(path), file_basename); path_concat(new_path, furi_string_get_cstr(file_basename), tmp_new_path); - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { if(storage_common_stat( storage, furi_string_get_cstr(tmp_new_path), &fileinfo) == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_common_mkdir(storage, furi_string_get_cstr(tmp_new_path)); - if(error != FSE_OK) { + if(error != FSE_OK && error != FSE_EXIST) { break; } } @@ -548,7 +569,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char error = storage_common_stat(storage, old_path, &fileinfo); if(error == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { error = storage_merge_recursive(storage, old_path, new_path); } else { error = storage_common_stat(storage, new_path, &fileinfo); @@ -556,7 +577,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char furi_string_set(new_path_next, new_path); FuriString* dir_path; FuriString* filename; - char extension[MAX_EXT_LEN]; + char extension[MAX_EXT_LEN] = {0}; dir_path = furi_string_alloc(); filename = furi_string_alloc(); @@ -608,7 +629,12 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char FS_Error storage_common_mkdir(Storage* storage, const char* path) { S_API_PROLOGUE; - S_API_DATA_PATH; + SAData data = { + .path = { + .path = path, + .thread_id = furi_thread_get_current_id(), + }}; + S_API_MESSAGE(StorageCommandCommonMkDir); S_API_EPILOGUE; return S_RETURN_ERROR; @@ -626,6 +652,7 @@ FS_Error storage_common_fs_info( .fs_path = fs_path, .total_space = total_space, .free_space = free_space, + .thread_id = furi_thread_get_current_id(), }}; S_API_MESSAGE(StorageCommandCommonFSInfo); @@ -633,6 +660,38 @@ FS_Error storage_common_fs_info( return S_RETURN_ERROR; } +void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path) { + S_API_PROLOGUE; + + SAData data = { + .cresolvepath = { + .path = path, + .thread_id = furi_thread_get_current_id(), + }}; + + S_API_MESSAGE(StorageCommandCommonResolvePath); + S_API_EPILOGUE; +} + +FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest) { + if(!storage_common_exists(storage, source)) { + return FSE_OK; + } + + FS_Error error = storage_common_merge(storage, source, dest); + + if(error == FSE_OK) { + storage_simply_remove_recursive(storage, source); + } + + return error; +} + +bool storage_common_exists(Storage* storage, const char* path) { + FileInfo file_info; + return storage_common_stat(storage, path, &file_info) == FSE_OK; +} + /****************** ERROR ******************/ const char* storage_error_get_desc(FS_Error error_id) { @@ -750,7 +809,7 @@ bool storage_simply_remove_recursive(Storage* storage, const char* path) { } while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { - if(fileinfo.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&fileinfo)) { furi_string_cat_printf(cur_dir, "/%s", name); go_deeper = true; break; diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index cccf4046..5dabfa38 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -5,21 +5,18 @@ void storage_file_init(StorageFile* obj) { obj->file = NULL; - obj->type = ST_ERROR; obj->file_data = NULL; obj->path = furi_string_alloc(); } void storage_file_init_set(StorageFile* obj, const StorageFile* src) { obj->file = src->file; - obj->type = src->type; obj->file_data = src->file_data; obj->path = furi_string_alloc_set(src->path); } void storage_file_set(StorageFile* obj, const StorageFile* src) { //-V524 obj->file = src->file; - obj->type = src->type; obj->file_data = src->file_data; furi_string_set(obj->path, src->path); } @@ -150,16 +147,10 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage) { return founded_file->file_data; } -void storage_push_storage_file( - File* file, - FuriString* path, - StorageType type, - StorageData* storage) { +void storage_push_storage_file(File* file, FuriString* path, StorageData* storage) { StorageFile* storage_file = StorageFileList_push_new(storage->files); - file->file_id = (uint32_t)storage_file; storage_file->file = file; - storage_file->type = type; furi_string_set(storage_file->path, path); } diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index 501c26ab..bf0a1c69 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -18,7 +18,6 @@ typedef struct { typedef struct { File* file; - StorageType type; void* file_data; FuriString* path; } StorageFile; @@ -66,11 +65,7 @@ bool storage_path_already_open(FuriString* path, StorageFileList_t files); void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); void* storage_get_storage_file_data(const File* file, StorageData* storage); -void storage_push_storage_file( - File* file, - FuriString* path, - StorageType type, - StorageData* storage); +void storage_push_storage_file(File* file, FuriString* path, StorageData* storage); bool storage_pop_storage_file(File* file, StorageData* storage); #ifdef __cplusplus diff --git a/applications/services/storage/storage_i.h b/applications/services/storage/storage_i.h index 406fc921..85df5d92 100644 --- a/applications/services/storage/storage_i.h +++ b/applications/services/storage/storage_i.h @@ -12,6 +12,8 @@ extern "C" { #define STORAGE_COUNT (ST_INT + 1) +#define APPS_DATA_PATH EXT_PATH("apps_data") + typedef struct { ViewPort* view_port; bool enabled; diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 3edb1018..9e13bf83 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -11,6 +11,7 @@ typedef struct { const char* path; FS_AccessMode access_mode; FS_OpenMode open_mode; + FuriThreadId thread_id; } SADataFOpen; typedef struct { @@ -34,6 +35,7 @@ typedef struct { typedef struct { File* file; const char* path; + FuriThreadId thread_id; } SADataDOpen; typedef struct { @@ -46,25 +48,34 @@ typedef struct { typedef struct { const char* path; uint32_t* timestamp; + FuriThreadId thread_id; } SADataCTimestamp; typedef struct { const char* path; FileInfo* fileinfo; + FuriThreadId thread_id; } SADataCStat; typedef struct { const char* fs_path; uint64_t* total_space; uint64_t* free_space; + FuriThreadId thread_id; } SADataCFSInfo; +typedef struct { + FuriString* path; + FuriThreadId thread_id; +} SADataCResolvePath; + typedef struct { uint32_t id; } SADataError; typedef struct { const char* path; + FuriThreadId thread_id; } SADataPath; typedef struct { @@ -87,6 +98,7 @@ typedef union { SADataCTimestamp ctimestamp; SADataCStat cstat; SADataCFSInfo cfsinfo; + SADataCResolvePath cresolvepath; SADataError error; @@ -128,6 +140,7 @@ typedef enum { StorageCommandSDUnmount, StorageCommandSDInfo, StorageCommandSDStatus, + StorageCommandCommonResolvePath, } StorageCommand; typedef struct { diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index b1ea5d29..cab1edff 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -4,17 +4,11 @@ #define FS_CALL(_storage, _fn) ret = _storage->fs_api->_fn; -static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { - furi_check(type == ST_EXT || type == ST_INT); - StorageData* storage = &app->storage[type]; - return storage; -} - -static bool storage_type_is_not_valid(StorageType type) { +static bool storage_type_is_valid(StorageType type) { #ifdef FURI_RAM_EXEC - return type != ST_EXT; + return type == ST_EXT; #else - return type >= ST_ERROR; + return type < ST_ERROR; #endif } @@ -30,25 +24,21 @@ static StorageData* get_storage_by_file(File* file, StorageData* storages) { return storage_data; } -static const char* remove_vfs(const char* path) { - return path + MIN(4u, strlen(path)); +static const char* cstr_path_without_vfs_prefix(FuriString* path) { + const char* path_cstr = furi_string_get_cstr(path); + return path_cstr + MIN(4u, strlen(path_cstr)); } -static StorageType storage_get_type_by_path(Storage* app, const char* path) { +static StorageType storage_get_type_by_path(FuriString* path) { StorageType type = ST_ERROR; - if(memcmp(path, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { - type = ST_EXT; - } else if(memcmp(path, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { - type = ST_INT; - } else if(memcmp(path, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { - type = ST_ANY; - } + const char* path_cstr = furi_string_get_cstr(path); - if(type == ST_ANY) { + if(memcmp(path_cstr, STORAGE_EXT_PATH_PREFIX, strlen(STORAGE_EXT_PATH_PREFIX)) == 0) { + type = ST_EXT; + } else if(memcmp(path_cstr, STORAGE_INT_PATH_PREFIX, strlen(STORAGE_INT_PATH_PREFIX)) == 0) { type = ST_INT; - if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) { - type = ST_EXT; - } + } else if(memcmp(path_cstr, STORAGE_ANY_PATH_PREFIX, strlen(STORAGE_ANY_PATH_PREFIX)) == 0) { + type = ST_ANY; } return type; @@ -71,38 +61,51 @@ static void storage_path_change_to_real_storage(FuriString* path, StorageType re } } +FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage) { + StorageType type = storage_get_type_by_path(path); + + if(storage_type_is_valid(type)) { + if(type == ST_ANY) { + type = ST_INT; + if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) { + type = ST_EXT; + } + storage_path_change_to_real_storage(path, type); + } + + furi_assert(type == ST_EXT || type == ST_INT); + *storage = &app->storage[type]; + + return FSE_OK; + } else { + return FSE_INVALID_NAME; + } +} + /******************* File Functions *******************/ bool storage_process_file_open( Storage* app, File* file, - const char* path, + FuriString* path, FS_AccessMode access_mode, FS_OpenMode open_mode) { bool ret = false; - StorageType type = storage_get_type_by_path(app, path); StorageData* storage; - file->error_id = FSE_OK; + file->error_id = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - file->error_id = FSE_INVALID_NAME; - } else { - storage = storage_get_storage_by_type(app, type); - FuriString* real_path; - real_path = furi_string_alloc_set(path); - storage_path_change_to_real_storage(real_path, type); - - if(storage_path_already_open(real_path, storage->files)) { + if(file->error_id == FSE_OK) { + if(storage_path_already_open(path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { if(access_mode & FSAM_WRITE) { storage_data_timestamp(storage); } - storage_push_storage_file(file, real_path, type, storage); - FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); - } + storage_push_storage_file(file, path, storage); - furi_string_free(real_path); + const char* path_cstr_no_vfs = cstr_path_without_vfs_prefix(path); + FS_CALL(storage, file.open(storage, file, path_cstr_no_vfs, access_mode, open_mode)); + } } return ret; @@ -243,27 +246,18 @@ static bool storage_process_file_eof(Storage* app, File* file) { /******************* Dir Functions *******************/ -bool storage_process_dir_open(Storage* app, File* file, const char* path) { +bool storage_process_dir_open(Storage* app, File* file, FuriString* path) { bool ret = false; - StorageType type = storage_get_type_by_path(app, path); StorageData* storage; - file->error_id = FSE_OK; + file->error_id = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - file->error_id = FSE_INVALID_NAME; - } else { - storage = storage_get_storage_by_type(app, type); - FuriString* real_path; - real_path = furi_string_alloc_set(path); - storage_path_change_to_real_storage(real_path, type); - - if(storage_path_already_open(real_path, storage->files)) { + if(file->error_id == FSE_OK) { + if(storage_path_already_open(path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { - storage_push_storage_file(file, real_path, type, storage); - FS_CALL(storage, dir.open(storage, file, remove_vfs(path))); + storage_push_storage_file(file, path, storage); + FS_CALL(storage, dir.open(storage, file, cstr_path_without_vfs_prefix(path))); } - furi_string_free(real_path); } return ret; @@ -320,73 +314,52 @@ bool storage_process_dir_rewind(Storage* app, File* file) { /******************* Common FS Functions *******************/ static FS_Error - storage_process_common_timestamp(Storage* app, const char* path, uint32_t* timestamp) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); + storage_process_common_timestamp(Storage* app, FuriString* path, uint32_t* timestamp) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); + if(ret == FSE_OK) { *timestamp = storage_data_get_timestamp(storage); } return ret; } -static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); +static FS_Error storage_process_common_stat(Storage* app, FuriString* path, FileInfo* fileinfo) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); - FS_CALL(storage, common.stat(storage, remove_vfs(path), fileinfo)); + if(ret == FSE_OK) { + FS_CALL(storage, common.stat(storage, cstr_path_without_vfs_prefix(path), fileinfo)); } return ret; } -static FS_Error storage_process_common_remove(Storage* app, const char* path) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); - - FuriString* real_path; - real_path = furi_string_alloc_set(path); - storage_path_change_to_real_storage(real_path, type); +static FS_Error storage_process_common_remove(Storage* app, FuriString* path) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); do { - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - break; - } - - StorageData* storage = storage_get_storage_by_type(app, type); - if(storage_path_already_open(real_path, storage->files)) { + if(storage_path_already_open(path, storage->files)) { ret = FSE_ALREADY_OPEN; break; } storage_data_timestamp(storage); - FS_CALL(storage, common.remove(storage, remove_vfs(path))); + FS_CALL(storage, common.remove(storage, cstr_path_without_vfs_prefix(path))); } while(false); - furi_string_free(real_path); - return ret; } -static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, path); +static FS_Error storage_process_common_mkdir(Storage* app, FuriString* path) { + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); + if(ret == FSE_OK) { storage_data_timestamp(storage); - FS_CALL(storage, common.mkdir(storage, remove_vfs(path))); + FS_CALL(storage, common.mkdir(storage, cstr_path_without_vfs_prefix(path))); } return ret; @@ -394,17 +367,16 @@ static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { static FS_Error storage_process_common_fs_info( Storage* app, - const char* fs_path, + FuriString* path, uint64_t* total_space, uint64_t* free_space) { - FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(app, fs_path); + StorageData* storage; + FS_Error ret = storage_get_data(app, path, &storage); - if(storage_type_is_not_valid(type)) { - ret = FSE_INVALID_NAME; - } else { - StorageData* storage = storage_get_storage_by_type(app, type); - FS_CALL(storage, common.fs_info(storage, remove_vfs(fs_path), total_space, free_space)); + if(ret == FSE_OK) { + FS_CALL( + storage, + common.fs_info(storage, cstr_path_without_vfs_prefix(path), total_space, free_space)); } return ret; @@ -471,14 +443,52 @@ static FS_Error storage_process_sd_status(Storage* app) { return ret; } +/******************** Aliases processing *******************/ + +void storage_process_alias( + Storage* app, + FuriString* path, + FuriThreadId thread_id, + bool create_folders) { + if(furi_string_start_with(path, STORAGE_APP_DATA_PATH_PREFIX)) { + FuriString* apps_data_path_with_appsid = furi_string_alloc_set(APPS_DATA_PATH "/"); + furi_string_cat(apps_data_path_with_appsid, furi_thread_get_appid(thread_id)); + + // "/app" -> "/ext/apps_data/appsid" + furi_string_replace_at( + path, + 0, + strlen(STORAGE_APP_DATA_PATH_PREFIX), + furi_string_get_cstr(apps_data_path_with_appsid)); + + // Create app data folder if not exists + if(create_folders && + storage_process_common_stat(app, apps_data_path_with_appsid, NULL) != FSE_OK) { + furi_string_set(apps_data_path_with_appsid, APPS_DATA_PATH); + storage_process_common_mkdir(app, apps_data_path_with_appsid); + furi_string_cat(apps_data_path_with_appsid, "/"); + furi_string_cat(apps_data_path_with_appsid, furi_thread_get_appid(thread_id)); + storage_process_common_mkdir(app, apps_data_path_with_appsid); + } + + furi_string_free(apps_data_path_with_appsid); + } +} + /****************** API calls processing ******************/ + void storage_process_message_internal(Storage* app, StorageMessage* message) { + FuriString* path = NULL; + switch(message->command) { + // File operations case StorageCommandFileOpen: + path = furi_string_alloc_set(message->data->fopen.path); + storage_process_alias(app, path, message->data->fopen.thread_id, true); message->return_data->bool_value = storage_process_file_open( app, message->data->fopen.file, - message->data->fopen.path, + path, message->data->fopen.access_mode, message->data->fopen.open_mode); break; @@ -527,9 +537,12 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { message->return_data->bool_value = storage_process_file_eof(app, message->data->file.file); break; + // Dir operations case StorageCommandDirOpen: + path = furi_string_alloc_set(message->data->dopen.path); + storage_process_alias(app, path, message->data->dopen.thread_id, true); message->return_data->bool_value = - storage_process_dir_open(app, message->data->dopen.file, message->data->dopen.path); + storage_process_dir_open(app, message->data->dopen.file, path); break; case StorageCommandDirClose: message->return_data->bool_value = @@ -547,29 +560,42 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { message->return_data->bool_value = storage_process_dir_rewind(app, message->data->file.file); break; + + // Common operations case StorageCommandCommonTimestamp: - message->return_data->error_value = storage_process_common_timestamp( - app, message->data->ctimestamp.path, message->data->ctimestamp.timestamp); + path = furi_string_alloc_set(message->data->ctimestamp.path); + storage_process_alias(app, path, message->data->ctimestamp.thread_id, false); + message->return_data->error_value = + storage_process_common_timestamp(app, path, message->data->ctimestamp.timestamp); break; case StorageCommandCommonStat: - message->return_data->error_value = storage_process_common_stat( - app, message->data->cstat.path, message->data->cstat.fileinfo); + path = furi_string_alloc_set(message->data->cstat.path); + storage_process_alias(app, path, message->data->cstat.thread_id, false); + message->return_data->error_value = + storage_process_common_stat(app, path, message->data->cstat.fileinfo); break; case StorageCommandCommonRemove: - message->return_data->error_value = - storage_process_common_remove(app, message->data->path.path); + path = furi_string_alloc_set(message->data->path.path); + storage_process_alias(app, path, message->data->path.thread_id, false); + message->return_data->error_value = storage_process_common_remove(app, path); break; case StorageCommandCommonMkDir: - message->return_data->error_value = - storage_process_common_mkdir(app, message->data->path.path); + path = furi_string_alloc_set(message->data->path.path); + storage_process_alias(app, path, message->data->path.thread_id, true); + message->return_data->error_value = storage_process_common_mkdir(app, path); break; case StorageCommandCommonFSInfo: + path = furi_string_alloc_set(message->data->cfsinfo.fs_path); + storage_process_alias(app, path, message->data->cfsinfo.thread_id, false); message->return_data->error_value = storage_process_common_fs_info( - app, - message->data->cfsinfo.fs_path, - message->data->cfsinfo.total_space, - message->data->cfsinfo.free_space); + app, path, message->data->cfsinfo.total_space, message->data->cfsinfo.free_space); break; + case StorageCommandCommonResolvePath: + storage_process_alias( + app, message->data->cresolvepath.path, message->data->cresolvepath.thread_id, true); + break; + + // SD operations case StorageCommandSDFormat: message->return_data->error_value = storage_process_sd_format(app); break; @@ -585,6 +611,10 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { break; } + if(path != NULL) { //-V547 + furi_string_free(path); + } + api_lock_unlock(message->lock); } diff --git a/applications/services/storage/storage_test_app.c b/applications/services/storage/storage_test_app.c deleted file mode 100644 index 852953e9..00000000 --- a/applications/services/storage/storage_test_app.c +++ /dev/null @@ -1,341 +0,0 @@ -#include -#include -#include - -#define TAG "StorageTest" -#define BYTES_COUNT 16 -#define TEST_STRING "TestDataStringProvidedByDiceRoll" -#define SEEK_OFFSET_FROM_START 10 -#define SEEK_OFFSET_INCREASE 12 -#define SEEK_OFFSET_SUM (SEEK_OFFSET_FROM_START + SEEK_OFFSET_INCREASE) - -static void do_file_test(Storage* api, const char* path) { - File* file = storage_file_alloc(api); - bool result; - uint8_t bytes[BYTES_COUNT + 1]; - uint8_t bytes_count; - uint64_t position; - uint64_t size; - - FURI_LOG_I(TAG, "--------- FILE \"%s\" ---------", path); - - // open - result = storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS); - if(result) { - FURI_LOG_I(TAG, "open"); - } else { - FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); - } - - // write - bytes_count = storage_file_write(file, TEST_STRING, strlen(TEST_STRING)); - if(bytes_count == 0) { - FURI_LOG_E(TAG, "write, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "write"); - } - - // sync - result = storage_file_sync(file); - if(result) { - FURI_LOG_I(TAG, "sync"); - } else { - FURI_LOG_E(TAG, "sync, %s", storage_file_get_error_desc(file)); - } - - // eof #1 - result = storage_file_eof(file); - if(result) { - FURI_LOG_I(TAG, "eof #1"); - } else { - FURI_LOG_E(TAG, "eof #1, %s", storage_file_get_error_desc(file)); - } - - // seek from start and tell - result = storage_file_seek(file, SEEK_OFFSET_FROM_START, true); - if(result) { - FURI_LOG_I(TAG, "seek #1"); - } else { - FURI_LOG_E(TAG, "seek #1, %s", storage_file_get_error_desc(file)); - } - position = storage_file_tell(file); - if(position != SEEK_OFFSET_FROM_START) { - FURI_LOG_E(TAG, "tell #1, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "tell #1"); - } - - // size - size = storage_file_size(file); - if(size != strlen(TEST_STRING)) { - FURI_LOG_E(TAG, "size #1, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "size #1"); - } - - // seek and tell - result = storage_file_seek(file, SEEK_OFFSET_INCREASE, false); - if(result) { - FURI_LOG_I(TAG, "seek #2"); - } else { - FURI_LOG_E(TAG, "seek #2, %s", storage_file_get_error_desc(file)); - } - position = storage_file_tell(file); - if(position != SEEK_OFFSET_SUM) { - FURI_LOG_E(TAG, "tell #2, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "tell #2"); - } - - // eof #2 - result = storage_file_eof(file); - if(!result) { - FURI_LOG_I(TAG, "eof #2"); - } else { - FURI_LOG_E(TAG, "eof #2, %s", storage_file_get_error_desc(file)); - } - - // truncate - result = storage_file_truncate(file); - if(result) { - FURI_LOG_I(TAG, "truncate"); - } else { - FURI_LOG_E(TAG, "truncate, %s", storage_file_get_error_desc(file)); - } - size = storage_file_size(file); - if(size != SEEK_OFFSET_SUM) { - FURI_LOG_E(TAG, "size #2, %s", storage_file_get_error_desc(file)); - } else { - FURI_LOG_I(TAG, "size #2"); - } - - // close - result = storage_file_close(file); - if(result) { - FURI_LOG_I(TAG, "close"); - } else { - FURI_LOG_E(TAG, "close, error"); - } - - // open - result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); - if(result) { - FURI_LOG_I(TAG, "open"); - } else { - FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); - } - - // read - memset(bytes, 0, BYTES_COUNT + 1); - bytes_count = storage_file_read(file, bytes, BYTES_COUNT); - if(bytes_count == 0) { - FURI_LOG_E(TAG, "read, %s", storage_file_get_error_desc(file)); - } else { - if(memcmp(TEST_STRING, bytes, bytes_count) == 0) { - FURI_LOG_I(TAG, "read"); - } else { - FURI_LOG_E(TAG, "read, garbage"); - } - } - - // close - result = storage_file_close(file); - if(result) { - FURI_LOG_I(TAG, "close"); - } else { - FURI_LOG_E(TAG, "close, error"); - } - - storage_file_free(file); -} - -static void do_dir_test(Storage* api, const char* path) { - File* file = storage_file_alloc(api); - bool result; - - FURI_LOG_I(TAG, "--------- DIR \"%s\" ---------", path); - - // open - result = storage_dir_open(file, path); - if(result) { - FURI_LOG_I(TAG, "open"); - } else { - FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); - } - - // read - const uint8_t filename_size = 100; - char* filename = malloc(filename_size); - FileInfo fileinfo; - - do { - result = storage_dir_read(file, &fileinfo, filename, filename_size); - if(result) { - if(strlen(filename)) { - FURI_LOG_I( - TAG, - "read #1, [%s]%s", - ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), - filename); - } - } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { - FURI_LOG_E(TAG, "read #1, %s", storage_file_get_error_desc(file)); - break; - } - - } while(result); - - // rewind - result = storage_dir_rewind(file); - if(result) { - FURI_LOG_I(TAG, "rewind"); - } else { - FURI_LOG_E(TAG, "rewind, %s", storage_file_get_error_desc(file)); - } - - // read - do { - result = storage_dir_read(file, &fileinfo, filename, filename_size); - if(result) { - if(strlen(filename)) { - FURI_LOG_I( - TAG, - "read #2, [%s]%s", - ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), - filename); - } - } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { - FURI_LOG_E(TAG, "read #2, %s", storage_file_get_error_desc(file)); - break; - } - - } while((strlen(filename))); - - // close - result = storage_dir_close(file); - if(result) { - FURI_LOG_I(TAG, "close"); - } else { - FURI_LOG_E(TAG, "close, error"); - } - - storage_file_free(file); - free(filename); -} - -static void do_test_start(Storage* api, const char* path) { - FuriString* str_path = furi_string_alloc_printf("%s/test-folder", path); - - FURI_LOG_I(TAG, "--------- START \"%s\" ---------", path); - - // mkdir - FS_Error result = storage_common_mkdir(api, furi_string_get_cstr(str_path)); - - if(result == FSE_OK) { - FURI_LOG_I(TAG, "mkdir ok"); - } else { - FURI_LOG_E(TAG, "mkdir, %s", storage_error_get_desc(result)); - } - - // stat - FileInfo fileinfo; - result = storage_common_stat(api, furi_string_get_cstr(str_path), &fileinfo); - - if(result == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { - FURI_LOG_I(TAG, "stat #1 ok"); - } else { - FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); - } - } else { - FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); - } - - furi_string_free(str_path); -} - -static void do_test_end(Storage* api, const char* path) { - uint64_t total_space; - uint64_t free_space; - FuriString* str_path_1 = furi_string_alloc_printf("%s/test-folder", path); - FuriString* str_path_2 = furi_string_alloc_printf("%s/test-folder2", path); - - FURI_LOG_I(TAG, "--------- END \"%s\" ---------", path); - - // fs stat - FS_Error result = storage_common_fs_info(api, path, &total_space, &free_space); - - if(result == FSE_OK) { - uint32_t total_kb = total_space / 1024; - uint32_t free_kb = free_space / 1024; - FURI_LOG_I(TAG, "fs_info: total %luk, free %luk", total_kb, free_kb); - } else { - FURI_LOG_E(TAG, "fs_info, %s", storage_error_get_desc(result)); - } - - // rename #1 - result = storage_common_rename( - api, furi_string_get_cstr(str_path_1), furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "rename #1 ok"); - } else { - FURI_LOG_E(TAG, "rename #1, %s", storage_error_get_desc(result)); - } - - // remove #1 - result = storage_common_remove(api, furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "remove #1 ok"); - } else { - FURI_LOG_E(TAG, "remove #1, %s", storage_error_get_desc(result)); - } - - // rename #2 - furi_string_printf(str_path_1, "%s/test.txt", path); - furi_string_printf(str_path_2, "%s/test2.txt", path); - - result = storage_common_rename( - api, furi_string_get_cstr(str_path_1), furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "rename #2 ok"); - } else { - FURI_LOG_E(TAG, "rename #2, %s", storage_error_get_desc(result)); - } - - // remove #2 - result = storage_common_remove(api, furi_string_get_cstr(str_path_2)); - if(result == FSE_OK) { - FURI_LOG_I(TAG, "remove #2 ok"); - } else { - FURI_LOG_E(TAG, "remove #2, %s", storage_error_get_desc(result)); - } - - furi_string_free(str_path_1); - furi_string_free(str_path_2); -} - -int32_t storage_test_app(void* p) { - UNUSED(p); - Storage* api = furi_record_open(RECORD_STORAGE); - do_test_start(api, STORAGE_INT_PATH_PREFIX); - do_test_start(api, STORAGE_ANY_PATH_PREFIX); - do_test_start(api, STORAGE_EXT_PATH_PREFIX); - - do_file_test(api, INT_PATH("test.txt")); - do_file_test(api, ANY_PATH("test.txt")); - do_file_test(api, EXT_PATH("test.txt")); - - do_dir_test(api, STORAGE_INT_PATH_PREFIX); - do_dir_test(api, STORAGE_ANY_PATH_PREFIX); - do_dir_test(api, STORAGE_EXT_PATH_PREFIX); - - do_test_end(api, STORAGE_INT_PATH_PREFIX); - do_test_end(api, STORAGE_ANY_PATH_PREFIX); - do_test_end(api, STORAGE_EXT_PATH_PREFIX); - - while(true) { - furi_delay_ms(1000); - } - - return 0; -} diff --git a/applications/system/storage_move_to_sd/storage_move_to_sd.c b/applications/system/storage_move_to_sd/storage_move_to_sd.c index 2027bd23..9c91b926 100644 --- a/applications/system/storage_move_to_sd/storage_move_to_sd.c +++ b/applications/system/storage_move_to_sd/storage_move_to_sd.c @@ -13,7 +13,7 @@ static bool storage_move_to_sd_check_entry(const char* name, FileInfo* fileinfo, void* ctx) { UNUSED(ctx); - if((fileinfo->flags & FSF_DIRECTORY) != 0) { + if(file_info_is_dir(fileinfo)) { return true; } diff --git a/assets/resources/music_player/Marble_Machine.fmf b/assets/resources/apps_data/music_player/Marble_Machine.fmf similarity index 100% rename from assets/resources/music_player/Marble_Machine.fmf rename to assets/resources/apps_data/music_player/Marble_Machine.fmf diff --git a/assets/resources/picopass/assets/iclass_elite_dict.txt b/assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt similarity index 100% rename from assets/resources/picopass/assets/iclass_elite_dict.txt rename to assets/resources/apps_data/picopass/assets/iclass_elite_dict.txt diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 8152095d..33bca491 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,15.0,, +Version,+,15.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -847,6 +847,7 @@ Function,+,file_browser_worker_set_folder_callback,void,"BrowserWorker*, Browser Function,+,file_browser_worker_set_item_callback,void,"BrowserWorker*, BrowserWorkerListItemCallback" Function,+,file_browser_worker_set_list_callback,void,"BrowserWorker*, BrowserWorkerListLoadCallback" Function,+,file_browser_worker_set_long_load_callback,void,"BrowserWorker*, BrowserWorkerLongLoadCallback" +Function,+,file_info_is_dir,_Bool,const FileInfo* Function,+,file_stream_alloc,Stream*,Storage* Function,+,file_stream_close,_Bool,Stream* Function,+,file_stream_get_error,FS_Error,Stream* @@ -1521,6 +1522,7 @@ Function,+,furi_thread_flags_get,uint32_t, Function,+,furi_thread_flags_set,uint32_t,"FuriThreadId, uint32_t" Function,+,furi_thread_flags_wait,uint32_t,"uint32_t, uint32_t, uint32_t" Function,+,furi_thread_free,void,FuriThread* +Function,+,furi_thread_get_appid,const char*,FuriThreadId Function,+,furi_thread_get_current,FuriThread*, Function,+,furi_thread_get_current_id,FuriThreadId, Function,+,furi_thread_get_current_priority,FuriThreadPriority, @@ -1535,6 +1537,7 @@ Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* Function,+,furi_thread_mark_as_service,void,FuriThread* Function,+,furi_thread_resume,void,FuriThreadId +Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" Function,+,furi_thread_set_context,void,"FuriThread*, void*" Function,+,furi_thread_set_current_priority,void,FuriThreadPriority @@ -2430,14 +2433,18 @@ Function,-,srand48,void,long Function,-,srandom,void,unsigned Function,+,sscanf,int,"const char*, const char*, ..." Function,+,storage_common_copy,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_exists,_Bool,"Storage*, const char*" Function,+,storage_common_fs_info,FS_Error,"Storage*, const char*, uint64_t*, uint64_t*" Function,+,storage_common_merge,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_migrate,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" Function,+,storage_common_remove,FS_Error,"Storage*, const char*" Function,+,storage_common_rename,FS_Error,"Storage*, const char*, const char*" +Function,+,storage_common_resolve_path_and_ensure_app_directory,void,"Storage*, FuriString*" Function,+,storage_common_stat,FS_Error,"Storage*, const char*, FileInfo*" Function,+,storage_common_timestamp,FS_Error,"Storage*, const char*, uint32_t*" Function,+,storage_dir_close,_Bool,File* +Function,+,storage_dir_exists,_Bool,"Storage*, const char*" Function,+,storage_dir_open,_Bool,"File*, const char*" Function,+,storage_dir_read,_Bool,"File*, FileInfo*, char*, uint16_t" Function,-,storage_dir_rewind,_Bool,File* diff --git a/furi/core/thread.c b/furi/core/thread.c index ea9f45e8..b45651c2 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -35,6 +35,8 @@ struct FuriThread { void* state_context; char* name; + char* appid; + configSTACK_DEPTH_TYPE stack_size; FuriThreadPriority priority; @@ -122,11 +124,25 @@ FuriThread* furi_thread_alloc() { thread->output.buffer = furi_string_alloc(); thread->is_service = false; + FuriThread* parent = NULL; + if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { + // TLS is not available, if we called not from thread context + parent = pvTaskGetThreadLocalStoragePointer(NULL, 0); + + if(parent && parent->appid) { + furi_thread_set_appid(thread, parent->appid); + } else { + furi_thread_set_appid(thread, "unknown"); + } + } else { + // if scheduler is not started, we are starting driver thread + furi_thread_set_appid(thread, "driver"); + } + FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode(); if(mode == FuriHalRtcHeapTrackModeAll) { thread->heap_trace_enabled = true; } else if(mode == FuriHalRtcHeapTrackModeTree && furi_thread_get_current_id()) { - FuriThread* parent = pvTaskGetThreadLocalStoragePointer(NULL, 0); if(parent) thread->heap_trace_enabled = parent->heap_trace_enabled; } else { thread->heap_trace_enabled = false; @@ -153,6 +169,7 @@ void furi_thread_free(FuriThread* thread) { furi_assert(thread->state == FuriThreadStateStopped); if(thread->name) free((void*)thread->name); + if(thread->appid) free((void*)thread->appid); furi_string_free(thread->output.buffer); free(thread); @@ -165,6 +182,13 @@ void furi_thread_set_name(FuriThread* thread, const char* name) { thread->name = name ? strdup(name) : NULL; } +void furi_thread_set_appid(FuriThread* thread, const char* appid) { + furi_assert(thread); + furi_assert(thread->state == FuriThreadStateStopped); + if(thread->appid) free((void*)thread->appid); + thread->appid = appid ? strdup(appid) : NULL; +} + void furi_thread_mark_as_service(FuriThread* thread) { thread->is_service = true; } @@ -498,6 +522,20 @@ const char* furi_thread_get_name(FuriThreadId thread_id) { return (name); } +const char* furi_thread_get_appid(FuriThreadId thread_id) { + TaskHandle_t hTask = (TaskHandle_t)thread_id; + const char* appid = "system"; + + if(!FURI_IS_IRQ_MODE() && (hTask != NULL)) { + FuriThread* thread = (FuriThread*)pvTaskGetThreadLocalStoragePointer(hTask, 0); + if(thread) { + appid = thread->appid; + } + } + + return (appid); +} + uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) { TaskHandle_t hTask = (TaskHandle_t)thread_id; uint32_t sz; diff --git a/furi/core/thread.h b/furi/core/thread.h index 2675f7ce..8f439841 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -87,6 +87,16 @@ void furi_thread_free(FuriThread* thread); */ void furi_thread_set_name(FuriThread* thread, const char* name); +/** + * @brief Set FuriThread appid + * Technically, it is like a "process id", but it is not a system-wide unique identifier. + * All threads spawned by the same app will have the same appid. + * + * @param thread + * @param appid + */ +void furi_thread_set_appid(FuriThread* thread, const char* appid); + /** Mark thread as service * The service cannot be stopped or removed, and cannot exit from the thread body * @@ -233,10 +243,37 @@ uint32_t furi_thread_flags_get(void); uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout); +/** + * @brief Enumerate threads + * + * @param thread_array array of FuriThreadId, where thread ids will be stored + * @param array_items array size + * @return uint32_t threads count + */ uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items); +/** + * @brief Get thread name + * + * @param thread_id + * @return const char* name or NULL + */ const char* furi_thread_get_name(FuriThreadId thread_id); +/** + * @brief Get thread appid + * + * @param thread_id + * @return const char* appid + */ +const char* furi_thread_get_appid(FuriThreadId thread_id); + +/** + * @brief Get thread stack watermark + * + * @param thread_id + * @return uint32_t + */ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); /** Get STDOUT callback for thead diff --git a/furi/flipper.c b/furi/flipper.c index d16a84a1..f0147c06 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -41,6 +41,7 @@ void flipper_init() { FLIPPER_SERVICES[i].app, NULL); furi_thread_mark_as_service(thread); + furi_thread_set_appid(thread, FLIPPER_SERVICES[i].appid); furi_thread_start(thread); } diff --git a/lib/toolbox/dir_walk.c b/lib/toolbox/dir_walk.c index e5a3cf32..509ceb5b 100644 --- a/lib/toolbox/dir_walk.c +++ b/lib/toolbox/dir_walk.c @@ -83,7 +83,7 @@ static DirWalkResult end = true; } - if((info.flags & FSF_DIRECTORY) && dir_walk->recursive) { + if(file_info_is_dir(&info) && dir_walk->recursive) { // step into DirIndexList_push_back(dir_walk->index_list, dir_walk->current_index); dir_walk->current_index = 0; diff --git a/lib/toolbox/tar/tar_archive.c b/lib/toolbox/tar/tar_archive.c index 01969b47..fcfc22a7 100644 --- a/lib/toolbox/tar/tar_archive.c +++ b/lib/toolbox/tar/tar_archive.c @@ -344,7 +344,7 @@ bool tar_archive_add_dir(TarArchive* archive, const char* fs_full_path, const ch furi_string_set(element_name, name); } - if(file_info.flags & FSF_DIRECTORY) { + if(file_info_is_dir(&file_info)) { success = tar_archive_dir_add_element(archive, furi_string_get_cstr(element_name)) && tar_archive_add_dir( diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index aa03d265..64b9f6f3 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -321,6 +321,7 @@ class ApplicationsCGenerator: return f""" {{.app = {app.entry_point}, .name = "{app.name}", + .appid = "{app.appid}", .stack_size = {app.stack_size}, .icon = {f"&{app.icon}" if app.icon else "NULL"}, .flags = {'|'.join(f"FlipperApplicationFlag{flag}" for flag in app.flags)} }}"""