mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-10 06:54:19 +00:00
[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 <alleteam@gmail.com>
This commit is contained in:
parent
9ae58f5462
commit
777a4d109d
52 changed files with 871 additions and 577 deletions
|
@ -44,3 +44,6 @@
|
|||
|
||||
# Functions that always return the same error code
|
||||
//-V:picopass_device_decrypt:1048
|
||||
|
||||
# Examples
|
||||
//V_EXCLUDE_PATH applications/examples/
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,9 +2,40 @@
|
|||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
// DO NOT USE THIS IN PRODUCTION CODE
|
||||
// This is a hack to access internal storage functions and definitions
|
||||
#include <storage/storage_i.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
18
applications/examples/example_apps_data/README.md
Normal file
18
applications/examples/example_apps_data/README.md
Normal file
|
@ -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")`.
|
9
applications/examples/example_apps_data/application.fam
Normal file
9
applications/examples/example_apps_data/application.fam
Normal file
|
@ -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",
|
||||
)
|
40
applications/examples/example_apps_data/example_apps_data.c
Normal file
40
applications/examples/example_apps_data/example_apps_data.c
Normal file
|
@ -0,0 +1,40 @@
|
|||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
// 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/<app_name>
|
||||
// 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <storage/storage.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <toolbox/path.h>
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
#include <lib/toolbox/args.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "dialogs_i.h"
|
||||
#include <toolbox/api_lock.h>
|
||||
#include <assets_icons.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
/****************** 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,341 +0,0 @@
|
|||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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*
|
||||
|
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)} }}"""
|
||||
|
|
Loading…
Reference in a new issue