mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-10 15:04:19 +00:00
[FL-1191][FL-1524] Filesystem rework (#568)
* FS-Api: removed datetime manipulation functions and most of the file flags * Filesystem: common proxy api * Filesystem: renamed to Storage. Work has begun on a glue layer. Added functions for reentrance. * Storage: sd mount and sd file open * Storage: sd file close * Storage: temporary test app * Storage: free filedata on close * Storage: sd file read and write * Storage: added internal storage (LittleFS) * Storage: renamed internal commands * Storage: seek, tell, truncate, size, sync, eof * Storage: error descriptions * Storage: directory management api (open, close, read, rewind) * Storage: common management api (stat, fs_stat, remove, rename, mkdir) * Dolphin app and Notifications app now use raw storage. * Storage: storage statuses renamed. Implemented sd card icon. * Storage: added raw sd-card api. * Storage settings: work started * Assets: use new icons approach * Storage settings: working storage settings * Storage: completely redesigned api, no longer sticking out FS_Api * Storage: more simplified api, getting error_id from file is hidden from user, pointer to api is hidden inside file * Storage: cli info and format commands * Storage-cli: file list * Storage: a simpler and more reliable api * FatFS: slightly lighter and faster config. Also disabled reentrancy and file locking functions. They moved to a storage service. * Storage-cli: accommodate to the new cli api. * Storage: filesystem api is separated into internal and common api. * Cli: added the ability to print the list of free heap blocks * Storage: uses a list instead of an array to store the StorageFile. Rewrote api calls to use semaphores instead of thread flags. * Storage settings: added the ability to benchmark the SD card. * Gui module file select: uses new storage api * Apps: removed deprecated sd_card_test application * Args lib: support for enquoted arguments * Dialogs: a new gui app for simple non-asynchronous apps * Dialogs: view holder for easy single view work * File worker: use new storage api * IButton and lfrrfid apps: save keys to any storage * Apps: fix ibutton and lfrfid stack, remove sd_card_test. * SD filesystem: app removed * File worker: fixed api pointer type * Subghz: loading assets using the new storage api * NFC: use the new storage api * Dialogs: the better api for the message element * Archive: use new storage api * Irda: changed assest path, changed app path * FileWorker: removed unused file_buf_cnt * Storage: copying and renaming files now works between storages * Storage cli: read, copy, remove, rename commands * Archive: removed commented code * Storage cli: write command * Applications: add SRV_STORAGE and SRV_DIALOGS * Internal-storage: removed * Storage: improved api * Storage app: changed api pointer from StorageApp to Storage * Storage: better file_id handling * Storage: more consistent errors * Loader: support for NULL icons * Storage: do nothing with the lfs file or directory if it is not open * Storage: fix typo * Storage: minor float usage cleanup, rename some symbols. * Storage: compact doxygen comments. Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
parent
a81203941b
commit
ad421a81bc
95 changed files with 6451 additions and 4349 deletions
|
@ -19,26 +19,26 @@ int32_t nfc_task(void* p);
|
|||
int32_t dolphin_task(void* p);
|
||||
int32_t power_task(void* p);
|
||||
int32_t bt_task(void* p);
|
||||
int32_t sd_card_test(void* p);
|
||||
int32_t application_vibro(void* p);
|
||||
int32_t app_gpio_test(void* p);
|
||||
int32_t app_ibutton(void* p);
|
||||
int32_t cli_task(void* p);
|
||||
int32_t music_player(void* p);
|
||||
int32_t sdnfc(void* p);
|
||||
int32_t sd_filesystem(void* p);
|
||||
int32_t subghz_app(void* p);
|
||||
int32_t gui_test(void* p);
|
||||
int32_t keypad_test(void* p);
|
||||
int32_t scene_app(void* p);
|
||||
int32_t passport(void* p);
|
||||
int32_t app_accessor(void* p);
|
||||
int32_t internal_storage_task(void* p);
|
||||
int32_t app_archive(void* p);
|
||||
int32_t notification_app(void* p);
|
||||
int32_t scened_app(void* p);
|
||||
int32_t lfrfid_app(void* p);
|
||||
int32_t lfrfid_debug_app(void* p);
|
||||
int32_t storage_app(void* p);
|
||||
int32_t storage_app_test(void* p);
|
||||
int32_t dialogs_app(void* p);
|
||||
|
||||
// On system start hooks declaration
|
||||
void irda_cli_init();
|
||||
|
@ -47,9 +47,11 @@ void subghz_cli_init();
|
|||
void bt_cli_init();
|
||||
void lfrfid_cli_init();
|
||||
void ibutton_cli_init();
|
||||
void storage_cli_init();
|
||||
|
||||
// Settings
|
||||
int32_t notification_app_settings(void* p);
|
||||
int32_t storage_settings(void* p);
|
||||
|
||||
const FlipperApplication FLIPPER_SERVICES[] = {
|
||||
#ifdef SRV_CLI
|
||||
|
@ -81,17 +83,6 @@ const FlipperApplication FLIPPER_SERVICES[] = {
|
|||
{.app = loader, .name = "loader", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_SD_FILESYSTEM
|
||||
{.app = sd_filesystem, .name = "sd_filesystem", .stack_size = 4096, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_INTERNAL_STORAGE
|
||||
{.app = internal_storage_task,
|
||||
.name = "internal_storage",
|
||||
.stack_size = 2048,
|
||||
.icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_DOLPHIN
|
||||
{.app = dolphin_task, .name = "dolphin_task", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
@ -105,8 +96,7 @@ const FlipperApplication FLIPPER_SERVICES[] = {
|
|||
#endif
|
||||
|
||||
#ifdef SRV_LF_RFID
|
||||
// TODO: fix stack size when sd api will be in separate thread
|
||||
{.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 4096, .icon = &A_Plugins_14},
|
||||
{.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_IRDA
|
||||
|
@ -128,16 +118,12 @@ const FlipperApplication FLIPPER_SERVICES[] = {
|
|||
.icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_SD_TEST
|
||||
{.app = sd_card_test, .name = "sd_card_test", .stack_size = 4096, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_MUSIC_PLAYER
|
||||
{.app = music_player, .name = "music player", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_IBUTTON
|
||||
{.app = app_ibutton, .name = "ibutton", .stack_size = 4096, .icon = &A_Plugins_14},
|
||||
{.app = app_ibutton, .name = "ibutton", .stack_size = 2048, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_GPIO_DEMO
|
||||
|
@ -164,6 +150,17 @@ const FlipperApplication FLIPPER_SERVICES[] = {
|
|||
{.app = notification_app, .name = "notification", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_STORAGE
|
||||
{.app = storage_app, .name = "storage", .stack_size = 4096, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_STORAGE_TEST
|
||||
{.app = storage_app_test, .name = "storage test", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_DIALOGS
|
||||
{.app = dialogs_app, .name = "dialogs", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
};
|
||||
|
||||
const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication);
|
||||
|
@ -172,7 +169,7 @@ const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperA
|
|||
const FlipperApplication FLIPPER_APPS[] = {
|
||||
|
||||
#ifdef APP_IBUTTON
|
||||
{.app = app_ibutton, .name = "iButton", .stack_size = 4096, .icon = &A_iButton_14},
|
||||
{.app = app_ibutton, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14},
|
||||
#endif
|
||||
|
||||
#ifdef APP_NFC
|
||||
|
@ -186,7 +183,7 @@ const FlipperApplication FLIPPER_APPS[] = {
|
|||
|
||||
#ifdef APP_LF_RFID
|
||||
// TODO: fix stack size when sd api will be in separate thread
|
||||
{.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 4096, .icon = &A_125khz_14},
|
||||
{.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14},
|
||||
#endif
|
||||
|
||||
#ifdef APP_IRDA
|
||||
|
@ -219,6 +216,9 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = {
|
|||
#ifdef SRV_BT
|
||||
bt_cli_init,
|
||||
#endif
|
||||
#ifdef SRV_STORAGE
|
||||
storage_cli_init,
|
||||
#endif
|
||||
};
|
||||
|
||||
const size_t FLIPPER_ON_SYSTEM_START_COUNT =
|
||||
|
@ -255,10 +255,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = {
|
|||
.icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef APP_SD_TEST
|
||||
{.app = sd_card_test, .name = "sd_card_test", .stack_size = 4096, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
||||
#ifdef APP_VIBRO_DEMO
|
||||
{.app = application_vibro, .name = "vibro", .stack_size = 1024, .icon = &A_Plugins_14},
|
||||
#endif
|
||||
|
@ -326,10 +322,11 @@ const size_t FLIPPER_SCENE_APPS_COUNT = sizeof(FLIPPER_SCENE_APPS) / sizeof(Flip
|
|||
// Settings menu
|
||||
const FlipperApplication FLIPPER_SETTINGS_APPS[] = {
|
||||
#ifdef SRV_NOTIFICATION
|
||||
{.app = notification_app_settings,
|
||||
.name = "Notification",
|
||||
.stack_size = 1024,
|
||||
.icon = &A_Plugins_14},
|
||||
{.app = notification_app_settings, .name = "Notification", .stack_size = 1024, .icon = NULL},
|
||||
#endif
|
||||
|
||||
#ifdef SRV_STORAGE
|
||||
{.app = storage_settings, .name = "Storage", .stack_size = 2048, .icon = NULL},
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@ SRV_MENU = 1
|
|||
SRV_POWER = 1
|
||||
SRV_BT = 1
|
||||
SRV_CLI = 1
|
||||
SRV_SD_FILESYSTEM = 1
|
||||
SRV_INTERNAL_STORAGE = 1
|
||||
SRV_DOLPHIN = 1
|
||||
SRV_NOTIFICATION = 1
|
||||
SRV_STORAGE = 1
|
||||
SRV_DIALOGS = 1
|
||||
|
||||
# Main Apps
|
||||
APP_IRDA = 1
|
||||
|
@ -189,19 +189,6 @@ SRV_INPUT = 1
|
|||
SRV_GUI = 1
|
||||
endif
|
||||
|
||||
SRV_SD_TEST ?= 0
|
||||
ifeq ($(SRV_SD_TEST), 1)
|
||||
CFLAGS += -DSRV_SD_TEST
|
||||
APP_SD_TEST = 1
|
||||
endif
|
||||
APP_SD_TEST ?= 0
|
||||
ifeq ($(APP_SD_TEST), 1)
|
||||
CFLAGS += -DAPP_SD_TEST
|
||||
SRV_INPUT = 1
|
||||
SRV_GUI = 1
|
||||
SRV_SD_FILESYSTEM = 1
|
||||
endif
|
||||
|
||||
SRV_SPEAKER_DEMO ?= 0
|
||||
ifeq ($(SRV_SPEAKER_DEMO), 1)
|
||||
CFLAGS += -DSRV_SPEAKER_DEMO
|
||||
|
@ -282,15 +269,6 @@ ifeq ($(APP_GUI_TEST), 1)
|
|||
CFLAGS += -DAPP_GUI_TEST
|
||||
endif
|
||||
|
||||
SRV_SDNFC ?= 0
|
||||
ifeq ($(SRV_SDNFC), 1)
|
||||
CFLAGS += -DSRV_SDNFC
|
||||
APP_SDNFC = 1
|
||||
endif
|
||||
APP_SDNFC ?= 0
|
||||
ifeq ($(APP_SDNFC), 1)
|
||||
CFLAGS += -DAPP_SDNFC
|
||||
endif
|
||||
# device drivers
|
||||
|
||||
SRV_GUI ?= 0
|
||||
|
@ -298,16 +276,6 @@ ifeq ($(SRV_GUI), 1)
|
|||
CFLAGS += -DSRV_GUI
|
||||
endif
|
||||
|
||||
SRV_SD_FILESYSTEM ?= 0
|
||||
ifeq ($(SRV_SD_FILESYSTEM), 1)
|
||||
CFLAGS += -DSRV_SD_FILESYSTEM
|
||||
endif
|
||||
|
||||
SRV_INTERNAL_STORAGE ?= 0
|
||||
ifeq ($(SRV_INTERNAL_STORAGE), 1)
|
||||
CFLAGS += -DSRV_INTERNAL_STORAGE
|
||||
endif
|
||||
|
||||
SRV_INPUT ?= 0
|
||||
ifeq ($(SRV_INPUT), 1)
|
||||
CFLAGS += -DSRV_INPUT
|
||||
|
@ -323,3 +291,13 @@ SRV_NOTIFICATION ?= 0
|
|||
ifeq ($(SRV_NOTIFICATION), 1)
|
||||
CFLAGS += -DSRV_NOTIFICATION
|
||||
endif
|
||||
|
||||
SRV_STORAGE ?= 0
|
||||
ifeq ($(SRV_STORAGE), 1)
|
||||
CFLAGS += -DSRV_STORAGE
|
||||
endif
|
||||
|
||||
SRV_DIALOGS ?= 0
|
||||
ifeq ($(SRV_DIALOGS), 1)
|
||||
CFLAGS += -DSRV_DIALOGS
|
||||
endif
|
|
@ -3,18 +3,17 @@
|
|||
static bool archive_get_filenames(ArchiveApp* archive);
|
||||
|
||||
static bool is_favorite(ArchiveApp* archive, ArchiveFile_t* file) {
|
||||
FS_Common_Api* common_api = &archive->fs_api->common;
|
||||
FileInfo file_info;
|
||||
FS_Error fr;
|
||||
string_t path;
|
||||
|
||||
string_init_printf(path, "favorites/%s", string_get_cstr(file->name));
|
||||
string_init_printf(path, "%s/%s", get_favorites_path(), string_get_cstr(file->name));
|
||||
|
||||
fr = common_api->info(string_get_cstr(path), &file_info, NULL, 0);
|
||||
fr = storage_common_stat(archive->api, string_get_cstr(path), &file_info);
|
||||
FURI_LOG_I("FAV", "%d", fr);
|
||||
|
||||
string_clear(path);
|
||||
return fr == 0 || fr == 2;
|
||||
return (fr == FSE_OK || fr == FSE_EXIST);
|
||||
}
|
||||
|
||||
static void update_offset(ArchiveApp* archive) {
|
||||
|
@ -147,14 +146,11 @@ static void set_file_type(ArchiveFile_t* file, FileInfo* file_info) {
|
|||
|
||||
static bool archive_get_filenames(ArchiveApp* archive) {
|
||||
furi_assert(archive);
|
||||
FS_Dir_Api* dir_api = &archive->fs_api->dir;
|
||||
|
||||
ArchiveFile_t item;
|
||||
FileInfo file_info;
|
||||
File directory;
|
||||
File* directory = storage_file_alloc(archive->api);
|
||||
char name[MAX_NAME_LEN];
|
||||
bool result;
|
||||
|
||||
result = dir_api->open(&directory, string_get_cstr(archive->browser.path));
|
||||
|
||||
with_view_model(
|
||||
archive->view_archive_main, (ArchiveViewModel * model) {
|
||||
|
@ -162,51 +158,50 @@ static bool archive_get_filenames(ArchiveApp* archive) {
|
|||
return true;
|
||||
});
|
||||
|
||||
if(!result) {
|
||||
dir_api->close(&directory);
|
||||
if(!storage_dir_open(directory, string_get_cstr(archive->browser.path))) {
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
return false;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
result = dir_api->read(&directory, &file_info, name, MAX_NAME_LEN);
|
||||
|
||||
if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) {
|
||||
if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(result) {
|
||||
uint16_t files_cnt;
|
||||
with_view_model(
|
||||
archive->view_archive_main, (ArchiveViewModel * model) {
|
||||
files_cnt = files_array_size(model->files);
|
||||
uint16_t files_cnt;
|
||||
with_view_model(
|
||||
archive->view_archive_main, (ArchiveViewModel * model) {
|
||||
files_cnt = files_array_size(model->files);
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
if(files_cnt > MAX_FILES) {
|
||||
break;
|
||||
} else if(directory.error_id == FSE_OK) {
|
||||
if(filter_by_extension(archive, &file_info, name)) {
|
||||
ArchiveFile_t_init(&item);
|
||||
string_init_set(item.name, name);
|
||||
set_file_type(&item, &file_info);
|
||||
if(files_cnt > MAX_FILES) {
|
||||
break;
|
||||
} else if(storage_file_get_error(directory) == FSE_OK) {
|
||||
if(filter_by_extension(archive, &file_info, name)) {
|
||||
ArchiveFile_t_init(&item);
|
||||
string_init_set(item.name, name);
|
||||
set_file_type(&item, &file_info);
|
||||
|
||||
with_view_model(
|
||||
archive->view_archive_main, (ArchiveViewModel * model) {
|
||||
files_array_push_back(model->files, item);
|
||||
return true;
|
||||
});
|
||||
with_view_model(
|
||||
archive->view_archive_main, (ArchiveViewModel * model) {
|
||||
files_array_push_back(model->files, item);
|
||||
return true;
|
||||
});
|
||||
|
||||
ArchiveFile_t_clear(&item);
|
||||
}
|
||||
} else {
|
||||
dir_api->close(&directory);
|
||||
return false;
|
||||
ArchiveFile_t_clear(&item);
|
||||
}
|
||||
} else {
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
dir_api->close(&directory);
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -226,17 +221,7 @@ static uint32_t archive_previous_callback(void* context) {
|
|||
static void archive_add_to_favorites(ArchiveApp* archive) {
|
||||
furi_assert(archive);
|
||||
|
||||
FS_Common_Api* common_api = &archive->fs_api->common;
|
||||
common_api->mkdir("favorites");
|
||||
|
||||
FS_File_Api* file_api = &archive->fs_api->file;
|
||||
File src;
|
||||
File dst;
|
||||
|
||||
bool fr;
|
||||
uint16_t buffer[MAX_FILE_SIZE];
|
||||
uint16_t bw = 0;
|
||||
uint16_t br = 0;
|
||||
storage_common_mkdir(archive->api, get_favorites_path());
|
||||
|
||||
string_t buffer_src;
|
||||
string_t buffer_dst;
|
||||
|
@ -246,22 +231,10 @@ static void archive_add_to_favorites(ArchiveApp* archive) {
|
|||
"%s/%s",
|
||||
string_get_cstr(archive->browser.path),
|
||||
string_get_cstr(archive->browser.name));
|
||||
string_init_printf(buffer_dst, "/favorites/%s", string_get_cstr(archive->browser.name));
|
||||
string_init_printf(
|
||||
buffer_dst, "%s/%s", get_favorites_path(), string_get_cstr(archive->browser.name));
|
||||
|
||||
fr = file_api->open(&src, string_get_cstr(buffer_src), FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
FURI_LOG_I("FATFS", "OPEN: %d", fr);
|
||||
fr = file_api->open(&dst, string_get_cstr(buffer_dst), FSAM_WRITE, FSOM_CREATE_ALWAYS);
|
||||
FURI_LOG_I("FATFS", "CREATE: %d", fr);
|
||||
|
||||
for(;;) {
|
||||
br = file_api->read(&src, &buffer, sizeof(buffer));
|
||||
if(br == 0) break;
|
||||
bw = file_api->write(&dst, &buffer, sizeof(buffer));
|
||||
if(bw < br) break;
|
||||
}
|
||||
|
||||
file_api->close(&src);
|
||||
file_api->close(&dst);
|
||||
storage_common_copy(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst));
|
||||
|
||||
string_clear(buffer_src);
|
||||
string_clear(buffer_dst);
|
||||
|
@ -271,7 +244,6 @@ static void archive_text_input_callback(void* context) {
|
|||
furi_assert(context);
|
||||
|
||||
ArchiveApp* archive = (ArchiveApp*)context;
|
||||
FS_Common_Api* common_api = &archive->fs_api->common;
|
||||
|
||||
string_t buffer_src;
|
||||
string_t buffer_dst;
|
||||
|
@ -299,7 +271,7 @@ static void archive_text_input_callback(void* context) {
|
|||
});
|
||||
|
||||
string_cat(buffer_dst, known_ext[file->type]);
|
||||
common_api->rename(string_get_cstr(buffer_src), string_get_cstr(buffer_dst));
|
||||
storage_common_rename(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst));
|
||||
|
||||
view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewMain);
|
||||
|
||||
|
@ -371,23 +343,22 @@ static void archive_delete_file(ArchiveApp* archive, ArchiveFile_t* file, bool f
|
|||
furi_assert(archive);
|
||||
furi_assert(file);
|
||||
|
||||
FS_Common_Api* common_api = &archive->fs_api->common;
|
||||
string_t path;
|
||||
string_init(path);
|
||||
|
||||
if(!fav && !orig) {
|
||||
string_printf(
|
||||
path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(file->name));
|
||||
common_api->remove(string_get_cstr(path));
|
||||
storage_common_remove(archive->api, string_get_cstr(path));
|
||||
|
||||
} else { // remove from favorites
|
||||
string_printf(path, "favorites/%s", string_get_cstr(file->name));
|
||||
common_api->remove(string_get_cstr(path));
|
||||
string_printf(path, "%s/%s", get_favorites_path(), string_get_cstr(file->name));
|
||||
storage_common_remove(archive->api, string_get_cstr(path));
|
||||
|
||||
if(orig) { // remove original file
|
||||
string_printf(
|
||||
path, "%s/%s", get_default_path(file->type), string_get_cstr(file->name));
|
||||
common_api->remove(string_get_cstr(path));
|
||||
storage_common_remove(archive->api, string_get_cstr(path));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -604,8 +575,8 @@ void archive_free(ArchiveApp* archive) {
|
|||
|
||||
text_input_free(archive->text_input);
|
||||
|
||||
furi_record_close("sdcard");
|
||||
archive->fs_api = NULL;
|
||||
furi_record_close("storage");
|
||||
archive->api = NULL;
|
||||
furi_record_close("gui");
|
||||
archive->gui = NULL;
|
||||
furi_record_close("loader");
|
||||
|
@ -623,7 +594,7 @@ ArchiveApp* archive_alloc() {
|
|||
archive->app_thread = furi_thread_alloc();
|
||||
archive->gui = furi_record_open("gui");
|
||||
archive->loader = furi_record_open("loader");
|
||||
archive->fs_api = furi_record_open("sdcard");
|
||||
archive->api = furi_record_open("storage");
|
||||
archive->text_input = text_input_alloc();
|
||||
archive->view_archive_main = view_alloc();
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#include <m-string.h>
|
||||
#include <m-array.h>
|
||||
#include <filesystem-api.h>
|
||||
#include <storage/storage.h>
|
||||
#include "archive_views.h"
|
||||
#include "applications.h"
|
||||
|
||||
|
@ -41,13 +41,13 @@ static const char* known_ext[] = {
|
|||
};
|
||||
|
||||
static const char* tab_default_paths[] = {
|
||||
[ArchiveTabFavorites] = "favorites",
|
||||
[ArchiveTabIButton] = "ibutton",
|
||||
[ArchiveTabNFC] = "nfc",
|
||||
[ArchiveTabSubOne] = "subone",
|
||||
[ArchiveTabLFRFID] = "lfrfid",
|
||||
[ArchiveTabIrda] = "irda",
|
||||
[ArchiveTabBrowser] = "/",
|
||||
[ArchiveTabFavorites] = "/any/favorites",
|
||||
[ArchiveTabIButton] = "/any/ibutton",
|
||||
[ArchiveTabNFC] = "/any/nfc",
|
||||
[ArchiveTabSubOne] = "/any/subone",
|
||||
[ArchiveTabLFRFID] = "/any/lfrfid",
|
||||
[ArchiveTabIrda] = "/any/irda",
|
||||
[ArchiveTabBrowser] = "/any",
|
||||
};
|
||||
|
||||
static inline const char* get_tab_ext(ArchiveTabEnum tab) {
|
||||
|
@ -84,6 +84,10 @@ static inline const char* get_default_path(ArchiveFileTypeEnum type) {
|
|||
}
|
||||
}
|
||||
|
||||
static inline const char* get_favorites_path() {
|
||||
return tab_default_paths[ArchiveTabFavorites];
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
|
@ -118,6 +122,6 @@ struct ArchiveApp {
|
|||
View* view_archive_main;
|
||||
TextInput* text_input;
|
||||
|
||||
FS_Api* fs_api;
|
||||
Storage* api;
|
||||
ArchiveBrowser browser;
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <gui/canvas.h>
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
#include <filesystem-api.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define MAX_LEN_PX 100
|
||||
#define MAX_NAME_LEN 255
|
||||
|
|
|
@ -375,6 +375,10 @@ void cli_command_free(Cli* cli, string_t args, void* context) {
|
|||
printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block());
|
||||
}
|
||||
|
||||
void cli_command_free_blocks(Cli* cli, string_t args, void* context) {
|
||||
memmgr_heap_printf_free_blocks();
|
||||
}
|
||||
|
||||
void cli_commands_init(Cli* cli) {
|
||||
cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
|
||||
cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL);
|
||||
|
@ -386,6 +390,7 @@ void cli_commands_init(Cli* cli) {
|
|||
cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
|
||||
cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL);
|
||||
cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL);
|
||||
cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
|
||||
|
||||
cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
|
||||
cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL);
|
||||
|
|
8
applications/dialogs/dialogs-api-lock.h
Normal file
8
applications/dialogs/dialogs-api-lock.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
#define API_LOCK_INIT_LOCKED() osSemaphoreNew(1, 0, NULL);
|
||||
|
||||
#define API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(_lock) \
|
||||
osSemaphoreAcquire(_lock, osWaitForever); \
|
||||
osSemaphoreDelete(_lock);
|
||||
|
||||
#define API_LOCK_UNLOCK(_lock) osSemaphoreRelease(_lock);
|
62
applications/dialogs/dialogs-api.c
Normal file
62
applications/dialogs/dialogs-api.c
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include "dialogs-i.h"
|
||||
#include "dialogs-api-lock.h"
|
||||
|
||||
/****************** File select ******************/
|
||||
|
||||
bool dialog_file_select_show(
|
||||
DialogsApp* context,
|
||||
const char* path,
|
||||
const char* extension,
|
||||
char* result,
|
||||
uint8_t result_size,
|
||||
const char* preselected_filename) {
|
||||
osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED();
|
||||
furi_check(semaphore != NULL);
|
||||
|
||||
DialogsAppData data = {
|
||||
.file_select = {
|
||||
.path = path,
|
||||
.extension = extension,
|
||||
.result = result,
|
||||
.result_size = result_size,
|
||||
.preselected_filename = preselected_filename,
|
||||
}};
|
||||
|
||||
DialogsAppReturn return_data;
|
||||
DialogsAppMessage message = {
|
||||
.semaphore = semaphore,
|
||||
.command = DialogsAppCommandFileOpen,
|
||||
.data = &data,
|
||||
.return_data = &return_data,
|
||||
};
|
||||
|
||||
furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore);
|
||||
|
||||
return return_data.bool_value;
|
||||
}
|
||||
|
||||
/****************** Message ******************/
|
||||
|
||||
DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) {
|
||||
osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED();
|
||||
furi_check(semaphore != NULL);
|
||||
|
||||
DialogsAppData data = {
|
||||
.dialog = {
|
||||
.message = dialog_message,
|
||||
}};
|
||||
|
||||
DialogsAppReturn return_data;
|
||||
DialogsAppMessage message = {
|
||||
.semaphore = semaphore,
|
||||
.command = DialogsAppCommandDialog,
|
||||
.data = &data,
|
||||
.return_data = &return_data,
|
||||
};
|
||||
|
||||
furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore);
|
||||
|
||||
return return_data.dialog_value;
|
||||
}
|
15
applications/dialogs/dialogs-i.h
Normal file
15
applications/dialogs/dialogs-i.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
#include "dialogs.h"
|
||||
#include "dialogs-message.h"
|
||||
#include "view-holder.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
struct DialogsApp {
|
||||
osMessageQueueId_t message_queue;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
45
applications/dialogs/dialogs-message.h
Normal file
45
applications/dialogs/dialogs-message.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "dialogs-i.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
const char* extension;
|
||||
char* result;
|
||||
uint8_t result_size;
|
||||
const char* preselected_filename;
|
||||
} DialogsAppMessageDataFileSelect;
|
||||
|
||||
typedef struct {
|
||||
const DialogMessage* message;
|
||||
} DialogsAppMessageDataDialog;
|
||||
|
||||
typedef union {
|
||||
DialogsAppMessageDataFileSelect file_select;
|
||||
DialogsAppMessageDataDialog dialog;
|
||||
} DialogsAppData;
|
||||
|
||||
typedef union {
|
||||
bool bool_value;
|
||||
DialogMessageButton dialog_value;
|
||||
} DialogsAppReturn;
|
||||
|
||||
typedef enum {
|
||||
DialogsAppCommandFileOpen,
|
||||
DialogsAppCommandDialog,
|
||||
} DialogsAppCommand;
|
||||
|
||||
typedef struct {
|
||||
osSemaphoreId_t semaphore;
|
||||
DialogsAppCommand command;
|
||||
DialogsAppData* data;
|
||||
DialogsAppReturn* return_data;
|
||||
} DialogsAppMessage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
59
applications/dialogs/dialogs-module-file-select.c
Normal file
59
applications/dialogs/dialogs-module-file-select.c
Normal file
|
@ -0,0 +1,59 @@
|
|||
#include "dialogs-i.h"
|
||||
#include "dialogs-api-lock.h"
|
||||
#include <gui/modules/file_select.h>
|
||||
|
||||
typedef struct {
|
||||
osSemaphoreId_t semaphore;
|
||||
bool result;
|
||||
} DialogsAppFileSelectContext;
|
||||
|
||||
static void dialogs_app_file_select_back_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppFileSelectContext* file_select_context = context;
|
||||
file_select_context->result = false;
|
||||
API_LOCK_UNLOCK(file_select_context->semaphore);
|
||||
}
|
||||
|
||||
static void dialogs_app_file_select_callback(bool result, void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppFileSelectContext* file_select_context = context;
|
||||
file_select_context->result = result;
|
||||
API_LOCK_UNLOCK(file_select_context->semaphore);
|
||||
}
|
||||
|
||||
bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) {
|
||||
bool ret = false;
|
||||
Gui* gui = furi_record_open("gui");
|
||||
|
||||
DialogsAppFileSelectContext* file_select_context =
|
||||
furi_alloc(sizeof(DialogsAppFileSelectContext));
|
||||
file_select_context->semaphore = API_LOCK_INIT_LOCKED();
|
||||
|
||||
ViewHolder* view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
view_holder_set_back_callback(
|
||||
view_holder, dialogs_app_file_select_back_callback, file_select_context);
|
||||
|
||||
FileSelect* file_select = file_select_alloc();
|
||||
file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context);
|
||||
file_select_set_filter(file_select, data->path, data->extension);
|
||||
file_select_set_result_buffer(file_select, data->result, data->result_size);
|
||||
file_select_init(file_select);
|
||||
if(data->preselected_filename != NULL) {
|
||||
file_select_set_selected_file(file_select, data->preselected_filename);
|
||||
}
|
||||
|
||||
view_holder_set_view(view_holder, file_select_get_view(file_select));
|
||||
view_holder_start(view_holder);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(file_select_context->semaphore);
|
||||
|
||||
ret = file_select_context->result;
|
||||
|
||||
free(file_select_context);
|
||||
view_holder_stop(view_holder);
|
||||
view_holder_free(view_holder);
|
||||
file_select_free(file_select);
|
||||
furi_record_close("gui");
|
||||
|
||||
return ret;
|
||||
}
|
12
applications/dialogs/dialogs-module-file-select.h
Normal file
12
applications/dialogs/dialogs-module-file-select.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
#include "dialogs-message.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
152
applications/dialogs/dialogs-module-message.c
Normal file
152
applications/dialogs/dialogs-module-message.c
Normal file
|
@ -0,0 +1,152 @@
|
|||
#include "dialogs-i.h"
|
||||
#include "dialogs-api-lock.h"
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
|
||||
typedef struct {
|
||||
osSemaphoreId_t semaphore;
|
||||
DialogMessageButton result;
|
||||
} DialogsAppMessageContext;
|
||||
|
||||
struct DialogMessage {
|
||||
const char* header_text;
|
||||
uint8_t header_text_x;
|
||||
uint8_t header_text_y;
|
||||
Align header_horizontal;
|
||||
Align header_vertical;
|
||||
const char* dialog_text;
|
||||
uint8_t dialog_text_x;
|
||||
uint8_t dialog_text_y;
|
||||
Align dialog_text_horizontal;
|
||||
Align dialog_text_vertical;
|
||||
const Icon* icon;
|
||||
uint8_t icon_x;
|
||||
uint8_t icon_y;
|
||||
const char* left_button_text;
|
||||
const char* center_button_text;
|
||||
const char* right_button_text;
|
||||
};
|
||||
|
||||
static void dialogs_app_message_back_callback(void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppMessageContext* message_context = context;
|
||||
message_context->result = DialogMessageButtonBack;
|
||||
API_LOCK_UNLOCK(message_context->semaphore);
|
||||
}
|
||||
|
||||
static void dialogs_app_message_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
DialogsAppMessageContext* message_context = context;
|
||||
switch(result) {
|
||||
case DialogExResultLeft:
|
||||
message_context->result = DialogMessageButtonLeft;
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
message_context->result = DialogMessageButtonRight;
|
||||
break;
|
||||
case DialogExResultCenter:
|
||||
message_context->result = DialogMessageButtonCenter;
|
||||
break;
|
||||
}
|
||||
API_LOCK_UNLOCK(message_context->semaphore);
|
||||
}
|
||||
|
||||
DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data) {
|
||||
DialogMessageButton ret = DialogMessageButtonBack;
|
||||
Gui* gui = furi_record_open("gui");
|
||||
const DialogMessage* message = data->message;
|
||||
DialogsAppMessageContext* message_context = furi_alloc(sizeof(DialogsAppMessageContext));
|
||||
message_context->semaphore = API_LOCK_INIT_LOCKED();
|
||||
|
||||
ViewHolder* view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(view_holder, gui);
|
||||
view_holder_set_back_callback(view_holder, dialogs_app_message_back_callback, message_context);
|
||||
|
||||
DialogEx* dialog_ex = dialog_ex_alloc();
|
||||
dialog_ex_set_result_callback(dialog_ex, dialogs_app_message_callback);
|
||||
dialog_ex_set_context(dialog_ex, message_context);
|
||||
dialog_ex_set_header(
|
||||
dialog_ex,
|
||||
message->header_text,
|
||||
message->header_text_x,
|
||||
message->header_text_y,
|
||||
message->header_horizontal,
|
||||
message->header_vertical);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
message->dialog_text,
|
||||
message->dialog_text_x,
|
||||
message->dialog_text_y,
|
||||
message->dialog_text_horizontal,
|
||||
message->dialog_text_vertical);
|
||||
dialog_ex_set_icon(dialog_ex, message->icon_x, message->icon_y, message->icon);
|
||||
dialog_ex_set_left_button_text(dialog_ex, message->left_button_text);
|
||||
dialog_ex_set_center_button_text(dialog_ex, message->center_button_text);
|
||||
dialog_ex_set_right_button_text(dialog_ex, message->right_button_text);
|
||||
|
||||
view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex));
|
||||
view_holder_start(view_holder);
|
||||
API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(message_context->semaphore);
|
||||
|
||||
ret = message_context->result;
|
||||
|
||||
free(message_context);
|
||||
view_holder_stop(view_holder);
|
||||
view_holder_free(view_holder);
|
||||
dialog_ex_free(dialog_ex);
|
||||
furi_record_close("gui");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DialogMessage* dialog_message_alloc() {
|
||||
DialogMessage* message = furi_alloc(sizeof(DialogMessage));
|
||||
return message;
|
||||
}
|
||||
|
||||
void dialog_message_free(DialogMessage* message) {
|
||||
free(message);
|
||||
}
|
||||
|
||||
void dialog_message_set_text(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical) {
|
||||
message->dialog_text = text;
|
||||
message->dialog_text_x = x;
|
||||
message->dialog_text_y = y;
|
||||
message->dialog_text_horizontal = horizontal;
|
||||
message->dialog_text_vertical = vertical;
|
||||
}
|
||||
|
||||
void dialog_message_set_header(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical) {
|
||||
message->header_text = text;
|
||||
message->header_text_x = x;
|
||||
message->header_text_y = y;
|
||||
message->header_horizontal = horizontal;
|
||||
message->header_vertical = vertical;
|
||||
}
|
||||
|
||||
void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y) {
|
||||
message->icon = icon;
|
||||
message->icon_x = x;
|
||||
message->icon_y = y;
|
||||
}
|
||||
|
||||
void dialog_message_set_buttons(
|
||||
DialogMessage* message,
|
||||
const char* left,
|
||||
const char* center,
|
||||
const char* right) {
|
||||
message->left_button_text = left;
|
||||
message->center_button_text = center;
|
||||
message->right_button_text = right;
|
||||
}
|
12
applications/dialogs/dialogs-module-message.h
Normal file
12
applications/dialogs/dialogs-module-message.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
#include "dialogs-message.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
39
applications/dialogs/dialogs.c
Normal file
39
applications/dialogs/dialogs.c
Normal file
|
@ -0,0 +1,39 @@
|
|||
#include "dialogs-i.h"
|
||||
#include "dialogs-api-lock.h"
|
||||
#include "dialogs-module-file-select.h"
|
||||
#include "dialogs-module-message.h"
|
||||
|
||||
static DialogsApp* dialogs_app_alloc() {
|
||||
DialogsApp* app = malloc(sizeof(DialogsApp));
|
||||
app->message_queue = osMessageQueueNew(8, sizeof(DialogsAppMessage), NULL);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) {
|
||||
switch(message->command) {
|
||||
case DialogsAppCommandFileOpen:
|
||||
message->return_data->bool_value =
|
||||
dialogs_app_process_module_file_select(&message->data->file_select);
|
||||
break;
|
||||
case DialogsAppCommandDialog:
|
||||
message->return_data->dialog_value =
|
||||
dialogs_app_process_module_message(&message->data->dialog);
|
||||
break;
|
||||
}
|
||||
API_LOCK_UNLOCK(message->semaphore);
|
||||
}
|
||||
|
||||
int32_t dialogs_app(void* p) {
|
||||
DialogsApp* app = dialogs_app_alloc();
|
||||
furi_record_create("dialogs", app);
|
||||
|
||||
DialogsAppMessage message;
|
||||
while(1) {
|
||||
if(osMessageQueueGet(app->message_queue, &message, NULL, osWaitForever) == osOK) {
|
||||
dialogs_app_process_message(app, &message);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
128
applications/dialogs/dialogs.h
Normal file
128
applications/dialogs/dialogs.h
Normal file
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <gui/canvas.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/****************** COMMON ******************/
|
||||
|
||||
typedef struct DialogsApp DialogsApp;
|
||||
|
||||
/****************** FILE SELECT ******************/
|
||||
|
||||
/**
|
||||
* Shows and processes the file selection dialog
|
||||
* @param context api pointer
|
||||
* @param path path to directory
|
||||
* @param extension file extension to be offered for selection
|
||||
* @param selected_filename buffer where the selected filename will be saved
|
||||
* @param selected_filename_size and the size of this buffer
|
||||
* @param preselected_filename filename to be preselected
|
||||
* @return bool whether a file was selected
|
||||
*/
|
||||
bool dialog_file_select_show(
|
||||
DialogsApp* context,
|
||||
const char* path,
|
||||
const char* extension,
|
||||
char* result,
|
||||
uint8_t result_size,
|
||||
const char* preselected_filename);
|
||||
|
||||
/****************** MESSAGE ******************/
|
||||
|
||||
/**
|
||||
* Message result type
|
||||
*/
|
||||
typedef enum {
|
||||
DialogMessageButtonBack,
|
||||
DialogMessageButtonLeft,
|
||||
DialogMessageButtonCenter,
|
||||
DialogMessageButtonRight,
|
||||
} DialogMessageButton;
|
||||
|
||||
/**
|
||||
* Message struct
|
||||
*/
|
||||
typedef struct DialogMessage DialogMessage;
|
||||
|
||||
/**
|
||||
* Allocate and fill message
|
||||
* @return DialogMessage*
|
||||
*/
|
||||
DialogMessage* dialog_message_alloc();
|
||||
|
||||
/**
|
||||
* Free message struct
|
||||
* @param message message pointer
|
||||
*/
|
||||
void dialog_message_free(DialogMessage* message);
|
||||
|
||||
/**
|
||||
* Set message text
|
||||
* @param message message pointer
|
||||
* @param text text, can be NULL if you don't want to display the text
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal alignment
|
||||
* @param vertical vertical alignment
|
||||
*/
|
||||
void dialog_message_set_text(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical);
|
||||
|
||||
/**
|
||||
* Set message header
|
||||
* @param message message pointer
|
||||
* @param text text, can be NULL if you don't want to display the header
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param horizontal horizontal alignment
|
||||
* @param vertical vertical alignment
|
||||
*/
|
||||
void dialog_message_set_header(
|
||||
DialogMessage* message,
|
||||
const char* text,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
Align horizontal,
|
||||
Align vertical);
|
||||
|
||||
/**
|
||||
* Set message icon
|
||||
* @param message message pointer
|
||||
* @param icon icon pointer, can be NULL if you don't want to display the icon
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
*/
|
||||
void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y);
|
||||
|
||||
/**
|
||||
* Set message buttons text, button text can be NULL if you don't want to display and process some buttons
|
||||
* @param message message pointer
|
||||
* @param left left button text, can be NULL if you don't want to display the left button
|
||||
* @param center center button text, can be NULL if you don't want to display the center button
|
||||
* @param right right button text, can be NULL if you don't want to display the right button
|
||||
*/
|
||||
void dialog_message_set_buttons(
|
||||
DialogMessage* message,
|
||||
const char* left,
|
||||
const char* center,
|
||||
const char* right);
|
||||
|
||||
/**
|
||||
* Show message from filled struct
|
||||
* @param context api pointer
|
||||
* @param message message struct pointer to be shown
|
||||
* @return DialogMessageButton type
|
||||
*/
|
||||
DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* message);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1,4 +1,4 @@
|
|||
#include "view_holder.h"
|
||||
#include "view-holder.h"
|
||||
#include <gui/view_i.h>
|
||||
|
||||
struct ViewHolder {
|
|
@ -1,10 +1,9 @@
|
|||
#include "dolphin_state.h"
|
||||
|
||||
#include <internal-storage/internal-storage.h>
|
||||
#include <storage/storage.h>
|
||||
#include <furi.h>
|
||||
#include <math.h>
|
||||
|
||||
#define DOLPHIN_STORE_KEY "dolphin_state"
|
||||
#define DOLPHIN_STORE_KEY "/int/dolphin.state"
|
||||
#define DOLPHIN_STORE_HEADER_MAGIC 0xD0
|
||||
#define DOLPHIN_STORE_HEADER_VERSION 0x01
|
||||
#define DOLPHIN_LVL_THRESHOLD 20.0f
|
||||
|
@ -34,24 +33,24 @@ typedef struct {
|
|||
} DolphinStore;
|
||||
|
||||
struct DolphinState {
|
||||
InternalStorage* internal_storage;
|
||||
Storage* fs_api;
|
||||
DolphinStoreData data;
|
||||
};
|
||||
|
||||
DolphinState* dolphin_state_alloc() {
|
||||
DolphinState* dolphin_state = furi_alloc(sizeof(DolphinState));
|
||||
dolphin_state->internal_storage = furi_record_open("internal-storage");
|
||||
dolphin_state->fs_api = furi_record_open("storage");
|
||||
return dolphin_state;
|
||||
}
|
||||
|
||||
void dolphin_state_free(DolphinState* dolphin_state) {
|
||||
furi_record_close("internal-storage");
|
||||
furi_record_close("storage");
|
||||
free(dolphin_state);
|
||||
}
|
||||
|
||||
bool dolphin_state_save(DolphinState* dolphin_state) {
|
||||
DolphinStore store;
|
||||
FURI_LOG_I("dolphin-state", "Saving state to internal-storage");
|
||||
FURI_LOG_I("dolphin-state", "Saving state to \"%s\"", DOLPHIN_STORE_KEY);
|
||||
// Calculate checksum
|
||||
uint8_t* source = (uint8_t*)&dolphin_state->data;
|
||||
uint8_t checksum = 0;
|
||||
|
@ -66,60 +65,95 @@ bool dolphin_state_save(DolphinState* dolphin_state) {
|
|||
store.header.timestamp = 0;
|
||||
// Set data
|
||||
store.data = dolphin_state->data;
|
||||
|
||||
// Store
|
||||
int ret = internal_storage_write_key(
|
||||
dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore));
|
||||
if(ret != sizeof(DolphinStore)) {
|
||||
FURI_LOG_E("dolphin-state", "Save failed. Storage returned: %d", ret);
|
||||
return false;
|
||||
File* file = storage_file_alloc(dolphin_state->fs_api);
|
||||
bool save_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_WRITE, FSOM_CREATE_ALWAYS);
|
||||
|
||||
if(save_result) {
|
||||
uint16_t bytes_count = storage_file_write(file, &store, sizeof(DolphinStore));
|
||||
|
||||
if(bytes_count != sizeof(DolphinStore)) {
|
||||
save_result = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!save_result) {
|
||||
FURI_LOG_E(
|
||||
"dolphin-state",
|
||||
"Save failed. Storage returned: %s",
|
||||
storage_file_get_error_desc(file));
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
FURI_LOG_I("dolphin-state", "Saved");
|
||||
return true;
|
||||
return save_result;
|
||||
}
|
||||
|
||||
bool dolphin_state_load(DolphinState* dolphin_state) {
|
||||
DolphinStore store;
|
||||
// Read Dolphin State Store
|
||||
FURI_LOG_I("dolphin-state", "Loading state from internal-storage");
|
||||
int ret = internal_storage_read_key(
|
||||
dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore));
|
||||
if(ret != sizeof(DolphinStore)) {
|
||||
FURI_LOG_E("dolphin-state", "Load failed. Storage returned: %d", ret);
|
||||
return false;
|
||||
FURI_LOG_I("dolphin-state", "Loading state from \"%s\"", DOLPHIN_STORE_KEY);
|
||||
|
||||
File* file = storage_file_alloc(dolphin_state->fs_api);
|
||||
bool load_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
|
||||
if(load_result) {
|
||||
uint16_t bytes_count = storage_file_read(file, &store, sizeof(DolphinStore));
|
||||
|
||||
if(bytes_count != sizeof(DolphinStore)) {
|
||||
load_result = false;
|
||||
}
|
||||
}
|
||||
|
||||
FURI_LOG_I("dolphin-state", "State loaded, verifying header");
|
||||
if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC &&
|
||||
store.header.version == DOLPHIN_STORE_HEADER_VERSION) {
|
||||
FURI_LOG_I(
|
||||
"dolphin-state",
|
||||
"Magic(%d) and Version(%d) match",
|
||||
store.header.magic,
|
||||
store.header.version);
|
||||
uint8_t checksum = 0;
|
||||
const uint8_t* source = (const uint8_t*)&store.data;
|
||||
for(size_t i = 0; i < sizeof(DolphinStoreData); i++) {
|
||||
checksum += source[i];
|
||||
}
|
||||
if(store.header.checksum == checksum) {
|
||||
FURI_LOG_I("dolphin-state", "Checksum(%d) match", store.header.checksum);
|
||||
dolphin_state->data = store.data;
|
||||
return true;
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
"dolphin-state", "Checksum(%d != %d) mismatch", store.header.checksum, checksum);
|
||||
}
|
||||
} else {
|
||||
if(!load_result) {
|
||||
FURI_LOG_E(
|
||||
"dolphin-state",
|
||||
"Magic(%d != %d) and Version(%d != %d) mismatch",
|
||||
store.header.magic,
|
||||
DOLPHIN_STORE_HEADER_MAGIC,
|
||||
store.header.version,
|
||||
DOLPHIN_STORE_HEADER_VERSION);
|
||||
"Load failed. Storage returned: %s",
|
||||
storage_file_get_error_desc(file));
|
||||
} else {
|
||||
FURI_LOG_I("dolphin-state", "State loaded, verifying header");
|
||||
if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC &&
|
||||
store.header.version == DOLPHIN_STORE_HEADER_VERSION) {
|
||||
FURI_LOG_I(
|
||||
"dolphin-state",
|
||||
"Magic(%d) and Version(%d) match",
|
||||
store.header.magic,
|
||||
store.header.version);
|
||||
uint8_t checksum = 0;
|
||||
const uint8_t* source = (const uint8_t*)&store.data;
|
||||
for(size_t i = 0; i < sizeof(DolphinStoreData); i++) {
|
||||
checksum += source[i];
|
||||
}
|
||||
|
||||
if(store.header.checksum == checksum) {
|
||||
FURI_LOG_I("dolphin-state", "Checksum(%d) match", store.header.checksum);
|
||||
dolphin_state->data = store.data;
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
"dolphin-state",
|
||||
"Checksum(%d != %d) mismatch",
|
||||
store.header.checksum,
|
||||
checksum);
|
||||
load_result = false;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
"dolphin-state",
|
||||
"Magic(%d != %d) and Version(%d != %d) mismatch",
|
||||
store.header.magic,
|
||||
DOLPHIN_STORE_HEADER_MAGIC,
|
||||
store.header.version,
|
||||
DOLPHIN_STORE_HEADER_VERSION);
|
||||
load_result = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
return load_result;
|
||||
}
|
||||
|
||||
void dolphin_state_clear(DolphinState* dolphin_state) {
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
#include <gui/elements.h>
|
||||
#include <m-string.h>
|
||||
#include <sys/param.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define FILENAME_COUNT 4
|
||||
|
||||
struct FileSelect {
|
||||
// public
|
||||
View* view;
|
||||
FS_Api* fs_api;
|
||||
Storage* fs_api;
|
||||
const char* path;
|
||||
const char* extension;
|
||||
|
||||
|
@ -180,6 +181,8 @@ static bool file_select_init_inner(FileSelect* file_select) {
|
|||
FileSelect* file_select_alloc() {
|
||||
FileSelect* file_select = furi_alloc(sizeof(FileSelect));
|
||||
file_select->view = view_alloc();
|
||||
file_select->fs_api = furi_record_open("storage");
|
||||
|
||||
view_set_context(file_select->view, file_select);
|
||||
view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel));
|
||||
view_set_draw_callback(file_select->view, file_select_draw_callback);
|
||||
|
@ -210,6 +213,7 @@ void file_select_free(FileSelect* file_select) {
|
|||
});
|
||||
view_free(file_select->view);
|
||||
free(file_select);
|
||||
furi_record_close("storage");
|
||||
}
|
||||
|
||||
View* file_select_get_view(FileSelect* file_select) {
|
||||
|
@ -217,11 +221,6 @@ View* file_select_get_view(FileSelect* file_select) {
|
|||
return file_select->view;
|
||||
}
|
||||
|
||||
void file_select_set_api(FileSelect* file_select, FS_Api* fs_api) {
|
||||
furi_assert(file_select);
|
||||
file_select->fs_api = fs_api;
|
||||
}
|
||||
|
||||
void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) {
|
||||
file_select->context = context;
|
||||
file_select->callback = callback;
|
||||
|
@ -272,13 +271,12 @@ bool file_select_fill_strings(FileSelect* file_select) {
|
|||
furi_assert(file_select->extension);
|
||||
|
||||
FileInfo file_info;
|
||||
File directory;
|
||||
bool result;
|
||||
FS_Dir_Api* dir_api = &file_select->fs_api->dir;
|
||||
File* directory = storage_file_alloc(file_select->fs_api);
|
||||
|
||||
uint8_t string_counter = 0;
|
||||
uint16_t file_counter = 0;
|
||||
const uint8_t name_length = 100;
|
||||
char* name = calloc(name_length, sizeof(char));
|
||||
char* name = furi_alloc(name_length);
|
||||
uint16_t first_file_index = 0;
|
||||
|
||||
with_view_model(
|
||||
|
@ -287,59 +285,50 @@ bool file_select_fill_strings(FileSelect* file_select) {
|
|||
return false;
|
||||
});
|
||||
|
||||
if(name == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = dir_api->open(&directory, file_select->path);
|
||||
|
||||
if(!result) {
|
||||
dir_api->close(&directory);
|
||||
if(!storage_dir_open(directory, file_select->path)) {
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
result = dir_api->read(&directory, &file_info, name, name_length);
|
||||
|
||||
if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) {
|
||||
if(!storage_dir_read(directory, &file_info, name, name_length)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(result) {
|
||||
if(directory.error_id == FSE_OK) {
|
||||
if(filter_file(file_select, &file_info, name)) {
|
||||
if(file_counter >= first_file_index) {
|
||||
with_view_model(
|
||||
file_select->view, (FileSelectModel * model) {
|
||||
string_set_str(model->filename[string_counter], name);
|
||||
if(storage_file_get_error(directory) == FSE_OK) {
|
||||
if(filter_file(file_select, &file_info, name)) {
|
||||
if(file_counter >= first_file_index) {
|
||||
with_view_model(
|
||||
file_select->view, (FileSelectModel * model) {
|
||||
string_set_str(model->filename[string_counter], name);
|
||||
|
||||
if(strcmp(file_select->extension, "*") != 0) {
|
||||
string_replace_all_str(
|
||||
model->filename[string_counter],
|
||||
file_select->extension,
|
||||
"");
|
||||
}
|
||||
if(strcmp(file_select->extension, "*") != 0) {
|
||||
string_replace_all_str(
|
||||
model->filename[string_counter], file_select->extension, "");
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
string_counter++;
|
||||
return true;
|
||||
});
|
||||
string_counter++;
|
||||
|
||||
if(string_counter >= FILENAME_COUNT) {
|
||||
break;
|
||||
}
|
||||
if(string_counter >= FILENAME_COUNT) {
|
||||
break;
|
||||
}
|
||||
file_counter++;
|
||||
}
|
||||
} else {
|
||||
dir_api->close(&directory);
|
||||
free(name);
|
||||
return false;
|
||||
file_counter++;
|
||||
}
|
||||
} else {
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
dir_api->close(&directory);
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return true;
|
||||
}
|
||||
|
@ -351,42 +340,33 @@ bool file_select_fill_count(FileSelect* file_select) {
|
|||
furi_assert(file_select->extension);
|
||||
|
||||
FileInfo file_info;
|
||||
File directory;
|
||||
bool result;
|
||||
FS_Dir_Api* dir_api = &file_select->fs_api->dir;
|
||||
File* directory = storage_file_alloc(file_select->fs_api);
|
||||
|
||||
uint16_t file_counter = 0;
|
||||
const uint8_t name_length = 100;
|
||||
char* name = calloc(name_length, sizeof(char));
|
||||
char* name = furi_alloc(name_length);
|
||||
|
||||
if(name == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = dir_api->open(&directory, file_select->path);
|
||||
|
||||
if(!result) {
|
||||
dir_api->close(&directory);
|
||||
if(!storage_dir_open(directory, file_select->path)) {
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
result = dir_api->read(&directory, &file_info, name, name_length);
|
||||
|
||||
if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) {
|
||||
if(!storage_dir_read(directory, &file_info, name, name_length)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(result) {
|
||||
if(directory.error_id == FSE_OK) {
|
||||
if(filter_file(file_select, &file_info, name)) {
|
||||
file_counter++;
|
||||
}
|
||||
} else {
|
||||
dir_api->close(&directory);
|
||||
free(name);
|
||||
return false;
|
||||
if(storage_file_get_error(directory) == FSE_OK) {
|
||||
if(filter_file(file_select, &file_info, name)) {
|
||||
file_counter++;
|
||||
}
|
||||
} else {
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -396,7 +376,8 @@ bool file_select_fill_count(FileSelect* file_select) {
|
|||
return false;
|
||||
});
|
||||
|
||||
dir_api->close(&directory);
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return true;
|
||||
}
|
||||
|
@ -411,16 +392,10 @@ void file_select_set_selected_file_internal(FileSelect* file_select, const char*
|
|||
if(strlen(filename) == 0) return;
|
||||
|
||||
FileInfo file_info;
|
||||
File directory;
|
||||
bool result;
|
||||
FS_Dir_Api* dir_api = &file_select->fs_api->dir;
|
||||
File* directory = storage_file_alloc(file_select->fs_api);
|
||||
|
||||
const uint8_t name_length = 100;
|
||||
char* name = calloc(name_length, sizeof(char));
|
||||
|
||||
if(name == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
char* name = furi_alloc(name_length);
|
||||
uint16_t file_position = 0;
|
||||
bool file_found = false;
|
||||
|
||||
|
@ -430,38 +405,34 @@ void file_select_set_selected_file_internal(FileSelect* file_select, const char*
|
|||
string_cat_str(filename_str, file_select->extension);
|
||||
}
|
||||
|
||||
result = dir_api->open(&directory, file_select->path);
|
||||
|
||||
if(!result) {
|
||||
if(!storage_dir_open(directory, file_select->path)) {
|
||||
string_clear(filename_str);
|
||||
dir_api->close(&directory);
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
result = dir_api->read(&directory, &file_info, name, name_length);
|
||||
|
||||
if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) {
|
||||
if(!storage_dir_read(directory, &file_info, name, name_length)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(result) {
|
||||
if(directory.error_id == FSE_OK) {
|
||||
if(filter_file(file_select, &file_info, name)) {
|
||||
if(strcmp(string_get_cstr(filename_str), name) == 0) {
|
||||
file_found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
file_position++;
|
||||
if(storage_file_get_error(directory) == FSE_OK) {
|
||||
if(filter_file(file_select, &file_info, name)) {
|
||||
if(strcmp(string_get_cstr(filename_str), name) == 0) {
|
||||
file_found = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
string_clear(filename_str);
|
||||
dir_api->close(&directory);
|
||||
free(name);
|
||||
return;
|
||||
|
||||
file_position++;
|
||||
}
|
||||
} else {
|
||||
string_clear(filename_str);
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,7 +459,8 @@ void file_select_set_selected_file_internal(FileSelect* file_select, const char*
|
|||
}
|
||||
|
||||
string_clear(filename_str);
|
||||
dir_api->close(&directory);
|
||||
storage_dir_close(directory);
|
||||
storage_file_free(directory);
|
||||
free(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
#include <gui/view.h>
|
||||
#include <filesystem-api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
@ -15,7 +14,6 @@ FileSelect* file_select_alloc();
|
|||
void file_select_free(FileSelect* file_select);
|
||||
View* file_select_get_view(FileSelect* file_select);
|
||||
|
||||
void file_select_set_api(FileSelect* file_select, FS_Api* fs_api);
|
||||
void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context);
|
||||
void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension);
|
||||
void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#include <file-worker-cpp.h>
|
||||
#include <path.h>
|
||||
|
||||
const char* iButtonApp::app_folder = "ibutton";
|
||||
const char* iButtonApp::app_folder = "/any/ibutton";
|
||||
const char* iButtonApp::app_extension = ".ibtn";
|
||||
|
||||
void iButtonApp::run(void* args) {
|
||||
|
@ -13,6 +13,8 @@ void iButtonApp::run(void* args) {
|
|||
bool consumed;
|
||||
bool exit = false;
|
||||
|
||||
make_app_folder();
|
||||
|
||||
if(args && load_key((const char*)args)) {
|
||||
current_scene = Scene::SceneEmulate;
|
||||
}
|
||||
|
@ -218,15 +220,13 @@ void iButtonApp::generate_random_name(char* name, uint8_t max_name_size) {
|
|||
|
||||
// file managment
|
||||
bool iButtonApp::save_key(const char* key_name) {
|
||||
// Create ibutton directory if necessary
|
||||
make_app_folder();
|
||||
|
||||
FileWorkerCpp file_worker;
|
||||
string_t key_file_name;
|
||||
bool result = false;
|
||||
|
||||
// Create ibutton directory if necessary
|
||||
if(!file_worker.mkdir(app_folder)) {
|
||||
return false;
|
||||
};
|
||||
|
||||
// First remove key if it was saved
|
||||
string_init_printf(key_file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension);
|
||||
if(!file_worker.remove(string_get_cstr(key_file_name))) {
|
||||
|
@ -370,4 +370,9 @@ bool iButtonApp::delete_key() {
|
|||
string_clear(file_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void iButtonApp::make_app_folder() {
|
||||
FileWorkerCpp file_worker;
|
||||
file_worker.mkdir(app_folder);
|
||||
}
|
|
@ -25,9 +25,6 @@
|
|||
|
||||
#include "helpers/key-worker.h"
|
||||
|
||||
#include <sd-card-api.h>
|
||||
#include <filesystem-api.h>
|
||||
|
||||
#include "one_wire_master.h"
|
||||
#include "maxim_crc.h"
|
||||
#include "ibutton-key.h"
|
||||
|
@ -142,4 +139,5 @@ private:
|
|||
static const char* app_extension;
|
||||
|
||||
bool load_key_data(string_t key_path);
|
||||
void make_app_folder();
|
||||
};
|
|
@ -4,7 +4,6 @@
|
|||
#include "../ibutton-event.h"
|
||||
#include "../ibutton-key.h"
|
||||
#include <callback-connector.h>
|
||||
#include <filesystem-api.h>
|
||||
|
||||
void iButtonSceneSaveName::on_enter(iButtonApp* app) {
|
||||
iButtonAppViewManager* view_manager = app->get_view_manager();
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "../ibutton-view-manager.h"
|
||||
#include "../ibutton-event.h"
|
||||
#include <callback-connector.h>
|
||||
#include <filesystem-api.h>
|
||||
|
||||
typedef enum {
|
||||
SubmenuIndexRead,
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "internal-storage.h"
|
||||
#include <furi.h>
|
||||
#include <api-hal.h>
|
||||
#include <lfs.h>
|
||||
|
||||
#define INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE (1)
|
||||
|
||||
struct InternalStorage {
|
||||
osMessageQueueId_t queue;
|
||||
InternalStorageState state;
|
||||
const size_t start_address;
|
||||
const size_t start_page;
|
||||
struct lfs_config config;
|
||||
lfs_t lfs;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const char* key;
|
||||
uint8_t* buffer;
|
||||
size_t size;
|
||||
int ret;
|
||||
} InternalStorageCommandKey;
|
||||
|
||||
typedef void (*InternalStorageCommandFunction)(InternalStorage* internal_storage, void* data);
|
||||
|
||||
typedef struct {
|
||||
osThreadId thread;
|
||||
InternalStorageCommandFunction function;
|
||||
void* data;
|
||||
} InternalStorageCommand;
|
||||
|
||||
int internal_storage_device_read(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
void* buffer,
|
||||
lfs_size_t size);
|
||||
|
||||
int internal_storage_device_prog(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
const void* buffer,
|
||||
lfs_size_t size);
|
||||
|
||||
int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block);
|
||||
|
||||
int internal_storage_device_sync(const struct lfs_config* c);
|
||||
|
||||
InternalStorage* internal_storage_alloc();
|
||||
|
||||
void internal_storage_free(InternalStorage* internal_storage);
|
||||
|
||||
int32_t internal_storage_task(void* p);
|
||||
|
||||
void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data);
|
||||
|
||||
void _internal_storage_write_key(
|
||||
InternalStorage* internal_storage,
|
||||
InternalStorageCommandKey* data);
|
|
@ -1,252 +0,0 @@
|
|||
#include "internal-storage-i.h"
|
||||
|
||||
int internal_storage_device_read(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
void* buffer,
|
||||
lfs_size_t size) {
|
||||
InternalStorage* internal_storage = c->context;
|
||||
size_t address = internal_storage->start_address + block * c->block_size + off;
|
||||
|
||||
FURI_LOG_D(
|
||||
"internal-storage",
|
||||
"Device read: block %d, off %d, buffer: %p, size %d, translated address: %p",
|
||||
block,
|
||||
off,
|
||||
buffer,
|
||||
size,
|
||||
address);
|
||||
|
||||
memcpy(buffer, (void*)address, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int internal_storage_device_prog(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
const void* buffer,
|
||||
lfs_size_t size) {
|
||||
InternalStorage* internal_storage = c->context;
|
||||
size_t address = internal_storage->start_address + block * c->block_size + off;
|
||||
|
||||
FURI_LOG_D(
|
||||
"internal-storage",
|
||||
"Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p",
|
||||
block,
|
||||
off,
|
||||
buffer,
|
||||
size,
|
||||
address);
|
||||
|
||||
int ret = 0;
|
||||
while(size > 0) {
|
||||
if(!api_hal_flash_write_dword(address, *(uint64_t*)buffer)) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
address += c->prog_size;
|
||||
buffer += c->prog_size;
|
||||
size -= c->prog_size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block) {
|
||||
InternalStorage* internal_storage = c->context;
|
||||
size_t page = internal_storage->start_page + block;
|
||||
|
||||
FURI_LOG_D("internal-storage", "Device erase: page %d, translated page: %d", block, page);
|
||||
|
||||
if(api_hal_flash_erase(page, 1)) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int internal_storage_device_sync(const struct lfs_config* c) {
|
||||
FURI_LOG_D("internal-storage", "Device sync: skipping, cause ");
|
||||
return 0;
|
||||
}
|
||||
|
||||
InternalStorage* internal_storage_alloc() {
|
||||
InternalStorage* internal_storage = furi_alloc(sizeof(InternalStorage));
|
||||
|
||||
internal_storage->queue = osMessageQueueNew(8, sizeof(InternalStorageCommand), NULL);
|
||||
|
||||
// Internal storage start address
|
||||
internal_storage->state = InternalStorageStateInitializing;
|
||||
|
||||
// Internal storage start address
|
||||
*(size_t*)(&internal_storage->start_address) = api_hal_flash_get_free_page_start_address();
|
||||
*(size_t*)(&internal_storage->start_page) =
|
||||
(internal_storage->start_address - api_hal_flash_get_base()) /
|
||||
api_hal_flash_get_page_size();
|
||||
|
||||
// LFS configuration
|
||||
// Glue and context
|
||||
internal_storage->config.context = internal_storage;
|
||||
internal_storage->config.read = internal_storage_device_read;
|
||||
internal_storage->config.prog = internal_storage_device_prog;
|
||||
internal_storage->config.erase = internal_storage_device_erase;
|
||||
internal_storage->config.sync = internal_storage_device_sync;
|
||||
// Block device description
|
||||
internal_storage->config.read_size = api_hal_flash_get_read_block_size();
|
||||
internal_storage->config.prog_size = api_hal_flash_get_write_block_size();
|
||||
internal_storage->config.block_size = api_hal_flash_get_page_size();
|
||||
internal_storage->config.block_count = api_hal_flash_get_free_page_count();
|
||||
internal_storage->config.block_cycles = api_hal_flash_get_cycles_count();
|
||||
internal_storage->config.cache_size = 16;
|
||||
internal_storage->config.lookahead_size = 16;
|
||||
|
||||
return internal_storage;
|
||||
}
|
||||
|
||||
void internal_storage_free(InternalStorage* internal_storage) {
|
||||
furi_assert(internal_storage);
|
||||
free(internal_storage);
|
||||
}
|
||||
|
||||
int32_t internal_storage_task(void* p) {
|
||||
FURI_LOG_I("internal-storage", "Starting");
|
||||
InternalStorage* internal_storage = internal_storage_alloc();
|
||||
FURI_LOG_I(
|
||||
"internal-storage",
|
||||
"Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d",
|
||||
internal_storage->start_address,
|
||||
internal_storage->config.read_size,
|
||||
internal_storage->config.prog_size,
|
||||
internal_storage->config.block_size,
|
||||
internal_storage->config.block_count,
|
||||
internal_storage->config.block_cycles);
|
||||
|
||||
int err;
|
||||
ApiHalBootFlag boot_flags = api_hal_boot_get_flags();
|
||||
if(boot_flags & ApiHalBootFlagFactoryReset) {
|
||||
// Factory reset
|
||||
err = lfs_format(&internal_storage->lfs, &internal_storage->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I("internal-storage", "Factory reset: Format successful, trying to mount");
|
||||
api_hal_boot_set_flags(boot_flags & ~ApiHalBootFlagFactoryReset);
|
||||
err = lfs_mount(&internal_storage->lfs, &internal_storage->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I("internal-storage", "Factory reset: Mounted");
|
||||
internal_storage->state = InternalStorageStateReady;
|
||||
} else {
|
||||
FURI_LOG_E("internal-storage", "Factory reset: Mount after format failed");
|
||||
internal_storage->state = InternalStorageStateBroken;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E("internal-storage", "Factory reset: Format failed");
|
||||
internal_storage->state = InternalStorageStateBroken;
|
||||
}
|
||||
} else {
|
||||
// Normal
|
||||
err = lfs_mount(&internal_storage->lfs, &internal_storage->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I("internal-storage", "Mounted");
|
||||
internal_storage->state = InternalStorageStateReady;
|
||||
} else {
|
||||
FURI_LOG_E("internal-storage", "Mount failed, formatting");
|
||||
err = lfs_format(&internal_storage->lfs, &internal_storage->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I("internal-storage", "Format successful, trying to mount");
|
||||
err = lfs_mount(&internal_storage->lfs, &internal_storage->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I("internal-storage", "Mounted");
|
||||
internal_storage->state = InternalStorageStateReady;
|
||||
} else {
|
||||
FURI_LOG_E("internal-storage", "Mount after format failed");
|
||||
internal_storage->state = InternalStorageStateBroken;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E("internal-storage", "Format failed");
|
||||
internal_storage->state = InternalStorageStateBroken;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_record_create("internal-storage", internal_storage);
|
||||
|
||||
InternalStorageCommand command;
|
||||
while(1) {
|
||||
furi_check(
|
||||
osMessageQueueGet(internal_storage->queue, &command, NULL, osWaitForever) == osOK);
|
||||
command.function(internal_storage, command.data);
|
||||
osThreadFlagsSet(command.thread, INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE);
|
||||
}
|
||||
|
||||
lfs_unmount(&internal_storage->lfs);
|
||||
internal_storage_free(internal_storage);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data) {
|
||||
lfs_file_t file;
|
||||
int ret = lfs_file_open(&internal_storage->lfs, &file, data->key, LFS_O_RDONLY);
|
||||
if(ret == 0) {
|
||||
ret = lfs_file_read(&internal_storage->lfs, &file, data->buffer, data->size);
|
||||
lfs_file_close(&internal_storage->lfs, &file);
|
||||
}
|
||||
data->ret = ret;
|
||||
}
|
||||
|
||||
int internal_storage_read_key(
|
||||
InternalStorage* internal_storage,
|
||||
const char* key,
|
||||
uint8_t* buffer,
|
||||
size_t size) {
|
||||
osThreadId_t caller_thread = osThreadGetId();
|
||||
if(caller_thread == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0};
|
||||
InternalStorageCommand command = {
|
||||
.thread = caller_thread,
|
||||
.function = (InternalStorageCommandFunction)_internal_storage_read_key,
|
||||
.data = &data,
|
||||
};
|
||||
furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK);
|
||||
osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever);
|
||||
return data.ret;
|
||||
}
|
||||
|
||||
void _internal_storage_write_key(
|
||||
InternalStorage* internal_storage,
|
||||
InternalStorageCommandKey* data) {
|
||||
lfs_file_t file;
|
||||
int ret = lfs_file_open(
|
||||
&internal_storage->lfs, &file, data->key, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
|
||||
if(ret == 0) {
|
||||
ret = lfs_file_write(&internal_storage->lfs, &file, data->buffer, data->size);
|
||||
lfs_file_close(&internal_storage->lfs, &file);
|
||||
}
|
||||
data->ret = ret;
|
||||
}
|
||||
|
||||
int internal_storage_write_key(
|
||||
InternalStorage* internal_storage,
|
||||
const char* key,
|
||||
uint8_t* buffer,
|
||||
size_t size) {
|
||||
osThreadId_t caller_thread = osThreadGetId();
|
||||
if(caller_thread == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0};
|
||||
InternalStorageCommand command = {
|
||||
.thread = caller_thread,
|
||||
.function = (InternalStorageCommandFunction)_internal_storage_write_key,
|
||||
.data = &data,
|
||||
};
|
||||
furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK);
|
||||
osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever);
|
||||
return data.ret;
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Internal storage state */
|
||||
typedef enum {
|
||||
InternalStorageStateInitializing,
|
||||
InternalStorageStateReady,
|
||||
InternalStorageStateBroken,
|
||||
} InternalStorageState;
|
||||
|
||||
typedef struct InternalStorage InternalStorage;
|
||||
|
||||
/** Read key, blocking api
|
||||
* @param internal_storage - InternalStorage instance
|
||||
* @param key - file name to read data from
|
||||
* @param buffer - pointer to data buffer
|
||||
* @param size - buffer size
|
||||
* @return negative on error, otherwise data read
|
||||
*/
|
||||
int internal_storage_read_key(
|
||||
InternalStorage* internal_storage,
|
||||
const char* key,
|
||||
uint8_t* buffer,
|
||||
size_t size);
|
||||
|
||||
/** Write key, blocking api
|
||||
* @param internal_storage - InternalStorage instance
|
||||
* @param key - file name to store data to
|
||||
* @param buffer - pointer to data buffer
|
||||
* @param size - buffer size
|
||||
* @return negative on error, otherwise data written
|
||||
*/
|
||||
int internal_storage_write_key(
|
||||
InternalStorage* internal_storage,
|
||||
const char* key,
|
||||
uint8_t* buffer,
|
||||
size_t size);
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
class IrdaAppBruteForce {
|
||||
const char* universal_db_filename;
|
||||
File file;
|
||||
std::string current_record;
|
||||
std::unique_ptr<IrdaAppFileParser> file_parser;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <file-worker-cpp.h>
|
||||
|
||||
uint32_t const IrdaAppFileParser::max_line_length = ((9 + 1) * 512 + 100);
|
||||
const char* IrdaAppFileParser::irda_directory = "/irda";
|
||||
const char* IrdaAppFileParser::irda_directory = "/any/irda";
|
||||
const char* IrdaAppFileParser::irda_extension = ".ir";
|
||||
uint32_t const IrdaAppFileParser::max_raw_timings_in_signal = 512;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
#include <file_reader/file_reader.h>
|
||||
#include <irda.h>
|
||||
#include <file-worker-cpp.h>
|
||||
#include "irda-app-signal.h"
|
||||
#include <memory>
|
||||
|
||||
class IrdaAppFileParser {
|
||||
public:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "irda-app-remote-manager.hpp"
|
||||
#include "filesystem-api.h"
|
||||
#include <storage/storage.h>
|
||||
#include "furi.h"
|
||||
#include "furi/check.h"
|
||||
#include "gui/modules/button_menu.h"
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
#include <vector>
|
||||
#include <memory>
|
||||
#include <irda.h>
|
||||
#include <sd-card-api.h>
|
||||
#include <filesystem-api.h>
|
||||
#include <storage/storage.h>
|
||||
#include "irda-app-signal.h"
|
||||
|
||||
class IrdaAppRemoteButton {
|
||||
|
|
|
@ -148,14 +148,16 @@ protected:
|
|||
class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon {
|
||||
public:
|
||||
void on_enter(IrdaApp* app) final;
|
||||
IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/assets/ext/irda/tv.ir") {}
|
||||
IrdaAppSceneUniversalTV()
|
||||
: IrdaAppSceneUniversalCommon("/ext/irda/universal/tv.ir") {
|
||||
}
|
||||
~IrdaAppSceneUniversalTV() {}
|
||||
};
|
||||
|
||||
class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon {
|
||||
public:
|
||||
void on_enter(IrdaApp* app) final;
|
||||
IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/assets/ext/irda/audio.ir") {}
|
||||
IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/ext/irda/universal/audio.ir") {}
|
||||
~IrdaAppSceneUniversalAudio() {}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,13 +19,11 @@
|
|||
#include <file-worker-cpp.h>
|
||||
#include <path.h>
|
||||
|
||||
const char* LfRfidApp::app_folder = "lfrfid";
|
||||
const char* LfRfidApp::app_folder = "/any/lfrfid";
|
||||
const char* LfRfidApp::app_extension = ".rfid";
|
||||
|
||||
LfRfidApp::LfRfidApp()
|
||||
: scene_controller{this}
|
||||
, fs_api{"sdcard"}
|
||||
, sd_ex_api{"sdcard-ex"}
|
||||
, notification{"notification"}
|
||||
, text_store(40) {
|
||||
api_hal_power_insomnia_enter();
|
||||
|
@ -41,6 +39,8 @@ LfRfidApp::~LfRfidApp() {
|
|||
void LfRfidApp::run(void* _args) {
|
||||
const char* args = reinterpret_cast<const char*>(_args);
|
||||
|
||||
make_app_folder();
|
||||
|
||||
if(strlen(args)) {
|
||||
load_key_data(args, &worker.key);
|
||||
scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate());
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
#include <view-modules/byte-input-vm.h>
|
||||
#include "view/container-vm.h"
|
||||
|
||||
#include <sd-card-api.h>
|
||||
#include <filesystem-api.h>
|
||||
#include <notification/notification-messages.h>
|
||||
|
||||
#include "helpers/rfid-worker.h"
|
||||
|
@ -64,8 +62,6 @@ public:
|
|||
~LfRfidApp();
|
||||
LfRfidApp();
|
||||
|
||||
RecordController<FS_Api> fs_api;
|
||||
RecordController<SdCard_Api> sd_ex_api;
|
||||
RecordController<NotificationApp> notification;
|
||||
|
||||
RfidWorker worker;
|
||||
|
|
|
@ -183,7 +183,7 @@ static void loader_build_menu() {
|
|||
menu,
|
||||
menu_item_alloc_function(
|
||||
FLIPPER_APPS[i].name,
|
||||
icon_animation_alloc(FLIPPER_APPS[i].icon),
|
||||
FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL,
|
||||
loader_menu_callback,
|
||||
(void*)&FLIPPER_APPS[i]));
|
||||
|
||||
|
@ -213,7 +213,8 @@ static void loader_build_menu() {
|
|||
menu_plugins,
|
||||
menu_item_alloc_function(
|
||||
FLIPPER_PLUGINS[i].name,
|
||||
icon_animation_alloc(FLIPPER_PLUGINS[i].icon),
|
||||
FLIPPER_PLUGINS[i].icon ? icon_animation_alloc(FLIPPER_PLUGINS[i].icon) :
|
||||
NULL,
|
||||
loader_menu_callback,
|
||||
(void*)&FLIPPER_PLUGINS[i]));
|
||||
|
||||
|
@ -245,7 +246,9 @@ static void loader_build_menu() {
|
|||
menu_debug,
|
||||
menu_item_alloc_function(
|
||||
FLIPPER_DEBUG_APPS[i].name,
|
||||
icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon),
|
||||
FLIPPER_DEBUG_APPS[i].icon ?
|
||||
icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon) :
|
||||
NULL,
|
||||
loader_menu_callback,
|
||||
(void*)&FLIPPER_DEBUG_APPS[i]));
|
||||
|
||||
|
@ -277,7 +280,9 @@ static void loader_build_menu() {
|
|||
menu_debug,
|
||||
menu_item_alloc_function(
|
||||
FLIPPER_SETTINGS_APPS[i].name,
|
||||
icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon),
|
||||
FLIPPER_SETTINGS_APPS[i].icon ?
|
||||
icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon) :
|
||||
NULL,
|
||||
loader_menu_callback,
|
||||
(void*)&FLIPPER_SETTINGS_APPS[i]));
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#define NFC_DEVICE_MAX_DATA_LEN 14
|
||||
|
||||
static const char* nfc_app_folder = "nfc";
|
||||
static const char* nfc_app_folder = "/any/nfc";
|
||||
static const char* nfc_app_extension = ".nfc";
|
||||
|
||||
static bool nfc_device_read_hex(string_t str, uint8_t* buff, uint16_t len) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include <furi.h>
|
||||
#include <api-hal.h>
|
||||
#include <internal-storage/internal-storage.h>
|
||||
#include <storage/storage.h>
|
||||
#include "notification.h"
|
||||
#include "notification-messages.h"
|
||||
#include "notification-app.h"
|
||||
|
@ -309,24 +309,30 @@ void notification_process_internal_message(NotificationApp* app, NotificationApp
|
|||
}
|
||||
}
|
||||
|
||||
static void notification_load_settings(NotificationApp* app) {
|
||||
static bool notification_load_settings(NotificationApp* app) {
|
||||
NotificationSettings settings;
|
||||
InternalStorage* internal_storage = furi_record_open("internal-storage");
|
||||
File* file = storage_file_alloc(furi_record_open("storage"));
|
||||
const size_t settings_size = sizeof(NotificationSettings);
|
||||
|
||||
FURI_LOG_I("notification", "Loading state from internal-storage");
|
||||
int ret = internal_storage_read_key(
|
||||
internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&settings, settings_size);
|
||||
FURI_LOG_I("notification", "loading settings from \"%s\"", NOTIFICATION_SETTINGS_PATH);
|
||||
bool fs_result =
|
||||
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
|
||||
if(ret != settings_size) {
|
||||
FURI_LOG_E("notification", "Load failed. Storage returned: %d", ret);
|
||||
} else {
|
||||
FURI_LOG_I("notification", "Load success", ret);
|
||||
if(fs_result) {
|
||||
uint16_t bytes_count = storage_file_read(file, &settings, settings_size);
|
||||
|
||||
if(bytes_count != settings_size) {
|
||||
fs_result = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(fs_result) {
|
||||
FURI_LOG_I("notification", "load success");
|
||||
|
||||
if(settings.version != NOTIFICATION_SETTINGS_VERSION) {
|
||||
FURI_LOG_E(
|
||||
"notification",
|
||||
"Version(%d != %d) mismatch",
|
||||
"version(%d != %d) mismatch",
|
||||
app->settings.version,
|
||||
NOTIFICATION_SETTINGS_VERSION);
|
||||
} else {
|
||||
|
@ -334,26 +340,50 @@ static void notification_load_settings(NotificationApp* app) {
|
|||
memcpy(&app->settings, &settings, settings_size);
|
||||
osKernelUnlock();
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E("notification", "load failed, %s", storage_file_get_error_desc(file));
|
||||
}
|
||||
|
||||
furi_record_close("internal-storage");
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close("storage");
|
||||
|
||||
return fs_result;
|
||||
};
|
||||
|
||||
static void notification_save_settings(NotificationApp* app) {
|
||||
InternalStorage* internal_storage = furi_record_open("internal-storage");
|
||||
static bool notification_save_settings(NotificationApp* app) {
|
||||
NotificationSettings settings;
|
||||
File* file = storage_file_alloc(furi_record_open("storage"));
|
||||
const size_t settings_size = sizeof(NotificationSettings);
|
||||
|
||||
FURI_LOG_I("notification", "Saving state to internal-storage");
|
||||
int ret = internal_storage_write_key(
|
||||
internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&app->settings, settings_size);
|
||||
FURI_LOG_I("notification", "saving settings to \"%s\"", NOTIFICATION_SETTINGS_PATH);
|
||||
|
||||
if(ret != settings_size) {
|
||||
FURI_LOG_E("notification", "Save failed. Storage returned: %d", ret);
|
||||
} else {
|
||||
FURI_LOG_I("notification", "Saved");
|
||||
osKernelLock();
|
||||
memcpy(&settings, &app->settings, settings_size);
|
||||
osKernelUnlock();
|
||||
|
||||
bool fs_result =
|
||||
storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS);
|
||||
|
||||
if(fs_result) {
|
||||
uint16_t bytes_count = storage_file_write(file, &settings, settings_size);
|
||||
|
||||
if(bytes_count != settings_size) {
|
||||
fs_result = false;
|
||||
}
|
||||
}
|
||||
|
||||
furi_record_close("internal-storage");
|
||||
if(fs_result) {
|
||||
FURI_LOG_I("notification", "save success");
|
||||
} else {
|
||||
FURI_LOG_E("notification", "save failed, %s", storage_file_get_error_desc(file));
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close("storage");
|
||||
|
||||
return fs_result;
|
||||
};
|
||||
|
||||
static void input_event_callback(const void* value, void* context) {
|
||||
|
@ -407,7 +437,9 @@ static NotificationApp* notification_app_alloc() {
|
|||
int32_t notification_app(void* p) {
|
||||
NotificationApp* app = notification_app_alloc();
|
||||
|
||||
notification_load_settings(app);
|
||||
if(!notification_load_settings(app)) {
|
||||
notification_save_settings(app);
|
||||
}
|
||||
|
||||
notification_vibro_off();
|
||||
notification_sound_off();
|
||||
|
|
|
@ -31,7 +31,7 @@ typedef struct {
|
|||
} NotificationLedLayer;
|
||||
|
||||
#define NOTIFICATION_SETTINGS_VERSION 0x01
|
||||
#define NOTIFICATION_SETTINGS_PATH "notification_settings"
|
||||
#define NOTIFICATION_SETTINGS_PATH "/int/notification.settings"
|
||||
|
||||
typedef struct {
|
||||
uint8_t version;
|
||||
|
|
|
@ -1,892 +0,0 @@
|
|||
#include "app-template.h"
|
||||
#include "stm32_adafruit_sd.h"
|
||||
#include "fnv1a-hash.h"
|
||||
#include "filesystem-api.h"
|
||||
#include "cli/cli.h"
|
||||
#include "callback-connector.h"
|
||||
#include <notification/notification-messages.h>
|
||||
|
||||
// event enumeration type
|
||||
typedef uint8_t event_t;
|
||||
|
||||
class SdTestState {
|
||||
public:
|
||||
// state data
|
||||
static const uint8_t lines_count = 6;
|
||||
const char* line[lines_count];
|
||||
|
||||
// state initializer
|
||||
SdTestState() {
|
||||
for(uint8_t i = 0; i < lines_count; i++) {
|
||||
line[i] = "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// events class
|
||||
class SdTestEvent {
|
||||
public:
|
||||
// events enum
|
||||
static const event_t EventTypeTick = 0;
|
||||
static const event_t EventTypeKey = 1;
|
||||
|
||||
// payload
|
||||
union {
|
||||
InputEvent input;
|
||||
} value;
|
||||
|
||||
// event type
|
||||
event_t type;
|
||||
};
|
||||
|
||||
// our app derived from base AppTemplate class
|
||||
// with template variables <state, events>
|
||||
class SdTest : public AppTemplate<SdTestState, SdTestEvent> {
|
||||
public:
|
||||
// vars
|
||||
const uint32_t benchmark_data_size = 4096;
|
||||
uint8_t* benchmark_data;
|
||||
FS_Api* fs_api;
|
||||
NotificationApp* notification;
|
||||
|
||||
// consts
|
||||
static const uint32_t BENCHMARK_ERROR = UINT_MAX;
|
||||
|
||||
// funcs
|
||||
void run();
|
||||
void render(Canvas* canvas);
|
||||
template <class T> void set_text(std::initializer_list<T> list);
|
||||
template <class T> void set_error(std::initializer_list<T> list);
|
||||
void wait_for_button(InputKey input_button);
|
||||
bool ask(InputKey input_button_cancel, InputKey input_button_ok);
|
||||
void blink_red();
|
||||
void blink_green();
|
||||
|
||||
// "tests"
|
||||
void detect_sd_card();
|
||||
void show_warning();
|
||||
void get_sd_card_info();
|
||||
|
||||
bool prepare_benchmark_data();
|
||||
void free_benchmark_data();
|
||||
void write_benchmark();
|
||||
uint32_t
|
||||
write_benchmark_internal(const uint32_t size, const uint32_t tcount, bool silent = false);
|
||||
|
||||
void read_benchmark();
|
||||
uint32_t read_benchmark_internal(
|
||||
const uint32_t size,
|
||||
const uint32_t count,
|
||||
File* file,
|
||||
bool silent = false);
|
||||
|
||||
void hash_benchmark();
|
||||
|
||||
// cli tests
|
||||
void cli_read_benchmark(Cli* cli, string_t args, void* _ctx);
|
||||
void cli_write_benchmark(Cli* cli, string_t args, void* _ctx);
|
||||
};
|
||||
|
||||
// start app
|
||||
void SdTest::run() {
|
||||
app_ready();
|
||||
|
||||
fs_api = static_cast<FS_Api*>(furi_record_open("sdcard"));
|
||||
notification = static_cast<NotificationApp*>(furi_record_open("notification"));
|
||||
|
||||
if(fs_api == NULL) {
|
||||
set_error({"cannot get sdcard api"});
|
||||
exit();
|
||||
}
|
||||
|
||||
Cli* cli = static_cast<Cli*>(furi_record_open("cli"));
|
||||
|
||||
// read_benchmark and write_benchmark signatures are same. so we must use tags
|
||||
auto cli_read_cb = cbc::obtain_connector<0>(this, &SdTest::cli_read_benchmark);
|
||||
cli_add_command(cli, "sd_read_test", CliCommandFlagDefault, cli_read_cb, this);
|
||||
|
||||
auto cli_write_cb = cbc::obtain_connector<1>(this, &SdTest::cli_write_benchmark);
|
||||
cli_add_command(cli, "sd_write_test", CliCommandFlagDefault, cli_write_cb, this);
|
||||
|
||||
detect_sd_card();
|
||||
get_sd_card_info();
|
||||
show_warning();
|
||||
|
||||
set_text({"preparing benchmark data"});
|
||||
bool data_prepared = prepare_benchmark_data();
|
||||
if(data_prepared) {
|
||||
set_text({"benchmark data prepared"});
|
||||
} else {
|
||||
set_error({"cannot allocate buffer", "for benchmark data"});
|
||||
}
|
||||
|
||||
write_benchmark();
|
||||
read_benchmark();
|
||||
hash_benchmark();
|
||||
free_benchmark_data();
|
||||
|
||||
set_text({
|
||||
"test complete",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"press BACK to exit",
|
||||
});
|
||||
wait_for_button(InputKeyBack);
|
||||
|
||||
furi_record_close("notification");
|
||||
exit();
|
||||
}
|
||||
|
||||
// detect sd card insertion
|
||||
void SdTest::detect_sd_card() {
|
||||
const uint8_t str_buffer_size = 40;
|
||||
const uint8_t dots_animation_size = 4;
|
||||
char str_buffer[str_buffer_size];
|
||||
const char dots[dots_animation_size][4] = {"", ".", "..", "..."};
|
||||
uint8_t i = 0;
|
||||
|
||||
// detect sd card pin
|
||||
while(fs_api->common.get_fs_info(NULL, NULL) == FSE_NOT_READY) {
|
||||
delay(100);
|
||||
|
||||
snprintf(str_buffer, str_buffer_size, "Waiting%s", dots[i]);
|
||||
set_text({static_cast<const char*>(str_buffer), "Please insert sd card"});
|
||||
|
||||
if(i < (dots_animation_size - 1)) {
|
||||
i++;
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
blink_green();
|
||||
}
|
||||
|
||||
// show warning about test
|
||||
void SdTest::show_warning() {
|
||||
set_text(
|
||||
{"!!Warning!!",
|
||||
"during the tests",
|
||||
"files can be overwritten",
|
||||
"or data on card may be lost",
|
||||
"",
|
||||
"press UP DOWN OK to continue"});
|
||||
|
||||
wait_for_button(InputKeyUp);
|
||||
wait_for_button(InputKeyDown);
|
||||
wait_for_button(InputKeyOk);
|
||||
}
|
||||
|
||||
// get info about sd card, label, sn
|
||||
// sector, cluster, total and free size
|
||||
void SdTest::get_sd_card_info() {
|
||||
const uint8_t str_buffer_size = 26;
|
||||
char str_buffer[2][str_buffer_size];
|
||||
FS_Error result;
|
||||
uint64_t bytes_total, bytes_free;
|
||||
int __attribute__((unused)) snprintf_count = 0;
|
||||
|
||||
result = fs_api->common.get_fs_info(&bytes_total, &bytes_free);
|
||||
if(result != FSE_OK) set_error({"get_fs_info error", fs_api->error.get_desc(result)});
|
||||
|
||||
snprintf(
|
||||
str_buffer[0], str_buffer_size, "%lu KB total", static_cast<uint32_t>(bytes_total / 1024));
|
||||
snprintf(
|
||||
str_buffer[1], str_buffer_size, "%lu KB free", static_cast<uint32_t>(bytes_free / 1024));
|
||||
|
||||
set_text(
|
||||
{static_cast<const char*>(str_buffer[0]),
|
||||
static_cast<const char*>(str_buffer[1]),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"press OK to continue"});
|
||||
|
||||
blink_green();
|
||||
|
||||
wait_for_button(InputKeyOk);
|
||||
}
|
||||
|
||||
// prepare benchmark data (allocate data in ram)
|
||||
bool SdTest::prepare_benchmark_data() {
|
||||
bool result = true;
|
||||
benchmark_data = static_cast<uint8_t*>(malloc(benchmark_data_size));
|
||||
|
||||
if(benchmark_data == NULL) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < benchmark_data_size; i++) {
|
||||
benchmark_data[i] = static_cast<uint8_t>(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void SdTest::free_benchmark_data() {
|
||||
free(benchmark_data);
|
||||
}
|
||||
|
||||
// write speed test
|
||||
void SdTest::write_benchmark() {
|
||||
const uint32_t b1_size = 1;
|
||||
const uint32_t b8_size = 8;
|
||||
const uint32_t b32_size = 32;
|
||||
const uint32_t b256_size = 256;
|
||||
const uint32_t b4096_size = 4096;
|
||||
|
||||
const uint32_t benchmark_data_size = 16384 * 4;
|
||||
|
||||
uint32_t benchmark_bps = 0;
|
||||
|
||||
const uint8_t str_buffer_size = 32;
|
||||
char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""};
|
||||
auto string_list = {
|
||||
static_cast<const char*>(str_buffer[0]),
|
||||
static_cast<const char*>(str_buffer[1]),
|
||||
static_cast<const char*>(str_buffer[2]),
|
||||
static_cast<const char*>(str_buffer[3]),
|
||||
static_cast<const char*>(str_buffer[4]),
|
||||
static_cast<const char*>(str_buffer[5])};
|
||||
|
||||
set_text({"write speed test", "procedure can be lengthy", "please wait"});
|
||||
delay(100);
|
||||
|
||||
// 1b test
|
||||
benchmark_bps = write_benchmark_internal(b1_size, benchmark_data_size / b1_size);
|
||||
snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 8b test
|
||||
benchmark_bps = write_benchmark_internal(b8_size, benchmark_data_size / b8_size);
|
||||
snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 32b test
|
||||
benchmark_bps = write_benchmark_internal(b32_size, benchmark_data_size / b32_size);
|
||||
snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 256b test
|
||||
benchmark_bps = write_benchmark_internal(b256_size, benchmark_data_size / b256_size);
|
||||
snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 4096b test
|
||||
benchmark_bps = write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size);
|
||||
snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps);
|
||||
snprintf(str_buffer[5], str_buffer_size, "press OK to continue");
|
||||
set_text(string_list);
|
||||
|
||||
blink_green();
|
||||
|
||||
wait_for_button(InputKeyOk);
|
||||
}
|
||||
|
||||
uint32_t SdTest::write_benchmark_internal(const uint32_t size, const uint32_t count, bool silent) {
|
||||
uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_written;
|
||||
File file;
|
||||
|
||||
const uint8_t str_buffer_size = 32;
|
||||
char str_buffer[str_buffer_size];
|
||||
|
||||
if(!fs_api->file.open(&file, "write.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
if(!silent) {
|
||||
snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size);
|
||||
set_error({"cannot open file ", static_cast<const char*>(str_buffer)});
|
||||
} else {
|
||||
benchmark_bps = BENCHMARK_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
start_tick = osKernelGetTickCount();
|
||||
for(size_t i = 0; i < count; i++) {
|
||||
bytes_written = fs_api->file.write(&file, benchmark_data, size);
|
||||
if(bytes_written != size || file.error_id != FSE_OK) {
|
||||
if(!silent) {
|
||||
snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size);
|
||||
set_error({"cannot write to file ", static_cast<const char*>(str_buffer)});
|
||||
} else {
|
||||
benchmark_bps = BENCHMARK_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
stop_tick = osKernelGetTickCount();
|
||||
|
||||
if(!fs_api->file.close(&file)) {
|
||||
if(!silent) {
|
||||
snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size);
|
||||
set_error({"cannot close file ", static_cast<const char*>(str_buffer)});
|
||||
} else {
|
||||
benchmark_bps = BENCHMARK_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if(benchmark_bps != BENCHMARK_ERROR) {
|
||||
benchmark_time = stop_tick - start_tick;
|
||||
benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time;
|
||||
}
|
||||
|
||||
return benchmark_bps;
|
||||
}
|
||||
|
||||
// read speed test
|
||||
void SdTest::read_benchmark() {
|
||||
const uint32_t benchmark_data_size = 16384 * 8;
|
||||
uint32_t bytes_written;
|
||||
uint32_t benchmark_bps = 0;
|
||||
|
||||
const uint8_t str_buffer_size = 32;
|
||||
char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""};
|
||||
auto string_list = {
|
||||
static_cast<const char*>(str_buffer[0]),
|
||||
static_cast<const char*>(str_buffer[1]),
|
||||
static_cast<const char*>(str_buffer[2]),
|
||||
static_cast<const char*>(str_buffer[3]),
|
||||
static_cast<const char*>(str_buffer[4]),
|
||||
static_cast<const char*>(str_buffer[5])};
|
||||
|
||||
File file;
|
||||
|
||||
const uint32_t b1_size = 1;
|
||||
const uint32_t b8_size = 8;
|
||||
const uint32_t b32_size = 32;
|
||||
const uint32_t b256_size = 256;
|
||||
const uint32_t b4096_size = 4096;
|
||||
|
||||
// prepare data for read test
|
||||
set_text({"prepare data", "for read speed test", "procedure can be lengthy", "please wait"});
|
||||
delay(100);
|
||||
|
||||
if(!fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
set_error({"cannot open file ", "in prepare read"});
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) {
|
||||
bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size);
|
||||
if(bytes_written != b4096_size || file.error_id != FSE_OK) {
|
||||
set_error({"cannot write to file ", "in prepare read"});
|
||||
}
|
||||
}
|
||||
|
||||
if(!fs_api->file.close(&file)) {
|
||||
set_error({"cannot close file ", "in prepare read"});
|
||||
}
|
||||
|
||||
// test start
|
||||
set_text({"read speed test", "procedure can be lengthy", "please wait"});
|
||||
delay(100);
|
||||
|
||||
// open file
|
||||
if(!fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
set_error({"cannot open file ", "in read benchmark"});
|
||||
}
|
||||
|
||||
// 1b test
|
||||
benchmark_bps = read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file);
|
||||
snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 8b test
|
||||
benchmark_bps = read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file);
|
||||
snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 32b test
|
||||
benchmark_bps = read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file);
|
||||
snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 256b test
|
||||
benchmark_bps = read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file);
|
||||
snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps);
|
||||
set_text(string_list);
|
||||
delay(100);
|
||||
|
||||
// 4096b test
|
||||
benchmark_bps = read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file);
|
||||
snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps);
|
||||
snprintf(str_buffer[5], str_buffer_size, "press OK to continue");
|
||||
set_text(string_list);
|
||||
|
||||
// close file
|
||||
if(!fs_api->file.close(&file)) {
|
||||
set_error({"cannot close file ", "in read test"});
|
||||
}
|
||||
|
||||
blink_green();
|
||||
|
||||
wait_for_button(InputKeyOk);
|
||||
}
|
||||
|
||||
uint32_t SdTest::read_benchmark_internal(
|
||||
const uint32_t size,
|
||||
const uint32_t count,
|
||||
File* file,
|
||||
bool silent) {
|
||||
uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_readed;
|
||||
|
||||
const uint8_t str_buffer_size = 32;
|
||||
char str_buffer[str_buffer_size];
|
||||
uint8_t* read_buffer;
|
||||
|
||||
read_buffer = static_cast<uint8_t*>(malloc(size));
|
||||
|
||||
if(read_buffer == NULL) {
|
||||
if(!silent) {
|
||||
snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size);
|
||||
set_error({"cannot allocate memory", static_cast<const char*>(str_buffer)});
|
||||
} else {
|
||||
benchmark_bps = BENCHMARK_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
fs_api->file.seek(file, 0, true);
|
||||
|
||||
start_tick = osKernelGetTickCount();
|
||||
for(size_t i = 0; i < count; i++) {
|
||||
bytes_readed = fs_api->file.read(file, read_buffer, size);
|
||||
if(bytes_readed != size || file->error_id != FSE_OK) {
|
||||
if(!silent) {
|
||||
snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size);
|
||||
set_error({"cannot read from file ", static_cast<const char*>(str_buffer)});
|
||||
} else {
|
||||
benchmark_bps = BENCHMARK_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
stop_tick = osKernelGetTickCount();
|
||||
|
||||
free(read_buffer);
|
||||
|
||||
if(benchmark_bps != BENCHMARK_ERROR) {
|
||||
benchmark_time = stop_tick - start_tick;
|
||||
benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time;
|
||||
}
|
||||
|
||||
return benchmark_bps;
|
||||
}
|
||||
|
||||
// hash benchmark, store data to sd with known hash
|
||||
// then read, calculate hash and compare both hashes
|
||||
void SdTest::hash_benchmark() {
|
||||
uint32_t mcu_data_hash = FNV_1A_INIT;
|
||||
uint32_t sdcard_data_hash = FNV_1A_INIT;
|
||||
uint8_t* read_buffer;
|
||||
uint32_t bytes_readed;
|
||||
|
||||
uint32_t bytes_written;
|
||||
|
||||
const uint8_t str_buffer_size = 32;
|
||||
char str_buffer[3][str_buffer_size] = {"", "", ""};
|
||||
|
||||
File file;
|
||||
|
||||
const uint32_t b4096_size = 4096;
|
||||
const uint32_t benchmark_count = 20;
|
||||
|
||||
// prepare data for hash test
|
||||
set_text({"prepare data", "for hash test"});
|
||||
delay(100);
|
||||
|
||||
// write data to test file and calculate hash
|
||||
if(!fs_api->file.open(&file, "hash.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
set_error({"cannot open file ", "in prepare hash"});
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < benchmark_count; i++) {
|
||||
mcu_data_hash = fnv1a_buffer_hash(benchmark_data, b4096_size, mcu_data_hash);
|
||||
bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size);
|
||||
|
||||
if(bytes_written != b4096_size || file.error_id != FSE_OK) {
|
||||
set_error({"cannot write to file ", "in prepare hash"});
|
||||
}
|
||||
|
||||
snprintf(str_buffer[0], str_buffer_size, "writing %lu of %lu x 4k", i, benchmark_count);
|
||||
set_text({"prepare data", "for hash test", static_cast<const char*>(str_buffer[0])});
|
||||
delay(100);
|
||||
}
|
||||
|
||||
if(!fs_api->file.close(&file)) {
|
||||
set_error({"cannot close file ", "in prepare hash"});
|
||||
}
|
||||
|
||||
// show hash of data located in mcu memory
|
||||
snprintf(str_buffer[0], str_buffer_size, "hash in mcu 0x%lx", mcu_data_hash);
|
||||
set_text({str_buffer[0]});
|
||||
delay(100);
|
||||
|
||||
// read data from sd card and calculate hash
|
||||
read_buffer = static_cast<uint8_t*>(malloc(b4096_size));
|
||||
|
||||
if(read_buffer == NULL) {
|
||||
set_error({"cannot allocate memory", "in hash test"});
|
||||
}
|
||||
|
||||
if(!fs_api->file.open(&file, "hash.test", FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
set_error({"cannot open file ", "in hash test"});
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < benchmark_count; i++) {
|
||||
bytes_readed = fs_api->file.read(&file, read_buffer, b4096_size);
|
||||
sdcard_data_hash = fnv1a_buffer_hash(read_buffer, b4096_size, sdcard_data_hash);
|
||||
|
||||
if(bytes_readed != b4096_size || file.error_id != FSE_OK) {
|
||||
set_error({"cannot read from file ", "in hash test"});
|
||||
}
|
||||
|
||||
snprintf(str_buffer[1], str_buffer_size, "reading %lu of %lu x 4k", i, benchmark_count);
|
||||
set_text({str_buffer[0], str_buffer[1]});
|
||||
delay(100);
|
||||
}
|
||||
|
||||
if(!fs_api->file.close(&file)) {
|
||||
set_error({"cannot close file ", "in hash test"});
|
||||
}
|
||||
|
||||
free(read_buffer);
|
||||
|
||||
snprintf(str_buffer[1], str_buffer_size, "hash in sdcard 0x%lx", sdcard_data_hash);
|
||||
if(mcu_data_hash == sdcard_data_hash) {
|
||||
snprintf(str_buffer[2], str_buffer_size, "hashes are equal, press OK");
|
||||
set_text(
|
||||
{static_cast<const char*>(str_buffer[0]),
|
||||
static_cast<const char*>(str_buffer[1]),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
static_cast<const char*>(str_buffer[2])});
|
||||
} else {
|
||||
snprintf(str_buffer[2], str_buffer_size, "hash error, press BACK to exit");
|
||||
set_error(
|
||||
{static_cast<const char*>(str_buffer[0]),
|
||||
static_cast<const char*>(str_buffer[1]),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
static_cast<const char*>(str_buffer[2])});
|
||||
}
|
||||
|
||||
blink_green();
|
||||
|
||||
wait_for_button(InputKeyOk);
|
||||
}
|
||||
|
||||
void SdTest::cli_read_benchmark(Cli* cli, string_t args, void* _ctx) {
|
||||
SdTest* _this = static_cast<SdTest*>(_ctx);
|
||||
|
||||
const uint32_t benchmark_data_size = 16384 * 8;
|
||||
uint32_t bytes_written;
|
||||
uint32_t benchmark_bps = 0;
|
||||
File file;
|
||||
|
||||
const uint32_t b1_size = 1;
|
||||
const uint32_t b8_size = 8;
|
||||
const uint32_t b32_size = 32;
|
||||
const uint32_t b256_size = 256;
|
||||
const uint32_t b4096_size = 4096;
|
||||
|
||||
const uint8_t str_buffer_size = 64;
|
||||
char str_buffer[str_buffer_size];
|
||||
|
||||
printf("preparing benchmark data\r\n");
|
||||
bool data_prepared = _this->prepare_benchmark_data();
|
||||
if(data_prepared) {
|
||||
printf("benchmark data prepared\r\n");
|
||||
} else {
|
||||
printf("error: cannot allocate buffer for benchmark data\r\n");
|
||||
}
|
||||
|
||||
// prepare data for read test
|
||||
printf("prepare data for read speed test, procedure can be lengthy, please wait\r\n");
|
||||
|
||||
if(!_this->fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
|
||||
printf("error: cannot open file in prepare read\r\n");
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) {
|
||||
bytes_written = _this->fs_api->file.write(&file, benchmark_data, b4096_size);
|
||||
if(bytes_written != b4096_size || file.error_id != FSE_OK) {
|
||||
printf("error: cannot write to file in prepare read\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(!_this->fs_api->file.close(&file)) {
|
||||
printf("error: cannot close file in prepare read\r\n");
|
||||
}
|
||||
|
||||
// test start
|
||||
printf("read speed test, procedure can be lengthy, please wait\r\n");
|
||||
|
||||
// open file
|
||||
if(!_this->fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
printf("error: cannot open file in read benchmark\r\n");
|
||||
}
|
||||
|
||||
// 1b test
|
||||
benchmark_bps =
|
||||
_this->read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 1-byte read test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 8b test
|
||||
benchmark_bps =
|
||||
_this->read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 8-byte read test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 32b test
|
||||
benchmark_bps =
|
||||
_this->read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 32-byte read test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 256b test
|
||||
benchmark_bps =
|
||||
_this->read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 256-byte read test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 4096b test
|
||||
benchmark_bps =
|
||||
_this->read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 4096-byte read test\r\n");
|
||||
} else {
|
||||
snprintf(
|
||||
str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// close file
|
||||
if(!_this->fs_api->file.close(&file)) {
|
||||
printf("error: cannot close file\r\n");
|
||||
}
|
||||
|
||||
_this->free_benchmark_data();
|
||||
|
||||
printf("test completed\r\n");
|
||||
}
|
||||
|
||||
void SdTest::cli_write_benchmark(Cli* cli, string_t args, void* _ctx) {
|
||||
SdTest* _this = static_cast<SdTest*>(_ctx);
|
||||
|
||||
const uint32_t b1_size = 1;
|
||||
const uint32_t b8_size = 8;
|
||||
const uint32_t b32_size = 32;
|
||||
const uint32_t b256_size = 256;
|
||||
const uint32_t b4096_size = 4096;
|
||||
|
||||
const uint32_t benchmark_data_size = 16384 * 4;
|
||||
|
||||
uint32_t benchmark_bps = 0;
|
||||
|
||||
const uint8_t str_buffer_size = 64;
|
||||
char str_buffer[str_buffer_size];
|
||||
|
||||
printf("preparing benchmark data\r\n");
|
||||
bool data_prepared = _this->prepare_benchmark_data();
|
||||
if(data_prepared) {
|
||||
printf("benchmark data prepared\r\n");
|
||||
} else {
|
||||
printf("error: cannot allocate buffer for benchmark data\r\n");
|
||||
}
|
||||
|
||||
printf("write speed test, procedure can be lengthy, please wait\r\n");
|
||||
|
||||
// 1b test
|
||||
benchmark_bps = _this->write_benchmark_internal(b1_size, benchmark_data_size / b1_size, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 1-byte write test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 8b test
|
||||
benchmark_bps = _this->write_benchmark_internal(b8_size, benchmark_data_size / b8_size, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 8-byte write test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 32b test
|
||||
benchmark_bps =
|
||||
_this->write_benchmark_internal(b32_size, benchmark_data_size / b32_size, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 32-byte write test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 256b test
|
||||
benchmark_bps =
|
||||
_this->write_benchmark_internal(b256_size, benchmark_data_size / b256_size, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 256-byte write test\r\n");
|
||||
} else {
|
||||
snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
// 4096b test
|
||||
benchmark_bps =
|
||||
_this->write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, true);
|
||||
if(benchmark_bps == BENCHMARK_ERROR) {
|
||||
printf("error: in 4096-byte write test\r\n");
|
||||
} else {
|
||||
snprintf(
|
||||
str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps);
|
||||
printf(str_buffer);
|
||||
}
|
||||
|
||||
_this->free_benchmark_data();
|
||||
|
||||
printf("test completed\r\n");
|
||||
}
|
||||
|
||||
// wait for button press
|
||||
void SdTest::wait_for_button(InputKey input_button) {
|
||||
SdTestEvent event;
|
||||
osMessageQueueReset(event_queue);
|
||||
while(1) {
|
||||
osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
|
||||
|
||||
if(result == osOK && event.type == SdTestEvent::EventTypeKey) {
|
||||
if(event.value.input.type == InputTypeShort) {
|
||||
if(event.value.input.key == InputKeyBack) {
|
||||
exit();
|
||||
} else {
|
||||
if(event.value.input.key == input_button) {
|
||||
blink_green();
|
||||
break;
|
||||
} else {
|
||||
blink_red();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
osMessageQueueReset(event_queue);
|
||||
}
|
||||
|
||||
// ask user to proceed or cancel
|
||||
bool SdTest::ask(InputKey input_button_cancel, InputKey input_button_ok) {
|
||||
bool return_result;
|
||||
SdTestEvent event;
|
||||
osMessageQueueReset(event_queue);
|
||||
while(1) {
|
||||
osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
|
||||
|
||||
if(result == osOK && event.type == SdTestEvent::EventTypeKey) {
|
||||
if(event.value.input.type == InputTypeShort) {
|
||||
if(event.value.input.key == InputKeyBack) {
|
||||
exit();
|
||||
} else {
|
||||
if(event.value.input.key == input_button_ok) {
|
||||
blink_green();
|
||||
return_result = true;
|
||||
break;
|
||||
} else if(event.value.input.key == input_button_cancel) {
|
||||
blink_green();
|
||||
return_result = false;
|
||||
break;
|
||||
} else {
|
||||
blink_red();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
osMessageQueueReset(event_queue);
|
||||
return return_result;
|
||||
}
|
||||
|
||||
// blink red led
|
||||
void SdTest::blink_red() {
|
||||
notification_message(notification, &sequence_blink_red_100);
|
||||
}
|
||||
|
||||
// blink green led
|
||||
void SdTest::blink_green() {
|
||||
notification_message(notification, &sequence_blink_green_100);
|
||||
}
|
||||
|
||||
// set text, but with infinite loop
|
||||
template <class T> void SdTest::set_error(std::initializer_list<T> list) {
|
||||
set_text(list);
|
||||
blink_red();
|
||||
wait_for_button(InputKeyBack);
|
||||
exit();
|
||||
}
|
||||
|
||||
// set text, sort of variadic function
|
||||
template <class T> void SdTest::set_text(std::initializer_list<T> list) {
|
||||
uint8_t line_position = 0;
|
||||
acquire_state();
|
||||
printf("------------------------\r\n");
|
||||
|
||||
// set line strings from args
|
||||
for(auto element : list) {
|
||||
state.line[line_position] = element;
|
||||
printf("%s\n", element);
|
||||
line_position++;
|
||||
if(line_position == state.lines_count) break;
|
||||
}
|
||||
|
||||
// set empty lines
|
||||
for(; line_position < state.lines_count; line_position++) {
|
||||
state.line[line_position] = "";
|
||||
printf("\r\n");
|
||||
}
|
||||
|
||||
printf("------------------------\r\n");
|
||||
release_state();
|
||||
update_gui();
|
||||
}
|
||||
|
||||
// render app
|
||||
void SdTest::render(Canvas* canvas) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
for(uint8_t i = 0; i < state.lines_count; i++) {
|
||||
canvas_draw_str(canvas, 0, (i + 1) * 10, state.line[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// app enter function
|
||||
extern "C" int32_t sd_card_test(void* p) {
|
||||
SdTest* app = new SdTest();
|
||||
app->run();
|
||||
return 0;
|
||||
}
|
|
@ -1,739 +0,0 @@
|
|||
#include "fatfs.h"
|
||||
#include "filesystem-api.h"
|
||||
#include "sd-filesystem.h"
|
||||
|
||||
/******************* Global vars for api *******************/
|
||||
|
||||
static SdFsInfo* fs_info;
|
||||
|
||||
/******************* Core Functions *******************/
|
||||
|
||||
bool _fs_init(SdFsInfo* _fs_info) {
|
||||
bool result = true;
|
||||
_fs_info->mutex = osMutexNew(NULL);
|
||||
if(_fs_info->mutex == NULL) result = false;
|
||||
|
||||
for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) {
|
||||
_fs_info->files[i].thread_id = NULL;
|
||||
}
|
||||
|
||||
_fs_info->path = "0:/";
|
||||
_fs_info->status = SD_NO_CARD;
|
||||
|
||||
// store pointer for api fns
|
||||
fs_info = _fs_info;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _fs_lock(SdFsInfo* fs_info) {
|
||||
api_hal_power_insomnia_enter();
|
||||
return (osMutexAcquire(fs_info->mutex, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
bool _fs_unlock(SdFsInfo* fs_info) {
|
||||
api_hal_power_insomnia_exit();
|
||||
return (osMutexRelease(fs_info->mutex) == osOK);
|
||||
}
|
||||
|
||||
SDError _get_filedata(SdFsInfo* fs_info, File* file, FileData** filedata, FiledataFilter filter) {
|
||||
SDError error = SD_OK;
|
||||
_fs_lock(fs_info);
|
||||
|
||||
if(fs_info->status == SD_OK) {
|
||||
if(file != NULL && file->file_id < SD_FS_MAX_FILES) {
|
||||
if(fs_info->files[file->file_id].thread_id == osThreadGetId()) {
|
||||
if(filter == FDF_ANY) {
|
||||
// any type
|
||||
*filedata = &fs_info->files[file->file_id];
|
||||
} else if(filter == FDF_FILE) {
|
||||
// file type
|
||||
if(!fs_info->files[file->file_id].is_dir) {
|
||||
*filedata = &fs_info->files[file->file_id];
|
||||
} else {
|
||||
error = SD_NOT_A_FILE;
|
||||
}
|
||||
} else if(filter == FDF_DIR) {
|
||||
// dir type
|
||||
if(fs_info->files[file->file_id].is_dir) {
|
||||
*filedata = &fs_info->files[file->file_id];
|
||||
} else {
|
||||
error = SD_NOT_A_DIR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error = SD_OTHER_APP;
|
||||
}
|
||||
} else {
|
||||
error = SD_INVALID_PARAMETER;
|
||||
}
|
||||
} else {
|
||||
error = SD_NO_CARD;
|
||||
}
|
||||
|
||||
_fs_unlock(fs_info);
|
||||
return error;
|
||||
}
|
||||
|
||||
SDError _get_file(SdFsInfo* fs_info, File* file, FileData** filedata) {
|
||||
return _get_filedata(fs_info, file, filedata, FDF_FILE);
|
||||
}
|
||||
|
||||
SDError _get_dir(SdFsInfo* fs_info, File* file, FileData** filedata) {
|
||||
return _get_filedata(fs_info, file, filedata, FDF_DIR);
|
||||
}
|
||||
|
||||
SDError _get_any(SdFsInfo* fs_info, File* file, FileData** filedata) {
|
||||
return _get_filedata(fs_info, file, filedata, FDF_ANY);
|
||||
}
|
||||
|
||||
SDError _fs_status(SdFsInfo* fs_info) {
|
||||
SDError result;
|
||||
|
||||
_fs_lock(fs_info);
|
||||
result = fs_info->status;
|
||||
_fs_unlock(fs_info);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void _fs_on_client_app_exit(SdFsInfo* fs_info) {
|
||||
_fs_lock(fs_info);
|
||||
for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) {
|
||||
if(fs_info->files[i].thread_id == osThreadGetId()) {
|
||||
if(fs_info->files[i].is_dir) {
|
||||
// TODO close dir
|
||||
} else {
|
||||
// TODO close file
|
||||
}
|
||||
}
|
||||
}
|
||||
_fs_unlock(fs_info);
|
||||
}
|
||||
|
||||
FS_Error _fs_parse_error(SDError error) {
|
||||
FS_Error result;
|
||||
switch(error) {
|
||||
case SD_OK:
|
||||
result = FSE_OK;
|
||||
break;
|
||||
case SD_INT_ERR:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_NO_FILE:
|
||||
result = FSE_NOT_EXIST;
|
||||
break;
|
||||
case SD_NO_PATH:
|
||||
result = FSE_NOT_EXIST;
|
||||
break;
|
||||
case SD_INVALID_NAME:
|
||||
result = FSE_INVALID_NAME;
|
||||
break;
|
||||
case SD_DENIED:
|
||||
result = FSE_DENIED;
|
||||
break;
|
||||
case SD_EXIST:
|
||||
result = FSE_EXIST;
|
||||
break;
|
||||
case SD_INVALID_OBJECT:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_WRITE_PROTECTED:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_INVALID_DRIVE:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_NOT_ENABLED:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_NO_FILESYSTEM:
|
||||
result = FSE_NOT_READY;
|
||||
break;
|
||||
case SD_MKFS_ABORTED:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_TIMEOUT:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_LOCKED:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_NOT_ENOUGH_CORE:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_TOO_MANY_OPEN_FILES:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case SD_INVALID_PARAMETER:
|
||||
result = FSE_INVALID_PARAMETER;
|
||||
break;
|
||||
case SD_NO_CARD:
|
||||
result = FSE_NOT_READY;
|
||||
break;
|
||||
case SD_NOT_A_FILE:
|
||||
result = FSE_INVALID_PARAMETER;
|
||||
break;
|
||||
case SD_NOT_A_DIR:
|
||||
result = FSE_INVALID_PARAMETER;
|
||||
break;
|
||||
case SD_OTHER_APP:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/******************* File Functions *******************/
|
||||
|
||||
// Open/Create a file
|
||||
bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode) {
|
||||
SDFile* sd_file = NULL;
|
||||
|
||||
_fs_lock(fs_info);
|
||||
for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) {
|
||||
FileData* filedata = &fs_info->files[index];
|
||||
|
||||
if(filedata->thread_id == NULL) {
|
||||
file->file_id = index;
|
||||
|
||||
memset(&(filedata->data), 0, sizeof(SDFileDirStorage));
|
||||
filedata->thread_id = osThreadGetId();
|
||||
filedata->is_dir = false;
|
||||
sd_file = &(filedata->data.file);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
_fs_unlock(fs_info);
|
||||
|
||||
if(sd_file == NULL) {
|
||||
file->internal_error_id = SD_TOO_MANY_OPEN_FILES;
|
||||
} else {
|
||||
uint8_t _mode = 0;
|
||||
|
||||
if(access_mode & FSAM_READ) _mode |= FA_READ;
|
||||
if(access_mode & FSAM_WRITE) _mode |= FA_WRITE;
|
||||
if(open_mode & FSOM_OPEN_EXISTING) _mode |= FA_OPEN_EXISTING;
|
||||
if(open_mode & FSOM_OPEN_ALWAYS) _mode |= FA_OPEN_ALWAYS;
|
||||
if(open_mode & FSOM_OPEN_APPEND) _mode |= FA_OPEN_APPEND;
|
||||
if(open_mode & FSOM_CREATE_NEW) _mode |= FA_CREATE_NEW;
|
||||
if(open_mode & FSOM_CREATE_ALWAYS) _mode |= FA_CREATE_ALWAYS;
|
||||
|
||||
file->internal_error_id = f_open(sd_file, path, _mode);
|
||||
}
|
||||
|
||||
// TODO on exit
|
||||
//furiac_onexit(_fs_on_client_app_exit, fs_info);
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
// Close an opened file
|
||||
bool fs_file_close(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
file->internal_error_id = f_close(&filedata->data.file);
|
||||
|
||||
_fs_lock(fs_info);
|
||||
filedata->thread_id = NULL;
|
||||
_fs_unlock(fs_info);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
// Read data from the file
|
||||
uint16_t fs_file_read(File* file, void* buff, uint16_t const bytes_to_read) {
|
||||
FileData* filedata = NULL;
|
||||
uint16_t bytes_readed = 0;
|
||||
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
file->internal_error_id = f_read(&filedata->data.file, buff, bytes_to_read, &bytes_readed);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return bytes_readed;
|
||||
}
|
||||
|
||||
// Write data to the file
|
||||
uint16_t fs_file_write(File* file, const void* buff, uint16_t const bytes_to_write) {
|
||||
FileData* filedata = NULL;
|
||||
uint16_t bytes_written = 0;
|
||||
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
file->internal_error_id =
|
||||
f_write(&filedata->data.file, buff, bytes_to_write, &bytes_written);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
// Move read/write pointer, expand size
|
||||
bool fs_file_seek(File* file, const uint32_t offset, const bool from_start) {
|
||||
FileData* filedata = NULL;
|
||||
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
if(from_start) {
|
||||
file->internal_error_id = f_lseek(&filedata->data.file, offset);
|
||||
} else {
|
||||
uint64_t position = f_tell(&filedata->data.file);
|
||||
position += offset;
|
||||
file->internal_error_id = f_lseek(&filedata->data.file, position);
|
||||
}
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
// Tell pointer position
|
||||
uint64_t fs_file_tell(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
uint64_t position = 0;
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
position = f_tell(&filedata->data.file);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return position;
|
||||
}
|
||||
|
||||
// Truncate file size to current pointer value
|
||||
bool fs_file_truncate(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
file->internal_error_id = f_truncate(&filedata->data.file);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
// Flush cached data
|
||||
bool fs_file_sync(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
file->internal_error_id = f_sync(&filedata->data.file);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
// Get size
|
||||
uint64_t fs_file_size(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
uint64_t size = 0;
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
size = f_size(&filedata->data.file);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return size;
|
||||
}
|
||||
|
||||
// Test EOF
|
||||
bool fs_file_eof(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
bool eof = true;
|
||||
file->internal_error_id = _get_file(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
eof = f_eof(&filedata->data.file);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return eof;
|
||||
}
|
||||
|
||||
/******************* Dir Functions *******************/
|
||||
|
||||
// Open directory
|
||||
bool fs_dir_open(File* file, const char* path) {
|
||||
SDDir* sd_dir = NULL;
|
||||
|
||||
_fs_lock(fs_info);
|
||||
for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) {
|
||||
FileData* filedata = &fs_info->files[index];
|
||||
|
||||
if(filedata->thread_id == NULL) {
|
||||
file->file_id = index;
|
||||
|
||||
memset(&(filedata->data), 0, sizeof(SDFileDirStorage));
|
||||
filedata->thread_id = osThreadGetId();
|
||||
filedata->is_dir = true;
|
||||
sd_dir = &(filedata->data.dir);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
_fs_unlock(fs_info);
|
||||
|
||||
if(sd_dir == NULL) {
|
||||
file->internal_error_id = SD_TOO_MANY_OPEN_FILES;
|
||||
} else {
|
||||
file->internal_error_id = f_opendir(sd_dir, path);
|
||||
}
|
||||
|
||||
// TODO on exit
|
||||
//furiac_onexit(_fs_on_client_app_exit, fs_info);
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
// Close directory
|
||||
bool fs_dir_close(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
file->internal_error_id = _get_dir(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
file->internal_error_id = f_closedir(&filedata->data.dir);
|
||||
|
||||
_fs_lock(fs_info);
|
||||
filedata->thread_id = NULL;
|
||||
_fs_unlock(fs_info);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
// Read next file info and name from directory
|
||||
bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, const uint16_t name_length) {
|
||||
FileData* filedata = NULL;
|
||||
file->internal_error_id = _get_dir(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
SDFileInfo _fileinfo;
|
||||
file->internal_error_id = f_readdir(&filedata->data.dir, &_fileinfo);
|
||||
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->date.value = _fileinfo.fdate;
|
||||
fileinfo->time.value = _fileinfo.ftime;
|
||||
fileinfo->size = _fileinfo.fsize;
|
||||
fileinfo->flags = 0;
|
||||
|
||||
if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY;
|
||||
if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN;
|
||||
if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM;
|
||||
if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE;
|
||||
}
|
||||
|
||||
if(name != NULL && name_length > 0) {
|
||||
strlcpy(name, _fileinfo.fname, name_length);
|
||||
}
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
bool fs_dir_rewind(File* file) {
|
||||
FileData* filedata = NULL;
|
||||
file->internal_error_id = _get_dir(fs_info, file, &filedata);
|
||||
|
||||
if(file->internal_error_id == SD_OK) {
|
||||
file->internal_error_id = f_readdir(&filedata->data.dir, NULL);
|
||||
}
|
||||
|
||||
file->error_id = _fs_parse_error(file->internal_error_id);
|
||||
return (file->internal_error_id == SD_OK);
|
||||
}
|
||||
|
||||
/******************* Common FS Functions *******************/
|
||||
|
||||
// Get info about file/dir
|
||||
FS_Error
|
||||
fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length) {
|
||||
SDFileInfo _fileinfo;
|
||||
SDError fresult = _fs_status(fs_info);
|
||||
|
||||
if(fresult == SD_OK) {
|
||||
fresult = f_stat(path, &_fileinfo);
|
||||
if((FRESULT)fresult == FR_OK) {
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->date.value = _fileinfo.fdate;
|
||||
fileinfo->time.value = _fileinfo.ftime;
|
||||
fileinfo->size = _fileinfo.fsize;
|
||||
fileinfo->flags = 0;
|
||||
|
||||
if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY;
|
||||
if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN;
|
||||
if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM;
|
||||
if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE;
|
||||
}
|
||||
|
||||
if(name != NULL && name_length > 0) {
|
||||
strlcpy(name, _fileinfo.fname, name_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _fs_parse_error(fresult);
|
||||
}
|
||||
|
||||
// Delete file/dir
|
||||
// File/dir must not have read-only attribute.
|
||||
// File/dir must be empty.
|
||||
// File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that.
|
||||
FS_Error fs_common_remove(const char* path) {
|
||||
SDError fresult = _fs_status(fs_info);
|
||||
|
||||
if(fresult == SD_OK) {
|
||||
fresult = f_unlink(path);
|
||||
}
|
||||
|
||||
return _fs_parse_error(fresult);
|
||||
}
|
||||
|
||||
// Rename file/dir
|
||||
// File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that.
|
||||
FS_Error fs_common_rename(const char* old_path, const char* new_path) {
|
||||
SDError fresult = _fs_status(fs_info);
|
||||
|
||||
if(fresult == SD_OK) {
|
||||
fresult = f_rename(old_path, new_path);
|
||||
}
|
||||
|
||||
return _fs_parse_error(fresult);
|
||||
}
|
||||
|
||||
// Set attributes of file/dir
|
||||
// For example:
|
||||
// set "read only" flag and remove "hidden" flag
|
||||
// fs_common_set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN);
|
||||
FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask) {
|
||||
SDError fresult = _fs_status(fs_info);
|
||||
|
||||
if(fresult == SD_OK) {
|
||||
uint8_t _mask = 0;
|
||||
uint8_t _attr = 0;
|
||||
|
||||
if(mask & FSF_READ_ONLY) _mask |= AM_RDO;
|
||||
if(mask & FSF_HIDDEN) _mask |= AM_HID;
|
||||
if(mask & FSF_SYSTEM) _mask |= AM_SYS;
|
||||
if(mask & FSF_DIRECTORY) _mask |= AM_DIR;
|
||||
if(mask & FSF_ARCHIVE) _mask |= AM_ARC;
|
||||
|
||||
if(attr & FSF_READ_ONLY) _attr |= AM_RDO;
|
||||
if(attr & FSF_HIDDEN) _attr |= AM_HID;
|
||||
if(attr & FSF_SYSTEM) _attr |= AM_SYS;
|
||||
if(attr & FSF_DIRECTORY) _attr |= AM_DIR;
|
||||
if(attr & FSF_ARCHIVE) _attr |= AM_ARC;
|
||||
|
||||
fresult = f_chmod(path, attr, mask);
|
||||
}
|
||||
|
||||
return _fs_parse_error(fresult);
|
||||
}
|
||||
|
||||
// Set time of file/dir
|
||||
FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time) {
|
||||
SDError fresult = _fs_status(fs_info);
|
||||
|
||||
if(fresult == SD_OK) {
|
||||
SDFileInfo _fileinfo;
|
||||
|
||||
_fileinfo.fdate = date.value;
|
||||
_fileinfo.ftime = time.value;
|
||||
|
||||
fresult = f_utime(path, &_fileinfo);
|
||||
}
|
||||
|
||||
return _fs_parse_error(fresult);
|
||||
}
|
||||
|
||||
// Create new directory
|
||||
FS_Error fs_common_mkdir(const char* path) {
|
||||
SDError fresult = _fs_status(fs_info);
|
||||
|
||||
if(fresult == SD_OK) {
|
||||
fresult = f_mkdir(path);
|
||||
}
|
||||
|
||||
return _fs_parse_error(fresult);
|
||||
}
|
||||
|
||||
// Get common info about FS
|
||||
FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space) {
|
||||
SDError fresult = _fs_status(fs_info);
|
||||
|
||||
if(fresult == SD_OK) {
|
||||
DWORD free_clusters;
|
||||
FATFS* fs;
|
||||
|
||||
fresult = f_getfree("0:/", &free_clusters, &fs);
|
||||
if((FRESULT)fresult == FR_OK) {
|
||||
uint32_t total_sectors = (fs->n_fatent - 2) * fs->csize;
|
||||
uint32_t free_sectors = free_clusters * fs->csize;
|
||||
|
||||
uint16_t sector_size = _MAX_SS;
|
||||
#if _MAX_SS != _MIN_SS
|
||||
sector_size = fs->ssize;
|
||||
#endif
|
||||
|
||||
if(total_space != NULL) {
|
||||
*total_space = (uint64_t)total_sectors * (uint64_t)sector_size;
|
||||
}
|
||||
|
||||
if(free_space != NULL) {
|
||||
*free_space = (uint64_t)free_sectors * (uint64_t)sector_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _fs_parse_error(fresult);
|
||||
}
|
||||
|
||||
/******************* Error Reporting Functions *******************/
|
||||
|
||||
// Get common error description
|
||||
const char* fs_error_get_desc(FS_Error error_id) {
|
||||
const char* result;
|
||||
switch(error_id) {
|
||||
case(FSE_OK):
|
||||
result = "OK";
|
||||
break;
|
||||
case(FSE_NOT_READY):
|
||||
result = "filesystem not ready";
|
||||
break;
|
||||
case(FSE_EXIST):
|
||||
result = "file/dir already exist";
|
||||
break;
|
||||
case(FSE_NOT_EXIST):
|
||||
result = "file/dir not exist";
|
||||
break;
|
||||
case(FSE_INVALID_PARAMETER):
|
||||
result = "invalid parameter";
|
||||
break;
|
||||
case(FSE_DENIED):
|
||||
result = "access denied";
|
||||
break;
|
||||
case(FSE_INVALID_NAME):
|
||||
result = "invalid name/path";
|
||||
break;
|
||||
case(FSE_INTERNAL):
|
||||
result = "internal error";
|
||||
break;
|
||||
case(FSE_NOT_IMPLEMENTED):
|
||||
result = "function not implemented";
|
||||
break;
|
||||
default:
|
||||
result = "unknown error";
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get internal error description
|
||||
const char* fs_error_get_internal_desc(uint32_t internal_error_id) {
|
||||
const char* result;
|
||||
switch(internal_error_id) {
|
||||
case(SD_OK):
|
||||
result = "OK";
|
||||
break;
|
||||
case(SD_DISK_ERR):
|
||||
result = "disk error";
|
||||
break;
|
||||
case(SD_INT_ERR):
|
||||
result = "internal error";
|
||||
break;
|
||||
case(SD_NO_FILE):
|
||||
result = "no file";
|
||||
break;
|
||||
case(SD_NO_PATH):
|
||||
result = "no path";
|
||||
break;
|
||||
case(SD_INVALID_NAME):
|
||||
result = "invalid name";
|
||||
break;
|
||||
case(SD_DENIED):
|
||||
result = "access denied";
|
||||
break;
|
||||
case(SD_EXIST):
|
||||
result = "file/dir exist";
|
||||
break;
|
||||
case(SD_INVALID_OBJECT):
|
||||
result = "invalid object";
|
||||
break;
|
||||
case(SD_WRITE_PROTECTED):
|
||||
result = "write protected";
|
||||
break;
|
||||
case(SD_INVALID_DRIVE):
|
||||
result = "invalid drive";
|
||||
break;
|
||||
case(SD_NOT_ENABLED):
|
||||
result = "not enabled";
|
||||
break;
|
||||
case(SD_NO_FILESYSTEM):
|
||||
result = "no filesystem";
|
||||
break;
|
||||
case(SD_MKFS_ABORTED):
|
||||
result = "aborted";
|
||||
break;
|
||||
case(SD_TIMEOUT):
|
||||
result = "timeout";
|
||||
break;
|
||||
case(SD_LOCKED):
|
||||
result = "file locked";
|
||||
break;
|
||||
case(SD_NOT_ENOUGH_CORE):
|
||||
result = "not enough memory";
|
||||
break;
|
||||
case(SD_TOO_MANY_OPEN_FILES):
|
||||
result = "too many open files";
|
||||
break;
|
||||
case(SD_INVALID_PARAMETER):
|
||||
result = "invalid parameter";
|
||||
break;
|
||||
case(SD_NO_CARD):
|
||||
result = "no SD Card";
|
||||
break;
|
||||
case(SD_NOT_A_FILE):
|
||||
result = "not a file";
|
||||
break;
|
||||
case(SD_NOT_A_DIR):
|
||||
result = "not a directory";
|
||||
break;
|
||||
case(SD_OTHER_APP):
|
||||
result = "opened by other app";
|
||||
break;
|
||||
case(SD_LOW_LEVEL_ERR):
|
||||
result = "low level error";
|
||||
break;
|
||||
default:
|
||||
result = "unknown error";
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,956 +0,0 @@
|
|||
#include "fatfs.h"
|
||||
#include "filesystem-api.h"
|
||||
#include "sd-filesystem.h"
|
||||
#include "menu/menu.h"
|
||||
#include "menu/menu_item.h"
|
||||
#include "cli/cli.h"
|
||||
#include "api-hal-sd.h"
|
||||
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/file_select.h>
|
||||
|
||||
typedef enum {
|
||||
FST_FAT12 = FS_FAT12,
|
||||
FST_FAT16 = FS_FAT16,
|
||||
FST_FAT32 = FS_FAT32,
|
||||
FST_EXFAT = FS_EXFAT,
|
||||
} SDFsType;
|
||||
|
||||
typedef struct {
|
||||
SDFsType fs_type;
|
||||
uint32_t kb_total;
|
||||
uint32_t kb_free;
|
||||
uint16_t cluster_size;
|
||||
uint16_t sector_size;
|
||||
char label[34];
|
||||
SDError error;
|
||||
} SDInfo;
|
||||
|
||||
typedef enum {
|
||||
SdAppEventTypeBack,
|
||||
SdAppEventTypeOK,
|
||||
SdAppEventTypeFormat,
|
||||
SdAppEventTypeInfo,
|
||||
SdAppEventTypeEject,
|
||||
SdAppEventTypeFileSelect,
|
||||
SdAppEventTypeCheckError,
|
||||
SdAppEventTypeShowError,
|
||||
} SdAppEventType;
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
const char* extension;
|
||||
char* result;
|
||||
uint8_t result_size;
|
||||
const char* selected_filename;
|
||||
} SdAppFileSelectData;
|
||||
|
||||
typedef struct {
|
||||
bool result;
|
||||
} SdAppFileSelectResultEvent;
|
||||
|
||||
typedef struct {
|
||||
SdAppEventType type;
|
||||
union {
|
||||
SdAppFileSelectData file_select_data;
|
||||
const char* error_text;
|
||||
} payload;
|
||||
} SdAppEvent;
|
||||
|
||||
static void sd_icon_draw_callback(Canvas* canvas, void* context);
|
||||
bool sd_api_file_select(
|
||||
SdApp* sd_app,
|
||||
const char* path,
|
||||
const char* extension,
|
||||
char* result,
|
||||
uint8_t result_size,
|
||||
const char* selected_filename);
|
||||
void sd_api_check_error(SdApp* sd_app);
|
||||
void sd_api_show_error(SdApp* sd_app, const char* error_text);
|
||||
|
||||
/******************* Allocators *******************/
|
||||
|
||||
FS_Api* fs_api_alloc() {
|
||||
FS_Api* fs_api = furi_alloc(sizeof(FS_Api));
|
||||
|
||||
// fill file api
|
||||
fs_api->file.open = fs_file_open;
|
||||
fs_api->file.close = fs_file_close;
|
||||
fs_api->file.read = fs_file_read;
|
||||
fs_api->file.write = fs_file_write;
|
||||
fs_api->file.seek = fs_file_seek;
|
||||
fs_api->file.tell = fs_file_tell;
|
||||
fs_api->file.truncate = fs_file_truncate;
|
||||
fs_api->file.size = fs_file_size;
|
||||
fs_api->file.sync = fs_file_sync;
|
||||
fs_api->file.eof = fs_file_eof;
|
||||
|
||||
// fill dir api
|
||||
fs_api->dir.open = fs_dir_open;
|
||||
fs_api->dir.close = fs_dir_close;
|
||||
fs_api->dir.read = fs_dir_read;
|
||||
fs_api->dir.rewind = fs_dir_rewind;
|
||||
|
||||
// fill common api
|
||||
fs_api->common.info = fs_common_info;
|
||||
fs_api->common.remove = fs_common_remove;
|
||||
fs_api->common.rename = fs_common_rename;
|
||||
fs_api->common.set_attr = fs_common_set_attr;
|
||||
fs_api->common.mkdir = fs_common_mkdir;
|
||||
fs_api->common.set_time = fs_common_set_time;
|
||||
fs_api->common.get_fs_info = fs_get_fs_info;
|
||||
|
||||
// fill errors api
|
||||
fs_api->error.get_desc = fs_error_get_desc;
|
||||
fs_api->error.get_internal_desc = fs_error_get_internal_desc;
|
||||
|
||||
return fs_api;
|
||||
}
|
||||
|
||||
SdApp* sd_app_alloc() {
|
||||
SdApp* sd_app = furi_alloc(sizeof(SdApp));
|
||||
|
||||
// init inner fs data
|
||||
furi_check(_fs_init(&sd_app->info));
|
||||
|
||||
sd_app->event_queue = osMessageQueueNew(8, sizeof(SdAppEvent), NULL);
|
||||
sd_app->result_receiver = osMessageQueueNew(1, sizeof(SdAppFileSelectResultEvent), NULL);
|
||||
|
||||
// init icon view_port
|
||||
sd_app->icon.view_port = view_port_alloc();
|
||||
sd_app->icon.mounted = &I_SDcardMounted_11x8;
|
||||
sd_app->icon.fail = &I_SDcardFail_11x8;
|
||||
view_port_set_width(sd_app->icon.view_port, icon_get_width(sd_app->icon.mounted));
|
||||
view_port_draw_callback_set(sd_app->icon.view_port, sd_icon_draw_callback, sd_app);
|
||||
view_port_enabled_set(sd_app->icon.view_port, false);
|
||||
|
||||
// init sd card api
|
||||
sd_app->sd_card_api.context = sd_app;
|
||||
sd_app->sd_card_api.file_select = sd_api_file_select;
|
||||
sd_app->sd_card_api.check_error = sd_api_check_error;
|
||||
sd_app->sd_card_api.show_error = sd_api_show_error;
|
||||
|
||||
sd_app->sd_app_state = SdAppStateBackground;
|
||||
string_init(sd_app->text_holder);
|
||||
|
||||
return sd_app;
|
||||
}
|
||||
|
||||
/******************* Internal sd card related fns *******************/
|
||||
|
||||
void get_sd_info(SdApp* sd_app, SDInfo* sd_info) {
|
||||
uint32_t free_clusters, free_sectors, total_sectors;
|
||||
FATFS* fs;
|
||||
|
||||
// clean data
|
||||
memset(sd_info, 0, sizeof(SDInfo));
|
||||
|
||||
// get fs info
|
||||
_fs_lock(&sd_app->info);
|
||||
sd_info->error = f_getlabel(sd_app->info.path, sd_info->label, NULL);
|
||||
if(sd_info->error == SD_OK) {
|
||||
sd_info->error = f_getfree(sd_app->info.path, &free_clusters, &fs);
|
||||
}
|
||||
_fs_unlock(&sd_app->info);
|
||||
|
||||
if(sd_info->error == SD_OK) {
|
||||
// calculate size
|
||||
total_sectors = (fs->n_fatent - 2) * fs->csize;
|
||||
free_sectors = free_clusters * fs->csize;
|
||||
|
||||
uint16_t sector_size = _MAX_SS;
|
||||
#if _MAX_SS != _MIN_SS
|
||||
sector_size = fs->ssize;
|
||||
#endif
|
||||
|
||||
sd_info->fs_type = fs->fs_type;
|
||||
|
||||
sd_info->kb_total = total_sectors / 1024 * sector_size;
|
||||
sd_info->kb_free = free_sectors / 1024 * sector_size;
|
||||
sd_info->cluster_size = fs->csize;
|
||||
sd_info->sector_size = sector_size;
|
||||
}
|
||||
}
|
||||
|
||||
const char* get_fs_type_text(SDFsType fs_type) {
|
||||
switch(fs_type) {
|
||||
case(FST_FAT12):
|
||||
return "FAT12";
|
||||
break;
|
||||
case(FST_FAT16):
|
||||
return "FAT16";
|
||||
break;
|
||||
case(FST_FAT32):
|
||||
return "FAT32";
|
||||
break;
|
||||
case(FST_EXFAT):
|
||||
return "EXFAT";
|
||||
break;
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_sd_format_internal(SdApp* sd_app) {
|
||||
uint8_t* work_area;
|
||||
|
||||
_fs_lock(&sd_app->info);
|
||||
work_area = malloc(_MAX_SS);
|
||||
if(work_area == NULL) {
|
||||
sd_app->info.status = SD_NOT_ENOUGH_CORE;
|
||||
} else {
|
||||
sd_app->info.status = f_mkfs(sd_app->info.path, FM_ANY, 0, work_area, _MAX_SS);
|
||||
free(work_area);
|
||||
|
||||
if(sd_app->info.status == SD_OK) {
|
||||
// set label and mount card
|
||||
f_setlabel("Flipper SD");
|
||||
sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1);
|
||||
}
|
||||
}
|
||||
|
||||
_fs_unlock(&sd_app->info);
|
||||
}
|
||||
|
||||
const NotificationSequence sd_sequence_success = {
|
||||
&message_green_255,
|
||||
&message_delay_50,
|
||||
&message_green_0,
|
||||
&message_delay_50,
|
||||
&message_green_255,
|
||||
&message_delay_50,
|
||||
&message_green_0,
|
||||
&message_delay_50,
|
||||
&message_green_255,
|
||||
&message_delay_50,
|
||||
&message_green_0,
|
||||
&message_delay_50,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sd_sequence_error = {
|
||||
&message_red_255,
|
||||
&message_delay_50,
|
||||
&message_red_0,
|
||||
&message_delay_50,
|
||||
&message_red_255,
|
||||
&message_delay_50,
|
||||
&message_red_0,
|
||||
&message_delay_50,
|
||||
&message_red_255,
|
||||
&message_delay_50,
|
||||
&message_red_0,
|
||||
&message_delay_50,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sd_sequence_eject = {
|
||||
&message_blue_255,
|
||||
&message_delay_50,
|
||||
&message_blue_0,
|
||||
&message_delay_50,
|
||||
&message_blue_255,
|
||||
&message_delay_50,
|
||||
&message_blue_0,
|
||||
&message_delay_50,
|
||||
&message_blue_255,
|
||||
&message_delay_50,
|
||||
&message_blue_0,
|
||||
&message_delay_50,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sd_sequence_wait = {
|
||||
&message_red_255,
|
||||
&message_blue_255,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
const NotificationSequence sd_sequence_wait_off = {
|
||||
&message_red_0,
|
||||
&message_blue_0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void app_sd_notify_wait(SdApp* sd_app) {
|
||||
notification_message(sd_app->notifications, &sd_sequence_wait);
|
||||
}
|
||||
|
||||
void app_sd_notify_wait_off(SdApp* sd_app) {
|
||||
notification_message(sd_app->notifications, &sd_sequence_wait_off);
|
||||
}
|
||||
|
||||
void app_sd_notify_success(SdApp* sd_app) {
|
||||
notification_message(sd_app->notifications, &sd_sequence_success);
|
||||
}
|
||||
|
||||
void app_sd_notify_eject(SdApp* sd_app) {
|
||||
notification_message(sd_app->notifications, &sd_sequence_eject);
|
||||
}
|
||||
|
||||
void app_sd_notify_error(SdApp* sd_app) {
|
||||
notification_message(sd_app->notifications, &sd_sequence_error);
|
||||
}
|
||||
|
||||
bool app_sd_mount_card(SdApp* sd_app) {
|
||||
bool result = false;
|
||||
const uint8_t max_init_counts = 10;
|
||||
uint8_t counter = max_init_counts;
|
||||
uint8_t bsp_result;
|
||||
|
||||
_fs_lock(&sd_app->info);
|
||||
|
||||
while(result == false && counter > 0 && hal_sd_detect()) {
|
||||
app_sd_notify_wait(sd_app);
|
||||
|
||||
if((counter % 10) == 0) {
|
||||
// power reset sd card
|
||||
bsp_result = BSP_SD_Init(true);
|
||||
} else {
|
||||
bsp_result = BSP_SD_Init(false);
|
||||
}
|
||||
|
||||
if(bsp_result) {
|
||||
// bsp error
|
||||
sd_app->info.status = SD_LOW_LEVEL_ERR;
|
||||
} else {
|
||||
sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1);
|
||||
|
||||
if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) {
|
||||
FATFS* fs;
|
||||
uint32_t free_clusters;
|
||||
|
||||
sd_app->info.status = f_getfree(sd_app->info.path, &free_clusters, &fs);
|
||||
|
||||
if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
app_sd_notify_wait_off(sd_app);
|
||||
|
||||
if(!result) {
|
||||
delay(1000);
|
||||
FURI_LOG_E(
|
||||
"SD FILESYSTEM",
|
||||
"init(%d), error: %s\r\n",
|
||||
counter,
|
||||
fs_error_get_internal_desc(sd_app->info.status));
|
||||
|
||||
counter--;
|
||||
}
|
||||
}
|
||||
|
||||
_fs_unlock(&sd_app->info);
|
||||
return result;
|
||||
}
|
||||
|
||||
void app_sd_unmount_card(SdApp* sd_app) {
|
||||
_fs_lock(&sd_app->info);
|
||||
|
||||
// set status
|
||||
sd_app->info.status = SD_NO_CARD;
|
||||
view_port_enabled_set(sd_app->icon.view_port, false);
|
||||
|
||||
// close files
|
||||
for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) {
|
||||
FileData* filedata = &sd_app->info.files[index];
|
||||
|
||||
if(filedata->thread_id != NULL) {
|
||||
if(filedata->is_dir) {
|
||||
f_closedir(&filedata->data.dir);
|
||||
} else {
|
||||
f_close(&filedata->data.file);
|
||||
}
|
||||
filedata->thread_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// unmount volume
|
||||
f_mount(0, sd_app->info.path, 0);
|
||||
|
||||
_fs_unlock(&sd_app->info);
|
||||
}
|
||||
|
||||
bool app_sd_make_path(const char* path) {
|
||||
furi_assert(path);
|
||||
|
||||
if(*path) {
|
||||
char* file_path = strdup(path);
|
||||
// Make parent directories
|
||||
for(char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) {
|
||||
*p = '\0';
|
||||
SDError result = f_mkdir(file_path);
|
||||
|
||||
if(result != SD_OK) {
|
||||
if(result != SD_EXIST) {
|
||||
*p = '/';
|
||||
free(file_path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
// Make origin directory
|
||||
SDError result = f_mkdir(file_path);
|
||||
if(result != SD_OK) {
|
||||
if(result != SD_EXIST) {
|
||||
free(file_path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
free(file_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/******************* Draw callbacks *******************/
|
||||
|
||||
static void sd_icon_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(context);
|
||||
SdApp* sd_app = context;
|
||||
|
||||
switch(sd_app->info.status) {
|
||||
case SD_NO_CARD:
|
||||
break;
|
||||
case SD_OK:
|
||||
canvas_draw_icon(canvas, 0, 0, sd_app->icon.mounted);
|
||||
break;
|
||||
default:
|
||||
canvas_draw_icon(canvas, 0, 0, sd_app->icon.fail);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/******************* SD-api callbacks *******************/
|
||||
|
||||
bool sd_api_file_select(
|
||||
SdApp* sd_app,
|
||||
const char* path,
|
||||
const char* extension,
|
||||
char* result,
|
||||
uint8_t result_size,
|
||||
const char* selected_filename) {
|
||||
bool retval = false;
|
||||
|
||||
SdAppEvent message = {
|
||||
.type = SdAppEventTypeFileSelect,
|
||||
.payload = {
|
||||
.file_select_data = {
|
||||
.path = path,
|
||||
.extension = extension,
|
||||
.result = result,
|
||||
.result_size = result_size,
|
||||
.selected_filename = selected_filename,
|
||||
}}};
|
||||
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
|
||||
SdAppFileSelectResultEvent event;
|
||||
while(1) {
|
||||
osStatus_t event_status =
|
||||
osMessageQueueGet(sd_app->result_receiver, &event, NULL, osWaitForever);
|
||||
if(event_status == osOK) {
|
||||
retval = event.result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!retval) {
|
||||
sd_api_check_error(sd_app);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void sd_api_check_error(SdApp* sd_app) {
|
||||
SdAppEvent message = {.type = SdAppEventTypeCheckError};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
void sd_api_show_error(SdApp* sd_app, const char* error_text) {
|
||||
SdAppEvent message = {.type = SdAppEventTypeShowError, .payload.error_text = error_text};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
/******************* View callbacks *******************/
|
||||
|
||||
void app_view_back_callback(void* context) {
|
||||
furi_assert(context);
|
||||
SdApp* sd_app = context;
|
||||
SdAppEvent message = {.type = SdAppEventTypeBack};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
void app_view_dialog_callback(DialogExResult result, void* context) {
|
||||
furi_assert(context);
|
||||
SdApp* sd_app = context;
|
||||
|
||||
if(result == DialogExResultLeft) {
|
||||
SdAppEvent message = {.type = SdAppEventTypeBack};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
} else if(result == DialogExResultRight) {
|
||||
SdAppEvent message = {.type = SdAppEventTypeOK};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
}
|
||||
|
||||
void app_view_file_select_callback(bool result, void* context) {
|
||||
furi_assert(context);
|
||||
SdApp* sd_app = context;
|
||||
|
||||
if(result) {
|
||||
SdAppEvent message = {.type = SdAppEventTypeOK};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
} else {
|
||||
SdAppEvent message = {.type = SdAppEventTypeBack};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
}
|
||||
|
||||
/******************* Menu callbacks *******************/
|
||||
|
||||
void app_sd_info_callback(void* context) {
|
||||
furi_assert(context);
|
||||
SdApp* sd_app = context;
|
||||
SdAppEvent message = {.type = SdAppEventTypeInfo};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
void app_sd_format_callback(void* context) {
|
||||
furi_assert(context);
|
||||
SdApp* sd_app = context;
|
||||
SdAppEvent message = {.type = SdAppEventTypeFormat};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
void app_sd_eject_callback(void* context) {
|
||||
furi_assert(context);
|
||||
SdApp* sd_app = context;
|
||||
SdAppEvent message = {.type = SdAppEventTypeEject};
|
||||
furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
/******************* Cli callbacks *******************/
|
||||
|
||||
static void cli_sd_format(Cli* cli, string_t args, void* _ctx) {
|
||||
SdApp* sd_app = (SdApp*)_ctx;
|
||||
|
||||
printf("Formatting SD card, all data will be lost. Are you sure (y/n)?\r\n");
|
||||
char c = cli_getc(cli);
|
||||
if(c == 'y' || c == 'Y') {
|
||||
printf("Formatting, please wait...\r\n");
|
||||
// format card
|
||||
app_sd_format_internal(sd_app);
|
||||
|
||||
if(sd_app->info.status != SD_OK) {
|
||||
printf("SD card format error: ");
|
||||
printf(fs_error_get_internal_desc(sd_app->info.status));
|
||||
printf("\r\n");
|
||||
} else {
|
||||
printf("SD card was successfully formatted.\r\n");
|
||||
}
|
||||
} else {
|
||||
printf("Cancelled.\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void cli_sd_info(Cli* cli, string_t args, void* _ctx) {
|
||||
SdApp* sd_app = (SdApp*)_ctx;
|
||||
SDInfo sd_info;
|
||||
|
||||
get_sd_info(sd_app, &sd_info);
|
||||
printf("SD Status: %s\r\n", fs_error_get_internal_desc(sd_app->info.status));
|
||||
|
||||
if(sd_info.error == SD_OK) {
|
||||
const char* fs_type = get_fs_type_text(sd_info.fs_type);
|
||||
printf("Label: %s\r\n", sd_info.label);
|
||||
printf("Filesystem: %s\r\n", fs_type);
|
||||
printf("Cluster: %d sectors\r\n", sd_info.cluster_size);
|
||||
printf("Sector: %d bytes\r\n", sd_info.sector_size);
|
||||
printf("%lu KB total\r\n", sd_info.kb_total);
|
||||
printf("%lu KB free\r\n", sd_info.kb_free);
|
||||
} else {
|
||||
printf("SD Info error: %s\r\n", fs_error_get_internal_desc(sd_info.error));
|
||||
}
|
||||
}
|
||||
|
||||
/******************* Test *******************/
|
||||
|
||||
bool try_to_alloc_view_holder(SdApp* sd_app, Gui* gui) {
|
||||
bool result = false;
|
||||
|
||||
_fs_lock(&sd_app->info);
|
||||
|
||||
if(sd_app->view_holder == NULL) {
|
||||
sd_app->view_holder = view_holder_alloc();
|
||||
view_holder_attach_to_gui(sd_app->view_holder, gui);
|
||||
view_holder_set_back_callback(sd_app->view_holder, app_view_back_callback, sd_app);
|
||||
result = true;
|
||||
}
|
||||
|
||||
_fs_unlock(&sd_app->info);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DialogEx* alloc_and_attach_dialog(SdApp* sd_app) {
|
||||
DialogEx* dialog = dialog_ex_alloc();
|
||||
dialog_ex_set_context(dialog, sd_app);
|
||||
dialog_ex_set_result_callback(dialog, app_view_dialog_callback);
|
||||
view_holder_set_view(sd_app->view_holder, dialog_ex_get_view(dialog));
|
||||
view_holder_set_free_callback(sd_app->view_holder, (FreeCallback)dialog_ex_free, dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
FileSelect* alloc_and_attach_file_select(SdApp* sd_app) {
|
||||
FileSelect* file_select = file_select_alloc();
|
||||
file_select_set_callback(file_select, app_view_file_select_callback, sd_app);
|
||||
view_holder_set_view(sd_app->view_holder, file_select_get_view(file_select));
|
||||
view_holder_set_free_callback(
|
||||
sd_app->view_holder, (FreeCallback)file_select_free, file_select);
|
||||
return file_select;
|
||||
}
|
||||
|
||||
void free_view_holder(SdApp* sd_app) {
|
||||
_fs_lock(&sd_app->info);
|
||||
|
||||
if(sd_app->view_holder) {
|
||||
view_holder_free(sd_app->view_holder);
|
||||
sd_app->view_holder = NULL;
|
||||
}
|
||||
|
||||
_fs_unlock(&sd_app->info);
|
||||
}
|
||||
|
||||
void app_reset_state(SdApp* sd_app) {
|
||||
_fs_lock(&sd_app->info);
|
||||
if(sd_app->view_holder) {
|
||||
view_holder_stop(sd_app->view_holder);
|
||||
}
|
||||
_fs_unlock(&sd_app->info);
|
||||
|
||||
free_view_holder(sd_app);
|
||||
string_set_str(sd_app->text_holder, "");
|
||||
sd_app->sd_app_state = SdAppStateBackground;
|
||||
}
|
||||
|
||||
/******************* Main app *******************/
|
||||
|
||||
int32_t sd_filesystem(void* p) {
|
||||
SdApp* sd_app = sd_app_alloc();
|
||||
FS_Api* fs_api = fs_api_alloc();
|
||||
|
||||
Gui* gui = furi_record_open("gui");
|
||||
Cli* cli = furi_record_open("cli");
|
||||
sd_app->notifications = furi_record_open("notification");
|
||||
ValueMutex* menu_vm = furi_record_open("menu");
|
||||
|
||||
gui_add_view_port(gui, sd_app->icon.view_port, GuiLayerStatusBarLeft);
|
||||
|
||||
cli_add_command(cli, "sd_format", CliCommandFlagDefault, cli_sd_format, sd_app);
|
||||
cli_add_command(cli, "sd_info", CliCommandFlagDefault, cli_sd_info, sd_app);
|
||||
|
||||
// add api record
|
||||
furi_record_create("sdcard", fs_api);
|
||||
|
||||
// init menu
|
||||
// TODO menu icon
|
||||
MenuItem* menu_item;
|
||||
menu_item = menu_item_alloc_menu("SD Card", icon_animation_alloc(&I_SDcardMounted_11x8));
|
||||
|
||||
menu_item_subitem_add(
|
||||
menu_item, menu_item_alloc_function("Info", NULL, app_sd_info_callback, sd_app));
|
||||
menu_item_subitem_add(
|
||||
menu_item, menu_item_alloc_function("Format", NULL, app_sd_format_callback, sd_app));
|
||||
menu_item_subitem_add(
|
||||
menu_item, menu_item_alloc_function("Eject", NULL, app_sd_eject_callback, sd_app));
|
||||
|
||||
// add item to menu
|
||||
furi_check(menu_vm);
|
||||
with_value_mutex(
|
||||
menu_vm, (Menu * menu) { menu_item_add(menu, menu_item); });
|
||||
|
||||
FURI_LOG_I("SD FILESYSTEM", "start");
|
||||
|
||||
// add api record
|
||||
furi_record_create("sdcard", fs_api);
|
||||
furi_record_create("sdcard-ex", &sd_app->sd_card_api);
|
||||
|
||||
// sd card cycle
|
||||
bool sd_was_present = true;
|
||||
|
||||
// init detect pins
|
||||
hal_sd_detect_init();
|
||||
|
||||
while(true) {
|
||||
if(sd_was_present) {
|
||||
if(hal_sd_detect()) {
|
||||
FURI_LOG_I("SD FILESYSTEM", "Card detected");
|
||||
app_sd_mount_card(sd_app);
|
||||
|
||||
if(sd_app->info.status != SD_OK) {
|
||||
FURI_LOG_E(
|
||||
"SD FILESYSTEM",
|
||||
"sd init error: %s",
|
||||
fs_error_get_internal_desc(sd_app->info.status));
|
||||
app_sd_notify_error(sd_app);
|
||||
} else {
|
||||
FURI_LOG_I("SD FILESYSTEM", "sd init ok");
|
||||
app_sd_notify_success(sd_app);
|
||||
}
|
||||
|
||||
view_port_enabled_set(sd_app->icon.view_port, true);
|
||||
sd_was_present = false;
|
||||
|
||||
if(!hal_sd_detect()) {
|
||||
FURI_LOG_I("SD FILESYSTEM", "card removed");
|
||||
|
||||
view_port_enabled_set(sd_app->icon.view_port, false);
|
||||
app_sd_unmount_card(sd_app);
|
||||
sd_was_present = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(!hal_sd_detect()) {
|
||||
FURI_LOG_I("SD FILESYSTEM", "card removed");
|
||||
|
||||
view_port_enabled_set(sd_app->icon.view_port, false);
|
||||
app_sd_unmount_card(sd_app);
|
||||
sd_was_present = true;
|
||||
app_sd_notify_eject(sd_app);
|
||||
}
|
||||
}
|
||||
|
||||
SdAppEvent event;
|
||||
osStatus_t event_status = osMessageQueueGet(sd_app->event_queue, &event, NULL, 1000);
|
||||
|
||||
const uint8_t y_1_line = 32;
|
||||
const uint8_t y_2_line = 32;
|
||||
const uint8_t y_4_line = 26;
|
||||
|
||||
if(event_status == osOK) {
|
||||
switch(event.type) {
|
||||
case SdAppEventTypeOK:
|
||||
switch(sd_app->sd_app_state) {
|
||||
case SdAppStateFormat: {
|
||||
DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder);
|
||||
dialog_ex_set_left_button_text(dialog, NULL);
|
||||
dialog_ex_set_right_button_text(dialog, NULL);
|
||||
dialog_ex_set_header(
|
||||
dialog, "Formatting...", 64, y_1_line, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
sd_app->sd_app_state = SdAppStateFormatInProgress;
|
||||
delay(100);
|
||||
app_sd_format_internal(sd_app);
|
||||
app_sd_notify_success(sd_app);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
dialog_ex_set_header(
|
||||
dialog, "SD card formatted", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog, "Press back to return", 64, y_1_line, AlignCenter, AlignCenter);
|
||||
sd_app->sd_app_state = SdAppStateFormatCompleted;
|
||||
}; break;
|
||||
case SdAppStateEject: {
|
||||
DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder);
|
||||
dialog_ex_set_right_button_text(dialog, NULL);
|
||||
dialog_ex_set_header(
|
||||
dialog, "SD card ejected", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog,
|
||||
"Now the SD card\ncan be removed.",
|
||||
64,
|
||||
y_2_line,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
sd_app->sd_app_state = SdAppStateEjected;
|
||||
app_sd_unmount_card(sd_app);
|
||||
app_sd_notify_eject(sd_app);
|
||||
}; break;
|
||||
case SdAppStateFileSelect: {
|
||||
SdAppFileSelectResultEvent retval = {.result = true};
|
||||
furi_check(
|
||||
osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
|
||||
osOK);
|
||||
app_reset_state(sd_app);
|
||||
}; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SdAppEventTypeBack:
|
||||
switch(sd_app->sd_app_state) {
|
||||
case SdAppStateFormatInProgress:
|
||||
break;
|
||||
case SdAppStateFileSelect: {
|
||||
SdAppFileSelectResultEvent retval = {.result = false};
|
||||
furi_check(
|
||||
osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
|
||||
osOK);
|
||||
app_reset_state(sd_app);
|
||||
}; break;
|
||||
|
||||
default:
|
||||
app_reset_state(sd_app);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SdAppEventTypeFormat:
|
||||
if(try_to_alloc_view_holder(sd_app, gui)) {
|
||||
DialogEx* dialog = alloc_and_attach_dialog(sd_app);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
dialog_ex_set_right_button_text(dialog, "Format");
|
||||
dialog_ex_set_header(
|
||||
dialog, "Format SD card?", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog, "All data will be lost.", 64, y_1_line, AlignCenter, AlignCenter);
|
||||
view_holder_start(sd_app->view_holder);
|
||||
sd_app->sd_app_state = SdAppStateFormat;
|
||||
}
|
||||
break;
|
||||
case SdAppEventTypeInfo:
|
||||
if(try_to_alloc_view_holder(sd_app, gui)) {
|
||||
DialogEx* dialog = alloc_and_attach_dialog(sd_app);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
|
||||
SDInfo sd_info;
|
||||
get_sd_info(sd_app, &sd_info);
|
||||
|
||||
if(sd_info.error == SD_OK) {
|
||||
string_printf(
|
||||
sd_app->text_holder,
|
||||
"Label: %s\nType: %s\n%lu KB total\n%lu KB free",
|
||||
sd_info.label,
|
||||
get_fs_type_text(sd_info.fs_type),
|
||||
sd_info.kb_total,
|
||||
sd_info.kb_free);
|
||||
dialog_ex_set_text(
|
||||
dialog,
|
||||
string_get_cstr(sd_app->text_holder),
|
||||
4,
|
||||
y_4_line,
|
||||
AlignLeft,
|
||||
AlignCenter);
|
||||
view_holder_start(sd_app->view_holder);
|
||||
} else {
|
||||
string_printf(
|
||||
sd_app->text_holder,
|
||||
"SD status: %s\n SD info: %s",
|
||||
fs_error_get_internal_desc(_fs_status(&sd_app->info)),
|
||||
fs_error_get_internal_desc(sd_info.error));
|
||||
dialog_ex_set_header(dialog, "Error", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog,
|
||||
string_get_cstr(sd_app->text_holder),
|
||||
64,
|
||||
y_2_line,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
view_holder_start(sd_app->view_holder);
|
||||
}
|
||||
|
||||
sd_app->sd_app_state = SdAppStateInfo;
|
||||
}
|
||||
break;
|
||||
case SdAppEventTypeEject:
|
||||
if(try_to_alloc_view_holder(sd_app, gui)) {
|
||||
DialogEx* dialog = alloc_and_attach_dialog(sd_app);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
dialog_ex_set_right_button_text(dialog, "Eject");
|
||||
dialog_ex_set_header(
|
||||
dialog, "Eject SD card?", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog,
|
||||
"SD card will be\nunavailable",
|
||||
64,
|
||||
y_2_line,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
view_holder_start(sd_app->view_holder);
|
||||
sd_app->sd_app_state = SdAppStateEject;
|
||||
}
|
||||
break;
|
||||
case SdAppEventTypeFileSelect:
|
||||
if(!app_sd_make_path(event.payload.file_select_data.path)) {
|
||||
SdAppFileSelectResultEvent retval = {.result = false};
|
||||
furi_check(
|
||||
osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
|
||||
osOK);
|
||||
break;
|
||||
}
|
||||
if(try_to_alloc_view_holder(sd_app, gui)) {
|
||||
FileSelect* file_select = alloc_and_attach_file_select(sd_app);
|
||||
SdAppFileSelectData* file_select_data = &event.payload.file_select_data;
|
||||
|
||||
file_select_set_api(file_select, fs_api);
|
||||
file_select_set_filter(
|
||||
file_select, file_select_data->path, file_select_data->extension);
|
||||
file_select_set_result_buffer(
|
||||
file_select, file_select_data->result, file_select_data->result_size);
|
||||
if(!file_select_init(file_select)) {
|
||||
SdAppFileSelectResultEvent retval = {.result = false};
|
||||
furi_check(
|
||||
osMessageQueuePut(
|
||||
sd_app->result_receiver, &retval, 0, osWaitForever) == osOK);
|
||||
app_reset_state(sd_app);
|
||||
} else {
|
||||
sd_app->sd_app_state = SdAppStateFileSelect;
|
||||
if(file_select_data->selected_filename != NULL) {
|
||||
file_select_set_selected_file(
|
||||
file_select, file_select_data->selected_filename);
|
||||
}
|
||||
view_holder_start(sd_app->view_holder);
|
||||
}
|
||||
} else {
|
||||
SdAppFileSelectResultEvent retval = {.result = false};
|
||||
furi_check(
|
||||
osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
|
||||
osOK);
|
||||
}
|
||||
break;
|
||||
case SdAppEventTypeCheckError:
|
||||
if(sd_app->info.status != SD_OK) {
|
||||
if(try_to_alloc_view_holder(sd_app, gui)) {
|
||||
DialogEx* dialog = alloc_and_attach_dialog(sd_app);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
if(sd_app->info.status == SD_NO_CARD) {
|
||||
dialog_ex_set_text(
|
||||
dialog,
|
||||
"SD card\nnot found",
|
||||
88,
|
||||
y_1_line,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43);
|
||||
} else {
|
||||
dialog_ex_set_text(
|
||||
dialog, "SD card\nerror", 88, y_1_line, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_icon(dialog, 5, 10, &I_SDError_43x35);
|
||||
}
|
||||
sd_app->sd_app_state = SdAppStateCheckError;
|
||||
view_holder_start(sd_app->view_holder);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SdAppEventTypeShowError:
|
||||
if(try_to_alloc_view_holder(sd_app, gui)) {
|
||||
DialogEx* dialog = alloc_and_attach_dialog(sd_app);
|
||||
dialog_ex_set_left_button_text(dialog, "Back");
|
||||
dialog_ex_set_text(
|
||||
dialog, event.payload.error_text, 88, y_1_line, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43);
|
||||
sd_app->sd_app_state = SdAppStateShowError;
|
||||
view_holder_start(sd_app->view_holder);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <api-hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <m-string.h>
|
||||
#include "sd-card-api.h"
|
||||
#include "view_holder.h"
|
||||
#include <notification/notification-messages.h>
|
||||
|
||||
#define SD_FS_MAX_FILES _FS_LOCK
|
||||
#define SD_STATE_LINES_COUNT 6
|
||||
|
||||
/* api data */
|
||||
typedef FIL SDFile;
|
||||
typedef DIR SDDir;
|
||||
typedef FILINFO SDFileInfo;
|
||||
|
||||
/* storage for file/directory objects*/
|
||||
typedef union {
|
||||
SDFile file;
|
||||
SDDir dir;
|
||||
} SDFileDirStorage;
|
||||
|
||||
typedef enum {
|
||||
SD_OK = FR_OK,
|
||||
SD_DISK_ERR = FR_DISK_ERR,
|
||||
SD_INT_ERR = FR_INT_ERR,
|
||||
SD_NO_FILE = FR_NO_FILE,
|
||||
SD_NO_PATH = FR_NO_PATH,
|
||||
SD_INVALID_NAME = FR_INVALID_NAME,
|
||||
SD_DENIED = FR_DENIED,
|
||||
SD_EXIST = FR_EXIST,
|
||||
SD_INVALID_OBJECT = FR_INVALID_OBJECT,
|
||||
SD_WRITE_PROTECTED = FR_WRITE_PROTECTED,
|
||||
SD_INVALID_DRIVE = FR_INVALID_DRIVE,
|
||||
SD_NOT_ENABLED = FR_NOT_ENABLED,
|
||||
SD_NO_FILESYSTEM = FR_NO_FILESYSTEM,
|
||||
SD_MKFS_ABORTED = FR_MKFS_ABORTED,
|
||||
SD_TIMEOUT = FR_TIMEOUT,
|
||||
SD_LOCKED = FR_LOCKED,
|
||||
SD_NOT_ENOUGH_CORE = FR_NOT_ENOUGH_CORE,
|
||||
SD_TOO_MANY_OPEN_FILES = FR_TOO_MANY_OPEN_FILES,
|
||||
SD_INVALID_PARAMETER = FR_INVALID_PARAMETER,
|
||||
SD_NO_CARD,
|
||||
SD_NOT_A_FILE,
|
||||
SD_NOT_A_DIR,
|
||||
SD_OTHER_APP,
|
||||
SD_LOW_LEVEL_ERR,
|
||||
} SDError;
|
||||
|
||||
typedef enum {
|
||||
FDF_DIR,
|
||||
FDF_FILE,
|
||||
FDF_ANY,
|
||||
} FiledataFilter;
|
||||
|
||||
typedef struct {
|
||||
osThreadId_t thread_id;
|
||||
bool is_dir;
|
||||
SDFileDirStorage data;
|
||||
} FileData;
|
||||
|
||||
/* application data */
|
||||
typedef struct {
|
||||
ViewPort* view_port;
|
||||
const Icon* mounted;
|
||||
const Icon* fail;
|
||||
} SdFsIcon;
|
||||
|
||||
typedef struct {
|
||||
osMutexId_t mutex;
|
||||
FileData files[SD_FS_MAX_FILES];
|
||||
SDError status;
|
||||
char* path;
|
||||
FATFS fat_fs;
|
||||
} SdFsInfo;
|
||||
|
||||
typedef enum {
|
||||
SdAppStateBackground,
|
||||
SdAppStateFormat,
|
||||
SdAppStateFormatInProgress,
|
||||
SdAppStateFormatCompleted,
|
||||
SdAppStateInfo,
|
||||
SdAppStateEject,
|
||||
SdAppStateEjected,
|
||||
SdAppStateFileSelect,
|
||||
SdAppStateCheckError,
|
||||
SdAppStateShowError,
|
||||
} SdAppState;
|
||||
|
||||
struct SdApp {
|
||||
SdFsInfo info;
|
||||
SdFsIcon icon;
|
||||
|
||||
SdCard_Api sd_card_api;
|
||||
SdAppState sd_app_state;
|
||||
|
||||
ViewHolder* view_holder;
|
||||
osMessageQueueId_t result_receiver;
|
||||
|
||||
osMessageQueueId_t event_queue;
|
||||
string_t text_holder;
|
||||
|
||||
NotificationApp* notifications;
|
||||
};
|
||||
|
||||
/* core api fns */
|
||||
bool _fs_init(SdFsInfo* _fs_info);
|
||||
bool _fs_lock(SdFsInfo* fs_info);
|
||||
bool _fs_unlock(SdFsInfo* fs_info);
|
||||
SDError _fs_status(SdFsInfo* fs_info);
|
||||
|
||||
/* sd api fns */
|
||||
bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode);
|
||||
bool fs_file_close(File* file);
|
||||
uint16_t fs_file_read(File* file, void* buff, uint16_t bytes_to_read);
|
||||
uint16_t fs_file_write(File* file, const void* buff, uint16_t bytes_to_write);
|
||||
bool fs_file_seek(File* file, uint32_t offset, bool from_start);
|
||||
uint64_t fs_file_tell(File* file);
|
||||
bool fs_file_truncate(File* file);
|
||||
uint64_t fs_file_size(File* file);
|
||||
bool fs_file_sync(File* file);
|
||||
bool fs_file_eof(File* file);
|
||||
|
||||
/* dir api */
|
||||
bool fs_dir_open(File* file, const char* path);
|
||||
bool fs_dir_close(File* file);
|
||||
bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length);
|
||||
bool fs_dir_rewind(File* file);
|
||||
|
||||
/* common api */
|
||||
FS_Error
|
||||
fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length);
|
||||
FS_Error fs_common_remove(const char* path);
|
||||
FS_Error fs_common_rename(const char* old_path, const char* new_path);
|
||||
FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask);
|
||||
FS_Error fs_common_mkdir(const char* path);
|
||||
FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time);
|
||||
FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space);
|
||||
|
||||
/* errors api */
|
||||
const char* fs_error_get_desc(FS_Error error_id);
|
||||
const char* fs_error_get_internal_desc(uint32_t internal_error_id);
|
|
@ -0,0 +1,161 @@
|
|||
#include "../storage-settings.h"
|
||||
|
||||
#define BENCH_DATA_SIZE 4096
|
||||
#define BENCH_COUNT 6
|
||||
#define BENCH_REPEATS 4
|
||||
#define BENCH_FILE "/ext/rwfiletest.bin"
|
||||
|
||||
static void
|
||||
storage_settings_scene_benchmark_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
static bool storage_settings_bench_write(
|
||||
Storage* api,
|
||||
uint16_t size,
|
||||
const uint8_t* data,
|
||||
uint32_t* speed) {
|
||||
File* file = storage_file_alloc(api);
|
||||
bool result = true;
|
||||
if(storage_file_open(file, BENCH_FILE, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
uint32_t ticks;
|
||||
ticks = osKernelGetTickCount();
|
||||
|
||||
for(size_t repeat = 0; repeat < BENCH_REPEATS; repeat++) {
|
||||
for(size_t i = 0; i < BENCH_DATA_SIZE / size; i++) {
|
||||
if(storage_file_write(file, (data + i * size), size) != size) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ticks = osKernelGetTickCount() - ticks;
|
||||
*speed = BENCH_DATA_SIZE * osKernelGetTickFreq() * BENCH_REPEATS;
|
||||
*speed /= ticks;
|
||||
*speed /= 1024;
|
||||
}
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool
|
||||
storage_settings_bench_read(Storage* api, uint16_t size, uint8_t* data, uint32_t* speed) {
|
||||
File* file = storage_file_alloc(api);
|
||||
bool result = true;
|
||||
*speed = -1;
|
||||
|
||||
if(storage_file_open(file, BENCH_FILE, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
uint32_t ticks;
|
||||
ticks = osKernelGetTickCount();
|
||||
|
||||
for(size_t repeat = 0; repeat < BENCH_REPEATS; repeat++) {
|
||||
for(size_t i = 0; i < BENCH_DATA_SIZE / size; i++) {
|
||||
if(storage_file_read(file, (data + i * size), size) != size) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ticks = osKernelGetTickCount() - ticks;
|
||||
*speed = BENCH_DATA_SIZE * osKernelGetTickFreq() * BENCH_REPEATS;
|
||||
*speed /= ticks;
|
||||
*speed /= 1024;
|
||||
}
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void storage_settings_benchmark(StorageSettings* app) {
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
uint8_t* bench_data;
|
||||
dialog_ex_set_header(dialog_ex, "Preparing data...", 64, 32, AlignCenter, AlignCenter);
|
||||
|
||||
bench_data = malloc(BENCH_DATA_SIZE);
|
||||
for(size_t i = 0; i < BENCH_DATA_SIZE; i++) {
|
||||
bench_data[i] = (uint8_t)i;
|
||||
}
|
||||
|
||||
uint16_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 1024, 4096};
|
||||
uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||
uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter);
|
||||
for(size_t i = 0; i < BENCH_COUNT; i++) {
|
||||
if(!storage_settings_bench_write(app->fs_api, bench_size[i], bench_data, &bench_w_speed[i]))
|
||||
break;
|
||||
|
||||
if(i > 0) string_cat_printf(app->text_string, "\n");
|
||||
string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]);
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter);
|
||||
|
||||
if(!storage_settings_bench_read(app->fs_api, bench_size[i], bench_data, &bench_r_speed[i]))
|
||||
break;
|
||||
|
||||
string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter);
|
||||
}
|
||||
|
||||
free(bench_data);
|
||||
}
|
||||
|
||||
void storage_settings_scene_benchmark_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_benchmark_dialog_callback);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
|
||||
if(storage_sd_status(app->fs_api) != FSE_OK) {
|
||||
dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
"If an SD card is inserted,\r\npull it out and reinsert it",
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Back");
|
||||
} else {
|
||||
storage_settings_benchmark(app);
|
||||
notification_message(app->notification, &sequence_blink_green_100);
|
||||
}
|
||||
}
|
||||
|
||||
bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_benchmark_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_right_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_result_callback(dialog_ex, NULL);
|
||||
dialog_ex_set_context(dialog_ex, NULL);
|
||||
|
||||
string_clean(app->text_string);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
ADD_SCENE(storage_settings, start, Start)
|
||||
ADD_SCENE(storage_settings, unmount_confirm, UnmountConfirm)
|
||||
ADD_SCENE(storage_settings, unmounted, Unmounted)
|
||||
ADD_SCENE(storage_settings, format_confirm, FormatConfirm)
|
||||
ADD_SCENE(storage_settings, formatting, Formatting)
|
||||
ADD_SCENE(storage_settings, sd_info, SDInfo)
|
||||
ADD_SCENE(storage_settings, internal_info, InternalInfo)
|
||||
ADD_SCENE(storage_settings, benchmark, Benchmark)
|
|
@ -0,0 +1,68 @@
|
|||
#include "../storage-settings.h"
|
||||
|
||||
static void
|
||||
storage_settings_scene_unmount_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void storage_settings_scene_unmount_confirm_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
FS_Error sd_status = storage_sd_status(app->fs_api);
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Back");
|
||||
|
||||
if(sd_status == FSE_NOT_READY) {
|
||||
dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
"If an SD card is inserted,\r\npull it out and reinsert it",
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
} else {
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Unmount");
|
||||
dialog_ex_set_header(dialog_ex, "Unmount SD card?", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, "SD card will be\nunavailable", 64, 32, AlignCenter, AlignCenter);
|
||||
}
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(
|
||||
dialog_ex, storage_settings_scene_unmount_confirm_dialog_callback);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
}
|
||||
|
||||
bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_unmount_confirm_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_right_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_result_callback(dialog_ex, NULL);
|
||||
dialog_ex_set_context(dialog_ex, NULL);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
#include "../storage-settings.h"
|
||||
|
||||
static void
|
||||
storage_settings_scene_unmounted_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void storage_settings_scene_unmounted_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
FS_Error error = storage_sd_unmount(app->fs_api);
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Back");
|
||||
|
||||
if(error == FSE_OK) {
|
||||
dialog_ex_set_header(dialog_ex, "SD card unmounted", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, "Now the SD card\ncan be removed.", 64, 32, AlignCenter, AlignCenter);
|
||||
notification_message(app->notification, &sequence_blink_green_100);
|
||||
} else {
|
||||
dialog_ex_set_header(
|
||||
dialog_ex, "Cannot unmount SD Card", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter);
|
||||
notification_message(app->notification, &sequence_blink_red_100);
|
||||
}
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
}
|
||||
|
||||
bool storage_settings_scene_unmounted_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed =
|
||||
scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart);
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeNavigation) {
|
||||
consumed = scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_unmounted_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_right_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_result_callback(dialog_ex, NULL);
|
||||
dialog_ex_set_context(dialog_ex, NULL);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
#include "../storage-settings.h"
|
||||
|
||||
static void
|
||||
storage_settings_scene_format_confirm_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void storage_settings_scene_format_confirm_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
FS_Error sd_status = storage_sd_status(app->fs_api);
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Back");
|
||||
|
||||
if(sd_status == FSE_NOT_READY) {
|
||||
dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
"If an SD card is inserted,\r\npull it out and reinsert it",
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
} else {
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Format");
|
||||
dialog_ex_set_header(dialog_ex, "Format SD card?", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, "All data will be lost", 64, 32, AlignCenter, AlignCenter);
|
||||
}
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(
|
||||
dialog_ex, storage_settings_scene_format_confirm_dialog_callback);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
}
|
||||
|
||||
bool storage_settings_scene_format_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsFormatting);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_format_confirm_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_right_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_result_callback(dialog_ex, NULL);
|
||||
dialog_ex_set_context(dialog_ex, NULL);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
#include "../storage-settings.h"
|
||||
|
||||
static const NotificationMessage message_green_165 = {
|
||||
.type = NotificationMessageTypeLedGreen,
|
||||
.data.led.value = 165,
|
||||
};
|
||||
|
||||
static const NotificationSequence sequence_set_formatting_leds = {
|
||||
&message_red_255,
|
||||
&message_green_165,
|
||||
&message_blue_0,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence sequence_reset_formatting_leds = {
|
||||
&message_red_0,
|
||||
&message_green_0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void
|
||||
storage_settings_scene_formatting_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void storage_settings_scene_formatting_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
FS_Error error;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Formatting...", 64, 32, AlignCenter, AlignCenter);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
|
||||
notification_message_block(app->notification, &sequence_set_formatting_leds);
|
||||
error = storage_sd_format(app->fs_api);
|
||||
notification_message(app->notification, &sequence_reset_formatting_leds);
|
||||
notification_message(app->notification, &sequence_blink_green_100);
|
||||
|
||||
if(error != FSE_OK) {
|
||||
dialog_ex_set_header(dialog_ex, "Cannot format SD Card", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter);
|
||||
} else {
|
||||
dialog_ex_set_header(dialog_ex, "SD card formatted", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, "Press back to return", 64, 32, AlignCenter, AlignCenter);
|
||||
}
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_formatting_dialog_callback);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Back");
|
||||
}
|
||||
|
||||
bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed =
|
||||
scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart);
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeNavigation) {
|
||||
consumed = scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_formatting_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_right_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_result_callback(dialog_ex, NULL);
|
||||
dialog_ex_set_context(dialog_ex, NULL);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#include "../storage-settings.h"
|
||||
#include <api-hal-version.h>
|
||||
|
||||
static void
|
||||
storage_settings_scene_internal_info_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void storage_settings_scene_internal_info_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
uint64_t total_space;
|
||||
uint64_t free_space;
|
||||
FS_Error error = storage_common_fs_info(app->fs_api, "/int", &total_space, &free_space);
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_internal_info_dialog_callback);
|
||||
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Back");
|
||||
if(error != FSE_OK) {
|
||||
dialog_ex_set_header(
|
||||
dialog_ex, "Internal storage error", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter);
|
||||
} else {
|
||||
string_printf(
|
||||
app->text_string,
|
||||
"Label: %s\nType: LittleFS\n%lu KB total\n%lu KB free",
|
||||
api_hal_version_get_name_ptr(),
|
||||
(uint32_t)(total_space / 1024),
|
||||
(uint32_t)(free_space / 1024));
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
}
|
||||
|
||||
bool storage_settings_scene_internal_info_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_internal_info_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_right_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_result_callback(dialog_ex, NULL);
|
||||
dialog_ex_set_context(dialog_ex, NULL);
|
||||
|
||||
string_clean(app->text_string);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
#include "../storage-settings.h"
|
||||
|
||||
static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
|
||||
void storage_settings_scene_sd_info_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
SDInfo sd_info;
|
||||
FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info);
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback);
|
||||
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Back");
|
||||
if(sd_status != FSE_OK) {
|
||||
dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex,
|
||||
"If an SD card is inserted,\r\npull it out and reinsert it",
|
||||
64,
|
||||
32,
|
||||
AlignCenter,
|
||||
AlignCenter);
|
||||
} else {
|
||||
string_printf(
|
||||
app->text_string,
|
||||
"Label: %s\nType: %s\n%lu KB total\n%lu KB free",
|
||||
sd_info.label,
|
||||
sd_api_get_fs_type_text(sd_info.fs_type),
|
||||
sd_info.kb_total,
|
||||
sd_info.kb_free);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
}
|
||||
|
||||
bool storage_settings_scene_sd_info_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_sd_info_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 0, 0, NULL);
|
||||
dialog_ex_set_left_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_right_button_text(dialog_ex, NULL);
|
||||
dialog_ex_set_result_callback(dialog_ex, NULL);
|
||||
dialog_ex_set_context(dialog_ex, NULL);
|
||||
|
||||
string_clean(app->text_string);
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
#include "../storage-settings.h"
|
||||
|
||||
enum StorageSettingsStartSubmenuIndex {
|
||||
StorageSettingsStartSubmenuIndexInternalInfo,
|
||||
StorageSettingsStartSubmenuIndexSDInfo,
|
||||
StorageSettingsStartSubmenuIndexUnmount,
|
||||
StorageSettingsStartSubmenuIndexFormat,
|
||||
StorageSettingsStartSubmenuIndexBenchy,
|
||||
};
|
||||
|
||||
static void storage_settings_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
StorageSettings* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void storage_settings_scene_start_on_enter(void* context) {
|
||||
StorageSettings* app = context;
|
||||
Submenu* submenu = app->submenu;
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"About internal storage",
|
||||
StorageSettingsStartSubmenuIndexInternalInfo,
|
||||
storage_settings_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"About SD Card",
|
||||
StorageSettingsStartSubmenuIndexSDInfo,
|
||||
storage_settings_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Unmount SD Card",
|
||||
StorageSettingsStartSubmenuIndexUnmount,
|
||||
storage_settings_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Format SD Card",
|
||||
StorageSettingsStartSubmenuIndexFormat,
|
||||
storage_settings_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Benchmark SD Card",
|
||||
StorageSettingsStartSubmenuIndexBenchy,
|
||||
storage_settings_scene_start_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(app->scene_manager, StorageSettingsStart));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewSubmenu);
|
||||
}
|
||||
|
||||
bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
StorageSettings* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case StorageSettingsStartSubmenuIndexSDInfo:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexSDInfo);
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsSDInfo);
|
||||
consumed = true;
|
||||
break;
|
||||
case StorageSettingsStartSubmenuIndexInternalInfo:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager,
|
||||
StorageSettingsStart,
|
||||
StorageSettingsStartSubmenuIndexInternalInfo);
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsInternalInfo);
|
||||
consumed = true;
|
||||
break;
|
||||
case StorageSettingsStartSubmenuIndexUnmount:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexUnmount);
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsUnmountConfirm);
|
||||
consumed = true;
|
||||
break;
|
||||
case StorageSettingsStartSubmenuIndexFormat:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexFormat);
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsFormatConfirm);
|
||||
consumed = true;
|
||||
break;
|
||||
case StorageSettingsStartSubmenuIndexBenchy:
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy);
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark);
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void storage_settings_scene_start_on_exit(void* context) {
|
||||
StorageSettings* app = context;
|
||||
submenu_clean(app->submenu);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#include "storage-settings-scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const storage_settings_on_enter_handlers[])(void*) = {
|
||||
#include "storage-settings-scene-config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const storage_settings_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "storage-settings-scene-config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const storage_settings_on_exit_handlers[])(void* context) = {
|
||||
#include "storage-settings-scene-config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers storage_settings_scene_handlers = {
|
||||
.on_enter_handlers = storage_settings_on_enter_handlers,
|
||||
.on_event_handlers = storage_settings_on_event_handlers,
|
||||
.on_exit_handlers = storage_settings_on_exit_handlers,
|
||||
.scene_num = StorageSettingsSceneNum,
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) StorageSettings##id,
|
||||
typedef enum {
|
||||
#include "storage-settings-scene-config.h"
|
||||
StorageSettingsSceneNum,
|
||||
} StorageSettingsScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers storage_settings_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "storage-settings-scene-config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "storage-settings-scene-config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "storage-settings-scene-config.h"
|
||||
#undef ADD_SCENE
|
75
applications/storage-settings/storage-settings.c
Normal file
75
applications/storage-settings/storage-settings.c
Normal file
|
@ -0,0 +1,75 @@
|
|||
#include "storage-settings.h"
|
||||
|
||||
static bool storage_settings_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
StorageSettings* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool storage_settings_navigation_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
StorageSettings* app = context;
|
||||
return scene_manager_handle_navigation_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static StorageSettings* storage_settings_alloc() {
|
||||
StorageSettings* app = malloc(sizeof(StorageSettings));
|
||||
|
||||
app->gui = furi_record_open("gui");
|
||||
app->fs_api = furi_record_open("storage");
|
||||
app->notification = furi_record_open("notification");
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&storage_settings_scene_handlers, app);
|
||||
string_init(app->text_string);
|
||||
|
||||
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
app->view_dispatcher, storage_settings_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
app->view_dispatcher, storage_settings_navigation_event_callback);
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, StorageSettingsViewSubmenu, submenu_get_view(app->submenu));
|
||||
|
||||
app->dialog_ex = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, StorageSettingsViewDialogEx, dialog_ex_get_view(app->dialog_ex));
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsStart);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void storage_settings_free(StorageSettings* app) {
|
||||
view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewSubmenu);
|
||||
submenu_free(app->submenu);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
dialog_ex_free(app->dialog_ex);
|
||||
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
furi_record_close("gui");
|
||||
furi_record_close("storage");
|
||||
furi_record_close("notification");
|
||||
|
||||
string_clear(app->text_string);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t storage_settings(void* p) {
|
||||
StorageSettings* app = storage_settings_alloc();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
storage_settings_free(app);
|
||||
return 0;
|
||||
}
|
47
applications/storage-settings/storage-settings.h
Normal file
47
applications/storage-settings/storage-settings.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <notification/notification-messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/popup.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <storage/storage-sd-api.h>
|
||||
|
||||
#include "scenes/storage-settings-scene.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
// records
|
||||
Gui* gui;
|
||||
NotificationApp* notification;
|
||||
Storage* fs_api;
|
||||
|
||||
// view managment
|
||||
SceneManager* scene_manager;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
|
||||
// view modules
|
||||
Submenu* submenu;
|
||||
DialogEx* dialog_ex;
|
||||
|
||||
// text
|
||||
string_t text_string;
|
||||
} StorageSettings;
|
||||
|
||||
typedef enum {
|
||||
StorageSettingsViewSubmenu,
|
||||
StorageSettingsViewDialogEx,
|
||||
} StorageSettingsView;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
59
applications/storage/filesystem-api-defines.h
Normal file
59
applications/storage/filesystem-api-defines.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Access mode flags */
|
||||
typedef enum {
|
||||
FSAM_READ = (1 << 0), /**< Read access */
|
||||
FSAM_WRITE = (1 << 1), /**< Write access */
|
||||
} FS_AccessMode;
|
||||
|
||||
/** Open mode flags */
|
||||
typedef enum {
|
||||
FSOM_OPEN_EXISTING = 1, /**< Open file, fail if file doesn't exist */
|
||||
FSOM_OPEN_ALWAYS = 2, /**< Open file. Create new file if not exist */
|
||||
FSOM_OPEN_APPEND = 4, /**< Open file. Create new file if not exist. Set R/W pointer to EOF */
|
||||
FSOM_CREATE_NEW = 8, /**< Creates a new file. Fails if the file is exist */
|
||||
FSOM_CREATE_ALWAYS = 16, /**< Creates a new file. If file exist, truncate to zero size */
|
||||
} FS_OpenMode;
|
||||
|
||||
/** API errors enumeration */
|
||||
typedef enum {
|
||||
FSE_OK, /**< No error */
|
||||
FSE_NOT_READY, /**< FS not ready */
|
||||
FSE_EXIST, /**< File/Dir alrady exist */
|
||||
FSE_NOT_EXIST, /**< File/Dir does not exist */
|
||||
FSE_INVALID_PARAMETER, /**< Invalid API parameter */
|
||||
FSE_DENIED, /**< Access denied */
|
||||
FSE_INVALID_NAME, /**< Invalid name/path */
|
||||
FSE_INTERNAL, /**< Internal error */
|
||||
FSE_NOT_IMPLEMENTED, /**< Functon not implemented */
|
||||
FSE_ALREADY_OPEN, /**< File/Dir already opened */
|
||||
} FS_Error;
|
||||
|
||||
/** FileInfo flags */
|
||||
typedef enum {
|
||||
FSF_DIRECTORY = (1 << 0), /**< Directory */
|
||||
} FS_Flags;
|
||||
|
||||
/** Structure that hold file index and returned api errors */
|
||||
typedef struct File File;
|
||||
|
||||
/** Structure that hold file info */
|
||||
typedef struct {
|
||||
uint8_t flags; /**< flags from FS_Flags enum */
|
||||
uint64_t size; /**< file size */
|
||||
} FileInfo;
|
||||
|
||||
/** Gets the error text from FS_Error
|
||||
* @param error_id error id
|
||||
* @return const char* error text
|
||||
*/
|
||||
const char* filesystem_api_error_get_desc(FS_Error error_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
192
applications/storage/filesystem-api-internal.h
Normal file
192
applications/storage/filesystem-api-internal.h
Normal file
|
@ -0,0 +1,192 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "filesystem-api-defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Structure that hold file index and returned api errors */
|
||||
struct File {
|
||||
uint32_t file_id; /**< File ID for internal references */
|
||||
FS_Error error_id; /**< Standart API error from FS_Error enum */
|
||||
int32_t internal_error_id; /**< Internal API error value */
|
||||
void* storage;
|
||||
};
|
||||
|
||||
/** File api structure
|
||||
* @var FS_File_Api::open
|
||||
* @brief Open file
|
||||
* @param file pointer to file object, filled by api
|
||||
* @param path path to file
|
||||
* @param access_mode access mode from FS_AccessMode
|
||||
* @param open_mode open mode from FS_OpenMode
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::close
|
||||
* @brief Close file
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::read
|
||||
* @brief Read bytes from file to buffer
|
||||
* @param file pointer to file object
|
||||
* @param buff pointer to buffer for reading
|
||||
* @param bytes_to_read how many bytes to read, must be smaller or equal to buffer size
|
||||
* @return how many bytes actually has been readed
|
||||
*
|
||||
* @var FS_File_Api::write
|
||||
* @brief Write bytes from buffer to file
|
||||
* @param file pointer to file object
|
||||
* @param buff pointer to buffer for writing
|
||||
* @param bytes_to_read how many bytes to write, must be smaller or equal to buffer size
|
||||
* @return how many bytes actually has been writed
|
||||
*
|
||||
* @var FS_File_Api::seek
|
||||
* @brief Move r/w pointer
|
||||
* @param file pointer to file object
|
||||
* @param offset offset to move r/w pointer
|
||||
* @param from_start set offset from start, or from current position
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::tell
|
||||
* @brief Get r/w pointer position
|
||||
* @param file pointer to file object
|
||||
* @return current r/w pointer position
|
||||
*
|
||||
* @var FS_File_Api::truncate
|
||||
* @brief Truncate file size to current r/w pointer position
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::size
|
||||
* @brief Fet file size
|
||||
* @param file pointer to file object
|
||||
* @return file size
|
||||
*
|
||||
* @var FS_File_Api::sync
|
||||
* @brief Write file cache to storage
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::eof
|
||||
* @brief Checks that the r/w pointer is at the end of the file
|
||||
* @param file pointer to file object
|
||||
* @return end of file flag
|
||||
*/
|
||||
typedef struct {
|
||||
bool (*open)(
|
||||
void* context,
|
||||
File* file,
|
||||
const char* path,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode);
|
||||
bool (*close)(void* context, File* file);
|
||||
uint16_t (*read)(void* context, File* file, void* buff, uint16_t bytes_to_read);
|
||||
uint16_t (*write)(void* context, File* file, const void* buff, uint16_t bytes_to_write);
|
||||
bool (*seek)(void* context, File* file, uint32_t offset, bool from_start);
|
||||
uint64_t (*tell)(void* context, File* file);
|
||||
bool (*truncate)(void* context, File* file);
|
||||
uint64_t (*size)(void* context, File* file);
|
||||
bool (*sync)(void* context, File* file);
|
||||
bool (*eof)(void* context, File* file);
|
||||
} FS_File_Api;
|
||||
|
||||
/** Dir api structure
|
||||
* @var FS_Dir_Api::open
|
||||
* @brief Open directory to get objects from
|
||||
* @param file pointer to file object, filled by api
|
||||
* @param path path to directory
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_Dir_Api::close
|
||||
* @brief Close directory
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_Dir_Api::read
|
||||
* @brief Read next object info in directory
|
||||
* @param file pointer to file object
|
||||
* @param fileinfo pointer to readed FileInfo, can be NULL
|
||||
* @param name pointer to name buffer, can be NULL
|
||||
* @param name_length name buffer length
|
||||
* @return success flag (if next object not exist also returns false and set error_id to FSE_NOT_EXIST)
|
||||
*
|
||||
* @var FS_Dir_Api::rewind
|
||||
* @brief Rewind to first object info in directory
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*/
|
||||
typedef struct {
|
||||
bool (*open)(void* context, File* file, const char* path);
|
||||
bool (*close)(void* context, File* file);
|
||||
bool (*read)(void* context, File* file, FileInfo* fileinfo, char* name, uint16_t name_length);
|
||||
bool (*rewind)(void* context, File* file);
|
||||
} FS_Dir_Api;
|
||||
|
||||
/** Common api structure
|
||||
* @var FS_Common_Api::stat
|
||||
* @brief Open directory to get objects from
|
||||
* @param path path to file/directory
|
||||
* @param fileinfo pointer to readed FileInfo, can be NULL
|
||||
* @param name pointer to name buffer, can be NULL
|
||||
* @param name_length name buffer length
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::remove
|
||||
* @brief Remove file/directory from storage,
|
||||
* directory must be empty,
|
||||
* file/directory must not be opened,
|
||||
* file/directory must not have FSF_READ_ONLY flag
|
||||
* @param path path to file/directory
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::rename
|
||||
* @brief Rename file/directory,
|
||||
* file/directory must not be opened
|
||||
* @param path path to file/directory
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::mkdir
|
||||
* @brief Create new directory
|
||||
* @param path path to new directory
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::fs_info
|
||||
* @brief Get total and free space storage values
|
||||
* @param fs_path path of fs
|
||||
* @param total_space pointer to total space value
|
||||
* @param free_space pointer to free space value
|
||||
* @return FS_Error error info
|
||||
*/
|
||||
typedef struct {
|
||||
FS_Error (*stat)(void* context, const char* path, FileInfo* fileinfo);
|
||||
FS_Error (*remove)(void* context, const char* path);
|
||||
FS_Error (*rename)(void* context, const char* old_path, const char* new_path);
|
||||
FS_Error (*mkdir)(void* context, const char* path);
|
||||
FS_Error (
|
||||
*fs_info)(void* context, const char* fs_path, uint64_t* total_space, uint64_t* free_space);
|
||||
} FS_Common_Api;
|
||||
|
||||
/** Errors api structure
|
||||
* @var FS_Error_Api::get_desc
|
||||
* @brief Get error description text
|
||||
* @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id)
|
||||
* @return pointer to description text
|
||||
*/
|
||||
typedef struct {
|
||||
const char* (*get_desc)(void* context, FS_Error error_id);
|
||||
} FS_Error_Api;
|
||||
|
||||
/** Full filesystem api structure */
|
||||
typedef struct {
|
||||
FS_File_Api file;
|
||||
FS_Dir_Api dir;
|
||||
FS_Common_Api common;
|
||||
FS_Error_Api error;
|
||||
void* context;
|
||||
} FS_Api;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
38
applications/storage/filesystem-api.c
Normal file
38
applications/storage/filesystem-api.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include "filesystem-api-defines.h"
|
||||
|
||||
const char* filesystem_api_error_get_desc(FS_Error error_id) {
|
||||
const char* result = "unknown error";
|
||||
switch(error_id) {
|
||||
case(FSE_OK):
|
||||
result = "OK";
|
||||
break;
|
||||
case(FSE_NOT_READY):
|
||||
result = "filesystem not ready";
|
||||
break;
|
||||
case(FSE_EXIST):
|
||||
result = "file/dir already exist";
|
||||
break;
|
||||
case(FSE_NOT_EXIST):
|
||||
result = "file/dir not exist";
|
||||
break;
|
||||
case(FSE_INVALID_PARAMETER):
|
||||
result = "invalid parameter";
|
||||
break;
|
||||
case(FSE_DENIED):
|
||||
result = "access denied";
|
||||
break;
|
||||
case(FSE_INVALID_NAME):
|
||||
result = "invalid name/path";
|
||||
break;
|
||||
case(FSE_INTERNAL):
|
||||
result = "internal error";
|
||||
break;
|
||||
case(FSE_NOT_IMPLEMENTED):
|
||||
result = "function not implemented";
|
||||
break;
|
||||
case(FSE_ALREADY_OPEN):
|
||||
result = "file is already open";
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
350
applications/storage/storage-cli.c
Normal file
350
applications/storage/storage-cli.c
Normal file
|
@ -0,0 +1,350 @@
|
|||
#include <furi.h>
|
||||
#include <cli/cli.h>
|
||||
#include <args.h>
|
||||
#include <storage/storage.h>
|
||||
#include <storage/storage-sd-api.h>
|
||||
#include <api-hal-version.h>
|
||||
|
||||
#define MAX_NAME_LENGTH 255
|
||||
|
||||
void storage_cli(Cli* cli, string_t args, void* context);
|
||||
|
||||
// app cli function
|
||||
void storage_cli_init() {
|
||||
Cli* cli = furi_record_open("cli");
|
||||
cli_add_command(cli, "storage", CliCommandFlagDefault, storage_cli, NULL);
|
||||
furi_record_close("cli");
|
||||
}
|
||||
|
||||
void storage_cli_print_usage() {
|
||||
printf("Usage:\r\n");
|
||||
printf("storage <cmd> <path> <args>\r\n");
|
||||
printf("The path must start with /int or /ext\r\n");
|
||||
printf("Cmd list:\r\n");
|
||||
printf("\tinfo\t - get FS info\r\n");
|
||||
printf("\tformat\t - format filesystem\r\n");
|
||||
printf("\tlist\t - list files and dirs\r\n");
|
||||
printf("\tremove\t - delete the file or directory\r\n");
|
||||
printf("\tread\t - read data from file and print file size and content to cli\r\n");
|
||||
printf(
|
||||
"\twrite\t - read data from cli and append it to file, <args> should contain how many bytes you want to write\r\n");
|
||||
printf("\tcopy\t - copy file to new file, <args> must contain new path\r\n");
|
||||
printf("\trename\t - move file to new file, <args> must contain new path\r\n");
|
||||
};
|
||||
|
||||
void storage_cli_print_error(FS_Error error) {
|
||||
printf("Storage error: %s\r\n", storage_error_get_desc(error));
|
||||
}
|
||||
|
||||
void storage_cli_print_path_error(string_t path, FS_Error error) {
|
||||
printf(
|
||||
"Storage error for path \"%s\": %s\r\n",
|
||||
string_get_cstr(path),
|
||||
storage_error_get_desc(error));
|
||||
}
|
||||
|
||||
void storage_cli_print_file_error(string_t path, File* file) {
|
||||
printf(
|
||||
"Storage error for path \"%s\": %s\r\n",
|
||||
string_get_cstr(path),
|
||||
storage_file_get_error_desc(file));
|
||||
}
|
||||
|
||||
void storage_cli_info(Cli* cli, string_t path) {
|
||||
Storage* api = furi_record_open("storage");
|
||||
|
||||
if(string_cmp_str(path, "/int") == 0) {
|
||||
uint64_t total_space;
|
||||
uint64_t free_space;
|
||||
FS_Error error = storage_common_fs_info(api, "/int", &total_space, &free_space);
|
||||
|
||||
if(error != FSE_OK) {
|
||||
storage_cli_print_path_error(path, error);
|
||||
} else {
|
||||
printf(
|
||||
"Label: %s\r\nType: LittleFS\r\n%lu KB total\r\n%lu KB free\r\n",
|
||||
api_hal_version_get_name_ptr(),
|
||||
(uint32_t)(total_space / 1024),
|
||||
(uint32_t)(free_space / 1024));
|
||||
}
|
||||
} else if(string_cmp_str(path, "/ext") == 0) {
|
||||
SDInfo sd_info;
|
||||
FS_Error error = storage_sd_info(api, &sd_info);
|
||||
|
||||
if(error != FSE_OK) {
|
||||
storage_cli_print_path_error(path, error);
|
||||
} else {
|
||||
printf(
|
||||
"Label: %s\r\nType: %s\r\n%lu KB total\r\n%lu KB free\r\n",
|
||||
sd_info.label,
|
||||
sd_api_get_fs_type_text(sd_info.fs_type),
|
||||
sd_info.kb_total,
|
||||
sd_info.kb_free);
|
||||
}
|
||||
} else {
|
||||
storage_cli_print_usage();
|
||||
}
|
||||
|
||||
furi_record_close("storage");
|
||||
};
|
||||
|
||||
void storage_cli_format(Cli* cli, string_t path) {
|
||||
if(string_cmp_str(path, "/int") == 0) {
|
||||
storage_cli_print_path_error(path, FSE_NOT_IMPLEMENTED);
|
||||
} else if(string_cmp_str(path, "/ext") == 0) {
|
||||
printf("Formatting SD card, all data will be lost. Are you sure (y/n)?\r\n");
|
||||
char answer = cli_getc(cli);
|
||||
if(answer == 'y' || answer == 'Y') {
|
||||
Storage* api = furi_record_open("storage");
|
||||
printf("Formatting, please wait...\r\n");
|
||||
|
||||
FS_Error error = storage_sd_format(api);
|
||||
|
||||
if(error != FSE_OK) {
|
||||
storage_cli_print_path_error(path, error);
|
||||
} else {
|
||||
printf("SD card was successfully formatted.\r\n");
|
||||
}
|
||||
furi_record_close("storage");
|
||||
} else {
|
||||
printf("Cancelled.\r\n");
|
||||
}
|
||||
} else {
|
||||
storage_cli_print_usage();
|
||||
}
|
||||
};
|
||||
|
||||
void storage_cli_list(Cli* cli, string_t path) {
|
||||
if(string_cmp_str(path, "/") == 0) {
|
||||
printf("\t[D] int\r\n");
|
||||
printf("\t[D] ext\r\n");
|
||||
printf("\t[D] any\r\n");
|
||||
} else {
|
||||
Storage* api = furi_record_open("storage");
|
||||
File* file = storage_file_alloc(api);
|
||||
|
||||
if(storage_dir_open(file, string_get_cstr(path))) {
|
||||
FileInfo fileinfo;
|
||||
char name[MAX_NAME_LENGTH];
|
||||
bool readed = false;
|
||||
|
||||
while(storage_dir_read(file, &fileinfo, name, MAX_NAME_LENGTH)) {
|
||||
readed = true;
|
||||
if(fileinfo.flags & FSF_DIRECTORY) {
|
||||
printf("\t[D] %s\r\n", name);
|
||||
} else {
|
||||
printf("\t[F] %s %lub\r\n", name, (uint32_t)(fileinfo.size));
|
||||
}
|
||||
}
|
||||
|
||||
if(!readed) {
|
||||
printf("\tEmpty\r\n");
|
||||
}
|
||||
} else {
|
||||
storage_cli_print_file_error(path, file);
|
||||
}
|
||||
|
||||
storage_dir_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close("storage");
|
||||
}
|
||||
}
|
||||
|
||||
void storage_cli_read(Cli* cli, string_t path) {
|
||||
Storage* api = furi_record_open("storage");
|
||||
File* file = storage_file_alloc(api);
|
||||
|
||||
if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
const uint16_t read_size = 128;
|
||||
uint16_t readed_size = 0;
|
||||
uint8_t* data = furi_alloc(read_size);
|
||||
|
||||
printf("Size: %lu\r\n", (uint32_t)storage_file_size(file));
|
||||
|
||||
do {
|
||||
readed_size = storage_file_read(file, data, read_size);
|
||||
for(uint16_t i = 0; i < readed_size; i++) {
|
||||
printf("%c", data[i]);
|
||||
}
|
||||
} while(readed_size > 0);
|
||||
printf("\r\n");
|
||||
|
||||
free(data);
|
||||
} else {
|
||||
storage_cli_print_file_error(path, file);
|
||||
}
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
furi_record_close("storage");
|
||||
}
|
||||
|
||||
void storage_cli_write(Cli* cli, string_t path, string_t args) {
|
||||
Storage* api = furi_record_open("storage");
|
||||
File* file = storage_file_alloc(api);
|
||||
|
||||
uint32_t size;
|
||||
int parsed_count = sscanf(string_get_cstr(args), "%lu", &size);
|
||||
|
||||
if(parsed_count == EOF || parsed_count != 1) {
|
||||
storage_cli_print_usage();
|
||||
} else {
|
||||
if(storage_file_open(file, string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
||||
const uint16_t write_size = 8;
|
||||
uint32_t readed_index = 0;
|
||||
uint8_t* data = furi_alloc(write_size);
|
||||
|
||||
while(true) {
|
||||
data[readed_index % write_size] = cli_getc(cli);
|
||||
printf("%c", data[readed_index % write_size]);
|
||||
fflush(stdout);
|
||||
readed_index++;
|
||||
|
||||
if(((readed_index % write_size) == 0)) {
|
||||
uint16_t writed_size = storage_file_write(file, data, write_size);
|
||||
|
||||
if(writed_size != write_size) {
|
||||
storage_cli_print_file_error(path, file);
|
||||
break;
|
||||
}
|
||||
} else if(readed_index == size) {
|
||||
uint16_t writed_size = storage_file_write(file, data, size % write_size);
|
||||
|
||||
if(writed_size != (size % write_size)) {
|
||||
storage_cli_print_file_error(path, file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(readed_index == size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("\r\n");
|
||||
|
||||
free(data);
|
||||
} else {
|
||||
storage_cli_print_file_error(path, file);
|
||||
}
|
||||
storage_file_close(file);
|
||||
}
|
||||
|
||||
storage_file_free(file);
|
||||
furi_record_close("storage");
|
||||
}
|
||||
|
||||
void storage_cli_copy(Cli* cli, string_t old_path, string_t args) {
|
||||
Storage* api = furi_record_open("storage");
|
||||
string_t new_path;
|
||||
string_init(new_path);
|
||||
|
||||
if(!args_read_probably_quoted_string_and_trim(args, new_path)) {
|
||||
storage_cli_print_usage();
|
||||
} else {
|
||||
FS_Error error =
|
||||
storage_common_copy(api, string_get_cstr(old_path), string_get_cstr(new_path));
|
||||
|
||||
if(error != FSE_OK) {
|
||||
storage_cli_print_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(new_path);
|
||||
furi_record_close("storage");
|
||||
}
|
||||
|
||||
void storage_cli_remove(Cli* cli, string_t path) {
|
||||
Storage* api = furi_record_open("storage");
|
||||
FS_Error error = storage_common_remove(api, string_get_cstr(path));
|
||||
|
||||
if(error != FSE_OK) {
|
||||
storage_cli_print_error(error);
|
||||
}
|
||||
|
||||
furi_record_close("storage");
|
||||
}
|
||||
|
||||
void storage_cli_rename(Cli* cli, string_t old_path, string_t args) {
|
||||
Storage* api = furi_record_open("storage");
|
||||
string_t new_path;
|
||||
string_init(new_path);
|
||||
|
||||
if(!args_read_probably_quoted_string_and_trim(args, new_path)) {
|
||||
storage_cli_print_usage();
|
||||
} else {
|
||||
FS_Error error =
|
||||
storage_common_rename(api, string_get_cstr(old_path), string_get_cstr(new_path));
|
||||
|
||||
if(error != FSE_OK) {
|
||||
storage_cli_print_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
string_clear(new_path);
|
||||
furi_record_close("storage");
|
||||
}
|
||||
|
||||
void storage_cli(Cli* cli, string_t args, void* context) {
|
||||
string_t cmd;
|
||||
string_t path;
|
||||
string_init(cmd);
|
||||
string_init(path);
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, cmd)) {
|
||||
storage_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(!args_read_probably_quoted_string_and_trim(args, path)) {
|
||||
storage_cli_print_usage();
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "info") == 0) {
|
||||
storage_cli_info(cli, path);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "format") == 0) {
|
||||
storage_cli_format(cli, path);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "list") == 0) {
|
||||
storage_cli_list(cli, path);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "read") == 0) {
|
||||
storage_cli_read(cli, path);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "write") == 0) {
|
||||
storage_cli_write(cli, path, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "copy") == 0) {
|
||||
storage_cli_copy(cli, path, args);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "remove") == 0) {
|
||||
storage_cli_remove(cli, path);
|
||||
break;
|
||||
}
|
||||
|
||||
if(string_cmp_str(cmd, "rename") == 0) {
|
||||
storage_cli_rename(cli, path, args);
|
||||
break;
|
||||
}
|
||||
|
||||
storage_cli_print_usage();
|
||||
} while(false);
|
||||
|
||||
string_clear(path);
|
||||
string_clear(cmd);
|
||||
}
|
383
applications/storage/storage-external-api.c
Normal file
383
applications/storage/storage-external-api.c
Normal file
|
@ -0,0 +1,383 @@
|
|||
#include "storage.h"
|
||||
#include "storage-i.h"
|
||||
#include "storage-message.h"
|
||||
|
||||
#define S_API_PROLOGUE \
|
||||
osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \
|
||||
furi_check(semaphore != NULL);
|
||||
|
||||
#define S_FILE_API_PROLOGUE \
|
||||
Storage* storage = file->storage; \
|
||||
furi_assert(storage);
|
||||
|
||||
#define S_API_EPILOGUE \
|
||||
furi_check(osMessageQueuePut(storage->message_queue, &message, 0, osWaitForever) == osOK); \
|
||||
osSemaphoreAcquire(semaphore, osWaitForever); \
|
||||
osSemaphoreDelete(semaphore);
|
||||
|
||||
#define S_API_MESSAGE(_command) \
|
||||
SAReturn return_data; \
|
||||
StorageMessage message = { \
|
||||
.semaphore = semaphore, \
|
||||
.command = _command, \
|
||||
.data = &data, \
|
||||
.return_data = &return_data, \
|
||||
};
|
||||
|
||||
#define S_API_DATA_FILE \
|
||||
SAData data = { \
|
||||
.file = { \
|
||||
.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);
|
||||
#define S_RETURN_ERROR (return_data.error_value);
|
||||
#define S_RETURN_CSTRING (return_data.cstring_value);
|
||||
|
||||
#define FILE_OPENED 1
|
||||
#define FILE_CLOSED 0
|
||||
|
||||
/****************** FILE ******************/
|
||||
|
||||
bool storage_file_open(
|
||||
File* file,
|
||||
const char* path,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.fopen = {
|
||||
.file = file,
|
||||
.path = path,
|
||||
.access_mode = access_mode,
|
||||
.open_mode = open_mode,
|
||||
}};
|
||||
|
||||
file->file_id = FILE_OPENED;
|
||||
|
||||
S_API_MESSAGE(StorageCommandFileOpen);
|
||||
S_API_EPILOGUE;
|
||||
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
bool storage_file_close(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandFileClose);
|
||||
S_API_EPILOGUE;
|
||||
|
||||
file->file_id = FILE_CLOSED;
|
||||
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.fread = {
|
||||
.file = file,
|
||||
.buff = buff,
|
||||
.bytes_to_read = bytes_to_read,
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandFileRead);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_UINT16;
|
||||
}
|
||||
|
||||
uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.fwrite = {
|
||||
.file = file,
|
||||
.buff = buff,
|
||||
.bytes_to_write = bytes_to_write,
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandFileWrite);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_UINT16;
|
||||
}
|
||||
|
||||
bool storage_file_seek(File* file, uint32_t offset, bool from_start) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.fseek = {
|
||||
.file = file,
|
||||
.offset = offset,
|
||||
.from_start = from_start,
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandFileSeek);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
uint64_t storage_file_tell(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandFileTell);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_UINT64;
|
||||
}
|
||||
|
||||
bool storage_file_truncate(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandFileTruncate);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
uint64_t storage_file_size(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandFileSize);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_UINT64;
|
||||
}
|
||||
|
||||
bool storage_file_sync(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandFileSync);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
bool storage_file_eof(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandFileEof);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
/****************** DIR ******************/
|
||||
|
||||
bool storage_dir_open(File* file, const char* path) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.dopen = {
|
||||
.file = file,
|
||||
.path = path,
|
||||
}};
|
||||
|
||||
file->file_id = FILE_OPENED;
|
||||
|
||||
S_API_MESSAGE(StorageCommandDirOpen);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
bool storage_dir_close(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandDirClose);
|
||||
S_API_EPILOGUE;
|
||||
|
||||
file->file_id = FILE_CLOSED;
|
||||
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.dread = {
|
||||
.file = file,
|
||||
.fileinfo = fileinfo,
|
||||
.name = name,
|
||||
.name_length = name_length,
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandDirRead);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
bool storage_dir_rewind(File* file) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_FILE;
|
||||
S_API_MESSAGE(StorageCommandDirRewind);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
/****************** COMMON ******************/
|
||||
|
||||
FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) {
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {.cstat = {.path = path, .fileinfo = fileinfo}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandCommonStat);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_common_remove(Storage* storage, const char* path) {
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_PATH;
|
||||
S_API_MESSAGE(StorageCommandCommonRemove);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) {
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.cpaths = {
|
||||
.old = old_path,
|
||||
.new = new_path,
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandCommonRename);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) {
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.cpaths = {
|
||||
.old = old_path,
|
||||
.new = new_path,
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandCommonCopy);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_common_mkdir(Storage* storage, const char* path) {
|
||||
S_API_PROLOGUE;
|
||||
S_API_DATA_PATH;
|
||||
S_API_MESSAGE(StorageCommandCommonMkDir);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_common_fs_info(
|
||||
Storage* storage,
|
||||
const char* fs_path,
|
||||
uint64_t* total_space,
|
||||
uint64_t* free_space) {
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.cfsinfo = {
|
||||
.fs_path = fs_path,
|
||||
.total_space = total_space,
|
||||
.free_space = free_space,
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandCommonFSInfo);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
/****************** ERROR ******************/
|
||||
|
||||
const char* storage_error_get_desc(FS_Error error_id) {
|
||||
return filesystem_api_error_get_desc(error_id);
|
||||
}
|
||||
|
||||
FS_Error storage_file_get_error(File* file) {
|
||||
furi_check(file != NULL);
|
||||
return file->error_id;
|
||||
}
|
||||
|
||||
const char* storage_file_get_error_desc(File* file) {
|
||||
furi_check(file != NULL);
|
||||
return filesystem_api_error_get_desc(file->error_id);
|
||||
}
|
||||
|
||||
/****************** Raw SD API ******************/
|
||||
|
||||
FS_Error storage_sd_format(Storage* storage) {
|
||||
S_API_PROLOGUE;
|
||||
SAData data = {};
|
||||
S_API_MESSAGE(StorageCommandSDFormat);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_sd_unmount(Storage* storage) {
|
||||
S_API_PROLOGUE;
|
||||
SAData data = {};
|
||||
S_API_MESSAGE(StorageCommandSDUnmount);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_sd_info(Storage* storage, SDInfo* info) {
|
||||
S_API_PROLOGUE;
|
||||
SAData data = {
|
||||
.sdinfo = {
|
||||
.info = info,
|
||||
}};
|
||||
S_API_MESSAGE(StorageCommandSDInfo);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
FS_Error storage_sd_status(Storage* storage) {
|
||||
S_API_PROLOGUE;
|
||||
SAData data = {};
|
||||
S_API_MESSAGE(StorageCommandSDStatus);
|
||||
S_API_EPILOGUE;
|
||||
return S_RETURN_ERROR;
|
||||
}
|
||||
|
||||
File* storage_file_alloc(Storage* storage) {
|
||||
File* file = furi_alloc(sizeof(File));
|
||||
file->file_id = FILE_CLOSED;
|
||||
file->storage = storage;
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
bool storage_file_is_open(File* file) {
|
||||
return (file->file_id != FILE_CLOSED);
|
||||
}
|
||||
|
||||
void storage_file_free(File* file) {
|
||||
if(storage_file_is_open(file)) {
|
||||
storage_file_close(file);
|
||||
}
|
||||
|
||||
free(file);
|
||||
}
|
212
applications/storage/storage-glue.c
Normal file
212
applications/storage/storage-glue.c
Normal file
|
@ -0,0 +1,212 @@
|
|||
#include "storage-glue.h"
|
||||
#include <api-hal.h>
|
||||
|
||||
/****************** storage file ******************/
|
||||
|
||||
void storage_file_init(StorageFile* obj) {
|
||||
obj->file = NULL;
|
||||
obj->type = ST_ERROR;
|
||||
obj->file_data = NULL;
|
||||
string_init(obj->path);
|
||||
}
|
||||
|
||||
void storage_file_init_set(StorageFile* obj, const StorageFile* src) {
|
||||
obj->file = src->file;
|
||||
obj->type = src->type;
|
||||
obj->file_data = src->file_data;
|
||||
string_init_set(obj->path, src->path);
|
||||
}
|
||||
|
||||
void storage_file_set(StorageFile* obj, const StorageFile* src) {
|
||||
obj->file = src->file;
|
||||
obj->type = src->type;
|
||||
obj->file_data = src->file_data;
|
||||
string_set(obj->path, src->path);
|
||||
}
|
||||
|
||||
void storage_file_clear(StorageFile* obj) {
|
||||
string_clear(obj->path);
|
||||
}
|
||||
|
||||
/****************** storage data ******************/
|
||||
|
||||
void storage_data_init(StorageData* storage) {
|
||||
storage->mutex = osMutexNew(NULL);
|
||||
furi_check(storage->mutex != NULL);
|
||||
storage->data = NULL;
|
||||
storage->status = StorageStatusNotReady;
|
||||
StorageFileList_init(storage->files);
|
||||
}
|
||||
|
||||
bool storage_data_lock(StorageData* storage) {
|
||||
api_hal_power_insomnia_enter();
|
||||
return (osMutexAcquire(storage->mutex, osWaitForever) == osOK);
|
||||
}
|
||||
|
||||
bool storage_data_unlock(StorageData* storage) {
|
||||
api_hal_power_insomnia_exit();
|
||||
return (osMutexRelease(storage->mutex) == osOK);
|
||||
}
|
||||
|
||||
StorageStatus storage_data_status(StorageData* storage) {
|
||||
StorageStatus status;
|
||||
|
||||
storage_data_lock(storage);
|
||||
status = storage->status;
|
||||
storage_data_unlock(storage);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
const char* storage_data_status_text(StorageData* storage) {
|
||||
const char* result = "unknown";
|
||||
switch(storage->status) {
|
||||
case StorageStatusOK:
|
||||
result = "ok";
|
||||
break;
|
||||
case StorageStatusNotReady:
|
||||
result = "not ready";
|
||||
break;
|
||||
case StorageStatusNotMounted:
|
||||
result = "not mounted";
|
||||
break;
|
||||
case StorageStatusNoFS:
|
||||
result = "no filesystem";
|
||||
break;
|
||||
case StorageStatusNotAccessible:
|
||||
result = "not accessible";
|
||||
break;
|
||||
case StorageStatusErrorInternal:
|
||||
result = "internal";
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/****************** storage glue ******************/
|
||||
|
||||
bool storage_has_file(const File* file, StorageData* storage_data) {
|
||||
bool result = false;
|
||||
|
||||
StorageFileList_it_t it;
|
||||
for(StorageFileList_it(it, storage_data->files); !StorageFileList_end_p(it);
|
||||
StorageFileList_next(it)) {
|
||||
const StorageFile* storage_file = StorageFileList_cref(it);
|
||||
|
||||
if(storage_file->file->file_id == file->file_id) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
StorageType storage_get_type_by_path(const char* path) {
|
||||
StorageType type = ST_ERROR;
|
||||
|
||||
const char* ext_path = "/ext";
|
||||
const char* int_path = "/int";
|
||||
const char* any_path = "/any";
|
||||
|
||||
if(strlen(path) >= strlen(ext_path) && memcmp(path, ext_path, strlen(ext_path)) == 0) {
|
||||
type = ST_EXT;
|
||||
} else if(strlen(path) >= strlen(int_path) && memcmp(path, int_path, strlen(int_path)) == 0) {
|
||||
type = ST_INT;
|
||||
} else if(strlen(path) >= strlen(any_path) && memcmp(path, any_path, strlen(any_path)) == 0) {
|
||||
type = ST_ANY;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
bool storage_path_already_open(const char* path, StorageFileList_t array) {
|
||||
bool open = false;
|
||||
|
||||
StorageFileList_it_t it;
|
||||
|
||||
for(StorageFileList_it(it, array); !StorageFileList_end_p(it); StorageFileList_next(it)) {
|
||||
const StorageFile* storage_file = StorageFileList_cref(it);
|
||||
|
||||
if(string_cmp(storage_file->path, path) == 0) {
|
||||
open = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return open;
|
||||
}
|
||||
|
||||
void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage) {
|
||||
StorageFile* founded_file = NULL;
|
||||
|
||||
StorageFileList_it_t it;
|
||||
|
||||
for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it);
|
||||
StorageFileList_next(it)) {
|
||||
StorageFile* storage_file = StorageFileList_ref(it);
|
||||
|
||||
if(storage_file->file->file_id == file->file_id) {
|
||||
founded_file = storage_file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_check(founded_file != NULL);
|
||||
|
||||
founded_file->file_data = file_data;
|
||||
}
|
||||
|
||||
void* storage_get_storage_file_data(const File* file, StorageData* storage) {
|
||||
const StorageFile* founded_file = NULL;
|
||||
|
||||
StorageFileList_it_t it;
|
||||
|
||||
for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it);
|
||||
StorageFileList_next(it)) {
|
||||
const StorageFile* storage_file = StorageFileList_cref(it);
|
||||
|
||||
if(storage_file->file->file_id == file->file_id) {
|
||||
founded_file = storage_file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_check(founded_file != NULL);
|
||||
|
||||
return founded_file->file_data;
|
||||
}
|
||||
|
||||
void storage_push_storage_file(
|
||||
File* file,
|
||||
const char* path,
|
||||
StorageType type,
|
||||
StorageData* storage) {
|
||||
StorageFile* storage_file = StorageFileList_push_new(storage->files);
|
||||
furi_check(storage_file != NULL);
|
||||
|
||||
file->file_id = (uint32_t)storage_file;
|
||||
storage_file->file = file;
|
||||
storage_file->type = type;
|
||||
string_set(storage_file->path, path);
|
||||
}
|
||||
|
||||
bool storage_pop_storage_file(File* file, StorageData* storage) {
|
||||
StorageFileList_it_t it;
|
||||
bool result = false;
|
||||
|
||||
for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it);
|
||||
StorageFileList_next(it)) {
|
||||
if(StorageFileList_cref(it)->file->file_id == file->file_id) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(result) {
|
||||
StorageFileList_remove(storage->files, it);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
79
applications/storage/storage-glue.h
Normal file
79
applications/storage/storage-glue.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "filesystem-api-internal.h"
|
||||
#include <m-string.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum { ST_EXT = 0, ST_INT = 1, ST_ANY, ST_ERROR } StorageType;
|
||||
|
||||
typedef struct StorageData StorageData;
|
||||
|
||||
typedef struct {
|
||||
void (*tick)(StorageData* storage);
|
||||
} StorageApi;
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
StorageType type;
|
||||
void* file_data;
|
||||
string_t path;
|
||||
} StorageFile;
|
||||
|
||||
typedef enum {
|
||||
StorageStatusOK, /**< storage ok */
|
||||
StorageStatusNotReady, /**< storage not ready (not initialized or waiting for data storage to appear) */
|
||||
StorageStatusNotMounted, /**< datastore appeared, but we cannot mount it */
|
||||
StorageStatusNoFS, /**< datastore appeared and mounted, but does not have a file system */
|
||||
StorageStatusNotAccessible, /**< datastore appeared and mounted, but not available */
|
||||
StorageStatusErrorInternal, /**< any other internal error */
|
||||
} StorageStatus;
|
||||
|
||||
void storage_file_init(StorageFile* obj);
|
||||
void storage_file_init_set(StorageFile* obj, const StorageFile* src);
|
||||
void storage_file_set(StorageFile* obj, const StorageFile* src);
|
||||
void storage_file_clear(StorageFile* obj);
|
||||
|
||||
void storage_data_init(StorageData* storage);
|
||||
bool storage_data_lock(StorageData* storage);
|
||||
bool storage_data_unlock(StorageData* storage);
|
||||
StorageStatus storage_data_status(StorageData* storage);
|
||||
const char* storage_data_status_text(StorageData* storage);
|
||||
|
||||
LIST_DEF(
|
||||
StorageFileList,
|
||||
StorageFile,
|
||||
(INIT(API_2(storage_file_init)),
|
||||
SET(API_6(storage_file_init_set)),
|
||||
INIT_SET(API_6(storage_file_set)),
|
||||
CLEAR(API_2(storage_file_clear))))
|
||||
|
||||
struct StorageData {
|
||||
FS_Api fs_api;
|
||||
StorageApi api;
|
||||
void* data;
|
||||
osMutexId_t mutex;
|
||||
StorageStatus status;
|
||||
StorageFileList_t files;
|
||||
};
|
||||
|
||||
bool storage_has_file(const File* file, StorageData* storage_data);
|
||||
StorageType storage_get_type_by_path(const char* path);
|
||||
bool storage_path_already_open(const char* 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,
|
||||
const char* path,
|
||||
StorageType type,
|
||||
StorageData* storage);
|
||||
bool storage_pop_storage_file(File* file, StorageData* storage);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
27
applications/storage/storage-i.h
Normal file
27
applications/storage/storage-i.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include "storage-glue.h"
|
||||
#include "storage-sd-api.h"
|
||||
#include "filesystem-api-internal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define STORAGE_COUNT (ST_INT + 1)
|
||||
|
||||
typedef struct {
|
||||
ViewPort* view_port;
|
||||
bool enabled;
|
||||
} StorageSDGui;
|
||||
|
||||
struct Storage {
|
||||
osMessageQueueId_t message_queue;
|
||||
StorageData storage[STORAGE_COUNT];
|
||||
StorageSDGui sd_gui;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
142
applications/storage/storage-message.h
Normal file
142
applications/storage/storage-message.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
const char* path;
|
||||
FS_AccessMode access_mode;
|
||||
FS_OpenMode open_mode;
|
||||
} SADataFOpen;
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
void* buff;
|
||||
uint16_t bytes_to_read;
|
||||
} SADataFRead;
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
const void* buff;
|
||||
uint16_t bytes_to_write;
|
||||
} SADataFWrite;
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
uint32_t offset;
|
||||
bool from_start;
|
||||
} SADataFSeek;
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
const char* path;
|
||||
} SADataDOpen;
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
FileInfo* fileinfo;
|
||||
char* name;
|
||||
uint16_t name_length;
|
||||
} SADataDRead;
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
FileInfo* fileinfo;
|
||||
} SADataCStat;
|
||||
|
||||
typedef struct {
|
||||
const char* old;
|
||||
const char* new;
|
||||
} SADataCPaths;
|
||||
|
||||
typedef struct {
|
||||
const char* fs_path;
|
||||
uint64_t* total_space;
|
||||
uint64_t* free_space;
|
||||
} SADataCFSInfo;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
} SADataError;
|
||||
|
||||
typedef struct {
|
||||
const char* path;
|
||||
} SADataPath;
|
||||
|
||||
typedef struct {
|
||||
File* file;
|
||||
} SADataFile;
|
||||
|
||||
typedef struct {
|
||||
SDInfo* info;
|
||||
} SAInfo;
|
||||
|
||||
typedef union {
|
||||
SADataFOpen fopen;
|
||||
SADataFRead fread;
|
||||
SADataFWrite fwrite;
|
||||
SADataFSeek fseek;
|
||||
|
||||
SADataDOpen dopen;
|
||||
SADataDRead dread;
|
||||
|
||||
SADataCStat cstat;
|
||||
SADataCPaths cpaths;
|
||||
SADataCFSInfo cfsinfo;
|
||||
|
||||
SADataError error;
|
||||
|
||||
SADataFile file;
|
||||
SADataPath path;
|
||||
|
||||
SAInfo sdinfo;
|
||||
} SAData;
|
||||
|
||||
typedef union {
|
||||
bool bool_value;
|
||||
uint16_t uint16_value;
|
||||
uint64_t uint64_value;
|
||||
FS_Error error_value;
|
||||
const char* cstring_value;
|
||||
} SAReturn;
|
||||
|
||||
typedef enum {
|
||||
StorageCommandFileOpen,
|
||||
StorageCommandFileClose,
|
||||
StorageCommandFileRead,
|
||||
StorageCommandFileWrite,
|
||||
StorageCommandFileSeek,
|
||||
StorageCommandFileTell,
|
||||
StorageCommandFileTruncate,
|
||||
StorageCommandFileSize,
|
||||
StorageCommandFileSync,
|
||||
StorageCommandFileEof,
|
||||
StorageCommandDirOpen,
|
||||
StorageCommandDirClose,
|
||||
StorageCommandDirRead,
|
||||
StorageCommandDirRewind,
|
||||
StorageCommandCommonStat,
|
||||
StorageCommandCommonRemove,
|
||||
StorageCommandCommonRename,
|
||||
StorageCommandCommonCopy,
|
||||
StorageCommandCommonMkDir,
|
||||
StorageCommandCommonFSInfo,
|
||||
StorageCommandSDFormat,
|
||||
StorageCommandSDUnmount,
|
||||
StorageCommandSDInfo,
|
||||
StorageCommandSDStatus,
|
||||
} StorageCommand;
|
||||
|
||||
typedef struct {
|
||||
osSemaphoreId_t semaphore;
|
||||
StorageCommand command;
|
||||
SAData* data;
|
||||
SAReturn* return_data;
|
||||
} StorageMessage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
584
applications/storage/storage-processing.c
Normal file
584
applications/storage/storage-processing.c
Normal file
|
@ -0,0 +1,584 @@
|
|||
#include "storage-processing.h"
|
||||
|
||||
#define FS_CALL(_storage, _fn) \
|
||||
storage_data_lock(_storage); \
|
||||
ret = _storage->fs_api._fn; \
|
||||
storage_data_unlock(_storage);
|
||||
|
||||
#define ST_CALL(_storage, _fn) \
|
||||
storage_data_lock(_storage); \
|
||||
ret = _storage->api._fn; \
|
||||
storage_data_unlock(_storage);
|
||||
|
||||
static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) {
|
||||
StorageData* storage;
|
||||
|
||||
if(type == ST_ANY) {
|
||||
type = ST_INT;
|
||||
StorageData* ext_storage = &app->storage[ST_EXT];
|
||||
|
||||
if(storage_data_status(ext_storage) == StorageStatusOK) {
|
||||
type = ST_EXT;
|
||||
}
|
||||
}
|
||||
storage = &app->storage[type];
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
static bool storage_type_is_not_valid(StorageType type) {
|
||||
return type >= ST_ERROR;
|
||||
}
|
||||
|
||||
static StorageData* get_storage_by_file(File* file, StorageData* storages) {
|
||||
StorageData* storage_data = NULL;
|
||||
|
||||
for(uint8_t i = 0; i < STORAGE_COUNT; i++) {
|
||||
if(storage_has_file(file, &storages[i])) {
|
||||
storage_data = &storages[i];
|
||||
}
|
||||
}
|
||||
|
||||
return storage_data;
|
||||
}
|
||||
|
||||
const char* remove_vfs(const char* path) {
|
||||
return path + MIN(4, strlen(path));
|
||||
}
|
||||
|
||||
/******************* File Functions *******************/
|
||||
|
||||
bool storage_process_file_open(
|
||||
Storage* app,
|
||||
File* file,
|
||||
const char* path,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode) {
|
||||
bool ret = false;
|
||||
StorageType type = storage_get_type_by_path(path);
|
||||
StorageData* storage;
|
||||
file->error_id = FSE_OK;
|
||||
|
||||
if(storage_type_is_not_valid(type)) {
|
||||
file->error_id = FSE_INVALID_NAME;
|
||||
} else {
|
||||
storage = storage_get_storage_by_type(app, type);
|
||||
if(storage_path_already_open(path, storage->files)) {
|
||||
file->error_id = FSE_ALREADY_OPEN;
|
||||
} else {
|
||||
storage_push_storage_file(file, path, type, storage);
|
||||
FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool storage_process_file_close(Storage* app, File* file) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.close(storage, file));
|
||||
storage_pop_storage_file(file, storage);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
storage_process_file_read(Storage* app, File* file, void* buff, uint16_t const bytes_to_read) {
|
||||
uint16_t ret = 0;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.read(storage, file, buff, bytes_to_read));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint16_t storage_process_file_write(
|
||||
Storage* app,
|
||||
File* file,
|
||||
const void* buff,
|
||||
uint16_t const bytes_to_write) {
|
||||
uint16_t ret = 0;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.write(storage, file, buff, bytes_to_write));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool storage_process_file_seek(
|
||||
Storage* app,
|
||||
File* file,
|
||||
const uint32_t offset,
|
||||
const bool from_start) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.seek(storage, file, offset, from_start));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t storage_process_file_tell(Storage* app, File* file) {
|
||||
uint64_t ret = 0;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.tell(storage, file));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool storage_process_file_truncate(Storage* app, File* file) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.truncate(storage, file));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool storage_process_file_sync(Storage* app, File* file) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.sync(storage, file));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t storage_process_file_size(Storage* app, File* file) {
|
||||
uint64_t ret = 0;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.size(storage, file));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool storage_process_file_eof(Storage* app, File* file) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, file.eof(storage, file));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/******************* Dir Functions *******************/
|
||||
|
||||
bool storage_process_dir_open(Storage* app, File* file, const char* path) {
|
||||
bool ret = false;
|
||||
StorageType type = storage_get_type_by_path(path);
|
||||
StorageData* storage;
|
||||
file->error_id = FSE_OK;
|
||||
|
||||
if(storage_type_is_not_valid(type)) {
|
||||
file->error_id = FSE_INVALID_NAME;
|
||||
} else {
|
||||
storage = storage_get_storage_by_type(app, type);
|
||||
if(storage_path_already_open(path, storage->files)) {
|
||||
file->error_id = FSE_ALREADY_OPEN;
|
||||
} else {
|
||||
storage_push_storage_file(file, path, type, storage);
|
||||
FS_CALL(storage, dir.open(storage, file, remove_vfs(path)));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool storage_process_dir_close(Storage* app, File* file) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, dir.close(storage, file));
|
||||
storage_pop_storage_file(file, storage);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool storage_process_dir_read(
|
||||
Storage* app,
|
||||
File* file,
|
||||
FileInfo* fileinfo,
|
||||
char* name,
|
||||
const uint16_t name_length) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool storage_process_dir_rewind(Storage* app, File* file) {
|
||||
bool ret = false;
|
||||
StorageData* storage = get_storage_by_file(file, app->storage);
|
||||
|
||||
if(storage == NULL) {
|
||||
file->error_id = FSE_INVALID_PARAMETER;
|
||||
} else {
|
||||
FS_CALL(storage, dir.rewind(storage, file));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/******************* Common FS Functions *******************/
|
||||
|
||||
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(path);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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(path);
|
||||
|
||||
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(path, storage->files)) {
|
||||
ret = FSE_ALREADY_OPEN;
|
||||
break;
|
||||
}
|
||||
|
||||
FS_CALL(storage, common.remove(storage, remove_vfs(path)));
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static FS_Error storage_process_common_copy(Storage* app, const char* old, const char* new) {
|
||||
FS_Error ret = FSE_INTERNAL;
|
||||
File file_old;
|
||||
File file_new;
|
||||
|
||||
do {
|
||||
if(!storage_process_file_open(app, &file_old, old, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
ret = storage_file_get_error(&file_old);
|
||||
storage_process_file_close(app, &file_old);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!storage_process_file_open(app, &file_new, new, FSAM_WRITE, FSOM_CREATE_NEW)) {
|
||||
ret = storage_file_get_error(&file_new);
|
||||
storage_process_file_close(app, &file_new);
|
||||
break;
|
||||
}
|
||||
|
||||
const uint16_t buffer_size = 64;
|
||||
uint8_t* buffer = malloc(buffer_size);
|
||||
uint16_t readed_size = 0;
|
||||
uint16_t writed_size = 0;
|
||||
|
||||
while(true) {
|
||||
readed_size = storage_process_file_read(app, &file_old, buffer, buffer_size);
|
||||
ret = storage_file_get_error(&file_old);
|
||||
if(readed_size == 0) break;
|
||||
|
||||
writed_size = storage_process_file_write(app, &file_new, buffer, readed_size);
|
||||
ret = storage_file_get_error(&file_new);
|
||||
if(writed_size < readed_size) break;
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
storage_process_file_close(app, &file_old);
|
||||
storage_process_file_close(app, &file_new);
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static FS_Error storage_process_common_rename(Storage* app, const char* old, const char* new) {
|
||||
FS_Error ret = FSE_INTERNAL;
|
||||
StorageType type_old = storage_get_type_by_path(old);
|
||||
StorageType type_new = storage_get_type_by_path(new);
|
||||
|
||||
if(storage_type_is_not_valid(type_old) || storage_type_is_not_valid(type_old)) {
|
||||
ret = FSE_INVALID_NAME;
|
||||
} else {
|
||||
if(type_old != type_new) {
|
||||
ret = storage_process_common_copy(app, old, new);
|
||||
if(ret == FSE_OK) {
|
||||
ret = storage_process_common_remove(app, old);
|
||||
}
|
||||
} else {
|
||||
StorageData* storage = storage_get_storage_by_type(app, type_old);
|
||||
FS_CALL(storage, common.rename(storage, remove_vfs(old), remove_vfs(new)));
|
||||
}
|
||||
}
|
||||
|
||||
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(path);
|
||||
|
||||
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.mkdir(storage, remove_vfs(path)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static FS_Error storage_process_common_fs_info(
|
||||
Storage* app,
|
||||
const char* fs_path,
|
||||
uint64_t* total_space,
|
||||
uint64_t* free_space) {
|
||||
FS_Error ret = FSE_OK;
|
||||
StorageType type = storage_get_type_by_path(fs_path);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************** Raw SD API ******************/
|
||||
// TODO think about implementing a custom storage API to split that kind of api linkage
|
||||
#include "storages/storage-ext.h"
|
||||
|
||||
static FS_Error storage_process_sd_format(Storage* app) {
|
||||
FS_Error ret = FSE_OK;
|
||||
|
||||
if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) {
|
||||
ret = FSE_NOT_READY;
|
||||
} else {
|
||||
ret = sd_format_card(&app->storage[ST_EXT]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static FS_Error storage_process_sd_unmount(Storage* app) {
|
||||
FS_Error ret = FSE_OK;
|
||||
|
||||
if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) {
|
||||
ret = FSE_NOT_READY;
|
||||
} else {
|
||||
sd_unmount_card(&app->storage[ST_EXT]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static FS_Error storage_process_sd_info(Storage* app, SDInfo* info) {
|
||||
FS_Error ret = FSE_OK;
|
||||
|
||||
if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) {
|
||||
ret = FSE_NOT_READY;
|
||||
} else {
|
||||
ret = sd_card_info(&app->storage[ST_EXT], info);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static FS_Error storage_process_sd_status(Storage* app) {
|
||||
FS_Error ret;
|
||||
StorageStatus status = storage_data_status(&app->storage[ST_EXT]);
|
||||
|
||||
switch(status) {
|
||||
case StorageStatusOK:
|
||||
ret = FSE_OK;
|
||||
break;
|
||||
case StorageStatusNotReady:
|
||||
ret = FSE_NOT_READY;
|
||||
break;
|
||||
default:
|
||||
ret = FSE_INTERNAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************** API calls processing ******************/
|
||||
|
||||
void storage_process_message(Storage* app, StorageMessage* message) {
|
||||
switch(message->command) {
|
||||
case StorageCommandFileOpen:
|
||||
message->return_data->bool_value = storage_process_file_open(
|
||||
app,
|
||||
message->data->fopen.file,
|
||||
message->data->fopen.path,
|
||||
message->data->fopen.access_mode,
|
||||
message->data->fopen.open_mode);
|
||||
break;
|
||||
case StorageCommandFileClose:
|
||||
message->return_data->bool_value =
|
||||
storage_process_file_close(app, message->data->fopen.file);
|
||||
break;
|
||||
case StorageCommandFileRead:
|
||||
message->return_data->uint16_value = storage_process_file_read(
|
||||
app,
|
||||
message->data->fread.file,
|
||||
message->data->fread.buff,
|
||||
message->data->fread.bytes_to_read);
|
||||
break;
|
||||
case StorageCommandFileWrite:
|
||||
message->return_data->uint16_value = storage_process_file_write(
|
||||
app,
|
||||
message->data->fwrite.file,
|
||||
message->data->fwrite.buff,
|
||||
message->data->fwrite.bytes_to_write);
|
||||
break;
|
||||
case StorageCommandFileSeek:
|
||||
message->return_data->bool_value = storage_process_file_seek(
|
||||
app,
|
||||
message->data->fseek.file,
|
||||
message->data->fseek.offset,
|
||||
message->data->fseek.from_start);
|
||||
break;
|
||||
case StorageCommandFileTell:
|
||||
message->return_data->uint64_value =
|
||||
storage_process_file_tell(app, message->data->file.file);
|
||||
break;
|
||||
case StorageCommandFileTruncate:
|
||||
message->return_data->bool_value =
|
||||
storage_process_file_truncate(app, message->data->file.file);
|
||||
break;
|
||||
case StorageCommandFileSync:
|
||||
message->return_data->bool_value =
|
||||
storage_process_file_sync(app, message->data->file.file);
|
||||
break;
|
||||
case StorageCommandFileSize:
|
||||
message->return_data->uint64_value =
|
||||
storage_process_file_size(app, message->data->file.file);
|
||||
break;
|
||||
case StorageCommandFileEof:
|
||||
message->return_data->bool_value = storage_process_file_eof(app, message->data->file.file);
|
||||
break;
|
||||
|
||||
case StorageCommandDirOpen:
|
||||
message->return_data->bool_value =
|
||||
storage_process_dir_open(app, message->data->dopen.file, message->data->dopen.path);
|
||||
break;
|
||||
case StorageCommandDirClose:
|
||||
message->return_data->bool_value =
|
||||
storage_process_dir_close(app, message->data->file.file);
|
||||
break;
|
||||
case StorageCommandDirRead:
|
||||
message->return_data->bool_value = storage_process_dir_read(
|
||||
app,
|
||||
message->data->dread.file,
|
||||
message->data->dread.fileinfo,
|
||||
message->data->dread.name,
|
||||
message->data->dread.name_length);
|
||||
break;
|
||||
case StorageCommandDirRewind:
|
||||
message->return_data->bool_value =
|
||||
storage_process_dir_rewind(app, message->data->file.file);
|
||||
break;
|
||||
case StorageCommandCommonStat:
|
||||
message->return_data->error_value = storage_process_common_stat(
|
||||
app, message->data->cstat.path, message->data->cstat.fileinfo);
|
||||
break;
|
||||
case StorageCommandCommonRemove:
|
||||
message->return_data->error_value =
|
||||
storage_process_common_remove(app, message->data->path.path);
|
||||
break;
|
||||
case StorageCommandCommonRename:
|
||||
message->return_data->error_value = storage_process_common_rename(
|
||||
app, message->data->cpaths.old, message->data->cpaths.new);
|
||||
break;
|
||||
case StorageCommandCommonCopy:
|
||||
message->return_data->error_value =
|
||||
storage_process_common_copy(app, message->data->cpaths.old, message->data->cpaths.new);
|
||||
break;
|
||||
case StorageCommandCommonMkDir:
|
||||
message->return_data->error_value =
|
||||
storage_process_common_mkdir(app, message->data->path.path);
|
||||
break;
|
||||
case StorageCommandCommonFSInfo:
|
||||
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);
|
||||
break;
|
||||
case StorageCommandSDFormat:
|
||||
message->return_data->error_value = storage_process_sd_format(app);
|
||||
break;
|
||||
case StorageCommandSDUnmount:
|
||||
message->return_data->error_value = storage_process_sd_unmount(app);
|
||||
break;
|
||||
case StorageCommandSDInfo:
|
||||
message->return_data->error_value =
|
||||
storage_process_sd_info(app, message->data->sdinfo.info);
|
||||
break;
|
||||
case StorageCommandSDStatus:
|
||||
message->return_data->error_value = storage_process_sd_status(app);
|
||||
break;
|
||||
}
|
||||
|
||||
osSemaphoreRelease(message->semaphore);
|
||||
}
|
16
applications/storage/storage-processing.h
Normal file
16
applications/storage/storage-processing.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "storage.h"
|
||||
#include "storage-i.h"
|
||||
#include "storage-message.h"
|
||||
#include "storage-glue.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void storage_process_message(Storage* app, StorageMessage* message);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
21
applications/storage/storage-sd-api.c
Normal file
21
applications/storage/storage-sd-api.c
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "storage-sd-api.h"
|
||||
|
||||
const char* sd_api_get_fs_type_text(SDFsType fs_type) {
|
||||
switch(fs_type) {
|
||||
case(FST_FAT12):
|
||||
return "FAT12";
|
||||
break;
|
||||
case(FST_FAT16):
|
||||
return "FAT16";
|
||||
break;
|
||||
case(FST_FAT32):
|
||||
return "FAT32";
|
||||
break;
|
||||
case(FST_EXFAT):
|
||||
return "EXFAT";
|
||||
break;
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
}
|
34
applications/storage/storage-sd-api.h
Normal file
34
applications/storage/storage-sd-api.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "filesystem-api-defines.h"
|
||||
#include <fatfs.h>
|
||||
#include "storage-glue.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SD_LABEL_LENGTH 34
|
||||
|
||||
typedef enum {
|
||||
FST_FAT12 = FS_FAT12,
|
||||
FST_FAT16 = FS_FAT16,
|
||||
FST_FAT32 = FS_FAT32,
|
||||
FST_EXFAT = FS_EXFAT,
|
||||
} SDFsType;
|
||||
|
||||
typedef struct {
|
||||
SDFsType fs_type;
|
||||
uint32_t kb_total;
|
||||
uint32_t kb_free;
|
||||
uint16_t cluster_size;
|
||||
uint16_t sector_size;
|
||||
char label[SD_LABEL_LENGTH];
|
||||
FS_Error error;
|
||||
} SDInfo;
|
||||
|
||||
const char* sd_api_get_fs_type_text(SDFsType fs_type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
341
applications/storage/storage-test-app.c
Normal file
341
applications/storage/storage-test-app.c
Normal file
|
@ -0,0 +1,341 @@
|
|||
#include <furi.h>
|
||||
#include <api-hal.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "storage-test"
|
||||
#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) {
|
||||
string_t str_path;
|
||||
string_init_printf(str_path, "%s/test-folder", path);
|
||||
|
||||
FURI_LOG_I(TAG, "--------- START \"%s\" ---------", path);
|
||||
|
||||
// mkdir
|
||||
FS_Error result = storage_common_mkdir(api, 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, 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));
|
||||
}
|
||||
|
||||
string_clear(str_path);
|
||||
}
|
||||
|
||||
static void do_test_end(Storage* api, const char* path) {
|
||||
uint64_t total_space;
|
||||
uint64_t free_space;
|
||||
string_t str_path_1;
|
||||
string_t str_path_2;
|
||||
string_init_printf(str_path_1, "%s/test-folder", path);
|
||||
string_init_printf(str_path_2, "%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, string_get_cstr(str_path_1), 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, 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
|
||||
string_printf(str_path_1, "%s/test.txt", path);
|
||||
string_printf(str_path_2, "%s/test2.txt", path);
|
||||
|
||||
result = storage_common_rename(api, string_get_cstr(str_path_1), 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, 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));
|
||||
}
|
||||
|
||||
string_clear(str_path_1);
|
||||
string_clear(str_path_2);
|
||||
}
|
||||
|
||||
int32_t storage_app_test(void* p) {
|
||||
Storage* api = furi_record_open("storage");
|
||||
do_test_start(api, "/int");
|
||||
do_test_start(api, "/any");
|
||||
do_test_start(api, "/ext");
|
||||
|
||||
do_file_test(api, "/int/test.txt");
|
||||
do_file_test(api, "/any/test.txt");
|
||||
do_file_test(api, "/ext/test.txt");
|
||||
|
||||
do_dir_test(api, "/int");
|
||||
do_dir_test(api, "/any");
|
||||
do_dir_test(api, "/ext");
|
||||
|
||||
do_test_end(api, "/int");
|
||||
do_test_end(api, "/any");
|
||||
do_test_end(api, "/ext");
|
||||
|
||||
while(true) {
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
96
applications/storage/storage.c
Normal file
96
applications/storage/storage.c
Normal file
|
@ -0,0 +1,96 @@
|
|||
#include "storage.h"
|
||||
#include "storage-i.h"
|
||||
#include "storage-message.h"
|
||||
#include "storage-processing.h"
|
||||
#include "storages/storage-int.h"
|
||||
#include "storages/storage-ext.h"
|
||||
|
||||
#define STORAGE_TICK 1000
|
||||
|
||||
#define ICON_SD_MOUNTED &I_SDcardMounted_11x8
|
||||
#define ICON_SD_ERROR &I_SDcardFail_11x8
|
||||
|
||||
static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(canvas);
|
||||
furi_assert(context);
|
||||
Storage* app = context;
|
||||
|
||||
// here we don't care about thread race when reading / writing status
|
||||
switch(app->storage[ST_EXT].status) {
|
||||
case StorageStatusNotReady:
|
||||
break;
|
||||
case StorageStatusOK:
|
||||
canvas_draw_icon(canvas, 0, 0, ICON_SD_MOUNTED);
|
||||
break;
|
||||
default:
|
||||
canvas_draw_icon(canvas, 0, 0, ICON_SD_ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Storage* storage_app_alloc() {
|
||||
Storage* app = malloc(sizeof(Storage));
|
||||
app->message_queue = osMessageQueueNew(8, sizeof(StorageMessage), NULL);
|
||||
|
||||
for(uint8_t i = 0; i < STORAGE_COUNT; i++) {
|
||||
storage_data_init(&app->storage[i]);
|
||||
}
|
||||
|
||||
storage_int_init(&app->storage[ST_INT]);
|
||||
storage_ext_init(&app->storage[ST_EXT]);
|
||||
|
||||
// sd icon gui
|
||||
app->sd_gui.enabled = false;
|
||||
app->sd_gui.view_port = view_port_alloc();
|
||||
view_port_set_width(app->sd_gui.view_port, icon_get_width(ICON_SD_MOUNTED));
|
||||
view_port_draw_callback_set(app->sd_gui.view_port, storage_app_sd_icon_draw_callback, app);
|
||||
view_port_enabled_set(app->sd_gui.view_port, false);
|
||||
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, app->sd_gui.view_port, GuiLayerStatusBarLeft);
|
||||
furi_record_close("gui");
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void storage_tick(Storage* app) {
|
||||
for(uint8_t i = 0; i < STORAGE_COUNT; i++) {
|
||||
StorageApi api = app->storage[i].api;
|
||||
if(api.tick != NULL) {
|
||||
api.tick(&app->storage[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// storage not enabled but was enabled (sd card unmount)
|
||||
if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) {
|
||||
app->sd_gui.enabled = false;
|
||||
view_port_enabled_set(app->sd_gui.view_port, false);
|
||||
}
|
||||
|
||||
// storage enabled (or in error state) but was not enabled (sd card mount)
|
||||
if((app->storage[ST_EXT].status == StorageStatusOK ||
|
||||
app->storage[ST_EXT].status == StorageStatusNotMounted ||
|
||||
app->storage[ST_EXT].status == StorageStatusNoFS ||
|
||||
app->storage[ST_EXT].status == StorageStatusNotAccessible ||
|
||||
app->storage[ST_EXT].status == StorageStatusErrorInternal) &&
|
||||
app->sd_gui.enabled == false) {
|
||||
app->sd_gui.enabled = true;
|
||||
view_port_enabled_set(app->sd_gui.view_port, true);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t storage_app(void* p) {
|
||||
Storage* app = storage_app_alloc();
|
||||
furi_record_create("storage", app);
|
||||
|
||||
StorageMessage message;
|
||||
while(1) {
|
||||
if(osMessageQueueGet(app->message_queue, &message, NULL, STORAGE_TICK) == osOK) {
|
||||
storage_process_message(app, &message);
|
||||
} else {
|
||||
storage_tick(app);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
235
applications/storage/storage.h
Normal file
235
applications/storage/storage.h
Normal file
|
@ -0,0 +1,235 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "filesystem-api-defines.h"
|
||||
#include "storage-sd-api.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct Storage Storage;
|
||||
|
||||
/** Allocates and initializes a file descriptor
|
||||
* @return File*
|
||||
*/
|
||||
File* storage_file_alloc(Storage* storage);
|
||||
|
||||
/** Frees the file descriptor. Closes the file if it was open.
|
||||
*/
|
||||
void storage_file_free(File* file);
|
||||
|
||||
/******************* File Functions *******************/
|
||||
|
||||
/** Opens an existing file or create a new one.
|
||||
* @param file pointer to file object.
|
||||
* @param path path to file
|
||||
* @param access_mode access mode from FS_AccessMode
|
||||
* @param open_mode open mode from FS_OpenMode
|
||||
* @return success flag. You need to close the file even if the open operation failed.
|
||||
*/
|
||||
bool storage_file_open(
|
||||
File* file,
|
||||
const char* path,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode);
|
||||
|
||||
/** Close the file.
|
||||
* @param file pointer to a file object, the file object will be freed.
|
||||
* @return success flag
|
||||
*/
|
||||
bool storage_file_close(File* file);
|
||||
|
||||
/** Tells if the file is open
|
||||
* @param file pointer to a file object
|
||||
* @return bool true if file is open
|
||||
*/
|
||||
bool storage_file_is_open(File* file);
|
||||
|
||||
/** Reads bytes from a file into a buffer
|
||||
* @param file pointer to file object.
|
||||
* @param buff pointer to a buffer, for reading
|
||||
* @param bytes_to_read how many bytes to read. Must be less than or equal to the size of the buffer.
|
||||
* @return uint16_t how many bytes were actually readed
|
||||
*/
|
||||
uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read);
|
||||
|
||||
/** Writes bytes from a buffer to a file
|
||||
* @param file pointer to file object.
|
||||
* @param buff pointer to buffer, for writing
|
||||
* @param bytes_to_write how many bytes to write. Must be less than or equal to the size of the buffer.
|
||||
* @return uint16_t how many bytes were actually written
|
||||
*/
|
||||
uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write);
|
||||
|
||||
/** Moves the r/w pointer
|
||||
* @param file pointer to file object.
|
||||
* @param offset offset to move the r/w pointer
|
||||
* @param from_start set an offset from the start or from the current position
|
||||
* @return success flag
|
||||
*/
|
||||
bool storage_file_seek(File* file, uint32_t offset, bool from_start);
|
||||
|
||||
/** Gets the position of the r/w pointer
|
||||
* @param file pointer to file object.
|
||||
* @return uint64_t position of the r/w pointer
|
||||
*/
|
||||
uint64_t storage_file_tell(File* file);
|
||||
|
||||
/** Truncates the file size to the current position of the r/w pointer
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
*/
|
||||
bool storage_file_truncate(File* file);
|
||||
|
||||
/** Gets the size of the file
|
||||
* @param file pointer to file object.
|
||||
* @return uint64_t size of the file
|
||||
*/
|
||||
uint64_t storage_file_size(File* file);
|
||||
|
||||
/** Writes file cache to storage
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
*/
|
||||
bool storage_file_sync(File* file);
|
||||
|
||||
/** Checks that the r/w pointer is at the end of the file
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
*/
|
||||
bool storage_file_eof(File* file);
|
||||
|
||||
/******************* Dir Functions *******************/
|
||||
|
||||
/** Opens a directory to get objects from it
|
||||
* @param app pointer to the api
|
||||
* @param file pointer to file object.
|
||||
* @param path path to directory
|
||||
* @return bool success flag. You need to close the directory even if the open operation failed.
|
||||
*/
|
||||
bool storage_dir_open(File* file, const char* path);
|
||||
|
||||
/** Close the directory. Also free file handle structure and point it to the NULL.
|
||||
* @param file pointer to a file object.
|
||||
* @return bool success flag
|
||||
*/
|
||||
bool storage_dir_close(File* file);
|
||||
|
||||
/** Reads the next object in the directory
|
||||
* @param file pointer to file object.
|
||||
* @param fileinfo pointer to the readed FileInfo, may be NULL
|
||||
* @param name pointer to name buffer, may be NULL
|
||||
* @param name_length name buffer length
|
||||
* @return success flag (if the next object does not exist, it also returns false and sets the file error id to FSE_NOT_EXIST)
|
||||
*/
|
||||
bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length);
|
||||
|
||||
/** Rewinds the read pointer to first item in the directory
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
*/
|
||||
bool storage_dir_rewind(File* file);
|
||||
|
||||
/******************* Common Functions *******************/
|
||||
|
||||
/** Retrieves information about a file/directory
|
||||
* @param app pointer to the api
|
||||
* @param path path to file/directory
|
||||
* @param fileinfo pointer to the readed FileInfo, may be NULL
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo);
|
||||
|
||||
/** Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open
|
||||
* @param app pointer to the api
|
||||
* @param path
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_common_remove(Storage* storage, const char* path);
|
||||
|
||||
/** Renames file/directory, file/directory must not be open
|
||||
* @param app pointer to the api
|
||||
* @param old_path old path
|
||||
* @param new_path new path
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path);
|
||||
|
||||
/** Copy file, file must not be open
|
||||
* @param app pointer to the api
|
||||
* @param old_path old path
|
||||
* @param new_path new path
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path);
|
||||
|
||||
/** Creates a directory
|
||||
* @param app pointer to the api
|
||||
* @param path directory path
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_common_mkdir(Storage* storage, const char* path);
|
||||
|
||||
/** Gets general information about the storage
|
||||
* @param app pointer to the api
|
||||
* @param fs_path the path to the storage of interest
|
||||
* @param total_space pointer to total space record, will be filled
|
||||
* @param free_space pointer to free space record, will be filled
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_common_fs_info(
|
||||
Storage* storage,
|
||||
const char* fs_path,
|
||||
uint64_t* total_space,
|
||||
uint64_t* free_space);
|
||||
|
||||
/******************* Error Functions *******************/
|
||||
|
||||
/** Retrieves the error text from the error id
|
||||
* @param error_id error id
|
||||
* @return const char* error text
|
||||
*/
|
||||
const char* storage_error_get_desc(FS_Error error_id);
|
||||
|
||||
/** Retrieves the error id from the file object
|
||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR ID IF THE FILE HAS BEEN CLOSED
|
||||
* @return FS_Error error id
|
||||
*/
|
||||
FS_Error storage_file_get_error(File* file);
|
||||
|
||||
/** Retrieves the error text from the file object
|
||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED
|
||||
* @return const char* error text
|
||||
*/
|
||||
const char* storage_file_get_error_desc(File* file);
|
||||
|
||||
/******************* SD Card Functions *******************/
|
||||
|
||||
/** Formats SD Card
|
||||
* @param api pointer to the api
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_sd_format(Storage* api);
|
||||
|
||||
/** Will unmount the SD card
|
||||
* @param api pointer to the api
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_sd_unmount(Storage* api);
|
||||
|
||||
/** Retrieves SD card information
|
||||
* @param api pointer to the api
|
||||
* @param info pointer to the info
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_sd_info(Storage* api, SDInfo* info);
|
||||
|
||||
/** Retrieves SD card status
|
||||
* @param api pointer to the api
|
||||
* @return FS_Error operation result
|
||||
*/
|
||||
FS_Error storage_sd_status(Storage* api);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
82
applications/storage/storages/sd-notify.c
Normal file
82
applications/storage/storages/sd-notify.c
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include "sd-notify.h"
|
||||
|
||||
static const NotificationSequence sd_sequence_success = {
|
||||
&message_green_255,
|
||||
&message_delay_50,
|
||||
&message_green_0,
|
||||
&message_delay_50,
|
||||
&message_green_255,
|
||||
&message_delay_50,
|
||||
&message_green_0,
|
||||
&message_delay_50,
|
||||
&message_green_255,
|
||||
&message_delay_50,
|
||||
&message_green_0,
|
||||
&message_delay_50,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence sd_sequence_error = {
|
||||
&message_red_255,
|
||||
&message_delay_50,
|
||||
&message_red_0,
|
||||
&message_delay_50,
|
||||
&message_red_255,
|
||||
&message_delay_50,
|
||||
&message_red_0,
|
||||
&message_delay_50,
|
||||
&message_red_255,
|
||||
&message_delay_50,
|
||||
&message_red_0,
|
||||
&message_delay_50,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence sd_sequence_eject = {
|
||||
&message_blue_255,
|
||||
&message_delay_50,
|
||||
&message_blue_0,
|
||||
&message_delay_50,
|
||||
&message_blue_255,
|
||||
&message_delay_50,
|
||||
&message_blue_0,
|
||||
&message_delay_50,
|
||||
&message_blue_255,
|
||||
&message_delay_50,
|
||||
&message_blue_0,
|
||||
&message_delay_50,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence sd_sequence_wait = {
|
||||
&message_red_255,
|
||||
&message_blue_255,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence sd_sequence_wait_off = {
|
||||
&message_red_0,
|
||||
&message_blue_0,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void sd_notify_wait(NotificationApp* notifications) {
|
||||
notification_message(notifications, &sd_sequence_wait);
|
||||
}
|
||||
|
||||
void sd_notify_wait_off(NotificationApp* notifications) {
|
||||
notification_message(notifications, &sd_sequence_wait_off);
|
||||
}
|
||||
|
||||
void sd_notify_success(NotificationApp* notifications) {
|
||||
notification_message(notifications, &sd_sequence_success);
|
||||
}
|
||||
|
||||
void sd_notify_eject(NotificationApp* notifications) {
|
||||
notification_message(notifications, &sd_sequence_eject);
|
||||
}
|
||||
|
||||
void sd_notify_error(NotificationApp* notifications) {
|
||||
notification_message(notifications, &sd_sequence_error);
|
||||
}
|
17
applications/storage/storages/sd-notify.h
Normal file
17
applications/storage/storages/sd-notify.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <notification/notification-messages.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void sd_notify_wait(NotificationApp* notifications);
|
||||
void sd_notify_wait_off(NotificationApp* notifications);
|
||||
void sd_notify_success(NotificationApp* notifications);
|
||||
void sd_notify_eject(NotificationApp* notifications);
|
||||
void sd_notify_error(NotificationApp* notifications);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
547
applications/storage/storages/storage-ext.c
Normal file
547
applications/storage/storages/storage-ext.c
Normal file
|
@ -0,0 +1,547 @@
|
|||
#include "fatfs.h"
|
||||
#include "../filesystem-api-internal.h"
|
||||
#include "storage-ext.h"
|
||||
#include <api-hal.h>
|
||||
#include "sd-notify.h"
|
||||
#include <api-hal-sd.h>
|
||||
|
||||
typedef FIL SDFile;
|
||||
typedef DIR SDDir;
|
||||
typedef FILINFO SDFileInfo;
|
||||
typedef FRESULT SDError;
|
||||
|
||||
#define TAG "storage-ext"
|
||||
#define STORAGE_PATH "/ext"
|
||||
/********************* Definitions ********************/
|
||||
|
||||
typedef struct {
|
||||
FATFS* fs;
|
||||
const char* path;
|
||||
bool sd_was_present;
|
||||
} SDData;
|
||||
|
||||
static FS_Error storage_ext_parse_error(SDError error);
|
||||
|
||||
/******************* Core Functions *******************/
|
||||
|
||||
static bool sd_mount_card(StorageData* storage, bool notify) {
|
||||
bool result = false;
|
||||
const uint8_t max_init_counts = 10;
|
||||
uint8_t counter = max_init_counts;
|
||||
uint8_t bsp_result;
|
||||
SDData* sd_data = storage->data;
|
||||
|
||||
storage_data_lock(storage);
|
||||
|
||||
while(result == false && counter > 0 && hal_sd_detect()) {
|
||||
if(notify) {
|
||||
NotificationApp* notification = furi_record_open("notification");
|
||||
sd_notify_wait(notification);
|
||||
furi_record_close("notification");
|
||||
}
|
||||
|
||||
if((counter % 2) == 0) {
|
||||
// power reset sd card
|
||||
bsp_result = BSP_SD_Init(true);
|
||||
} else {
|
||||
bsp_result = BSP_SD_Init(false);
|
||||
}
|
||||
|
||||
if(bsp_result) {
|
||||
// bsp error
|
||||
storage->status = StorageStatusErrorInternal;
|
||||
} else {
|
||||
SDError status = f_mount(sd_data->fs, sd_data->path, 1);
|
||||
|
||||
if(status == FR_OK || status == FR_NO_FILESYSTEM) {
|
||||
FATFS* fs;
|
||||
uint32_t free_clusters;
|
||||
|
||||
status = f_getfree(sd_data->path, &free_clusters, &fs);
|
||||
|
||||
if(status == FR_OK || status == FR_NO_FILESYSTEM) {
|
||||
result = true;
|
||||
}
|
||||
|
||||
if(status == FR_OK) {
|
||||
storage->status = StorageStatusOK;
|
||||
} else if(status == FR_NO_FILESYSTEM) {
|
||||
storage->status = StorageStatusNoFS;
|
||||
} else {
|
||||
storage->status = StorageStatusNotAccessible;
|
||||
}
|
||||
} else {
|
||||
storage->status = StorageStatusNotMounted;
|
||||
}
|
||||
}
|
||||
|
||||
if(notify) {
|
||||
NotificationApp* notification = furi_record_open("notification");
|
||||
sd_notify_wait_off(notification);
|
||||
furi_record_close("notification");
|
||||
}
|
||||
|
||||
if(!result) {
|
||||
delay(1000);
|
||||
FURI_LOG_E(
|
||||
TAG, "init cycle %d, error: %s", counter, storage_data_status_text(storage));
|
||||
counter--;
|
||||
}
|
||||
}
|
||||
|
||||
storage_data_unlock(storage);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FS_Error sd_unmount_card(StorageData* storage) {
|
||||
SDData* sd_data = storage->data;
|
||||
SDError error;
|
||||
|
||||
storage_data_lock(storage);
|
||||
error = storage->status = StorageStatusNotReady;
|
||||
|
||||
// TODO do i need to close the files?
|
||||
|
||||
f_mount(0, sd_data->path, 0);
|
||||
storage_data_unlock(storage);
|
||||
return storage_ext_parse_error(error);
|
||||
}
|
||||
|
||||
FS_Error sd_format_card(StorageData* storage) {
|
||||
uint8_t* work_area;
|
||||
SDData* sd_data = storage->data;
|
||||
SDError error;
|
||||
|
||||
storage_data_lock(storage);
|
||||
|
||||
work_area = malloc(_MAX_SS);
|
||||
error = f_mkfs(sd_data->path, FM_ANY, 0, work_area, _MAX_SS);
|
||||
free(work_area);
|
||||
|
||||
do {
|
||||
storage->status = StorageStatusNotAccessible;
|
||||
if(error != FR_OK) break;
|
||||
storage->status = StorageStatusNoFS;
|
||||
error = f_setlabel("Flipper SD");
|
||||
if(error != FR_OK) break;
|
||||
storage->status = StorageStatusNotMounted;
|
||||
error = f_mount(sd_data->fs, sd_data->path, 1);
|
||||
if(error != FR_OK) break;
|
||||
storage->status = StorageStatusOK;
|
||||
} while(false);
|
||||
|
||||
storage_data_unlock(storage);
|
||||
|
||||
return storage_ext_parse_error(error);
|
||||
}
|
||||
|
||||
FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) {
|
||||
uint32_t free_clusters, free_sectors, total_sectors;
|
||||
FATFS* fs;
|
||||
SDData* sd_data = storage->data;
|
||||
SDError error;
|
||||
|
||||
// clean data
|
||||
memset(sd_info, 0, sizeof(SDInfo));
|
||||
|
||||
// get fs info
|
||||
storage_data_lock(storage);
|
||||
error = f_getlabel(sd_data->path, sd_info->label, NULL);
|
||||
if(error == FR_OK) {
|
||||
error = f_getfree(sd_data->path, &free_clusters, &fs);
|
||||
}
|
||||
storage_data_unlock(storage);
|
||||
|
||||
if(error == FR_OK) {
|
||||
// calculate size
|
||||
total_sectors = (fs->n_fatent - 2) * fs->csize;
|
||||
free_sectors = free_clusters * fs->csize;
|
||||
|
||||
uint16_t sector_size = _MAX_SS;
|
||||
#if _MAX_SS != _MIN_SS
|
||||
sector_size = fs->ssize;
|
||||
#endif
|
||||
|
||||
sd_info->fs_type = fs->fs_type;
|
||||
|
||||
sd_info->kb_total = total_sectors / 1024 * sector_size;
|
||||
sd_info->kb_free = free_sectors / 1024 * sector_size;
|
||||
sd_info->cluster_size = fs->csize;
|
||||
sd_info->sector_size = sector_size;
|
||||
}
|
||||
|
||||
return storage_ext_parse_error(error);
|
||||
}
|
||||
|
||||
static void storage_ext_tick_internal(StorageData* storage, bool notify) {
|
||||
SDData* sd_data = storage->data;
|
||||
|
||||
if(sd_data->sd_was_present) {
|
||||
if(hal_sd_detect()) {
|
||||
FURI_LOG_I(TAG, "card detected");
|
||||
sd_mount_card(storage, notify);
|
||||
|
||||
if(storage->status != StorageStatusOK) {
|
||||
FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage));
|
||||
if(notify) {
|
||||
NotificationApp* notification = furi_record_open("notification");
|
||||
sd_notify_error(notification);
|
||||
furi_record_close("notification");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "card mounted");
|
||||
if(notify) {
|
||||
NotificationApp* notification = furi_record_open("notification");
|
||||
sd_notify_success(notification);
|
||||
furi_record_close("notification");
|
||||
}
|
||||
}
|
||||
|
||||
sd_data->sd_was_present = false;
|
||||
|
||||
if(!hal_sd_detect()) {
|
||||
FURI_LOG_I(TAG, "card removed while mounting");
|
||||
sd_unmount_card(storage);
|
||||
sd_data->sd_was_present = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(!hal_sd_detect()) {
|
||||
FURI_LOG_I(TAG, "card removed");
|
||||
sd_data->sd_was_present = true;
|
||||
|
||||
sd_unmount_card(storage);
|
||||
if(notify) {
|
||||
NotificationApp* notification = furi_record_open("notification");
|
||||
sd_notify_eject(notification);
|
||||
furi_record_close("notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void storage_ext_tick(StorageData* storage) {
|
||||
storage_ext_tick_internal(storage, true);
|
||||
}
|
||||
|
||||
/****************** Common Functions ******************/
|
||||
|
||||
static FS_Error storage_ext_parse_error(SDError error) {
|
||||
FS_Error result;
|
||||
switch(error) {
|
||||
case FR_OK:
|
||||
result = FSE_OK;
|
||||
break;
|
||||
case FR_NOT_READY:
|
||||
result = FSE_NOT_READY;
|
||||
break;
|
||||
case FR_NO_FILE:
|
||||
case FR_NO_PATH:
|
||||
case FR_NO_FILESYSTEM:
|
||||
result = FSE_NOT_EXIST;
|
||||
break;
|
||||
case FR_EXIST:
|
||||
result = FSE_EXIST;
|
||||
break;
|
||||
case FR_INVALID_NAME:
|
||||
result = FSE_INVALID_NAME;
|
||||
break;
|
||||
case FR_INVALID_OBJECT:
|
||||
case FR_INVALID_PARAMETER:
|
||||
result = FSE_INVALID_PARAMETER;
|
||||
break;
|
||||
case FR_DENIED:
|
||||
result = FSE_DENIED;
|
||||
break;
|
||||
default:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/******************* File Functions *******************/
|
||||
|
||||
static bool storage_ext_file_open(
|
||||
void* ctx,
|
||||
File* file,
|
||||
const char* path,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode) {
|
||||
StorageData* storage = ctx;
|
||||
uint8_t _mode = 0;
|
||||
|
||||
if(access_mode & FSAM_READ) _mode |= FA_READ;
|
||||
if(access_mode & FSAM_WRITE) _mode |= FA_WRITE;
|
||||
if(open_mode & FSOM_OPEN_EXISTING) _mode |= FA_OPEN_EXISTING;
|
||||
if(open_mode & FSOM_OPEN_ALWAYS) _mode |= FA_OPEN_ALWAYS;
|
||||
if(open_mode & FSOM_OPEN_APPEND) _mode |= FA_OPEN_APPEND;
|
||||
if(open_mode & FSOM_CREATE_NEW) _mode |= FA_CREATE_NEW;
|
||||
if(open_mode & FSOM_CREATE_ALWAYS) _mode |= FA_CREATE_ALWAYS;
|
||||
|
||||
SDFile* file_data = malloc(sizeof(SDFile));
|
||||
storage_set_storage_file_data(file, file_data, storage);
|
||||
|
||||
file->internal_error_id = f_open(file_data, path, _mode);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_ext_file_close(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
file->internal_error_id = f_close(file_data);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
free(file_data);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
storage_ext_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
uint16_t bytes_readed = 0;
|
||||
file->internal_error_id = f_read(file_data, buff, bytes_to_read, &bytes_readed);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return bytes_readed;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
storage_ext_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
uint16_t bytes_written = 0;
|
||||
file->internal_error_id = f_write(file_data, buff, bytes_to_write, &bytes_written);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
static bool
|
||||
storage_ext_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(from_start) {
|
||||
file->internal_error_id = f_lseek(file_data, offset);
|
||||
} else {
|
||||
uint64_t position = f_tell(file_data);
|
||||
position += offset;
|
||||
file->internal_error_id = f_lseek(file_data, position);
|
||||
}
|
||||
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static uint64_t storage_ext_file_tell(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
uint64_t position = 0;
|
||||
position = f_tell(file_data);
|
||||
file->error_id = FSE_OK;
|
||||
return position;
|
||||
}
|
||||
|
||||
static bool storage_ext_file_truncate(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
file->internal_error_id = f_truncate(file_data);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_ext_file_sync(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
file->internal_error_id = f_sync(file_data);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static uint64_t storage_ext_file_size(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
uint64_t size = 0;
|
||||
size = f_size(file_data);
|
||||
file->error_id = FSE_OK;
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool storage_ext_file_eof(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDFile* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
bool eof = f_eof(file_data);
|
||||
file->internal_error_id = 0;
|
||||
file->error_id = FSE_OK;
|
||||
return eof;
|
||||
}
|
||||
|
||||
/******************* Dir Functions *******************/
|
||||
|
||||
static bool storage_ext_dir_open(void* ctx, File* file, const char* path) {
|
||||
StorageData* storage = ctx;
|
||||
|
||||
SDDir* file_data = malloc(sizeof(SDDir));
|
||||
storage_set_storage_file_data(file, file_data, storage);
|
||||
file->internal_error_id = f_opendir(file_data, path);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_ext_dir_close(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDDir* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
file->internal_error_id = f_closedir(file_data);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
free(file_data);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_ext_dir_read(
|
||||
void* ctx,
|
||||
File* file,
|
||||
FileInfo* fileinfo,
|
||||
char* name,
|
||||
const uint16_t name_length) {
|
||||
StorageData* storage = ctx;
|
||||
SDDir* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
SDFileInfo _fileinfo;
|
||||
file->internal_error_id = f_readdir(file_data, &_fileinfo);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->size = _fileinfo.fsize;
|
||||
fileinfo->flags = 0;
|
||||
|
||||
if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
}
|
||||
|
||||
if(name != NULL) {
|
||||
snprintf(name, name_length, "%s", _fileinfo.fname);
|
||||
}
|
||||
|
||||
if(_fileinfo.fname[0] == 0) {
|
||||
file->error_id = FSE_NOT_EXIST;
|
||||
}
|
||||
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_ext_dir_rewind(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
SDDir* file_data = storage_get_storage_file_data(file, storage);
|
||||
|
||||
file->internal_error_id = f_readdir(file_data, NULL);
|
||||
file->error_id = storage_ext_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
/******************* Common FS Functions *******************/
|
||||
|
||||
static FS_Error storage_ext_common_stat(void* ctx, const char* path, FileInfo* fileinfo) {
|
||||
SDFileInfo _fileinfo;
|
||||
SDError result = f_stat(path, &_fileinfo);
|
||||
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->size = _fileinfo.fsize;
|
||||
fileinfo->flags = 0;
|
||||
|
||||
if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
}
|
||||
|
||||
return storage_ext_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_ext_common_remove(void* ctx, const char* path) {
|
||||
SDError result = f_unlink(path);
|
||||
return storage_ext_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_ext_common_rename(void* ctx, const char* old_path, const char* new_path) {
|
||||
SDError result = f_rename(old_path, new_path);
|
||||
return storage_ext_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_ext_common_mkdir(void* ctx, const char* path) {
|
||||
SDError result = f_mkdir(path);
|
||||
return storage_ext_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_ext_common_fs_info(
|
||||
void* ctx,
|
||||
const char* fs_path,
|
||||
uint64_t* total_space,
|
||||
uint64_t* free_space) {
|
||||
StorageData* storage = ctx;
|
||||
SDData* sd_data = storage->data;
|
||||
|
||||
DWORD free_clusters;
|
||||
FATFS* fs;
|
||||
|
||||
SDError fresult = f_getfree(sd_data->path, &free_clusters, &fs);
|
||||
if((FRESULT)fresult == FR_OK) {
|
||||
uint32_t total_sectors = (fs->n_fatent - 2) * fs->csize;
|
||||
uint32_t free_sectors = free_clusters * fs->csize;
|
||||
|
||||
uint16_t sector_size = _MAX_SS;
|
||||
#if _MAX_SS != _MIN_SS
|
||||
sector_size = fs->ssize;
|
||||
#endif
|
||||
|
||||
if(total_space != NULL) {
|
||||
*total_space = (uint64_t)total_sectors * (uint64_t)sector_size;
|
||||
}
|
||||
|
||||
if(free_space != NULL) {
|
||||
*free_space = (uint64_t)free_sectors * (uint64_t)sector_size;
|
||||
}
|
||||
}
|
||||
|
||||
return storage_ext_parse_error(fresult);
|
||||
}
|
||||
|
||||
/******************* Init Storage *******************/
|
||||
|
||||
void storage_ext_init(StorageData* storage) {
|
||||
SDData* sd_data = malloc(sizeof(SDData));
|
||||
sd_data->fs = &USERFatFS;
|
||||
sd_data->path = "0:/";
|
||||
sd_data->sd_was_present = true;
|
||||
|
||||
storage->data = sd_data;
|
||||
storage->api.tick = storage_ext_tick;
|
||||
storage->fs_api.file.open = storage_ext_file_open;
|
||||
storage->fs_api.file.close = storage_ext_file_close;
|
||||
storage->fs_api.file.read = storage_ext_file_read;
|
||||
storage->fs_api.file.write = storage_ext_file_write;
|
||||
storage->fs_api.file.seek = storage_ext_file_seek;
|
||||
storage->fs_api.file.tell = storage_ext_file_tell;
|
||||
storage->fs_api.file.truncate = storage_ext_file_truncate;
|
||||
storage->fs_api.file.size = storage_ext_file_size;
|
||||
storage->fs_api.file.sync = storage_ext_file_sync;
|
||||
storage->fs_api.file.eof = storage_ext_file_eof;
|
||||
|
||||
storage->fs_api.dir.open = storage_ext_dir_open;
|
||||
storage->fs_api.dir.close = storage_ext_dir_close;
|
||||
storage->fs_api.dir.read = storage_ext_dir_read;
|
||||
storage->fs_api.dir.rewind = storage_ext_dir_rewind;
|
||||
|
||||
storage->fs_api.common.stat = storage_ext_common_stat;
|
||||
storage->fs_api.common.mkdir = storage_ext_common_mkdir;
|
||||
storage->fs_api.common.rename = storage_ext_common_rename;
|
||||
storage->fs_api.common.remove = storage_ext_common_remove;
|
||||
storage->fs_api.common.fs_info = storage_ext_common_fs_info;
|
||||
|
||||
hal_sd_detect_init();
|
||||
|
||||
// do not notify on first launch, notifications app is waiting for our thread to read settings
|
||||
storage_ext_tick_internal(storage, false);
|
||||
}
|
16
applications/storage/storages/storage-ext.h
Normal file
16
applications/storage/storages/storage-ext.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "../storage-glue.h"
|
||||
#include "../storage-sd-api.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void storage_ext_init(StorageData* storage);
|
||||
FS_Error sd_unmount_card(StorageData* storage);
|
||||
FS_Error sd_format_card(StorageData* storage);
|
||||
FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
690
applications/storage/storages/storage-int.c
Normal file
690
applications/storage/storages/storage-int.c
Normal file
|
@ -0,0 +1,690 @@
|
|||
#include "storage-int.h"
|
||||
#include <lfs.h>
|
||||
#include <api-hal.h>
|
||||
|
||||
#define TAG "storage-int"
|
||||
#define STORAGE_PATH "/int"
|
||||
|
||||
typedef struct {
|
||||
const size_t start_address;
|
||||
const size_t start_page;
|
||||
struct lfs_config config;
|
||||
lfs_t lfs;
|
||||
} LFSData;
|
||||
|
||||
typedef struct {
|
||||
void* data;
|
||||
bool open;
|
||||
} LFSHandle;
|
||||
|
||||
static LFSHandle* lfs_handle_alloc_file() {
|
||||
LFSHandle* handle = furi_alloc(sizeof(LFSHandle));
|
||||
handle->data = furi_alloc(sizeof(lfs_file_t));
|
||||
return handle;
|
||||
}
|
||||
|
||||
static LFSHandle* lfs_handle_alloc_dir() {
|
||||
LFSHandle* handle = furi_alloc(sizeof(LFSHandle));
|
||||
handle->data = furi_alloc(sizeof(lfs_dir_t));
|
||||
return handle;
|
||||
}
|
||||
|
||||
/* INTERNALS */
|
||||
|
||||
static lfs_dir_t* lfs_handle_get_dir(LFSHandle* handle) {
|
||||
return handle->data;
|
||||
}
|
||||
|
||||
static lfs_file_t* lfs_handle_get_file(LFSHandle* handle) {
|
||||
return handle->data;
|
||||
}
|
||||
|
||||
static void lfs_handle_free(LFSHandle* handle) {
|
||||
free(handle->data);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
static void lfs_handle_set_open(LFSHandle* handle) {
|
||||
handle->open = true;
|
||||
}
|
||||
|
||||
static bool lfs_handle_is_open(LFSHandle* handle) {
|
||||
return handle->open;
|
||||
}
|
||||
|
||||
static lfs_t* lfs_get_from_storage(StorageData* storage) {
|
||||
return &((LFSData*)storage->data)->lfs;
|
||||
}
|
||||
|
||||
static LFSData* lfs_data_get_from_storage(StorageData* storage) {
|
||||
return (LFSData*)storage->data;
|
||||
}
|
||||
|
||||
static int storage_int_device_read(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
void* buffer,
|
||||
lfs_size_t size) {
|
||||
LFSData* lfs_data = c->context;
|
||||
size_t address = lfs_data->start_address + block * c->block_size + off;
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Device read: block %d, off %d, buffer: %p, size %d, translated address: %p",
|
||||
block,
|
||||
off,
|
||||
buffer,
|
||||
size,
|
||||
address);
|
||||
|
||||
memcpy(buffer, (void*)address, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int storage_int_device_prog(
|
||||
const struct lfs_config* c,
|
||||
lfs_block_t block,
|
||||
lfs_off_t off,
|
||||
const void* buffer,
|
||||
lfs_size_t size) {
|
||||
LFSData* lfs_data = c->context;
|
||||
size_t address = lfs_data->start_address + block * c->block_size + off;
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p",
|
||||
block,
|
||||
off,
|
||||
buffer,
|
||||
size,
|
||||
address);
|
||||
|
||||
int ret = 0;
|
||||
while(size > 0) {
|
||||
if(!api_hal_flash_write_dword(address, *(uint64_t*)buffer)) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
address += c->prog_size;
|
||||
buffer += c->prog_size;
|
||||
size -= c->prog_size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t block) {
|
||||
LFSData* lfs_data = c->context;
|
||||
size_t page = lfs_data->start_page + block;
|
||||
|
||||
FURI_LOG_D(TAG, "Device erase: page %d, translated page: %d", block, page);
|
||||
|
||||
if(api_hal_flash_erase(page, 1)) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int storage_int_device_sync(const struct lfs_config* c) {
|
||||
FURI_LOG_D(TAG, "Device sync: skipping, cause ");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static LFSData* storage_int_lfs_data_alloc() {
|
||||
LFSData* lfs_data = furi_alloc(sizeof(LFSData));
|
||||
|
||||
// Internal storage start address
|
||||
*(size_t*)(&lfs_data->start_address) = api_hal_flash_get_free_page_start_address();
|
||||
*(size_t*)(&lfs_data->start_page) =
|
||||
(lfs_data->start_address - api_hal_flash_get_base()) / api_hal_flash_get_page_size();
|
||||
|
||||
// LFS configuration
|
||||
// Glue and context
|
||||
lfs_data->config.context = lfs_data;
|
||||
lfs_data->config.read = storage_int_device_read;
|
||||
lfs_data->config.prog = storage_int_device_prog;
|
||||
lfs_data->config.erase = storage_int_device_erase;
|
||||
lfs_data->config.sync = storage_int_device_sync;
|
||||
|
||||
// Block device description
|
||||
lfs_data->config.read_size = api_hal_flash_get_read_block_size();
|
||||
lfs_data->config.prog_size = api_hal_flash_get_write_block_size();
|
||||
lfs_data->config.block_size = api_hal_flash_get_page_size();
|
||||
lfs_data->config.block_count = api_hal_flash_get_free_page_count();
|
||||
lfs_data->config.block_cycles = api_hal_flash_get_cycles_count();
|
||||
lfs_data->config.cache_size = 16;
|
||||
lfs_data->config.lookahead_size = 16;
|
||||
|
||||
return lfs_data;
|
||||
};
|
||||
|
||||
static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) {
|
||||
int err;
|
||||
ApiHalBootFlag boot_flags = api_hal_boot_get_flags();
|
||||
lfs_t* lfs = &lfs_data->lfs;
|
||||
|
||||
if(boot_flags & ApiHalBootFlagFactoryReset) {
|
||||
// Factory reset
|
||||
err = lfs_format(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount");
|
||||
api_hal_boot_set_flags(boot_flags & ~ApiHalBootFlagFactoryReset);
|
||||
err = lfs_mount(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Factory reset: Mounted");
|
||||
storage->status = StorageStatusOK;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Factory reset: Mount after format failed");
|
||||
storage->status = StorageStatusNotMounted;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Factory reset: Format failed");
|
||||
storage->status = StorageStatusNoFS;
|
||||
}
|
||||
} else {
|
||||
// Normal
|
||||
err = lfs_mount(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Mounted");
|
||||
storage->status = StorageStatusOK;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Mount failed, formatting");
|
||||
err = lfs_format(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Format successful, trying to mount");
|
||||
err = lfs_mount(lfs, &lfs_data->config);
|
||||
if(err == 0) {
|
||||
FURI_LOG_I(TAG, "Mounted");
|
||||
storage->status = StorageStatusOK;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Mount after format failed");
|
||||
storage->status = StorageStatusNotMounted;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Format failed");
|
||||
storage->status = StorageStatusNoFS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************** Common Functions ******************/
|
||||
|
||||
static FS_Error storage_int_parse_error(int error) {
|
||||
FS_Error result = FSE_INTERNAL;
|
||||
|
||||
if(error >= LFS_ERR_OK) {
|
||||
result = FSE_OK;
|
||||
} else {
|
||||
switch(error) {
|
||||
case LFS_ERR_IO:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case LFS_ERR_CORRUPT:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case LFS_ERR_NOENT:
|
||||
result = FSE_NOT_EXIST;
|
||||
break;
|
||||
case LFS_ERR_EXIST:
|
||||
result = FSE_EXIST;
|
||||
break;
|
||||
case LFS_ERR_NOTDIR:
|
||||
result = FSE_INVALID_NAME;
|
||||
break;
|
||||
case LFS_ERR_ISDIR:
|
||||
result = FSE_INVALID_NAME;
|
||||
break;
|
||||
case LFS_ERR_NOTEMPTY:
|
||||
result = FSE_DENIED;
|
||||
break;
|
||||
case LFS_ERR_BADF:
|
||||
result = FSE_INVALID_NAME;
|
||||
break;
|
||||
case LFS_ERR_FBIG:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case LFS_ERR_INVAL:
|
||||
result = FSE_INVALID_PARAMETER;
|
||||
break;
|
||||
case LFS_ERR_NOSPC:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case LFS_ERR_NOMEM:
|
||||
result = FSE_INTERNAL;
|
||||
break;
|
||||
case LFS_ERR_NOATTR:
|
||||
result = FSE_INVALID_PARAMETER;
|
||||
break;
|
||||
case LFS_ERR_NAMETOOLONG:
|
||||
result = FSE_INVALID_NAME;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/******************* File Functions *******************/
|
||||
|
||||
static bool storage_int_file_open(
|
||||
void* ctx,
|
||||
File* file,
|
||||
const char* path,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
|
||||
int flags = 0;
|
||||
|
||||
if(access_mode & FSAM_READ) flags |= LFS_O_RDONLY;
|
||||
if(access_mode & FSAM_WRITE) flags |= LFS_O_WRONLY;
|
||||
|
||||
if(open_mode & FSOM_OPEN_EXISTING) flags = flags;
|
||||
if(open_mode & FSOM_OPEN_ALWAYS) flags |= LFS_O_CREAT;
|
||||
if(open_mode & FSOM_OPEN_APPEND) flags |= LFS_O_CREAT | LFS_O_APPEND;
|
||||
if(open_mode & FSOM_CREATE_NEW) flags |= LFS_O_CREAT | LFS_O_EXCL;
|
||||
if(open_mode & FSOM_CREATE_ALWAYS) flags |= LFS_O_CREAT | LFS_O_TRUNC;
|
||||
|
||||
LFSHandle* handle = lfs_handle_alloc_file();
|
||||
storage_set_storage_file_data(file, handle, storage);
|
||||
file->internal_error_id = lfs_file_open(lfs, lfs_handle_get_file(handle), path, flags);
|
||||
|
||||
if(file->internal_error_id >= LFS_ERR_OK) {
|
||||
lfs_handle_set_open(handle);
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_int_file_close(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_close(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
lfs_handle_free(handle);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
storage_int_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
uint16_t bytes_readed = 0;
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id =
|
||||
lfs_file_read(lfs, lfs_handle_get_file(handle), buff, bytes_to_read);
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
if(file->error_id == FSE_OK) {
|
||||
bytes_readed = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
return bytes_readed;
|
||||
}
|
||||
|
||||
static uint16_t
|
||||
storage_int_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
uint16_t bytes_written = 0;
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id =
|
||||
lfs_file_write(lfs, lfs_handle_get_file(handle), buff, bytes_to_write);
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
if(file->error_id == FSE_OK) {
|
||||
bytes_written = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
static bool
|
||||
storage_int_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
if(from_start) {
|
||||
file->internal_error_id =
|
||||
lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_SET);
|
||||
} else {
|
||||
file->internal_error_id =
|
||||
lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_CUR);
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static uint64_t storage_int_file_tell(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
int32_t position = 0;
|
||||
if(file->error_id == FSE_OK) {
|
||||
position = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
static bool storage_int_file_truncate(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle));
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
if(file->error_id == FSE_OK) {
|
||||
uint32_t position = file->internal_error_id;
|
||||
file->internal_error_id =
|
||||
lfs_file_truncate(lfs, lfs_handle_get_file(handle), position);
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
}
|
||||
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_int_file_sync(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_sync(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static uint64_t storage_int_file_size(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_file_size(lfs, lfs_handle_get_file(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
|
||||
uint32_t size = 0;
|
||||
if(file->error_id == FSE_OK) {
|
||||
size = file->internal_error_id;
|
||||
file->internal_error_id = 0;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool storage_int_file_eof(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
bool eof = true;
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
int32_t position = lfs_file_tell(lfs, lfs_handle_get_file(handle));
|
||||
int32_t size = lfs_file_size(lfs, lfs_handle_get_file(handle));
|
||||
|
||||
if(position < 0) {
|
||||
file->internal_error_id = position;
|
||||
} else if(size < 0) {
|
||||
file->internal_error_id = size;
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_OK;
|
||||
eof = (position >= size);
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return eof;
|
||||
}
|
||||
|
||||
/******************* Dir Functions *******************/
|
||||
|
||||
static bool storage_int_dir_open(void* ctx, File* file, const char* path) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
|
||||
LFSHandle* handle = lfs_handle_alloc_dir();
|
||||
storage_set_storage_file_data(file, handle, storage);
|
||||
|
||||
file->internal_error_id = lfs_dir_open(lfs, lfs_handle_get_dir(handle), path);
|
||||
if(file->internal_error_id >= LFS_ERR_OK) {
|
||||
lfs_handle_set_open(handle);
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_int_dir_close(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_dir_close(lfs, lfs_handle_get_dir(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
lfs_handle_free(handle);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_int_dir_read(
|
||||
void* ctx,
|
||||
File* file,
|
||||
FileInfo* fileinfo,
|
||||
char* name,
|
||||
const uint16_t name_length) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
struct lfs_info _fileinfo;
|
||||
|
||||
// LFS returns virtual directories "." and "..", so we read until we get something meaningful or an empty string
|
||||
do {
|
||||
file->internal_error_id = lfs_dir_read(lfs, lfs_handle_get_dir(handle), &_fileinfo);
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
} while(strcmp(_fileinfo.name, ".") == 0 || strcmp(_fileinfo.name, "..") == 0);
|
||||
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->size = _fileinfo.size;
|
||||
fileinfo->flags = 0;
|
||||
if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
}
|
||||
|
||||
if(name != NULL) {
|
||||
snprintf(name, name_length, "%s", _fileinfo.name);
|
||||
}
|
||||
|
||||
// set FSE_NOT_EXIST error on end of directory
|
||||
if(file->internal_error_id == 0) {
|
||||
file->error_id = FSE_NOT_EXIST;
|
||||
}
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
}
|
||||
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
static bool storage_int_dir_rewind(void* ctx, File* file) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSHandle* handle = storage_get_storage_file_data(file, storage);
|
||||
|
||||
if(lfs_handle_is_open(handle)) {
|
||||
file->internal_error_id = lfs_dir_rewind(lfs, lfs_handle_get_dir(handle));
|
||||
} else {
|
||||
file->internal_error_id = LFS_ERR_BADF;
|
||||
}
|
||||
|
||||
file->error_id = storage_int_parse_error(file->internal_error_id);
|
||||
return (file->error_id == FSE_OK);
|
||||
}
|
||||
|
||||
/******************* Common FS Functions *******************/
|
||||
|
||||
static FS_Error storage_int_common_stat(void* ctx, const char* path, FileInfo* fileinfo) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
struct lfs_info _fileinfo;
|
||||
int result = lfs_stat(lfs, path, &_fileinfo);
|
||||
|
||||
if(fileinfo != NULL) {
|
||||
fileinfo->size = _fileinfo.size;
|
||||
fileinfo->flags = 0;
|
||||
if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY;
|
||||
}
|
||||
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_int_common_remove(void* ctx, const char* path) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
int result = lfs_remove(lfs, path);
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_int_common_rename(void* ctx, const char* old_path, const char* new_path) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
int result = lfs_rename(lfs, old_path, new_path);
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_int_common_mkdir(void* ctx, const char* path) {
|
||||
StorageData* storage = ctx;
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
int result = lfs_mkdir(lfs, path);
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static FS_Error storage_int_common_fs_info(
|
||||
void* ctx,
|
||||
const char* fs_path,
|
||||
uint64_t* total_space,
|
||||
uint64_t* free_space) {
|
||||
StorageData* storage = ctx;
|
||||
|
||||
lfs_t* lfs = lfs_get_from_storage(storage);
|
||||
LFSData* lfs_data = lfs_data_get_from_storage(storage);
|
||||
|
||||
*total_space = lfs_data->config.block_size * lfs_data->config.block_count;
|
||||
|
||||
lfs_ssize_t result = lfs_fs_size(lfs);
|
||||
if(result >= 0) {
|
||||
*free_space = *total_space - (result * lfs_data->config.block_size);
|
||||
}
|
||||
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
/******************* Init Storage *******************/
|
||||
|
||||
void storage_int_init(StorageData* storage) {
|
||||
FURI_LOG_I(TAG, "Starting");
|
||||
LFSData* lfs_data = storage_int_lfs_data_alloc();
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d",
|
||||
lfs_data->start_address,
|
||||
lfs_data->config.read_size,
|
||||
lfs_data->config.prog_size,
|
||||
lfs_data->config.block_size,
|
||||
lfs_data->config.block_count,
|
||||
lfs_data->config.block_cycles);
|
||||
|
||||
storage_int_lfs_mount(lfs_data, storage);
|
||||
|
||||
storage->data = lfs_data;
|
||||
storage->api.tick = NULL;
|
||||
storage->fs_api.file.open = storage_int_file_open;
|
||||
storage->fs_api.file.close = storage_int_file_close;
|
||||
storage->fs_api.file.read = storage_int_file_read;
|
||||
storage->fs_api.file.write = storage_int_file_write;
|
||||
storage->fs_api.file.seek = storage_int_file_seek;
|
||||
storage->fs_api.file.tell = storage_int_file_tell;
|
||||
storage->fs_api.file.truncate = storage_int_file_truncate;
|
||||
storage->fs_api.file.size = storage_int_file_size;
|
||||
storage->fs_api.file.sync = storage_int_file_sync;
|
||||
storage->fs_api.file.eof = storage_int_file_eof;
|
||||
|
||||
storage->fs_api.dir.open = storage_int_dir_open;
|
||||
storage->fs_api.dir.close = storage_int_dir_close;
|
||||
storage->fs_api.dir.read = storage_int_dir_read;
|
||||
storage->fs_api.dir.rewind = storage_int_dir_rewind;
|
||||
|
||||
storage->fs_api.common.stat = storage_int_common_stat;
|
||||
storage->fs_api.common.mkdir = storage_int_common_mkdir;
|
||||
storage->fs_api.common.rename = storage_int_common_rename;
|
||||
storage->fs_api.common.remove = storage_int_common_remove;
|
||||
storage->fs_api.common.fs_info = storage_int_common_fs_info;
|
||||
}
|
13
applications/storage/storages/storage-int.h
Normal file
13
applications/storage/storages/storage-int.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
#include "../storage-glue.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void storage_int_init(StorageData* storage);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -223,8 +223,8 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) {
|
|||
furi_check(instance->stream);
|
||||
|
||||
SubGhzProtocol* protocol = subghz_protocol_alloc();
|
||||
subghz_protocol_load_keeloq_file(protocol, "/assets/subghz/keeloq_mfcodes");
|
||||
subghz_protocol_load_nice_flor_s_file(protocol, "/assets/subghz/nice_floor_s_rx");
|
||||
subghz_protocol_load_keeloq_file(protocol, "/ext/assets/subghz/keeloq_mfcodes");
|
||||
subghz_protocol_load_nice_flor_s_file(protocol, "/ext/assets/subghz/nice_floor_s_rx");
|
||||
subghz_protocol_enable_dump_text(protocol, subghz_cli_command_rx_text_callback, instance);
|
||||
|
||||
// Configure radio
|
||||
|
|
|
@ -207,9 +207,10 @@ SubghzCapture* subghz_capture_alloc() {
|
|||
subghz_capture->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse);
|
||||
subghz_worker_set_context(subghz_capture->worker, subghz_capture->protocol);
|
||||
|
||||
subghz_protocol_load_keeloq_file(subghz_capture->protocol, "/assets/subghz/keeloq_mfcodes");
|
||||
subghz_protocol_load_keeloq_file(
|
||||
subghz_capture->protocol, "/ext/assets/subghz/keeloq_mfcodes");
|
||||
subghz_protocol_load_nice_flor_s_file(
|
||||
subghz_capture->protocol, "/assets/subghz/nice_floor_s_rx");
|
||||
subghz_capture->protocol, "/ext/assets/subghz/nice_floor_s_rx");
|
||||
subghz_protocol_enable_dump_text(
|
||||
subghz_capture->protocol, subghz_capture_text_callback, subghz_capture);
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,67 +10,67 @@ extern const Icon A_MDWRB_32x32;
|
|||
extern const Icon A_MDWR_32x32;
|
||||
extern const Icon A_WatchingTV_128x64;
|
||||
extern const Icon A_Wink_128x64;
|
||||
extern const Icon I_125_10px;
|
||||
extern const Icon I_ble_10px;
|
||||
extern const Icon I_dir_10px;
|
||||
extern const Icon I_ibutt_10px;
|
||||
extern const Icon I_ir_10px;
|
||||
extern const Icon I_Nfc_10px;
|
||||
extern const Icon I_sub1_10px;
|
||||
extern const Icon I_ir_10px;
|
||||
extern const Icon I_ibutt_10px;
|
||||
extern const Icon I_unknown_10px;
|
||||
extern const Icon I_ble_10px;
|
||||
extern const Icon I_125_10px;
|
||||
extern const Icon I_ButtonRightSmall_3x5;
|
||||
extern const Icon I_ButtonLeft_4x7;
|
||||
extern const Icon I_ButtonLeftSmall_3x5;
|
||||
extern const Icon I_ButtonRight_4x7;
|
||||
extern const Icon I_ButtonCenter_7x7;
|
||||
extern const Icon I_FX_SittingB_40x27;
|
||||
extern const Icon I_ButtonLeftSmall_3x5;
|
||||
extern const Icon I_ButtonLeft_4x7;
|
||||
extern const Icon I_ButtonRightSmall_3x5;
|
||||
extern const Icon I_ButtonRight_4x7;
|
||||
extern const Icon I_BigBurger_24x24;
|
||||
extern const Icon I_BigGames_24x24;
|
||||
extern const Icon I_BigProfile_24x24;
|
||||
extern const Icon I_DolphinOkay_41x43;
|
||||
extern const Icon I_DolphinFirstStart5_45x53;
|
||||
extern const Icon I_DolphinFirstStart4_67x53;
|
||||
extern const Icon I_DolphinFirstStart2_59x51;
|
||||
extern const Icon I_DolphinFirstStart0_70x53;
|
||||
extern const Icon I_DolphinFirstStart6_58x54;
|
||||
extern const Icon I_DolphinFirstStart1_59x53;
|
||||
extern const Icon I_DolphinFirstStart8_56x51;
|
||||
extern const Icon I_DolphinFirstStart7_61x51;
|
||||
extern const Icon I_Flipper_young_80x60;
|
||||
extern const Icon I_BigBurger_24x24;
|
||||
extern const Icon I_FX_Bang_32x6;
|
||||
extern const Icon I_DolphinFirstStart2_59x51;
|
||||
extern const Icon I_DolphinFirstStart3_57x48;
|
||||
extern const Icon I_PassportBottom_128x17;
|
||||
extern const Icon I_DolphinFirstStart4_67x53;
|
||||
extern const Icon I_DolphinFirstStart5_45x53;
|
||||
extern const Icon I_DolphinFirstStart6_58x54;
|
||||
extern const Icon I_DolphinFirstStart7_61x51;
|
||||
extern const Icon I_DolphinFirstStart8_56x51;
|
||||
extern const Icon I_DolphinOkay_41x43;
|
||||
extern const Icon I_Flipper_young_80x60;
|
||||
extern const Icon I_FX_Bang_32x6;
|
||||
extern const Icon I_FX_SittingB_40x27;
|
||||
extern const Icon I_DoorLeft_70x55;
|
||||
extern const Icon I_DoorLeft_8x56;
|
||||
extern const Icon I_DoorLocked_10x56;
|
||||
extern const Icon I_DoorRight_8x56;
|
||||
extern const Icon I_DoorLeft_70x55;
|
||||
extern const Icon I_PassportLeft_6x47;
|
||||
extern const Icon I_DoorRight_70x55;
|
||||
extern const Icon I_DoorRight_8x56;
|
||||
extern const Icon I_LockPopup_100x49;
|
||||
extern const Icon I_Mute_25x27;
|
||||
extern const Icon I_IrdaArrowUp_4x8;
|
||||
extern const Icon I_Up_hvr_25x27;
|
||||
extern const Icon I_Mute_hvr_25x27;
|
||||
extern const Icon I_Vol_down_25x27;
|
||||
extern const Icon I_PassportBottom_128x17;
|
||||
extern const Icon I_PassportLeft_6x47;
|
||||
extern const Icon I_Back_15x10;
|
||||
extern const Icon I_Down_25x27;
|
||||
extern const Icon I_Power_hvr_25x27;
|
||||
extern const Icon I_IrdaLearnShort_128x31;
|
||||
extern const Icon I_IrdaArrowDown_4x8;
|
||||
extern const Icon I_Vol_down_hvr_25x27;
|
||||
extern const Icon I_IrdaLearn_128x64;
|
||||
extern const Icon I_Down_hvr_25x27;
|
||||
extern const Icon I_Fill_marker_7x7;
|
||||
extern const Icon I_Power_25x27;
|
||||
extern const Icon I_Vol_up_25x27;
|
||||
extern const Icon I_Up_25x27;
|
||||
extern const Icon I_Back_15x10;
|
||||
extern const Icon I_IrdaSend_128x64;
|
||||
extern const Icon I_IrdaArrowDown_4x8;
|
||||
extern const Icon I_IrdaArrowUp_4x8;
|
||||
extern const Icon I_IrdaLearnShort_128x31;
|
||||
extern const Icon I_IrdaLearn_128x64;
|
||||
extern const Icon I_IrdaSendShort_128x34;
|
||||
extern const Icon I_IrdaSend_128x64;
|
||||
extern const Icon I_Mute_25x27;
|
||||
extern const Icon I_Mute_hvr_25x27;
|
||||
extern const Icon I_Power_25x27;
|
||||
extern const Icon I_Power_hvr_25x27;
|
||||
extern const Icon I_Up_25x27;
|
||||
extern const Icon I_Up_hvr_25x27;
|
||||
extern const Icon I_Vol_down_25x27;
|
||||
extern const Icon I_Vol_down_hvr_25x27;
|
||||
extern const Icon I_Vol_up_25x27;
|
||||
extern const Icon I_Vol_up_hvr_25x27;
|
||||
extern const Icon I_KeySave_24x11;
|
||||
extern const Icon I_KeyBackspaceSelected_16x9;
|
||||
extern const Icon I_KeySaveSelected_24x11;
|
||||
extern const Icon I_KeyBackspace_16x9;
|
||||
extern const Icon I_KeySaveSelected_24x11;
|
||||
extern const Icon I_KeySave_24x11;
|
||||
extern const Icon A_125khz_14;
|
||||
extern const Icon A_Bluetooth_14;
|
||||
extern const Icon A_FileManager_14;
|
||||
|
@ -86,61 +86,61 @@ extern const Icon A_Sub1ghz_14;
|
|||
extern const Icon A_Tamagotchi_14;
|
||||
extern const Icon A_U2F_14;
|
||||
extern const Icon A_iButton_14;
|
||||
extern const Icon I_Medium_chip_22x21;
|
||||
extern const Icon I_EMV_Chip_14x11;
|
||||
extern const Icon I_passport_happy1_43x45;
|
||||
extern const Icon I_passport_bad3_43x45;
|
||||
extern const Icon I_passport_okay2_43x45;
|
||||
extern const Icon I_passport_bad2_43x45;
|
||||
extern const Icon I_passport_okay3_43x45;
|
||||
extern const Icon I_Medium_chip_22x21;
|
||||
extern const Icon I_passport_bad1_43x45;
|
||||
extern const Icon I_passport_happy3_43x45;
|
||||
extern const Icon I_passport_bad2_43x45;
|
||||
extern const Icon I_passport_bad3_43x45;
|
||||
extern const Icon I_passport_happy1_43x45;
|
||||
extern const Icon I_passport_happy2_43x45;
|
||||
extern const Icon I_passport_happy3_43x45;
|
||||
extern const Icon I_passport_okay1_43x45;
|
||||
extern const Icon I_Health_16x16;
|
||||
extern const Icon I_FaceCharging_29x14;
|
||||
extern const Icon I_passport_okay2_43x45;
|
||||
extern const Icon I_passport_okay3_43x45;
|
||||
extern const Icon I_BatteryBody_52x28;
|
||||
extern const Icon I_Voltage_16x16;
|
||||
extern const Icon I_Temperature_16x16;
|
||||
extern const Icon I_Battery_16x16;
|
||||
extern const Icon I_FaceCharging_29x14;
|
||||
extern const Icon I_FaceConfused_29x14;
|
||||
extern const Icon I_FaceNopower_29x14;
|
||||
extern const Icon I_FaceNormal_29x14;
|
||||
extern const Icon I_Battery_16x16;
|
||||
extern const Icon I_FaceConfused_29x14;
|
||||
extern const Icon I_RFIDDolphinSuccess_108x57;
|
||||
extern const Icon I_Health_16x16;
|
||||
extern const Icon I_Temperature_16x16;
|
||||
extern const Icon I_Voltage_16x16;
|
||||
extern const Icon I_RFIDBigChip_37x36;
|
||||
extern const Icon I_RFIDDolphinSend_97x61;
|
||||
extern const Icon I_RFIDDolphinReceive_97x61;
|
||||
extern const Icon I_SDQuestion_35x43;
|
||||
extern const Icon I_RFIDDolphinSend_97x61;
|
||||
extern const Icon I_RFIDDolphinSuccess_108x57;
|
||||
extern const Icon I_SDError_43x35;
|
||||
extern const Icon I_WalkR2_32x32;
|
||||
extern const Icon I_WalkL2_32x32;
|
||||
extern const Icon I_WalkRB1_32x32;
|
||||
extern const Icon I_SDQuestion_35x43;
|
||||
extern const Icon I_Home_painting_17x20;
|
||||
extern const Icon I_WalkLB2_32x32;
|
||||
extern const Icon I_Sofa_40x13;
|
||||
extern const Icon I_WalkLB1_32x32;
|
||||
extern const Icon I_PC_22x29;
|
||||
extern const Icon I_WalkL1_32x32;
|
||||
extern const Icon I_Sofa_40x13;
|
||||
extern const Icon I_TV_20x20;
|
||||
extern const Icon I_WalkR1_32x32;
|
||||
extern const Icon I_WalkRB2_32x32;
|
||||
extern const Icon I_TV_20x24;
|
||||
extern const Icon I_BadUsb_9x8;
|
||||
extern const Icon I_PlaceholderR_30x13;
|
||||
extern const Icon I_Background_128x8;
|
||||
extern const Icon I_Lock_8x8;
|
||||
extern const Icon I_Battery_26x8;
|
||||
extern const Icon I_PlaceholderL_11x13;
|
||||
extern const Icon I_Battery_19x8;
|
||||
extern const Icon I_SDcardMounted_11x8;
|
||||
extern const Icon I_SDcardFail_11x8;
|
||||
extern const Icon I_USBConnected_15x8;
|
||||
extern const Icon I_Bluetooth_5x8;
|
||||
extern const Icon I_WalkL1_32x32;
|
||||
extern const Icon I_WalkL2_32x32;
|
||||
extern const Icon I_WalkLB1_32x32;
|
||||
extern const Icon I_WalkLB2_32x32;
|
||||
extern const Icon I_WalkR1_32x32;
|
||||
extern const Icon I_WalkR2_32x32;
|
||||
extern const Icon I_WalkRB1_32x32;
|
||||
extern const Icon I_WalkRB2_32x32;
|
||||
extern const Icon I_Background_128x11;
|
||||
extern const Icon I_DolphinMafia_115x62;
|
||||
extern const Icon I_Background_128x8;
|
||||
extern const Icon I_BadUsb_9x8;
|
||||
extern const Icon I_Battery_19x8;
|
||||
extern const Icon I_Battery_26x8;
|
||||
extern const Icon I_Bluetooth_5x8;
|
||||
extern const Icon I_Lock_8x8;
|
||||
extern const Icon I_PlaceholderL_11x13;
|
||||
extern const Icon I_PlaceholderR_30x13;
|
||||
extern const Icon I_SDcardFail_11x8;
|
||||
extern const Icon I_SDcardMounted_11x8;
|
||||
extern const Icon I_USBConnected_15x8;
|
||||
extern const Icon I_DolphinExcited_64x63;
|
||||
extern const Icon I_DolphinMafia_115x62;
|
||||
extern const Icon I_DolphinNice_96x59;
|
||||
extern const Icon I_DolphinWait_61x59;
|
||||
extern const Icon I_iButtonDolphinSuccess_109x60;
|
||||
extern const Icon I_iButtonDolphinVerySuccess_108x52;
|
||||
extern const Icon I_iButtonKey_49x44;
|
||||
extern const Icon I_DolphinNice_96x59;
|
||||
extern const Icon I_DolphinWait_61x59;
|
||||
|
|
|
@ -223,6 +223,20 @@ size_t memmgr_heap_get_max_free_block() {
|
|||
osKernelUnlock();
|
||||
return max_free_size;
|
||||
}
|
||||
|
||||
void memmgr_heap_printf_free_blocks() {
|
||||
BlockLink_t* pxBlock;
|
||||
//TODO enable when we can do printf with a locked scheduler
|
||||
//osKernelLock();
|
||||
|
||||
pxBlock = xStart.pxNextFreeBlock;
|
||||
while(pxBlock->pxNextFreeBlock != NULL) {
|
||||
printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize);
|
||||
pxBlock = pxBlock->pxNextFreeBlock;
|
||||
}
|
||||
|
||||
//osKernelUnlock();
|
||||
}
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
void* pvPortMalloc(size_t xWantedSize) {
|
||||
|
|
|
@ -28,6 +28,11 @@ size_t memmgr_heap_get_thread_memory(osThreadId_t thread_id);
|
|||
*/
|
||||
size_t memmgr_heap_get_max_free_block();
|
||||
|
||||
/**
|
||||
* Print the address and size of all free blocks to stdout
|
||||
*/
|
||||
void memmgr_heap_printf_free_blocks();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
#define _USE_EXPAND 0
|
||||
/* This option switches f_expand function. (0:Disable or 1:Enable) */
|
||||
|
||||
#define _USE_CHMOD 1
|
||||
#define _USE_CHMOD 0
|
||||
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
@ -133,7 +133,7 @@
|
|||
/ To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1.
|
||||
/ This option also affects behavior of string I/O functions. */
|
||||
|
||||
#define _STRF_ENCODE 3
|
||||
#define _STRF_ENCODE 0
|
||||
/* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
|
||||
/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
|
||||
/
|
||||
|
@ -205,7 +205,7 @@
|
|||
/ System Configurations
|
||||
/----------------------------------------------------------------------------*/
|
||||
|
||||
#define _FS_TINY 1 /* 0:Normal or 1:Tiny */
|
||||
#define _FS_TINY 0 /* 0:Normal or 1:Tiny */
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is reduced _MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
|
@ -216,10 +216,10 @@
|
|||
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards C89 compatibility. */
|
||||
|
||||
#define _FS_NORTC 0
|
||||
#define _NORTC_MON 6
|
||||
#define _NORTC_MDAY 4
|
||||
#define _NORTC_YEAR 2015
|
||||
#define _FS_NORTC 1
|
||||
#define _NORTC_MON 7
|
||||
#define _NORTC_MDAY 20
|
||||
#define _NORTC_YEAR 2021
|
||||
/* The option _FS_NORTC switches timestamp functiton. If the system does not have
|
||||
/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable
|
||||
/ the timestamp function. All objects modified by FatFs will have a fixed timestamp
|
||||
|
@ -229,7 +229,7 @@
|
|||
/ _NORTC_MDAY and _NORTC_YEAR have no effect.
|
||||
/ These options have no effect at read-only configuration (_FS_READONLY = 1). */
|
||||
|
||||
#define _FS_LOCK 2 /* 0:Disable or >=1:Enable */
|
||||
#define _FS_LOCK 0 /* 0:Disable or >=1:Enable */
|
||||
/* The option _FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when _FS_READONLY
|
||||
/ is 1.
|
||||
|
@ -240,7 +240,7 @@
|
|||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
#define _FS_REENTRANT 1 /* 0:Disable or 1:Enable */
|
||||
#define _FS_REENTRANT 0 /* 0:Disable or 1:Enable */
|
||||
#define _FS_TIMEOUT 1000 /* Timeout period in unit of time ticks */
|
||||
#define _SYNC_t osMutexId_t
|
||||
/* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
#include "file-worker.h"
|
||||
#include "m-string.h"
|
||||
#include <hex.h>
|
||||
#include <sd-card-api.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <furi.h>
|
||||
|
||||
struct FileWorker {
|
||||
FS_Api* fs_api;
|
||||
SdCard_Api* sd_ex_api;
|
||||
Storage* api;
|
||||
bool silent;
|
||||
File file;
|
||||
char file_buf[48];
|
||||
size_t file_buf_cnt;
|
||||
File* file;
|
||||
};
|
||||
|
||||
bool file_worker_check_common_errors(FileWorker* file_worker);
|
||||
|
@ -26,16 +23,15 @@ bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool
|
|||
FileWorker* file_worker_alloc(bool _silent) {
|
||||
FileWorker* file_worker = malloc(sizeof(FileWorker));
|
||||
file_worker->silent = _silent;
|
||||
file_worker->fs_api = furi_record_open("sdcard");
|
||||
file_worker->sd_ex_api = furi_record_open("sdcard-ex");
|
||||
file_worker->file_buf_cnt = 0;
|
||||
file_worker->api = furi_record_open("storage");
|
||||
file_worker->file = storage_file_alloc(file_worker->api);
|
||||
|
||||
return file_worker;
|
||||
}
|
||||
|
||||
void file_worker_free(FileWorker* file_worker) {
|
||||
furi_record_close("sdcard");
|
||||
furi_record_close("sdcard-ex");
|
||||
storage_file_free(file_worker->file);
|
||||
furi_record_close("storage");
|
||||
free(file_worker);
|
||||
}
|
||||
|
||||
|
@ -44,8 +40,7 @@ bool file_worker_open(
|
|||
const char* filename,
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode) {
|
||||
bool result =
|
||||
file_worker->fs_api->file.open(&file_worker->file, filename, access_mode, open_mode);
|
||||
bool result = storage_file_open(file_worker->file, filename, access_mode, open_mode);
|
||||
|
||||
if(!result) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot open\nfile");
|
||||
|
@ -56,13 +51,15 @@ bool file_worker_open(
|
|||
}
|
||||
|
||||
bool file_worker_close(FileWorker* file_worker) {
|
||||
file_worker->fs_api->file.close(&file_worker->file);
|
||||
if(storage_file_is_open(file_worker->file)) {
|
||||
storage_file_close(file_worker->file);
|
||||
}
|
||||
|
||||
return file_worker_check_common_errors(file_worker);
|
||||
}
|
||||
|
||||
bool file_worker_mkdir(FileWorker* file_worker, const char* dirname) {
|
||||
FS_Error fs_result = file_worker->fs_api->common.mkdir(dirname);
|
||||
FS_Error fs_result = storage_common_mkdir(file_worker->api, dirname);
|
||||
|
||||
if(fs_result != FSE_OK && fs_result != FSE_EXIST) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot create\nfolder");
|
||||
|
@ -73,7 +70,7 @@ bool file_worker_mkdir(FileWorker* file_worker, const char* dirname) {
|
|||
}
|
||||
|
||||
bool file_worker_remove(FileWorker* file_worker, const char* filename) {
|
||||
FS_Error fs_result = file_worker->fs_api->common.remove(filename);
|
||||
FS_Error fs_result = storage_common_remove(file_worker->api, filename);
|
||||
if(fs_result != FSE_OK && fs_result != FSE_NOT_EXIST) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot remove\nold file");
|
||||
return false;
|
||||
|
@ -96,9 +93,8 @@ bool file_worker_read_until(FileWorker* file_worker, string_t str_result, char s
|
|||
uint8_t buffer[buffer_size];
|
||||
|
||||
do {
|
||||
uint16_t read_count =
|
||||
file_worker->fs_api->file.read(&file_worker->file, buffer, buffer_size);
|
||||
if(file_worker->file.error_id != FSE_OK) {
|
||||
uint16_t read_count = storage_file_read(file_worker->file, buffer, buffer_size);
|
||||
if(storage_file_get_error(file_worker->file) != FSE_OK) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot read\nfile");
|
||||
return false;
|
||||
}
|
||||
|
@ -210,7 +206,16 @@ bool file_worker_write_hex(FileWorker* file_worker, const uint8_t* buffer, uint1
|
|||
}
|
||||
|
||||
void file_worker_show_error(FileWorker* file_worker, const char* error_text) {
|
||||
file_worker->sd_ex_api->show_error(file_worker->sd_ex_api->context, error_text);
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_text(message, error_text, 88, 32, AlignCenter, AlignCenter);
|
||||
dialog_message_set_icon(message, &I_SDQuestion_35x43, 5, 6);
|
||||
dialog_message_set_buttons(message, "Back", NULL, NULL);
|
||||
dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
|
||||
furi_record_close("dialogs");
|
||||
}
|
||||
|
||||
bool file_worker_file_select(
|
||||
|
@ -220,16 +225,17 @@ bool file_worker_file_select(
|
|||
char* result,
|
||||
uint8_t result_size,
|
||||
const char* selected_filename) {
|
||||
return file_worker->sd_ex_api->file_select(
|
||||
file_worker->sd_ex_api->context, path, extension, result, result_size, selected_filename);
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
bool ret =
|
||||
dialog_file_select_show(dialogs, path, extension, result, result_size, selected_filename);
|
||||
furi_record_close("dialogs");
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool file_worker_check_common_errors(FileWorker* file_worker) {
|
||||
//TODO remove
|
||||
/* TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info(). */
|
||||
FS_Error fs_err = file_worker->fs_api->common.get_fs_info(NULL, NULL);
|
||||
if(fs_err != FSE_OK)
|
||||
file_worker->sd_ex_api->show_error(file_worker->sd_ex_api->context, "SD card not found");
|
||||
return fs_err == FSE_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text) {
|
||||
|
@ -239,10 +245,9 @@ void file_worker_show_error_internal(FileWorker* file_worker, const char* error_
|
|||
}
|
||||
|
||||
bool file_worker_read_internal(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) {
|
||||
uint16_t read_count =
|
||||
file_worker->fs_api->file.read(&file_worker->file, buffer, bytes_to_read);
|
||||
uint16_t read_count = storage_file_read(file_worker->file, buffer, bytes_to_read);
|
||||
|
||||
if(file_worker->file.error_id != FSE_OK || read_count != bytes_to_read) {
|
||||
if(storage_file_get_error(file_worker->file) != FSE_OK || read_count != bytes_to_read) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot read\nfile");
|
||||
return false;
|
||||
}
|
||||
|
@ -254,10 +259,9 @@ bool file_worker_write_internal(
|
|||
FileWorker* file_worker,
|
||||
const void* buffer,
|
||||
uint16_t bytes_to_write) {
|
||||
uint16_t write_count =
|
||||
file_worker->fs_api->file.write(&file_worker->file, buffer, bytes_to_write);
|
||||
uint16_t write_count = storage_file_write(file_worker->file, buffer, bytes_to_write);
|
||||
|
||||
if(file_worker->file.error_id != FSE_OK || write_count != bytes_to_write) {
|
||||
if(storage_file_get_error(file_worker->file) != FSE_OK || write_count != bytes_to_write) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot write\nto file");
|
||||
return false;
|
||||
}
|
||||
|
@ -266,9 +270,9 @@ bool file_worker_write_internal(
|
|||
}
|
||||
|
||||
bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position) {
|
||||
*position = file_worker->fs_api->file.tell(&file_worker->file);
|
||||
*position = storage_file_tell(file_worker->file);
|
||||
|
||||
if(file_worker->file.error_id != FSE_OK) {
|
||||
if(storage_file_get_error(file_worker->file) != FSE_OK) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot tell\nfile offset");
|
||||
return false;
|
||||
}
|
||||
|
@ -277,8 +281,8 @@ bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position) {
|
|||
}
|
||||
|
||||
bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool from_start) {
|
||||
file_worker->fs_api->file.seek(&file_worker->file, position, from_start);
|
||||
if(file_worker->file.error_id != FSE_OK) {
|
||||
storage_file_seek(file_worker->file, position, from_start);
|
||||
if(storage_file_get_error(file_worker->file) != FSE_OK) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot seek\nfile");
|
||||
return false;
|
||||
}
|
||||
|
@ -286,9 +290,17 @@ bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool
|
|||
return true;
|
||||
}
|
||||
|
||||
bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t file_buf_size, char separator) {
|
||||
bool file_worker_read_until_buffered(
|
||||
FileWorker* file_worker,
|
||||
string_t str_result,
|
||||
char* file_buf,
|
||||
size_t* file_buf_cnt,
|
||||
size_t file_buf_size,
|
||||
char separator) {
|
||||
furi_assert(string_capacity(str_result) > 0);
|
||||
furi_assert(file_buf_size <= 512); /* fs_api->file.read now supports up to 512 bytes reading at a time */
|
||||
|
||||
// fs_api->file.read now supports up to 512 bytes reading at a time
|
||||
furi_assert(file_buf_size <= 512);
|
||||
|
||||
string_clean(str_result);
|
||||
size_t newline_index = 0;
|
||||
|
@ -311,11 +323,11 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul
|
|||
furi_assert(0);
|
||||
}
|
||||
|
||||
if (max_length && (string_size(str_result) + end_index > max_length))
|
||||
if(max_length && (string_size(str_result) + end_index > max_length))
|
||||
max_length_exceeded = true;
|
||||
|
||||
if (!max_length_exceeded) {
|
||||
for (size_t i = 0; i < end_index; ++i) {
|
||||
if(!max_length_exceeded) {
|
||||
for(size_t i = 0; i < end_index; ++i) {
|
||||
string_push_back(str_result, file_buf[i]);
|
||||
}
|
||||
}
|
||||
|
@ -325,9 +337,9 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul
|
|||
if(found_eol) break;
|
||||
}
|
||||
|
||||
*file_buf_cnt +=
|
||||
file_worker->fs_api->file.read(&file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt);
|
||||
if(file_worker->file.error_id != FSE_OK) {
|
||||
*file_buf_cnt += storage_file_read(
|
||||
file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt);
|
||||
if(storage_file_get_error(file_worker->file) != FSE_OK) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot read\nfile");
|
||||
string_clear(str_result);
|
||||
*file_buf_cnt = 0;
|
||||
|
@ -338,14 +350,13 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul
|
|||
}
|
||||
}
|
||||
|
||||
if (max_length_exceeded)
|
||||
string_clear(str_result);
|
||||
if(max_length_exceeded) string_clear(str_result);
|
||||
|
||||
return string_size(str_result) || *file_buf_cnt;
|
||||
}
|
||||
|
||||
bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path) {
|
||||
FS_Error fs_result = file_worker->fs_api->common.rename(old_path, new_path);
|
||||
FS_Error fs_result = storage_common_rename(file_worker->api, old_path, new_path);
|
||||
|
||||
if(fs_result != FSE_OK && fs_result != FSE_EXIST) {
|
||||
file_worker_show_error_internal(file_worker, "Cannot rename\n file/directory");
|
||||
|
@ -359,16 +370,12 @@ bool file_worker_check_errors(FileWorker* file_worker) {
|
|||
return file_worker_check_common_errors(file_worker);
|
||||
}
|
||||
|
||||
bool file_worker_is_file_exist(
|
||||
FileWorker* file_worker,
|
||||
const char* filename,
|
||||
bool* exist) {
|
||||
bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist) {
|
||||
File* file = storage_file_alloc(file_worker->api);
|
||||
|
||||
File file;
|
||||
*exist = file_worker->fs_api->file.open(&file, filename, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
if (*exist)
|
||||
file_worker->fs_api->file.close(&file);
|
||||
*exist = storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
return file_worker_check_common_errors(file_worker);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include <m-string.h>
|
||||
#include <filesystem-api.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
@ -150,20 +150,20 @@ void file_worker_show_error(FileWorker* file_worker, const char* error_text);
|
|||
* @brief Show system file select widget
|
||||
*
|
||||
* @param file_worker FileWorker instance
|
||||
* @param path
|
||||
* @param extension
|
||||
* @param result
|
||||
* @param result_size
|
||||
* @param selected_filename
|
||||
* @return true if file was selected
|
||||
* @param path path to directory
|
||||
* @param extension file extension to be offered for selection
|
||||
* @param selected_filename buffer where the selected filename will be saved
|
||||
* @param selected_filename_size and the size of this buffer
|
||||
* @param preselected_filename filename to be preselected
|
||||
* @return bool whether a file was selected
|
||||
*/
|
||||
bool file_worker_file_select(
|
||||
FileWorker* file_worker,
|
||||
const char* path,
|
||||
const char* extension,
|
||||
char* result,
|
||||
uint8_t result_size,
|
||||
const char* selected_filename);
|
||||
char* selected_filename,
|
||||
uint8_t selected_filename_size,
|
||||
const char* preselected_filename);
|
||||
|
||||
/**
|
||||
* @brief Reads data from a file until separator or EOF is found.
|
||||
|
@ -177,7 +177,13 @@ bool file_worker_file_select(
|
|||
* @param separator
|
||||
* @return true on success
|
||||
*/
|
||||
bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t max_length, char separator);
|
||||
bool file_worker_read_until_buffered(
|
||||
FileWorker* file_worker,
|
||||
string_t str_result,
|
||||
char* file_buf,
|
||||
size_t* file_buf_cnt,
|
||||
size_t max_length,
|
||||
char separator);
|
||||
|
||||
/**
|
||||
* @brief Check whether file exist or not
|
||||
|
@ -187,10 +193,7 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul
|
|||
* @param exist - flag to show file exist
|
||||
* @return true on success
|
||||
*/
|
||||
bool file_worker_is_file_exist(
|
||||
FileWorker* file_worker,
|
||||
const char* filename,
|
||||
bool* exist);
|
||||
bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist);
|
||||
|
||||
/**
|
||||
* @brief Rename file or directory
|
||||
|
@ -200,9 +203,7 @@ bool file_worker_is_file_exist(
|
|||
* @param new_filename
|
||||
* @return true on success
|
||||
*/
|
||||
bool file_worker_rename(FileWorker* file_worker,
|
||||
const char* old_path,
|
||||
const char* new_path);
|
||||
bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path);
|
||||
|
||||
/**
|
||||
* @brief Check errors
|
||||
|
|
|
@ -28,6 +28,23 @@ bool args_read_string_and_trim(string_t args, string_t word) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool args_read_probably_quoted_string_and_trim(string_t args, string_t word) {
|
||||
if(string_size(args) > 1 && string_get_char(args, 0) == '\"') {
|
||||
size_t second_quote_pos = string_search_char(args, '\"', 1);
|
||||
|
||||
if(second_quote_pos == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string_set_n(word, args, 1, second_quote_pos - 1);
|
||||
string_right(args, second_quote_pos + 1);
|
||||
string_strim(args);
|
||||
return true;
|
||||
} else {
|
||||
return args_read_string_and_trim(args, word);
|
||||
}
|
||||
}
|
||||
|
||||
bool args_char_to_hex(char hi_nibble, char low_nibble, uint8_t* byte) {
|
||||
uint8_t hi_nibble_value = 0;
|
||||
uint8_t low_nibble_value = 0;
|
||||
|
|
|
@ -8,15 +8,25 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
/**
|
||||
* @brief Extract first word from arguments string and trim arguments string
|
||||
* @brief Extract first argument from arguments string and trim arguments string
|
||||
*
|
||||
* @param args arguments string
|
||||
* @param word first word, output
|
||||
* @param word first argument, output
|
||||
* @return true - success
|
||||
* @return false - arguments string does not contain anything
|
||||
*/
|
||||
bool args_read_string_and_trim(string_t args, string_t word);
|
||||
|
||||
/**
|
||||
* @brief Extract the first quoted argument from the argument string and trim the argument string. If the argument is not quoted, calls args_read_string_and_trim.
|
||||
*
|
||||
* @param args arguments string
|
||||
* @param word first argument, output, without quotes
|
||||
* @return true - success
|
||||
* @return false - arguments string does not contain anything
|
||||
*/
|
||||
bool args_read_probably_quoted_string_and_trim(string_t args, string_t word);
|
||||
|
||||
/**
|
||||
* @brief Convert hex ASCII values to byte array
|
||||
*
|
||||
|
|
|
@ -1,329 +0,0 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Access mode flags
|
||||
*/
|
||||
typedef enum {
|
||||
FSAM_READ = (1 << 0), /**< Read access */
|
||||
FSAM_WRITE = (1 << 1), /**< Write access */
|
||||
} FS_AccessMode;
|
||||
|
||||
/**
|
||||
* @brief Open mode flags
|
||||
*/
|
||||
typedef enum {
|
||||
FSOM_OPEN_EXISTING = 1, /**< Open file, fail if file doesn't exist */
|
||||
FSOM_OPEN_ALWAYS = 2, /**< Open file. Create new file if not exist */
|
||||
FSOM_OPEN_APPEND = 4, /**< Open file. Create new file if not exist. Set R/W pointer to EOF */
|
||||
FSOM_CREATE_NEW = 8, /**< Creates a new file. Fails if the file is exist */
|
||||
FSOM_CREATE_ALWAYS = 16, /**< Creates a new file. If file exist, truncate to zero size */
|
||||
} FS_OpenMode;
|
||||
|
||||
/**
|
||||
* @brief API errors enumeration
|
||||
*/
|
||||
typedef enum {
|
||||
FSE_OK, /**< No error */
|
||||
FSE_NOT_READY, /**< FS not ready */
|
||||
FSE_EXIST, /**< File/Dir alrady exist */
|
||||
FSE_NOT_EXIST, /**< File/Dir does not exist */
|
||||
FSE_INVALID_PARAMETER, /**< Invalid API parameter */
|
||||
FSE_DENIED, /**< Access denied */
|
||||
FSE_INVALID_NAME, /**< Invalid name/path */
|
||||
FSE_INTERNAL, /**< Internal error */
|
||||
FSE_NOT_IMPLEMENTED, /**< Functon not implemented */
|
||||
} FS_Error;
|
||||
|
||||
/**
|
||||
* @brief FileInfo flags
|
||||
*/
|
||||
typedef enum {
|
||||
FSF_READ_ONLY = (1 << 0), /**< Readonly */
|
||||
FSF_HIDDEN = (1 << 1), /**< Hidden */
|
||||
FSF_SYSTEM = (1 << 2), /**< System */
|
||||
FSF_DIRECTORY = (1 << 3), /**< Directory */
|
||||
FSF_ARCHIVE = (1 << 4), /**< Archive */
|
||||
} FS_Flags;
|
||||
|
||||
/**
|
||||
* @brief Structure that hold file index and returned api errors
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t file_id; /**< File ID for internal references */
|
||||
FS_Error error_id; /**< Standart API error from FS_Error enum */
|
||||
uint32_t internal_error_id; /**< Internal API error value */
|
||||
} File;
|
||||
|
||||
// TODO: solve year 2107 problem
|
||||
/**
|
||||
* @brief Structure that hold packed date values
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t month_day : 5; /**< month day */
|
||||
uint16_t month : 4; /**< month index */
|
||||
uint16_t year : 7; /**< year, year + 1980 to get actual value */
|
||||
} FileDate;
|
||||
|
||||
/**
|
||||
* @brief Structure that hold packed time values
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t second : 5; /**< second, second * 2 to get actual value */
|
||||
uint16_t minute : 6; /**< minute */
|
||||
uint16_t hour : 5; /**< hour */
|
||||
} FileTime;
|
||||
|
||||
/**
|
||||
* @brief Union of simple date and real value
|
||||
*/
|
||||
typedef union {
|
||||
FileDate simple; /**< simple access to date */
|
||||
uint16_t value; /**< real date value */
|
||||
} FileDateUnion;
|
||||
|
||||
/**
|
||||
* @brief Union of simple time and real value
|
||||
*/
|
||||
typedef union {
|
||||
FileTime simple; /**< simple access to time */
|
||||
uint16_t value; /**< real time value */
|
||||
} FileTimeUnion;
|
||||
|
||||
/**
|
||||
* @brief Structure that hold file info
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t flags; /**< flags from FS_Flags enum */
|
||||
uint64_t size; /**< file size */
|
||||
FileDateUnion date; /**< file date */
|
||||
FileTimeUnion time; /**< file time */
|
||||
} FileInfo;
|
||||
|
||||
/** @struct FS_File_Api
|
||||
* @brief File api structure
|
||||
*
|
||||
* @var FS_File_Api::open
|
||||
* @brief Open file
|
||||
* @param file pointer to file object, filled by api
|
||||
* @param path path to file
|
||||
* @param access_mode access mode from FS_AccessMode
|
||||
* @param open_mode open mode from FS_OpenMode
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::close
|
||||
* @brief Close file
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::read
|
||||
* @brief Read bytes from file to buffer
|
||||
* @param file pointer to file object
|
||||
* @param buff pointer to buffer for reading
|
||||
* @param bytes_to_read how many bytes to read, must be smaller or equal to buffer size
|
||||
* @return how many bytes actually has been readed
|
||||
*
|
||||
* @var FS_File_Api::write
|
||||
* @brief Write bytes from buffer to file
|
||||
* @param file pointer to file object
|
||||
* @param buff pointer to buffer for writing
|
||||
* @param bytes_to_read how many bytes to write, must be smaller or equal to buffer size
|
||||
* @return how many bytes actually has been writed
|
||||
*
|
||||
* @var FS_File_Api::seek
|
||||
* @brief Move r/w pointer
|
||||
* @param file pointer to file object
|
||||
* @param offset offset to move r/w pointer
|
||||
* @param from_start set offset from start, or from current position
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::tell
|
||||
* @brief Get r/w pointer position
|
||||
* @param file pointer to file object
|
||||
* @return current r/w pointer position
|
||||
*
|
||||
* @var FS_File_Api::truncate
|
||||
* @brief Truncate file size to current r/w pointer position
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::size
|
||||
* @brief Fet file size
|
||||
* @param file pointer to file object
|
||||
* @return file size
|
||||
*
|
||||
* @var FS_File_Api::sync
|
||||
* @brief Write file cache to storage
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_File_Api::eof
|
||||
* @brief Checks that the r/w pointer is at the end of the file
|
||||
* @param file pointer to file object
|
||||
* @return end of file flag
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief File api structure
|
||||
*/
|
||||
typedef struct {
|
||||
bool (*open)(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode);
|
||||
bool (*close)(File* file);
|
||||
uint16_t (*read)(File* file, void* buff, uint16_t bytes_to_read);
|
||||
uint16_t (*write)(File* file, const void* buff, uint16_t bytes_to_write);
|
||||
bool (*seek)(File* file, uint32_t offset, bool from_start);
|
||||
uint64_t (*tell)(File* file);
|
||||
bool (*truncate)(File* file);
|
||||
uint64_t (*size)(File* file);
|
||||
bool (*sync)(File* file);
|
||||
bool (*eof)(File* file);
|
||||
} FS_File_Api;
|
||||
|
||||
/** @struct FS_Dir_Api
|
||||
* @brief Dir api structure
|
||||
*
|
||||
* @var FS_Dir_Api::open
|
||||
* @brief Open directory to get objects from
|
||||
* @param file pointer to file object, filled by api
|
||||
* @param path path to directory
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_Dir_Api::close
|
||||
* @brief Close directory
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*
|
||||
* @var FS_Dir_Api::read
|
||||
* @brief Read next object info in directory
|
||||
* @param file pointer to file object
|
||||
* @param fileinfo pointer to readed FileInfo, can be NULL
|
||||
* @param name pointer to name buffer, can be NULL
|
||||
* @param name_length name buffer length
|
||||
* @return success flag (if next object not exist also returns false and set error_id to FSE_NOT_EXIST)
|
||||
*
|
||||
* @var FS_Dir_Api::rewind
|
||||
* @brief Rewind to first object info in directory
|
||||
* @param file pointer to file object
|
||||
* @return success flag
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Dir api structure
|
||||
*/
|
||||
typedef struct {
|
||||
bool (*open)(File* file, const char* path);
|
||||
bool (*close)(File* file);
|
||||
bool (*read)(File* file, FileInfo* fileinfo, char* name, uint16_t name_length);
|
||||
bool (*rewind)(File* file);
|
||||
} FS_Dir_Api;
|
||||
|
||||
/** @struct FS_Common_Api
|
||||
* @brief Common api structure
|
||||
*
|
||||
* @var FS_Common_Api::info
|
||||
* @brief Open directory to get objects from
|
||||
* @param path path to file/directory
|
||||
* @param fileinfo pointer to readed FileInfo, can be NULL
|
||||
* @param name pointer to name buffer, can be NULL
|
||||
* @param name_length name buffer length
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::remove
|
||||
* @brief Remove file/directory from storage,
|
||||
* directory must be empty,
|
||||
* file/directory must not be opened,
|
||||
* file/directory must not have FSF_READ_ONLY flag
|
||||
* @param path path to file/directory
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::rename
|
||||
* @brief Rename file/directory,
|
||||
* file/directory must not be opened
|
||||
* @param path path to file/directory
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::set_attr
|
||||
* @brief Set attributes of file/directory,
|
||||
* for example:
|
||||
* @code
|
||||
* set "read only" flag and remove "hidden" flag
|
||||
* set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN);
|
||||
* @endcode
|
||||
* @param path path to file/directory
|
||||
* @param attr attribute values consist of FS_Flags
|
||||
* @param mask attribute mask consist of FS_Flags
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::mkdir
|
||||
* @brief Create new directory
|
||||
* @param path path to new directory
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::set_time
|
||||
* @brief Set file/directory modification time
|
||||
* @param path path to file/directory
|
||||
* @param date modification date
|
||||
* @param time modification time
|
||||
* @see FileDateUnion
|
||||
* @see FileTimeUnion
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::get_fs_info
|
||||
* @brief Get total and free space storage values
|
||||
* @param total_space pointer to total space value
|
||||
* @param free_space pointer to free space value
|
||||
* @return FS_Error error info
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Common api structure
|
||||
*/
|
||||
typedef struct {
|
||||
FS_Error (*info)(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length);
|
||||
FS_Error (*remove)(const char* path);
|
||||
FS_Error (*rename)(const char* old_path, const char* new_path);
|
||||
FS_Error (*set_attr)(const char* path, uint8_t attr, uint8_t mask);
|
||||
FS_Error (*mkdir)(const char* path);
|
||||
FS_Error (*set_time)(const char* path, FileDateUnion date, FileTimeUnion time);
|
||||
FS_Error (*get_fs_info)(uint64_t* total_space, uint64_t* free_space);
|
||||
} FS_Common_Api;
|
||||
|
||||
/** @struct FS_Error_Api
|
||||
* @brief Errors api structure
|
||||
*
|
||||
* @var FS_Error_Api::get_desc
|
||||
* @brief Get error description text
|
||||
* @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id)
|
||||
* @return pointer to description text
|
||||
*
|
||||
* @var FS_Error_Api::get_internal_desc
|
||||
* @brief Get internal error description text
|
||||
* @param internal_error_id error id (for fire/dir functions result can be obtained from File.internal_error_id)
|
||||
* @return pointer to description text
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Errors api structure
|
||||
*/
|
||||
typedef struct {
|
||||
const char* (*get_desc)(FS_Error error_id);
|
||||
const char* (*get_internal_desc)(uint32_t internal_error_id);
|
||||
} FS_Error_Api;
|
||||
|
||||
/**
|
||||
* @brief Full filesystem api structure
|
||||
*/
|
||||
typedef struct {
|
||||
FS_File_Api file;
|
||||
FS_Dir_Api dir;
|
||||
FS_Common_Api common;
|
||||
FS_Error_Api error;
|
||||
} FS_Api;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1,25 +0,0 @@
|
|||
#pragma once
|
||||
#include <furi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SdApp SdApp;
|
||||
|
||||
typedef struct {
|
||||
SdApp* context;
|
||||
bool (*file_select)(
|
||||
SdApp* context,
|
||||
const char* path,
|
||||
const char* extension,
|
||||
char* result,
|
||||
uint8_t result_size,
|
||||
const char* selected_filename);
|
||||
void (*check_error)(SdApp* context);
|
||||
void (*show_error)(SdApp* context, const char* error_text);
|
||||
} SdCard_Api;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -1,48 +0,0 @@
|
|||
#include <file_reader.h>
|
||||
|
||||
std::string FileReader::getline(File* file) {
|
||||
std::string str;
|
||||
size_t newline_index = 0;
|
||||
bool found_eol = false;
|
||||
bool max_length_exceeded = false;
|
||||
|
||||
while(1) {
|
||||
if(file_buf_cnt > 0) {
|
||||
size_t end_index = 0;
|
||||
char* endline_ptr = (char*)memchr(file_buf, '\n', file_buf_cnt);
|
||||
newline_index = endline_ptr - file_buf;
|
||||
|
||||
if(endline_ptr == 0) {
|
||||
end_index = file_buf_cnt;
|
||||
} else if(newline_index < file_buf_cnt) {
|
||||
end_index = newline_index + 1;
|
||||
found_eol = true;
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
|
||||
if (max_line_length && (str.size() + end_index > max_line_length))
|
||||
max_length_exceeded = true;
|
||||
|
||||
if (!max_length_exceeded)
|
||||
str.append(file_buf, end_index);
|
||||
|
||||
memmove(file_buf, &file_buf[end_index], file_buf_cnt - end_index);
|
||||
file_buf_cnt = file_buf_cnt - end_index;
|
||||
if(found_eol) break;
|
||||
}
|
||||
|
||||
file_buf_cnt +=
|
||||
fs_api->file.read(file, &file_buf[file_buf_cnt], sizeof(file_buf) - file_buf_cnt);
|
||||
if(file_buf_cnt == 0) {
|
||||
break; // end of reading
|
||||
}
|
||||
}
|
||||
|
||||
if (max_length_exceeded)
|
||||
str.clear();
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "sd-card-api.h"
|
||||
#include "filesystem-api.h"
|
||||
|
||||
class FileReader {
|
||||
private:
|
||||
char file_buf[48];
|
||||
size_t file_buf_cnt = 0;
|
||||
size_t max_line_length = 0;
|
||||
SdCard_Api* sd_ex_api;
|
||||
FS_Api* fs_api;
|
||||
|
||||
public:
|
||||
FileReader() {
|
||||
sd_ex_api = static_cast<SdCard_Api*>(furi_record_open("sdcard-ex"));
|
||||
fs_api = static_cast<FS_Api*>(furi_record_open("sdcard"));
|
||||
reset();
|
||||
}
|
||||
~FileReader() {
|
||||
furi_record_close("sdcard");
|
||||
furi_record_close("sdcard-ex");
|
||||
}
|
||||
|
||||
std::string getline(File* file);
|
||||
|
||||
void reset(void) {
|
||||
file_buf_cnt = 0;
|
||||
}
|
||||
|
||||
SdCard_Api& get_sd_api() {
|
||||
return *sd_ex_api;
|
||||
}
|
||||
|
||||
FS_Api& get_fs_api() {
|
||||
return *fs_api;
|
||||
}
|
||||
|
||||
void set_max_line_length(size_t value) {
|
||||
max_line_length = value;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#include "subghz_keystore.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <filesystem-api.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define FILE_BUFFER_SIZE 64
|
||||
|
||||
|
@ -52,17 +52,15 @@ static void subghz_keystore_process_line(SubGhzKeystore* instance, string_t line
|
|||
}
|
||||
|
||||
void subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) {
|
||||
File manufacture_keys_file;
|
||||
FS_Api* fs_api = furi_record_open("sdcard");
|
||||
fs_api->file.open(&manufacture_keys_file, file_name, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
File* manufacture_keys_file = storage_file_alloc(furi_record_open("storage"));
|
||||
string_t line;
|
||||
string_init(line);
|
||||
if(manufacture_keys_file.error_id == FSE_OK) {
|
||||
if(storage_file_open(manufacture_keys_file, file_name, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
printf("Loading manufacture keys file %s\r\n", file_name);
|
||||
char buffer[FILE_BUFFER_SIZE];
|
||||
uint16_t ret;
|
||||
do {
|
||||
ret = fs_api->file.read(&manufacture_keys_file, buffer, FILE_BUFFER_SIZE);
|
||||
ret = storage_file_read(manufacture_keys_file, buffer, FILE_BUFFER_SIZE);
|
||||
for (uint16_t i=0; i < ret; i++) {
|
||||
if (buffer[i] == '\n' && string_size(line) > 0) {
|
||||
subghz_keystore_process_line(instance, line);
|
||||
|
@ -76,8 +74,9 @@ void subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) {
|
|||
printf("Manufacture keys file is not found: %s\r\n", file_name);
|
||||
}
|
||||
string_clear(line);
|
||||
fs_api->file.close(&manufacture_keys_file);
|
||||
furi_record_close("sdcard");
|
||||
storage_file_close(manufacture_keys_file);
|
||||
storage_file_free(manufacture_keys_file);
|
||||
furi_record_close("storage");
|
||||
}
|
||||
|
||||
SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance) {
|
||||
|
|
Loading…
Reference in a new issue