[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:
SG 2021-07-23 22:20:19 +10:00 committed by GitHub
parent a81203941b
commit ad421a81bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
95 changed files with 6451 additions and 4349 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View 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

View 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

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

View 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

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

View 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

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

View 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

View file

@ -1,4 +1,4 @@
#include "view_holder.h"
#include "view-holder.h"
#include <gui/view_i.h>
struct ViewHolder {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,6 @@
#include "../ibutton-view-manager.h"
#include "../ibutton-event.h"
#include <callback-connector.h>
#include <filesystem-api.h>
typedef enum {
SubmenuIndexRead,

View file

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

View file

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

View file

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

View file

@ -6,7 +6,6 @@
class IrdaAppBruteForce {
const char* universal_db_filename;
File file;
std::string current_record;
std::unique_ptr<IrdaAppFileParser> file_parser;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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

View 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

View 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

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

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

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

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

View 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

View 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

View 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

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

View 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

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

View 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

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

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

View 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

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

View 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

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

View 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

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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