[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:
Sergey Gavrilov 2023-03-01 20:57:27 +03:00 committed by GitHub
parent 9ae58f5462
commit 777a4d109d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 871 additions and 577 deletions

View file

@ -44,3 +44,6 @@
# Functions that always return the same error code
//-V:picopass_device_decrypt:1048
# Examples
//V_EXCLUDE_PATH applications/examples/

View file

@ -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;

View file

@ -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);

View file

@ -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;
}

View 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")`.

View 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",
)

View 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;
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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");

View file

@ -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,

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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(

View file

@ -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) {

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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]),

View file

@ -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;
}

View file

@ -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++;
}

View file

@ -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(

View file

@ -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);

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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));

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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;

View file

@ -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 {

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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*

1 entry status name type params
2 Version + 15.0 15.1
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
847 Function + file_browser_worker_set_item_callback void BrowserWorker*, BrowserWorkerListItemCallback
848 Function + file_browser_worker_set_list_callback void BrowserWorker*, BrowserWorkerListLoadCallback
849 Function + file_browser_worker_set_long_load_callback void BrowserWorker*, BrowserWorkerLongLoadCallback
850 Function + file_info_is_dir _Bool const FileInfo*
851 Function + file_stream_alloc Stream* Storage*
852 Function + file_stream_close _Bool Stream*
853 Function + file_stream_get_error FS_Error Stream*
1522 Function + furi_thread_flags_set uint32_t FuriThreadId, uint32_t
1523 Function + furi_thread_flags_wait uint32_t uint32_t, uint32_t, uint32_t
1524 Function + furi_thread_free void FuriThread*
1525 Function + furi_thread_get_appid const char* FuriThreadId
1526 Function + furi_thread_get_current FuriThread*
1527 Function + furi_thread_get_current_id FuriThreadId
1528 Function + furi_thread_get_current_priority FuriThreadPriority
1537 Function + furi_thread_join _Bool FuriThread*
1538 Function + furi_thread_mark_as_service void FuriThread*
1539 Function + furi_thread_resume void FuriThreadId
1540 Function + furi_thread_set_appid void FuriThread*, const char*
1541 Function + furi_thread_set_callback void FuriThread*, FuriThreadCallback
1542 Function + furi_thread_set_context void FuriThread*, void*
1543 Function + furi_thread_set_current_priority void FuriThreadPriority
2433 Function - srandom void unsigned
2434 Function + sscanf int const char*, const char*, ...
2435 Function + storage_common_copy FS_Error Storage*, const char*, const char*
2436 Function + storage_common_exists _Bool Storage*, const char*
2437 Function + storage_common_fs_info FS_Error Storage*, const char*, uint64_t*, uint64_t*
2438 Function + storage_common_merge FS_Error Storage*, const char*, const char*
2439 Function + storage_common_migrate FS_Error Storage*, const char*, const char*
2440 Function + storage_common_mkdir FS_Error Storage*, const char*
2441 Function + storage_common_remove FS_Error Storage*, const char*
2442 Function + storage_common_rename FS_Error Storage*, const char*, const char*
2443 Function + storage_common_resolve_path_and_ensure_app_directory void Storage*, FuriString*
2444 Function + storage_common_stat FS_Error Storage*, const char*, FileInfo*
2445 Function + storage_common_timestamp FS_Error Storage*, const char*, uint32_t*
2446 Function + storage_dir_close _Bool File*
2447 Function + storage_dir_exists _Bool Storage*, const char*
2448 Function + storage_dir_open _Bool File*, const char*
2449 Function + storage_dir_read _Bool File*, FileInfo*, char*, uint16_t
2450 Function - storage_dir_rewind _Bool File*

View 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;

View file

@ -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

View file

@ -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);
}

View file

@ -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;

View file

@ -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(

View file

@ -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)} }}"""