[FL-1191][FL-1524] Filesystem rework (#568)
* FS-Api: removed datetime manipulation functions and most of the file flags * Filesystem: common proxy api * Filesystem: renamed to Storage. Work has begun on a glue layer. Added functions for reentrance. * Storage: sd mount and sd file open * Storage: sd file close * Storage: temporary test app * Storage: free filedata on close * Storage: sd file read and write * Storage: added internal storage (LittleFS) * Storage: renamed internal commands * Storage: seek, tell, truncate, size, sync, eof * Storage: error descriptions * Storage: directory management api (open, close, read, rewind) * Storage: common management api (stat, fs_stat, remove, rename, mkdir) * Dolphin app and Notifications app now use raw storage. * Storage: storage statuses renamed. Implemented sd card icon. * Storage: added raw sd-card api. * Storage settings: work started * Assets: use new icons approach * Storage settings: working storage settings * Storage: completely redesigned api, no longer sticking out FS_Api * Storage: more simplified api, getting error_id from file is hidden from user, pointer to api is hidden inside file * Storage: cli info and format commands * Storage-cli: file list * Storage: a simpler and more reliable api * FatFS: slightly lighter and faster config. Also disabled reentrancy and file locking functions. They moved to a storage service. * Storage-cli: accommodate to the new cli api. * Storage: filesystem api is separated into internal and common api. * Cli: added the ability to print the list of free heap blocks * Storage: uses a list instead of an array to store the StorageFile. Rewrote api calls to use semaphores instead of thread flags. * Storage settings: added the ability to benchmark the SD card. * Gui module file select: uses new storage api * Apps: removed deprecated sd_card_test application * Args lib: support for enquoted arguments * Dialogs: a new gui app for simple non-asynchronous apps * Dialogs: view holder for easy single view work * File worker: use new storage api * IButton and lfrrfid apps: save keys to any storage * Apps: fix ibutton and lfrfid stack, remove sd_card_test. * SD filesystem: app removed * File worker: fixed api pointer type * Subghz: loading assets using the new storage api * NFC: use the new storage api * Dialogs: the better api for the message element * Archive: use new storage api * Irda: changed assest path, changed app path * FileWorker: removed unused file_buf_cnt * Storage: copying and renaming files now works between storages * Storage cli: read, copy, remove, rename commands * Archive: removed commented code * Storage cli: write command * Applications: add SRV_STORAGE and SRV_DIALOGS * Internal-storage: removed * Storage: improved api * Storage app: changed api pointer from StorageApp to Storage * Storage: better file_id handling * Storage: more consistent errors * Loader: support for NULL icons * Storage: do nothing with the lfs file or directory if it is not open * Storage: fix typo * Storage: minor float usage cleanup, rename some symbols. * Storage: compact doxygen comments. Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									a81203941b
								
							
						
					
					
						commit
						ad421a81bc
					
				| @ -19,26 +19,26 @@ int32_t nfc_task(void* p); | |||||||
| int32_t dolphin_task(void* p); | int32_t dolphin_task(void* p); | ||||||
| int32_t power_task(void* p); | int32_t power_task(void* p); | ||||||
| int32_t bt_task(void* p); | int32_t bt_task(void* p); | ||||||
| int32_t sd_card_test(void* p); |  | ||||||
| int32_t application_vibro(void* p); | int32_t application_vibro(void* p); | ||||||
| int32_t app_gpio_test(void* p); | int32_t app_gpio_test(void* p); | ||||||
| int32_t app_ibutton(void* p); | int32_t app_ibutton(void* p); | ||||||
| int32_t cli_task(void* p); | int32_t cli_task(void* p); | ||||||
| int32_t music_player(void* p); | int32_t music_player(void* p); | ||||||
| int32_t sdnfc(void* p); | int32_t sdnfc(void* p); | ||||||
| int32_t sd_filesystem(void* p); |  | ||||||
| int32_t subghz_app(void* p); | int32_t subghz_app(void* p); | ||||||
| int32_t gui_test(void* p); | int32_t gui_test(void* p); | ||||||
| int32_t keypad_test(void* p); | int32_t keypad_test(void* p); | ||||||
| int32_t scene_app(void* p); | int32_t scene_app(void* p); | ||||||
| int32_t passport(void* p); | int32_t passport(void* p); | ||||||
| int32_t app_accessor(void* p); | int32_t app_accessor(void* p); | ||||||
| int32_t internal_storage_task(void* p); |  | ||||||
| int32_t app_archive(void* p); | int32_t app_archive(void* p); | ||||||
| int32_t notification_app(void* p); | int32_t notification_app(void* p); | ||||||
| int32_t scened_app(void* p); | int32_t scened_app(void* p); | ||||||
| int32_t lfrfid_app(void* p); | int32_t lfrfid_app(void* p); | ||||||
| int32_t lfrfid_debug_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
 | // On system start hooks declaration
 | ||||||
| void irda_cli_init(); | void irda_cli_init(); | ||||||
| @ -47,9 +47,11 @@ void subghz_cli_init(); | |||||||
| void bt_cli_init(); | void bt_cli_init(); | ||||||
| void lfrfid_cli_init(); | void lfrfid_cli_init(); | ||||||
| void ibutton_cli_init(); | void ibutton_cli_init(); | ||||||
|  | void storage_cli_init(); | ||||||
| 
 | 
 | ||||||
| // Settings
 | // Settings
 | ||||||
| int32_t notification_app_settings(void* p); | int32_t notification_app_settings(void* p); | ||||||
|  | int32_t storage_settings(void* p); | ||||||
| 
 | 
 | ||||||
| const FlipperApplication FLIPPER_SERVICES[] = { | const FlipperApplication FLIPPER_SERVICES[] = { | ||||||
| #ifdef SRV_CLI | #ifdef SRV_CLI | ||||||
| @ -81,17 +83,6 @@ const FlipperApplication FLIPPER_SERVICES[] = { | |||||||
|     {.app = loader, .name = "loader", .stack_size = 1024, .icon = &A_Plugins_14}, |     {.app = loader, .name = "loader", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||||
| #endif | #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 | #ifdef SRV_DOLPHIN | ||||||
|     {.app = dolphin_task, .name = "dolphin_task", .stack_size = 1024, .icon = &A_Plugins_14}, |     {.app = dolphin_task, .name = "dolphin_task", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
| @ -105,8 +96,7 @@ const FlipperApplication FLIPPER_SERVICES[] = { | |||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_LF_RFID | #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 = 2048, .icon = &A_Plugins_14}, | ||||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 4096, .icon = &A_Plugins_14}, |  | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_IRDA | #ifdef SRV_IRDA | ||||||
| @ -128,16 +118,12 @@ const FlipperApplication FLIPPER_SERVICES[] = { | |||||||
|      .icon = &A_Plugins_14}, |      .icon = &A_Plugins_14}, | ||||||
| #endif | #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 | #ifdef SRV_MUSIC_PLAYER | ||||||
|     {.app = music_player, .name = "music player", .stack_size = 1024, .icon = &A_Plugins_14}, |     {.app = music_player, .name = "music player", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_IBUTTON | #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 | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef SRV_GPIO_DEMO | #ifdef SRV_GPIO_DEMO | ||||||
| @ -164,6 +150,17 @@ const FlipperApplication FLIPPER_SERVICES[] = { | |||||||
|     {.app = notification_app, .name = "notification", .stack_size = 1024, .icon = &A_Plugins_14}, |     {.app = notification_app, .name = "notification", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||||
| #endif | #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); | 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[] = { | const FlipperApplication FLIPPER_APPS[] = { | ||||||
| 
 | 
 | ||||||
| #ifdef APP_IBUTTON | #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 | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_NFC | #ifdef APP_NFC | ||||||
| @ -186,7 +183,7 @@ const FlipperApplication FLIPPER_APPS[] = { | |||||||
| 
 | 
 | ||||||
| #ifdef APP_LF_RFID | #ifdef APP_LF_RFID | ||||||
|     // TODO: fix stack size when sd api will be in separate thread
 |     // 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 | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef APP_IRDA | #ifdef APP_IRDA | ||||||
| @ -219,6 +216,9 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | |||||||
| #ifdef SRV_BT | #ifdef SRV_BT | ||||||
|     bt_cli_init, |     bt_cli_init, | ||||||
| #endif | #endif | ||||||
|  | #ifdef SRV_STORAGE | ||||||
|  |     storage_cli_init, | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const size_t FLIPPER_ON_SYSTEM_START_COUNT = | const size_t FLIPPER_ON_SYSTEM_START_COUNT = | ||||||
| @ -255,10 +255,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | |||||||
|      .icon = &A_Plugins_14}, |      .icon = &A_Plugins_14}, | ||||||
| #endif | #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 | #ifdef APP_VIBRO_DEMO | ||||||
|     {.app = application_vibro, .name = "vibro", .stack_size = 1024, .icon = &A_Plugins_14}, |     {.app = application_vibro, .name = "vibro", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
| @ -326,10 +322,11 @@ const size_t FLIPPER_SCENE_APPS_COUNT = sizeof(FLIPPER_SCENE_APPS) / sizeof(Flip | |||||||
| // Settings menu
 | // Settings menu
 | ||||||
| const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | ||||||
| #ifdef SRV_NOTIFICATION | #ifdef SRV_NOTIFICATION | ||||||
|     {.app = notification_app_settings, |     {.app = notification_app_settings, .name = "Notification", .stack_size = 1024, .icon = NULL}, | ||||||
|      .name = "Notification", | #endif | ||||||
|      .stack_size = 1024, | 
 | ||||||
|      .icon = &A_Plugins_14}, | #ifdef SRV_STORAGE | ||||||
|  |     {.app = storage_settings, .name = "Storage", .stack_size = 2048, .icon = NULL}, | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,10 +16,10 @@ SRV_MENU = 1 | |||||||
| SRV_POWER = 1 | SRV_POWER = 1 | ||||||
| SRV_BT = 1 | SRV_BT = 1 | ||||||
| SRV_CLI = 1 | SRV_CLI = 1 | ||||||
| SRV_SD_FILESYSTEM = 1 |  | ||||||
| SRV_INTERNAL_STORAGE = 1 |  | ||||||
| SRV_DOLPHIN = 1 | SRV_DOLPHIN = 1 | ||||||
| SRV_NOTIFICATION = 1 | SRV_NOTIFICATION = 1 | ||||||
|  | SRV_STORAGE = 1 | ||||||
|  | SRV_DIALOGS = 1 | ||||||
| 
 | 
 | ||||||
| # Main Apps
 | # Main Apps
 | ||||||
| APP_IRDA  = 1 | APP_IRDA  = 1 | ||||||
| @ -189,19 +189,6 @@ SRV_INPUT = 1 | |||||||
| SRV_GUI = 1 | SRV_GUI = 1 | ||||||
| endif | 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 | SRV_SPEAKER_DEMO ?= 0 | ||||||
| ifeq ($(SRV_SPEAKER_DEMO), 1) | ifeq ($(SRV_SPEAKER_DEMO), 1) | ||||||
| CFLAGS		+= -DSRV_SPEAKER_DEMO | CFLAGS		+= -DSRV_SPEAKER_DEMO | ||||||
| @ -282,15 +269,6 @@ ifeq ($(APP_GUI_TEST), 1) | |||||||
| CFLAGS		+= -DAPP_GUI_TEST | CFLAGS		+= -DAPP_GUI_TEST | ||||||
| endif | 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
 | # device drivers
 | ||||||
| 
 | 
 | ||||||
| SRV_GUI	?= 0 | SRV_GUI	?= 0 | ||||||
| @ -298,16 +276,6 @@ ifeq ($(SRV_GUI), 1) | |||||||
| CFLAGS		+= -DSRV_GUI | CFLAGS		+= -DSRV_GUI | ||||||
| endif | 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 | SRV_INPUT	?= 0 | ||||||
| ifeq ($(SRV_INPUT), 1) | ifeq ($(SRV_INPUT), 1) | ||||||
| CFLAGS		+= -DSRV_INPUT | CFLAGS		+= -DSRV_INPUT | ||||||
| @ -323,3 +291,13 @@ SRV_NOTIFICATION ?= 0 | |||||||
| ifeq ($(SRV_NOTIFICATION), 1) | ifeq ($(SRV_NOTIFICATION), 1) | ||||||
| CFLAGS		+= -DSRV_NOTIFICATION | CFLAGS		+= -DSRV_NOTIFICATION | ||||||
| endif | endif | ||||||
|  | 
 | ||||||
|  | SRV_STORAGE ?= 0 | ||||||
|  | ifeq ($(SRV_STORAGE), 1) | ||||||
|  | CFLAGS		+= -DSRV_STORAGE | ||||||
|  | endif | ||||||
|  | 
 | ||||||
|  | SRV_DIALOGS ?= 0 | ||||||
|  | ifeq ($(SRV_DIALOGS), 1) | ||||||
|  | CFLAGS		+= -DSRV_DIALOGS | ||||||
|  | endif | ||||||
| @ -3,18 +3,17 @@ | |||||||
| static bool archive_get_filenames(ArchiveApp* archive); | static bool archive_get_filenames(ArchiveApp* archive); | ||||||
| 
 | 
 | ||||||
| static bool is_favorite(ArchiveApp* archive, ArchiveFile_t* file) { | static bool is_favorite(ArchiveApp* archive, ArchiveFile_t* file) { | ||||||
|     FS_Common_Api* common_api = &archive->fs_api->common; |  | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     FS_Error fr; |     FS_Error fr; | ||||||
|     string_t path; |     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); |     FURI_LOG_I("FAV", "%d", fr); | ||||||
| 
 | 
 | ||||||
|     string_clear(path); |     string_clear(path); | ||||||
|     return fr == 0 || fr == 2; |     return (fr == FSE_OK || fr == FSE_EXIST); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void update_offset(ArchiveApp* archive) { | 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) { | static bool archive_get_filenames(ArchiveApp* archive) { | ||||||
|     furi_assert(archive); |     furi_assert(archive); | ||||||
|     FS_Dir_Api* dir_api = &archive->fs_api->dir; | 
 | ||||||
|     ArchiveFile_t item; |     ArchiveFile_t item; | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     File directory; |     File* directory = storage_file_alloc(archive->api); | ||||||
|     char name[MAX_NAME_LEN]; |     char name[MAX_NAME_LEN]; | ||||||
|     bool result; |  | ||||||
| 
 |  | ||||||
|     result = dir_api->open(&directory, string_get_cstr(archive->browser.path)); |  | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
|         archive->view_archive_main, (ArchiveViewModel * model) { |         archive->view_archive_main, (ArchiveViewModel * model) { | ||||||
| @ -162,51 +158,50 @@ static bool archive_get_filenames(ArchiveApp* archive) { | |||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     if(!result) { |     if(!storage_dir_open(directory, string_get_cstr(archive->browser.path))) { | ||||||
|         dir_api->close(&directory); |         storage_dir_close(directory); | ||||||
|  |         storage_file_free(directory); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         result = dir_api->read(&directory, &file_info, name, MAX_NAME_LEN); |         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||||
| 
 |  | ||||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(result) { |         uint16_t files_cnt; | ||||||
|             uint16_t files_cnt; |         with_view_model( | ||||||
|             with_view_model( |             archive->view_archive_main, (ArchiveViewModel * model) { | ||||||
|                 archive->view_archive_main, (ArchiveViewModel * model) { |                 files_cnt = files_array_size(model->files); | ||||||
|                     files_cnt = files_array_size(model->files); |  | ||||||
| 
 | 
 | ||||||
|                     return true; |                 return true; | ||||||
|                 }); |             }); | ||||||
| 
 | 
 | ||||||
|             if(files_cnt > MAX_FILES) { |         if(files_cnt > MAX_FILES) { | ||||||
|                 break; |             break; | ||||||
|             } else if(directory.error_id == FSE_OK) { |         } else if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|                 if(filter_by_extension(archive, &file_info, name)) { |             if(filter_by_extension(archive, &file_info, name)) { | ||||||
|                     ArchiveFile_t_init(&item); |                 ArchiveFile_t_init(&item); | ||||||
|                     string_init_set(item.name, name); |                 string_init_set(item.name, name); | ||||||
|                     set_file_type(&item, &file_info); |                 set_file_type(&item, &file_info); | ||||||
| 
 | 
 | ||||||
|                     with_view_model( |                 with_view_model( | ||||||
|                         archive->view_archive_main, (ArchiveViewModel * model) { |                     archive->view_archive_main, (ArchiveViewModel * model) { | ||||||
|                             files_array_push_back(model->files, item); |                         files_array_push_back(model->files, item); | ||||||
|                             return true; |                         return true; | ||||||
|                         }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     ArchiveFile_t_clear(&item); |                 ArchiveFile_t_clear(&item); | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 dir_api->close(&directory); |  | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |         } 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; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -226,17 +221,7 @@ static uint32_t archive_previous_callback(void* context) { | |||||||
| static void archive_add_to_favorites(ArchiveApp* archive) { | static void archive_add_to_favorites(ArchiveApp* archive) { | ||||||
|     furi_assert(archive); |     furi_assert(archive); | ||||||
| 
 | 
 | ||||||
|     FS_Common_Api* common_api = &archive->fs_api->common; |     storage_common_mkdir(archive->api, get_favorites_path()); | ||||||
|     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; |  | ||||||
| 
 | 
 | ||||||
|     string_t buffer_src; |     string_t buffer_src; | ||||||
|     string_t buffer_dst; |     string_t buffer_dst; | ||||||
| @ -246,22 +231,10 @@ static void archive_add_to_favorites(ArchiveApp* archive) { | |||||||
|         "%s/%s", |         "%s/%s", | ||||||
|         string_get_cstr(archive->browser.path), |         string_get_cstr(archive->browser.path), | ||||||
|         string_get_cstr(archive->browser.name)); |         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); |     storage_common_copy(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); | ||||||
|     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); |  | ||||||
| 
 | 
 | ||||||
|     string_clear(buffer_src); |     string_clear(buffer_src); | ||||||
|     string_clear(buffer_dst); |     string_clear(buffer_dst); | ||||||
| @ -271,7 +244,6 @@ static void archive_text_input_callback(void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
| 
 | 
 | ||||||
|     ArchiveApp* archive = (ArchiveApp*)context; |     ArchiveApp* archive = (ArchiveApp*)context; | ||||||
|     FS_Common_Api* common_api = &archive->fs_api->common; |  | ||||||
| 
 | 
 | ||||||
|     string_t buffer_src; |     string_t buffer_src; | ||||||
|     string_t buffer_dst; |     string_t buffer_dst; | ||||||
| @ -299,7 +271,7 @@ static void archive_text_input_callback(void* context) { | |||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     string_cat(buffer_dst, known_ext[file->type]); |     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); |     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(archive); | ||||||
|     furi_assert(file); |     furi_assert(file); | ||||||
| 
 | 
 | ||||||
|     FS_Common_Api* common_api = &archive->fs_api->common; |  | ||||||
|     string_t path; |     string_t path; | ||||||
|     string_init(path); |     string_init(path); | ||||||
| 
 | 
 | ||||||
|     if(!fav && !orig) { |     if(!fav && !orig) { | ||||||
|         string_printf( |         string_printf( | ||||||
|             path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(file->name)); |             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
 |     } else { // remove from favorites
 | ||||||
|         string_printf(path, "favorites/%s", string_get_cstr(file->name)); |         string_printf(path, "%s/%s", get_favorites_path(), string_get_cstr(file->name)); | ||||||
|         common_api->remove(string_get_cstr(path)); |         storage_common_remove(archive->api, string_get_cstr(path)); | ||||||
| 
 | 
 | ||||||
|         if(orig) { // remove original file
 |         if(orig) { // remove original file
 | ||||||
|             string_printf( |             string_printf( | ||||||
|                 path, "%s/%s", get_default_path(file->type), string_get_cstr(file->name)); |                 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); |     text_input_free(archive->text_input); | ||||||
| 
 | 
 | ||||||
|     furi_record_close("sdcard"); |     furi_record_close("storage"); | ||||||
|     archive->fs_api = NULL; |     archive->api = NULL; | ||||||
|     furi_record_close("gui"); |     furi_record_close("gui"); | ||||||
|     archive->gui = NULL; |     archive->gui = NULL; | ||||||
|     furi_record_close("loader"); |     furi_record_close("loader"); | ||||||
| @ -623,7 +594,7 @@ ArchiveApp* archive_alloc() { | |||||||
|     archive->app_thread = furi_thread_alloc(); |     archive->app_thread = furi_thread_alloc(); | ||||||
|     archive->gui = furi_record_open("gui"); |     archive->gui = furi_record_open("gui"); | ||||||
|     archive->loader = furi_record_open("loader"); |     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->text_input = text_input_alloc(); | ||||||
|     archive->view_archive_main = view_alloc(); |     archive->view_archive_main = view_alloc(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ | |||||||
| 
 | 
 | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <m-array.h> | #include <m-array.h> | ||||||
| #include <filesystem-api.h> | #include <storage/storage.h> | ||||||
| #include "archive_views.h" | #include "archive_views.h" | ||||||
| #include "applications.h" | #include "applications.h" | ||||||
| 
 | 
 | ||||||
| @ -41,13 +41,13 @@ static const char* known_ext[] = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static const char* tab_default_paths[] = { | static const char* tab_default_paths[] = { | ||||||
|     [ArchiveTabFavorites] = "favorites", |     [ArchiveTabFavorites] = "/any/favorites", | ||||||
|     [ArchiveTabIButton] = "ibutton", |     [ArchiveTabIButton] = "/any/ibutton", | ||||||
|     [ArchiveTabNFC] = "nfc", |     [ArchiveTabNFC] = "/any/nfc", | ||||||
|     [ArchiveTabSubOne] = "subone", |     [ArchiveTabSubOne] = "/any/subone", | ||||||
|     [ArchiveTabLFRFID] = "lfrfid", |     [ArchiveTabLFRFID] = "/any/lfrfid", | ||||||
|     [ArchiveTabIrda] = "irda", |     [ArchiveTabIrda] = "/any/irda", | ||||||
|     [ArchiveTabBrowser] = "/", |     [ArchiveTabBrowser] = "/any", | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static inline const char* get_tab_ext(ArchiveTabEnum tab) { | 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 { | typedef enum { | ||||||
|     EventTypeTick, |     EventTypeTick, | ||||||
|     EventTypeKey, |     EventTypeKey, | ||||||
| @ -118,6 +122,6 @@ struct ArchiveApp { | |||||||
|     View* view_archive_main; |     View* view_archive_main; | ||||||
|     TextInput* text_input; |     TextInput* text_input; | ||||||
| 
 | 
 | ||||||
|     FS_Api* fs_api; |     Storage* api; | ||||||
|     ArchiveBrowser browser; |     ArchiveBrowser browser; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| #include <gui/canvas.h> | #include <gui/canvas.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <filesystem-api.h> | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| #define MAX_LEN_PX 100 | #define MAX_LEN_PX 100 | ||||||
| #define MAX_NAME_LEN 255 | #define MAX_NAME_LEN 255 | ||||||
|  | |||||||
| @ -375,6 +375,10 @@ void cli_command_free(Cli* cli, string_t args, void* context) { | |||||||
|     printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block()); |     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) { | void cli_commands_init(Cli* cli) { | ||||||
|     cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL); |     cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL); | ||||||
|     cli_add_command(cli, "device_info", 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, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); | ||||||
|     cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, 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", 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, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); | ||||||
|     cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); |     cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								applications/dialogs/dialogs-api-lock.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								applications/dialogs/dialogs-api-lock.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | #pragma once | ||||||
|  | #define API_LOCK_INIT_LOCKED() osSemaphoreNew(1, 0, NULL); | ||||||
|  | 
 | ||||||
|  | #define API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(_lock) \ | ||||||
|  |     osSemaphoreAcquire(_lock, osWaitForever);      \ | ||||||
|  |     osSemaphoreDelete(_lock); | ||||||
|  | 
 | ||||||
|  | #define API_LOCK_UNLOCK(_lock) osSemaphoreRelease(_lock); | ||||||
							
								
								
									
										62
									
								
								applications/dialogs/dialogs-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								applications/dialogs/dialogs-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | #include "dialogs-i.h" | ||||||
|  | #include "dialogs-api-lock.h" | ||||||
|  | 
 | ||||||
|  | /****************** File select ******************/ | ||||||
|  | 
 | ||||||
|  | bool dialog_file_select_show( | ||||||
|  |     DialogsApp* context, | ||||||
|  |     const char* path, | ||||||
|  |     const char* extension, | ||||||
|  |     char* result, | ||||||
|  |     uint8_t result_size, | ||||||
|  |     const char* preselected_filename) { | ||||||
|  |     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); | ||||||
|  |     furi_check(semaphore != NULL); | ||||||
|  | 
 | ||||||
|  |     DialogsAppData data = { | ||||||
|  |         .file_select = { | ||||||
|  |             .path = path, | ||||||
|  |             .extension = extension, | ||||||
|  |             .result = result, | ||||||
|  |             .result_size = result_size, | ||||||
|  |             .preselected_filename = preselected_filename, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     DialogsAppReturn return_data; | ||||||
|  |     DialogsAppMessage message = { | ||||||
|  |         .semaphore = semaphore, | ||||||
|  |         .command = DialogsAppCommandFileOpen, | ||||||
|  |         .data = &data, | ||||||
|  |         .return_data = &return_data, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|  |     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore); | ||||||
|  | 
 | ||||||
|  |     return return_data.bool_value; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** Message ******************/ | ||||||
|  | 
 | ||||||
|  | DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) { | ||||||
|  |     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); | ||||||
|  |     furi_check(semaphore != NULL); | ||||||
|  | 
 | ||||||
|  |     DialogsAppData data = { | ||||||
|  |         .dialog = { | ||||||
|  |             .message = dialog_message, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     DialogsAppReturn return_data; | ||||||
|  |     DialogsAppMessage message = { | ||||||
|  |         .semaphore = semaphore, | ||||||
|  |         .command = DialogsAppCommandDialog, | ||||||
|  |         .data = &data, | ||||||
|  |         .return_data = &return_data, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); | ||||||
|  |     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore); | ||||||
|  | 
 | ||||||
|  |     return return_data.dialog_value; | ||||||
|  | } | ||||||
							
								
								
									
										15
									
								
								applications/dialogs/dialogs-i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								applications/dialogs/dialogs-i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "dialogs.h" | ||||||
|  | #include "dialogs-message.h" | ||||||
|  | #include "view-holder.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | struct DialogsApp { | ||||||
|  |     osMessageQueueId_t message_queue; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										45
									
								
								applications/dialogs/dialogs-message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								applications/dialogs/dialogs-message.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "dialogs-i.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const char* path; | ||||||
|  |     const char* extension; | ||||||
|  |     char* result; | ||||||
|  |     uint8_t result_size; | ||||||
|  |     const char* preselected_filename; | ||||||
|  | } DialogsAppMessageDataFileSelect; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const DialogMessage* message; | ||||||
|  | } DialogsAppMessageDataDialog; | ||||||
|  | 
 | ||||||
|  | typedef union { | ||||||
|  |     DialogsAppMessageDataFileSelect file_select; | ||||||
|  |     DialogsAppMessageDataDialog dialog; | ||||||
|  | } DialogsAppData; | ||||||
|  | 
 | ||||||
|  | typedef union { | ||||||
|  |     bool bool_value; | ||||||
|  |     DialogMessageButton dialog_value; | ||||||
|  | } DialogsAppReturn; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     DialogsAppCommandFileOpen, | ||||||
|  |     DialogsAppCommandDialog, | ||||||
|  | } DialogsAppCommand; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     osSemaphoreId_t semaphore; | ||||||
|  |     DialogsAppCommand command; | ||||||
|  |     DialogsAppData* data; | ||||||
|  |     DialogsAppReturn* return_data; | ||||||
|  | } DialogsAppMessage; | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										59
									
								
								applications/dialogs/dialogs-module-file-select.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/dialogs/dialogs-module-file-select.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | #include "dialogs-i.h" | ||||||
|  | #include "dialogs-api-lock.h" | ||||||
|  | #include <gui/modules/file_select.h> | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     osSemaphoreId_t semaphore; | ||||||
|  |     bool result; | ||||||
|  | } DialogsAppFileSelectContext; | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_file_select_back_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppFileSelectContext* file_select_context = context; | ||||||
|  |     file_select_context->result = false; | ||||||
|  |     API_LOCK_UNLOCK(file_select_context->semaphore); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_file_select_callback(bool result, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppFileSelectContext* file_select_context = context; | ||||||
|  |     file_select_context->result = result; | ||||||
|  |     API_LOCK_UNLOCK(file_select_context->semaphore); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { | ||||||
|  |     bool ret = false; | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  | 
 | ||||||
|  |     DialogsAppFileSelectContext* file_select_context = | ||||||
|  |         furi_alloc(sizeof(DialogsAppFileSelectContext)); | ||||||
|  |     file_select_context->semaphore = API_LOCK_INIT_LOCKED(); | ||||||
|  | 
 | ||||||
|  |     ViewHolder* view_holder = view_holder_alloc(); | ||||||
|  |     view_holder_attach_to_gui(view_holder, gui); | ||||||
|  |     view_holder_set_back_callback( | ||||||
|  |         view_holder, dialogs_app_file_select_back_callback, file_select_context); | ||||||
|  | 
 | ||||||
|  |     FileSelect* file_select = file_select_alloc(); | ||||||
|  |     file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context); | ||||||
|  |     file_select_set_filter(file_select, data->path, data->extension); | ||||||
|  |     file_select_set_result_buffer(file_select, data->result, data->result_size); | ||||||
|  |     file_select_init(file_select); | ||||||
|  |     if(data->preselected_filename != NULL) { | ||||||
|  |         file_select_set_selected_file(file_select, data->preselected_filename); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_holder_set_view(view_holder, file_select_get_view(file_select)); | ||||||
|  |     view_holder_start(view_holder); | ||||||
|  |     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(file_select_context->semaphore); | ||||||
|  | 
 | ||||||
|  |     ret = file_select_context->result; | ||||||
|  | 
 | ||||||
|  |     free(file_select_context); | ||||||
|  |     view_holder_stop(view_holder); | ||||||
|  |     view_holder_free(view_holder); | ||||||
|  |     file_select_free(file_select); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								applications/dialogs/dialogs-module-file-select.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/dialogs/dialogs-module-file-select.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "dialogs-message.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										152
									
								
								applications/dialogs/dialogs-module-message.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								applications/dialogs/dialogs-module-message.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,152 @@ | |||||||
|  | #include "dialogs-i.h" | ||||||
|  | #include "dialogs-api-lock.h" | ||||||
|  | #include <gui/modules/dialog_ex.h> | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     osSemaphoreId_t semaphore; | ||||||
|  |     DialogMessageButton result; | ||||||
|  | } DialogsAppMessageContext; | ||||||
|  | 
 | ||||||
|  | struct DialogMessage { | ||||||
|  |     const char* header_text; | ||||||
|  |     uint8_t header_text_x; | ||||||
|  |     uint8_t header_text_y; | ||||||
|  |     Align header_horizontal; | ||||||
|  |     Align header_vertical; | ||||||
|  |     const char* dialog_text; | ||||||
|  |     uint8_t dialog_text_x; | ||||||
|  |     uint8_t dialog_text_y; | ||||||
|  |     Align dialog_text_horizontal; | ||||||
|  |     Align dialog_text_vertical; | ||||||
|  |     const Icon* icon; | ||||||
|  |     uint8_t icon_x; | ||||||
|  |     uint8_t icon_y; | ||||||
|  |     const char* left_button_text; | ||||||
|  |     const char* center_button_text; | ||||||
|  |     const char* right_button_text; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_message_back_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppMessageContext* message_context = context; | ||||||
|  |     message_context->result = DialogMessageButtonBack; | ||||||
|  |     API_LOCK_UNLOCK(message_context->semaphore); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_message_callback(DialogExResult result, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     DialogsAppMessageContext* message_context = context; | ||||||
|  |     switch(result) { | ||||||
|  |     case DialogExResultLeft: | ||||||
|  |         message_context->result = DialogMessageButtonLeft; | ||||||
|  |         break; | ||||||
|  |     case DialogExResultRight: | ||||||
|  |         message_context->result = DialogMessageButtonRight; | ||||||
|  |         break; | ||||||
|  |     case DialogExResultCenter: | ||||||
|  |         message_context->result = DialogMessageButtonCenter; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     API_LOCK_UNLOCK(message_context->semaphore); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data) { | ||||||
|  |     DialogMessageButton ret = DialogMessageButtonBack; | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  |     const DialogMessage* message = data->message; | ||||||
|  |     DialogsAppMessageContext* message_context = furi_alloc(sizeof(DialogsAppMessageContext)); | ||||||
|  |     message_context->semaphore = API_LOCK_INIT_LOCKED(); | ||||||
|  | 
 | ||||||
|  |     ViewHolder* view_holder = view_holder_alloc(); | ||||||
|  |     view_holder_attach_to_gui(view_holder, gui); | ||||||
|  |     view_holder_set_back_callback(view_holder, dialogs_app_message_back_callback, message_context); | ||||||
|  | 
 | ||||||
|  |     DialogEx* dialog_ex = dialog_ex_alloc(); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, dialogs_app_message_callback); | ||||||
|  |     dialog_ex_set_context(dialog_ex, message_context); | ||||||
|  |     dialog_ex_set_header( | ||||||
|  |         dialog_ex, | ||||||
|  |         message->header_text, | ||||||
|  |         message->header_text_x, | ||||||
|  |         message->header_text_y, | ||||||
|  |         message->header_horizontal, | ||||||
|  |         message->header_vertical); | ||||||
|  |     dialog_ex_set_text( | ||||||
|  |         dialog_ex, | ||||||
|  |         message->dialog_text, | ||||||
|  |         message->dialog_text_x, | ||||||
|  |         message->dialog_text_y, | ||||||
|  |         message->dialog_text_horizontal, | ||||||
|  |         message->dialog_text_vertical); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, message->icon_x, message->icon_y, message->icon); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, message->left_button_text); | ||||||
|  |     dialog_ex_set_center_button_text(dialog_ex, message->center_button_text); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, message->right_button_text); | ||||||
|  | 
 | ||||||
|  |     view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex)); | ||||||
|  |     view_holder_start(view_holder); | ||||||
|  |     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(message_context->semaphore); | ||||||
|  | 
 | ||||||
|  |     ret = message_context->result; | ||||||
|  | 
 | ||||||
|  |     free(message_context); | ||||||
|  |     view_holder_stop(view_holder); | ||||||
|  |     view_holder_free(view_holder); | ||||||
|  |     dialog_ex_free(dialog_ex); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DialogMessage* dialog_message_alloc() { | ||||||
|  |     DialogMessage* message = furi_alloc(sizeof(DialogMessage)); | ||||||
|  |     return message; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dialog_message_free(DialogMessage* message) { | ||||||
|  |     free(message); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dialog_message_set_text( | ||||||
|  |     DialogMessage* message, | ||||||
|  |     const char* text, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical) { | ||||||
|  |     message->dialog_text = text; | ||||||
|  |     message->dialog_text_x = x; | ||||||
|  |     message->dialog_text_y = y; | ||||||
|  |     message->dialog_text_horizontal = horizontal; | ||||||
|  |     message->dialog_text_vertical = vertical; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dialog_message_set_header( | ||||||
|  |     DialogMessage* message, | ||||||
|  |     const char* text, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical) { | ||||||
|  |     message->header_text = text; | ||||||
|  |     message->header_text_x = x; | ||||||
|  |     message->header_text_y = y; | ||||||
|  |     message->header_horizontal = horizontal; | ||||||
|  |     message->header_vertical = vertical; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y) { | ||||||
|  |     message->icon = icon; | ||||||
|  |     message->icon_x = x; | ||||||
|  |     message->icon_y = y; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void dialog_message_set_buttons( | ||||||
|  |     DialogMessage* message, | ||||||
|  |     const char* left, | ||||||
|  |     const char* center, | ||||||
|  |     const char* right) { | ||||||
|  |     message->left_button_text = left; | ||||||
|  |     message->center_button_text = center; | ||||||
|  |     message->right_button_text = right; | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								applications/dialogs/dialogs-module-message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/dialogs/dialogs-module-message.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "dialogs-message.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										39
									
								
								applications/dialogs/dialogs.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								applications/dialogs/dialogs.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | #include "dialogs-i.h" | ||||||
|  | #include "dialogs-api-lock.h" | ||||||
|  | #include "dialogs-module-file-select.h" | ||||||
|  | #include "dialogs-module-message.h" | ||||||
|  | 
 | ||||||
|  | static DialogsApp* dialogs_app_alloc() { | ||||||
|  |     DialogsApp* app = malloc(sizeof(DialogsApp)); | ||||||
|  |     app->message_queue = osMessageQueueNew(8, sizeof(DialogsAppMessage), NULL); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { | ||||||
|  |     switch(message->command) { | ||||||
|  |     case DialogsAppCommandFileOpen: | ||||||
|  |         message->return_data->bool_value = | ||||||
|  |             dialogs_app_process_module_file_select(&message->data->file_select); | ||||||
|  |         break; | ||||||
|  |     case DialogsAppCommandDialog: | ||||||
|  |         message->return_data->dialog_value = | ||||||
|  |             dialogs_app_process_module_message(&message->data->dialog); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     API_LOCK_UNLOCK(message->semaphore); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t dialogs_app(void* p) { | ||||||
|  |     DialogsApp* app = dialogs_app_alloc(); | ||||||
|  |     furi_record_create("dialogs", app); | ||||||
|  | 
 | ||||||
|  |     DialogsAppMessage message; | ||||||
|  |     while(1) { | ||||||
|  |         if(osMessageQueueGet(app->message_queue, &message, NULL, osWaitForever) == osOK) { | ||||||
|  |             dialogs_app_process_message(app, &message); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										128
									
								
								applications/dialogs/dialogs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								applications/dialogs/dialogs.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/canvas.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /****************** COMMON ******************/ | ||||||
|  | 
 | ||||||
|  | typedef struct DialogsApp DialogsApp; | ||||||
|  | 
 | ||||||
|  | /****************** FILE SELECT ******************/ | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Shows and processes the file selection dialog | ||||||
|  |  * @param context api pointer | ||||||
|  |  * @param path path to directory | ||||||
|  |  * @param extension file extension to be offered for selection | ||||||
|  |  * @param selected_filename buffer where the selected filename will be saved | ||||||
|  |  * @param selected_filename_size and the size of this buffer | ||||||
|  |  * @param preselected_filename filename to be preselected | ||||||
|  |  * @return bool whether a file was selected | ||||||
|  |  */ | ||||||
|  | bool dialog_file_select_show( | ||||||
|  |     DialogsApp* context, | ||||||
|  |     const char* path, | ||||||
|  |     const char* extension, | ||||||
|  |     char* result, | ||||||
|  |     uint8_t result_size, | ||||||
|  |     const char* preselected_filename); | ||||||
|  | 
 | ||||||
|  | /****************** MESSAGE ******************/ | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Message result type | ||||||
|  |  */ | ||||||
|  | typedef enum { | ||||||
|  |     DialogMessageButtonBack, | ||||||
|  |     DialogMessageButtonLeft, | ||||||
|  |     DialogMessageButtonCenter, | ||||||
|  |     DialogMessageButtonRight, | ||||||
|  | } DialogMessageButton; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Message struct | ||||||
|  |  */ | ||||||
|  | typedef struct DialogMessage DialogMessage; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Allocate and fill message | ||||||
|  |  * @return DialogMessage*  | ||||||
|  |  */ | ||||||
|  | DialogMessage* dialog_message_alloc(); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Free message struct | ||||||
|  |  * @param message message pointer | ||||||
|  |  */ | ||||||
|  | void dialog_message_free(DialogMessage* message); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set message text | ||||||
|  |  * @param message message pointer | ||||||
|  |  * @param text text, can be NULL if you don't want to display the text | ||||||
|  |  * @param x x position | ||||||
|  |  * @param y y position | ||||||
|  |  * @param horizontal horizontal alignment | ||||||
|  |  * @param vertical vertical alignment | ||||||
|  |  */ | ||||||
|  | void dialog_message_set_text( | ||||||
|  |     DialogMessage* message, | ||||||
|  |     const char* text, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set message header | ||||||
|  |  * @param message message pointer | ||||||
|  |  * @param text text, can be NULL if you don't want to display the header | ||||||
|  |  * @param x x position | ||||||
|  |  * @param y y position | ||||||
|  |  * @param horizontal horizontal alignment | ||||||
|  |  * @param vertical vertical alignment | ||||||
|  |  */ | ||||||
|  | void dialog_message_set_header( | ||||||
|  |     DialogMessage* message, | ||||||
|  |     const char* text, | ||||||
|  |     uint8_t x, | ||||||
|  |     uint8_t y, | ||||||
|  |     Align horizontal, | ||||||
|  |     Align vertical); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set message icon | ||||||
|  |  * @param message message pointer | ||||||
|  |  * @param icon icon pointer, can be NULL if you don't want to display the icon | ||||||
|  |  * @param x x position | ||||||
|  |  * @param y y position | ||||||
|  |  */ | ||||||
|  | void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Set message buttons text, button text can be NULL if you don't want to display and process some buttons | ||||||
|  |  * @param message message pointer | ||||||
|  |  * @param left left button text, can be NULL if you don't want to display the left button | ||||||
|  |  * @param center center button text, can be NULL if you don't want to display the center button | ||||||
|  |  * @param right right button text, can be NULL if you don't want to display the right button | ||||||
|  |  */ | ||||||
|  | void dialog_message_set_buttons( | ||||||
|  |     DialogMessage* message, | ||||||
|  |     const char* left, | ||||||
|  |     const char* center, | ||||||
|  |     const char* right); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Show message from filled struct | ||||||
|  |  * @param context api pointer | ||||||
|  |  * @param message message struct pointer to be shown | ||||||
|  |  * @return DialogMessageButton type | ||||||
|  |  */ | ||||||
|  | DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* message); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -1,4 +1,4 @@ | |||||||
| #include "view_holder.h" | #include "view-holder.h" | ||||||
| #include <gui/view_i.h> | #include <gui/view_i.h> | ||||||
| 
 | 
 | ||||||
| struct ViewHolder { | struct ViewHolder { | ||||||
| @ -1,10 +1,9 @@ | |||||||
| #include "dolphin_state.h" | #include "dolphin_state.h" | ||||||
| 
 | #include <storage/storage.h> | ||||||
| #include <internal-storage/internal-storage.h> |  | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <math.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_MAGIC 0xD0 | ||||||
| #define DOLPHIN_STORE_HEADER_VERSION 0x01 | #define DOLPHIN_STORE_HEADER_VERSION 0x01 | ||||||
| #define DOLPHIN_LVL_THRESHOLD 20.0f | #define DOLPHIN_LVL_THRESHOLD 20.0f | ||||||
| @ -34,24 +33,24 @@ typedef struct { | |||||||
| } DolphinStore; | } DolphinStore; | ||||||
| 
 | 
 | ||||||
| struct DolphinState { | struct DolphinState { | ||||||
|     InternalStorage* internal_storage; |     Storage* fs_api; | ||||||
|     DolphinStoreData data; |     DolphinStoreData data; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| DolphinState* dolphin_state_alloc() { | DolphinState* dolphin_state_alloc() { | ||||||
|     DolphinState* dolphin_state = furi_alloc(sizeof(DolphinState)); |     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; |     return dolphin_state; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void dolphin_state_free(DolphinState* dolphin_state) { | void dolphin_state_free(DolphinState* dolphin_state) { | ||||||
|     furi_record_close("internal-storage"); |     furi_record_close("storage"); | ||||||
|     free(dolphin_state); |     free(dolphin_state); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool dolphin_state_save(DolphinState* dolphin_state) { | bool dolphin_state_save(DolphinState* dolphin_state) { | ||||||
|     DolphinStore store; |     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
 |     // Calculate checksum
 | ||||||
|     uint8_t* source = (uint8_t*)&dolphin_state->data; |     uint8_t* source = (uint8_t*)&dolphin_state->data; | ||||||
|     uint8_t checksum = 0; |     uint8_t checksum = 0; | ||||||
| @ -66,60 +65,95 @@ bool dolphin_state_save(DolphinState* dolphin_state) { | |||||||
|     store.header.timestamp = 0; |     store.header.timestamp = 0; | ||||||
|     // Set data
 |     // Set data
 | ||||||
|     store.data = dolphin_state->data; |     store.data = dolphin_state->data; | ||||||
|  | 
 | ||||||
|     // Store
 |     // Store
 | ||||||
|     int ret = internal_storage_write_key( |     File* file = storage_file_alloc(dolphin_state->fs_api); | ||||||
|         dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore)); |     bool save_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||||
|     if(ret != sizeof(DolphinStore)) { | 
 | ||||||
|         FURI_LOG_E("dolphin-state", "Save failed. Storage returned: %d", ret); |     if(save_result) { | ||||||
|         return false; |         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"); |     FURI_LOG_I("dolphin-state", "Saved"); | ||||||
|     return true; |     return save_result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool dolphin_state_load(DolphinState* dolphin_state) { | bool dolphin_state_load(DolphinState* dolphin_state) { | ||||||
|     DolphinStore store; |     DolphinStore store; | ||||||
|     // Read Dolphin State Store
 |     // Read Dolphin State Store
 | ||||||
|     FURI_LOG_I("dolphin-state", "Loading state from internal-storage"); |     FURI_LOG_I("dolphin-state", "Loading state from \"%s\"", DOLPHIN_STORE_KEY); | ||||||
|     int ret = internal_storage_read_key( | 
 | ||||||
|         dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore)); |     File* file = storage_file_alloc(dolphin_state->fs_api); | ||||||
|     if(ret != sizeof(DolphinStore)) { |     bool load_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|         FURI_LOG_E("dolphin-state", "Load failed. Storage returned: %d", ret); | 
 | ||||||
|         return false; |     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(!load_result) { | ||||||
|     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 { |  | ||||||
|         FURI_LOG_E( |         FURI_LOG_E( | ||||||
|             "dolphin-state", |             "dolphin-state", | ||||||
|             "Magic(%d != %d) and Version(%d != %d) mismatch", |             "Load failed. Storage returned: %s", | ||||||
|             store.header.magic, |             storage_file_get_error_desc(file)); | ||||||
|             DOLPHIN_STORE_HEADER_MAGIC, |     } else { | ||||||
|             store.header.version, |         FURI_LOG_I("dolphin-state", "State loaded, verifying header"); | ||||||
|             DOLPHIN_STORE_HEADER_VERSION); |         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) { | void dolphin_state_clear(DolphinState* dolphin_state) { | ||||||
|  | |||||||
| @ -2,13 +2,14 @@ | |||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <sys/param.h> | #include <sys/param.h> | ||||||
|  | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| #define FILENAME_COUNT 4 | #define FILENAME_COUNT 4 | ||||||
| 
 | 
 | ||||||
| struct FileSelect { | struct FileSelect { | ||||||
|     // public
 |     // public
 | ||||||
|     View* view; |     View* view; | ||||||
|     FS_Api* fs_api; |     Storage* fs_api; | ||||||
|     const char* path; |     const char* path; | ||||||
|     const char* extension; |     const char* extension; | ||||||
| 
 | 
 | ||||||
| @ -180,6 +181,8 @@ static bool file_select_init_inner(FileSelect* file_select) { | |||||||
| FileSelect* file_select_alloc() { | FileSelect* file_select_alloc() { | ||||||
|     FileSelect* file_select = furi_alloc(sizeof(FileSelect)); |     FileSelect* file_select = furi_alloc(sizeof(FileSelect)); | ||||||
|     file_select->view = view_alloc(); |     file_select->view = view_alloc(); | ||||||
|  |     file_select->fs_api = furi_record_open("storage"); | ||||||
|  | 
 | ||||||
|     view_set_context(file_select->view, file_select); |     view_set_context(file_select->view, file_select); | ||||||
|     view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); |     view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); | ||||||
|     view_set_draw_callback(file_select->view, file_select_draw_callback); |     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); |     view_free(file_select->view); | ||||||
|     free(file_select); |     free(file_select); | ||||||
|  |     furi_record_close("storage"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| View* file_select_get_view(FileSelect* file_select) { | View* file_select_get_view(FileSelect* file_select) { | ||||||
| @ -217,11 +221,6 @@ View* file_select_get_view(FileSelect* file_select) { | |||||||
|     return file_select->view; |     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) { | void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { | ||||||
|     file_select->context = context; |     file_select->context = context; | ||||||
|     file_select->callback = callback; |     file_select->callback = callback; | ||||||
| @ -272,13 +271,12 @@ bool file_select_fill_strings(FileSelect* file_select) { | |||||||
|     furi_assert(file_select->extension); |     furi_assert(file_select->extension); | ||||||
| 
 | 
 | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     File directory; |     File* directory = storage_file_alloc(file_select->fs_api); | ||||||
|     bool result; | 
 | ||||||
|     FS_Dir_Api* dir_api = &file_select->fs_api->dir; |  | ||||||
|     uint8_t string_counter = 0; |     uint8_t string_counter = 0; | ||||||
|     uint16_t file_counter = 0; |     uint16_t file_counter = 0; | ||||||
|     const uint8_t name_length = 100; |     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; |     uint16_t first_file_index = 0; | ||||||
| 
 | 
 | ||||||
|     with_view_model( |     with_view_model( | ||||||
| @ -287,59 +285,50 @@ bool file_select_fill_strings(FileSelect* file_select) { | |||||||
|             return false; |             return false; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     if(name == NULL) { |     if(!storage_dir_open(directory, file_select->path)) { | ||||||
|         return false; |         storage_dir_close(directory); | ||||||
|     } |         storage_file_free(directory); | ||||||
| 
 |  | ||||||
|     result = dir_api->open(&directory, file_select->path); |  | ||||||
| 
 |  | ||||||
|     if(!result) { |  | ||||||
|         dir_api->close(&directory); |  | ||||||
|         free(name); |         free(name); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         result = dir_api->read(&directory, &file_info, name, name_length); |         if(!storage_dir_read(directory, &file_info, name, name_length)) { | ||||||
| 
 |  | ||||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(result) { |         if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|             if(directory.error_id == FSE_OK) { |             if(filter_file(file_select, &file_info, name)) { | ||||||
|                 if(filter_file(file_select, &file_info, name)) { |                 if(file_counter >= first_file_index) { | ||||||
|                     if(file_counter >= first_file_index) { |                     with_view_model( | ||||||
|                         with_view_model( |                         file_select->view, (FileSelectModel * model) { | ||||||
|                             file_select->view, (FileSelectModel * model) { |                             string_set_str(model->filename[string_counter], name); | ||||||
|                                 string_set_str(model->filename[string_counter], name); |  | ||||||
| 
 | 
 | ||||||
|                                 if(strcmp(file_select->extension, "*") != 0) { |                             if(strcmp(file_select->extension, "*") != 0) { | ||||||
|                                     string_replace_all_str( |                                 string_replace_all_str( | ||||||
|                                         model->filename[string_counter], |                                     model->filename[string_counter], file_select->extension, ""); | ||||||
|                                         file_select->extension, |                             } | ||||||
|                                         ""); |  | ||||||
|                                 } |  | ||||||
| 
 | 
 | ||||||
|                                 return true; |                             return true; | ||||||
|                             }); |                         }); | ||||||
|                         string_counter++; |                     string_counter++; | ||||||
| 
 | 
 | ||||||
|                         if(string_counter >= FILENAME_COUNT) { |                     if(string_counter >= FILENAME_COUNT) { | ||||||
|                             break; |                         break; | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                     file_counter++; |  | ||||||
|                 } |                 } | ||||||
|             } else { |                 file_counter++; | ||||||
|                 dir_api->close(&directory); |  | ||||||
|                 free(name); |  | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |         } 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); |     free(name); | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| @ -351,42 +340,33 @@ bool file_select_fill_count(FileSelect* file_select) { | |||||||
|     furi_assert(file_select->extension); |     furi_assert(file_select->extension); | ||||||
| 
 | 
 | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     File directory; |     File* directory = storage_file_alloc(file_select->fs_api); | ||||||
|     bool result; | 
 | ||||||
|     FS_Dir_Api* dir_api = &file_select->fs_api->dir; |  | ||||||
|     uint16_t file_counter = 0; |     uint16_t file_counter = 0; | ||||||
|     const uint8_t name_length = 100; |     const uint8_t name_length = 100; | ||||||
|     char* name = calloc(name_length, sizeof(char)); |     char* name = furi_alloc(name_length); | ||||||
| 
 | 
 | ||||||
|     if(name == NULL) { |     if(!storage_dir_open(directory, file_select->path)) { | ||||||
|         return false; |         storage_dir_close(directory); | ||||||
|     } |         storage_file_free(directory); | ||||||
| 
 |  | ||||||
|     result = dir_api->open(&directory, file_select->path); |  | ||||||
| 
 |  | ||||||
|     if(!result) { |  | ||||||
|         dir_api->close(&directory); |  | ||||||
|         free(name); |         free(name); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         result = dir_api->read(&directory, &file_info, name, name_length); |         if(!storage_dir_read(directory, &file_info, name, name_length)) { | ||||||
| 
 |  | ||||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(result) { |         if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|             if(directory.error_id == FSE_OK) { |             if(filter_file(file_select, &file_info, name)) { | ||||||
|                 if(filter_file(file_select, &file_info, name)) { |                 file_counter++; | ||||||
|                     file_counter++; |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 dir_api->close(&directory); |  | ||||||
|                 free(name); |  | ||||||
|                 return false; |  | ||||||
|             } |             } | ||||||
|  |         } 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; |             return false; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     dir_api->close(&directory); |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
|     free(name); |     free(name); | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| @ -411,16 +392,10 @@ void file_select_set_selected_file_internal(FileSelect* file_select, const char* | |||||||
|     if(strlen(filename) == 0) return; |     if(strlen(filename) == 0) return; | ||||||
| 
 | 
 | ||||||
|     FileInfo file_info; |     FileInfo file_info; | ||||||
|     File directory; |     File* directory = storage_file_alloc(file_select->fs_api); | ||||||
|     bool result; | 
 | ||||||
|     FS_Dir_Api* dir_api = &file_select->fs_api->dir; |  | ||||||
|     const uint8_t name_length = 100; |     const uint8_t name_length = 100; | ||||||
|     char* name = calloc(name_length, sizeof(char)); |     char* name = furi_alloc(name_length); | ||||||
| 
 |  | ||||||
|     if(name == NULL) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     uint16_t file_position = 0; |     uint16_t file_position = 0; | ||||||
|     bool file_found = false; |     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); |         string_cat_str(filename_str, file_select->extension); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     result = dir_api->open(&directory, file_select->path); |     if(!storage_dir_open(directory, file_select->path)) { | ||||||
| 
 |  | ||||||
|     if(!result) { |  | ||||||
|         string_clear(filename_str); |         string_clear(filename_str); | ||||||
|         dir_api->close(&directory); |         storage_dir_close(directory); | ||||||
|  |         storage_file_free(directory); | ||||||
|         free(name); |         free(name); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         result = dir_api->read(&directory, &file_info, name, name_length); |         if(!storage_dir_read(directory, &file_info, name, name_length)) { | ||||||
| 
 |  | ||||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(result) { |         if(storage_file_get_error(directory) == FSE_OK) { | ||||||
|             if(directory.error_id == FSE_OK) { |             if(filter_file(file_select, &file_info, name)) { | ||||||
|                 if(filter_file(file_select, &file_info, name)) { |                 if(strcmp(string_get_cstr(filename_str), name) == 0) { | ||||||
|                     if(strcmp(string_get_cstr(filename_str), name) == 0) { |                     file_found = true; | ||||||
|                         file_found = true; |                     break; | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     file_position++; |  | ||||||
|                 } |                 } | ||||||
|             } else { | 
 | ||||||
|                 string_clear(filename_str); |                 file_position++; | ||||||
|                 dir_api->close(&directory); |  | ||||||
|                 free(name); |  | ||||||
|                 return; |  | ||||||
|             } |             } | ||||||
|  |         } 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); |     string_clear(filename_str); | ||||||
|     dir_api->close(&directory); |     storage_dir_close(directory); | ||||||
|  |     storage_file_free(directory); | ||||||
|     free(name); |     free(name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <gui/view.h> | #include <gui/view.h> | ||||||
| #include <filesystem-api.h> |  | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -15,7 +14,6 @@ FileSelect* file_select_alloc(); | |||||||
| void file_select_free(FileSelect* file_select); | void file_select_free(FileSelect* file_select); | ||||||
| View* file_select_get_view(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_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_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); | void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| #include <file-worker-cpp.h> | #include <file-worker-cpp.h> | ||||||
| #include <path.h> | #include <path.h> | ||||||
| 
 | 
 | ||||||
| const char* iButtonApp::app_folder = "ibutton"; | const char* iButtonApp::app_folder = "/any/ibutton"; | ||||||
| const char* iButtonApp::app_extension = ".ibtn"; | const char* iButtonApp::app_extension = ".ibtn"; | ||||||
| 
 | 
 | ||||||
| void iButtonApp::run(void* args) { | void iButtonApp::run(void* args) { | ||||||
| @ -13,6 +13,8 @@ void iButtonApp::run(void* args) { | |||||||
|     bool consumed; |     bool consumed; | ||||||
|     bool exit = false; |     bool exit = false; | ||||||
| 
 | 
 | ||||||
|  |     make_app_folder(); | ||||||
|  | 
 | ||||||
|     if(args && load_key((const char*)args)) { |     if(args && load_key((const char*)args)) { | ||||||
|         current_scene = Scene::SceneEmulate; |         current_scene = Scene::SceneEmulate; | ||||||
|     } |     } | ||||||
| @ -218,15 +220,13 @@ void iButtonApp::generate_random_name(char* name, uint8_t max_name_size) { | |||||||
| 
 | 
 | ||||||
| // file managment
 | // file managment
 | ||||||
| bool iButtonApp::save_key(const char* key_name) { | bool iButtonApp::save_key(const char* key_name) { | ||||||
|  |     // Create ibutton directory if necessary
 | ||||||
|  |     make_app_folder(); | ||||||
|  | 
 | ||||||
|     FileWorkerCpp file_worker; |     FileWorkerCpp file_worker; | ||||||
|     string_t key_file_name; |     string_t key_file_name; | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     // Create ibutton directory if necessary
 |  | ||||||
|     if(!file_worker.mkdir(app_folder)) { |  | ||||||
|         return false; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     // First remove key if it was saved
 |     // First remove key if it was saved
 | ||||||
|     string_init_printf(key_file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension); |     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))) { |     if(!file_worker.remove(string_get_cstr(key_file_name))) { | ||||||
| @ -370,4 +370,9 @@ bool iButtonApp::delete_key() { | |||||||
|     string_clear(file_name); |     string_clear(file_name); | ||||||
| 
 | 
 | ||||||
|     return result; |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void iButtonApp::make_app_folder() { | ||||||
|  |     FileWorkerCpp file_worker; | ||||||
|  |     file_worker.mkdir(app_folder); | ||||||
| } | } | ||||||
| @ -25,9 +25,6 @@ | |||||||
| 
 | 
 | ||||||
| #include "helpers/key-worker.h" | #include "helpers/key-worker.h" | ||||||
| 
 | 
 | ||||||
| #include <sd-card-api.h> |  | ||||||
| #include <filesystem-api.h> |  | ||||||
| 
 |  | ||||||
| #include "one_wire_master.h" | #include "one_wire_master.h" | ||||||
| #include "maxim_crc.h" | #include "maxim_crc.h" | ||||||
| #include "ibutton-key.h" | #include "ibutton-key.h" | ||||||
| @ -142,4 +139,5 @@ private: | |||||||
|     static const char* app_extension; |     static const char* app_extension; | ||||||
| 
 | 
 | ||||||
|     bool load_key_data(string_t key_path); |     bool load_key_data(string_t key_path); | ||||||
|  |     void make_app_folder(); | ||||||
| }; | }; | ||||||
| @ -4,7 +4,6 @@ | |||||||
| #include "../ibutton-event.h" | #include "../ibutton-event.h" | ||||||
| #include "../ibutton-key.h" | #include "../ibutton-key.h" | ||||||
| #include <callback-connector.h> | #include <callback-connector.h> | ||||||
| #include <filesystem-api.h> |  | ||||||
| 
 | 
 | ||||||
| void iButtonSceneSaveName::on_enter(iButtonApp* app) { | void iButtonSceneSaveName::on_enter(iButtonApp* app) { | ||||||
|     iButtonAppViewManager* view_manager = app->get_view_manager(); |     iButtonAppViewManager* view_manager = app->get_view_manager(); | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ | |||||||
| #include "../ibutton-view-manager.h" | #include "../ibutton-view-manager.h" | ||||||
| #include "../ibutton-event.h" | #include "../ibutton-event.h" | ||||||
| #include <callback-connector.h> | #include <callback-connector.h> | ||||||
| #include <filesystem-api.h> |  | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     SubmenuIndexRead, |     SubmenuIndexRead, | ||||||
|  | |||||||
| @ -1,62 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include "internal-storage.h" |  | ||||||
| #include <furi.h> |  | ||||||
| #include <api-hal.h> |  | ||||||
| #include <lfs.h> |  | ||||||
| 
 |  | ||||||
| #define INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE (1) |  | ||||||
| 
 |  | ||||||
| struct InternalStorage { |  | ||||||
|     osMessageQueueId_t queue; |  | ||||||
|     InternalStorageState state; |  | ||||||
|     const size_t start_address; |  | ||||||
|     const size_t start_page; |  | ||||||
|     struct lfs_config config; |  | ||||||
|     lfs_t lfs; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     const char* key; |  | ||||||
|     uint8_t* buffer; |  | ||||||
|     size_t size; |  | ||||||
|     int ret; |  | ||||||
| } InternalStorageCommandKey; |  | ||||||
| 
 |  | ||||||
| typedef void (*InternalStorageCommandFunction)(InternalStorage* internal_storage, void* data); |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     osThreadId thread; |  | ||||||
|     InternalStorageCommandFunction function; |  | ||||||
|     void* data; |  | ||||||
| } InternalStorageCommand; |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_read( |  | ||||||
|     const struct lfs_config* c, |  | ||||||
|     lfs_block_t block, |  | ||||||
|     lfs_off_t off, |  | ||||||
|     void* buffer, |  | ||||||
|     lfs_size_t size); |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_prog( |  | ||||||
|     const struct lfs_config* c, |  | ||||||
|     lfs_block_t block, |  | ||||||
|     lfs_off_t off, |  | ||||||
|     const void* buffer, |  | ||||||
|     lfs_size_t size); |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block); |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_sync(const struct lfs_config* c); |  | ||||||
| 
 |  | ||||||
| InternalStorage* internal_storage_alloc(); |  | ||||||
| 
 |  | ||||||
| void internal_storage_free(InternalStorage* internal_storage); |  | ||||||
| 
 |  | ||||||
| int32_t internal_storage_task(void* p); |  | ||||||
| 
 |  | ||||||
| void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data); |  | ||||||
| 
 |  | ||||||
| void _internal_storage_write_key( |  | ||||||
|     InternalStorage* internal_storage, |  | ||||||
|     InternalStorageCommandKey* data); |  | ||||||
| @ -1,252 +0,0 @@ | |||||||
| #include "internal-storage-i.h" |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_read( |  | ||||||
|     const struct lfs_config* c, |  | ||||||
|     lfs_block_t block, |  | ||||||
|     lfs_off_t off, |  | ||||||
|     void* buffer, |  | ||||||
|     lfs_size_t size) { |  | ||||||
|     InternalStorage* internal_storage = c->context; |  | ||||||
|     size_t address = internal_storage->start_address + block * c->block_size + off; |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_D( |  | ||||||
|         "internal-storage", |  | ||||||
|         "Device read: block %d, off %d, buffer: %p, size %d, translated address: %p", |  | ||||||
|         block, |  | ||||||
|         off, |  | ||||||
|         buffer, |  | ||||||
|         size, |  | ||||||
|         address); |  | ||||||
| 
 |  | ||||||
|     memcpy(buffer, (void*)address, size); |  | ||||||
| 
 |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_prog( |  | ||||||
|     const struct lfs_config* c, |  | ||||||
|     lfs_block_t block, |  | ||||||
|     lfs_off_t off, |  | ||||||
|     const void* buffer, |  | ||||||
|     lfs_size_t size) { |  | ||||||
|     InternalStorage* internal_storage = c->context; |  | ||||||
|     size_t address = internal_storage->start_address + block * c->block_size + off; |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_D( |  | ||||||
|         "internal-storage", |  | ||||||
|         "Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p", |  | ||||||
|         block, |  | ||||||
|         off, |  | ||||||
|         buffer, |  | ||||||
|         size, |  | ||||||
|         address); |  | ||||||
| 
 |  | ||||||
|     int ret = 0; |  | ||||||
|     while(size > 0) { |  | ||||||
|         if(!api_hal_flash_write_dword(address, *(uint64_t*)buffer)) { |  | ||||||
|             ret = -1; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|         address += c->prog_size; |  | ||||||
|         buffer += c->prog_size; |  | ||||||
|         size -= c->prog_size; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block) { |  | ||||||
|     InternalStorage* internal_storage = c->context; |  | ||||||
|     size_t page = internal_storage->start_page + block; |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_D("internal-storage", "Device erase: page %d, translated page: %d", block, page); |  | ||||||
| 
 |  | ||||||
|     if(api_hal_flash_erase(page, 1)) { |  | ||||||
|         return 0; |  | ||||||
|     } else { |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int internal_storage_device_sync(const struct lfs_config* c) { |  | ||||||
|     FURI_LOG_D("internal-storage", "Device sync: skipping, cause "); |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| InternalStorage* internal_storage_alloc() { |  | ||||||
|     InternalStorage* internal_storage = furi_alloc(sizeof(InternalStorage)); |  | ||||||
| 
 |  | ||||||
|     internal_storage->queue = osMessageQueueNew(8, sizeof(InternalStorageCommand), NULL); |  | ||||||
| 
 |  | ||||||
|     // Internal storage start address
 |  | ||||||
|     internal_storage->state = InternalStorageStateInitializing; |  | ||||||
| 
 |  | ||||||
|     // Internal storage start address
 |  | ||||||
|     *(size_t*)(&internal_storage->start_address) = api_hal_flash_get_free_page_start_address(); |  | ||||||
|     *(size_t*)(&internal_storage->start_page) = |  | ||||||
|         (internal_storage->start_address - api_hal_flash_get_base()) / |  | ||||||
|         api_hal_flash_get_page_size(); |  | ||||||
| 
 |  | ||||||
|     // LFS configuration
 |  | ||||||
|     // Glue and context
 |  | ||||||
|     internal_storage->config.context = internal_storage; |  | ||||||
|     internal_storage->config.read = internal_storage_device_read; |  | ||||||
|     internal_storage->config.prog = internal_storage_device_prog; |  | ||||||
|     internal_storage->config.erase = internal_storage_device_erase; |  | ||||||
|     internal_storage->config.sync = internal_storage_device_sync; |  | ||||||
|     // Block device description
 |  | ||||||
|     internal_storage->config.read_size = api_hal_flash_get_read_block_size(); |  | ||||||
|     internal_storage->config.prog_size = api_hal_flash_get_write_block_size(); |  | ||||||
|     internal_storage->config.block_size = api_hal_flash_get_page_size(); |  | ||||||
|     internal_storage->config.block_count = api_hal_flash_get_free_page_count(); |  | ||||||
|     internal_storage->config.block_cycles = api_hal_flash_get_cycles_count(); |  | ||||||
|     internal_storage->config.cache_size = 16; |  | ||||||
|     internal_storage->config.lookahead_size = 16; |  | ||||||
| 
 |  | ||||||
|     return internal_storage; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void internal_storage_free(InternalStorage* internal_storage) { |  | ||||||
|     furi_assert(internal_storage); |  | ||||||
|     free(internal_storage); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int32_t internal_storage_task(void* p) { |  | ||||||
|     FURI_LOG_I("internal-storage", "Starting"); |  | ||||||
|     InternalStorage* internal_storage = internal_storage_alloc(); |  | ||||||
|     FURI_LOG_I( |  | ||||||
|         "internal-storage", |  | ||||||
|         "Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d", |  | ||||||
|         internal_storage->start_address, |  | ||||||
|         internal_storage->config.read_size, |  | ||||||
|         internal_storage->config.prog_size, |  | ||||||
|         internal_storage->config.block_size, |  | ||||||
|         internal_storage->config.block_count, |  | ||||||
|         internal_storage->config.block_cycles); |  | ||||||
| 
 |  | ||||||
|     int err; |  | ||||||
|     ApiHalBootFlag boot_flags = api_hal_boot_get_flags(); |  | ||||||
|     if(boot_flags & ApiHalBootFlagFactoryReset) { |  | ||||||
|         // Factory reset
 |  | ||||||
|         err = lfs_format(&internal_storage->lfs, &internal_storage->config); |  | ||||||
|         if(err == 0) { |  | ||||||
|             FURI_LOG_I("internal-storage", "Factory reset: Format successful, trying to mount"); |  | ||||||
|             api_hal_boot_set_flags(boot_flags & ~ApiHalBootFlagFactoryReset); |  | ||||||
|             err = lfs_mount(&internal_storage->lfs, &internal_storage->config); |  | ||||||
|             if(err == 0) { |  | ||||||
|                 FURI_LOG_I("internal-storage", "Factory reset: Mounted"); |  | ||||||
|                 internal_storage->state = InternalStorageStateReady; |  | ||||||
|             } else { |  | ||||||
|                 FURI_LOG_E("internal-storage", "Factory reset: Mount after format failed"); |  | ||||||
|                 internal_storage->state = InternalStorageStateBroken; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             FURI_LOG_E("internal-storage", "Factory reset: Format failed"); |  | ||||||
|             internal_storage->state = InternalStorageStateBroken; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         // Normal
 |  | ||||||
|         err = lfs_mount(&internal_storage->lfs, &internal_storage->config); |  | ||||||
|         if(err == 0) { |  | ||||||
|             FURI_LOG_I("internal-storage", "Mounted"); |  | ||||||
|             internal_storage->state = InternalStorageStateReady; |  | ||||||
|         } else { |  | ||||||
|             FURI_LOG_E("internal-storage", "Mount failed, formatting"); |  | ||||||
|             err = lfs_format(&internal_storage->lfs, &internal_storage->config); |  | ||||||
|             if(err == 0) { |  | ||||||
|                 FURI_LOG_I("internal-storage", "Format successful, trying to mount"); |  | ||||||
|                 err = lfs_mount(&internal_storage->lfs, &internal_storage->config); |  | ||||||
|                 if(err == 0) { |  | ||||||
|                     FURI_LOG_I("internal-storage", "Mounted"); |  | ||||||
|                     internal_storage->state = InternalStorageStateReady; |  | ||||||
|                 } else { |  | ||||||
|                     FURI_LOG_E("internal-storage", "Mount after format failed"); |  | ||||||
|                     internal_storage->state = InternalStorageStateBroken; |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 FURI_LOG_E("internal-storage", "Format failed"); |  | ||||||
|                 internal_storage->state = InternalStorageStateBroken; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     furi_record_create("internal-storage", internal_storage); |  | ||||||
| 
 |  | ||||||
|     InternalStorageCommand command; |  | ||||||
|     while(1) { |  | ||||||
|         furi_check( |  | ||||||
|             osMessageQueueGet(internal_storage->queue, &command, NULL, osWaitForever) == osOK); |  | ||||||
|         command.function(internal_storage, command.data); |  | ||||||
|         osThreadFlagsSet(command.thread, INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     lfs_unmount(&internal_storage->lfs); |  | ||||||
|     internal_storage_free(internal_storage); |  | ||||||
| 
 |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data) { |  | ||||||
|     lfs_file_t file; |  | ||||||
|     int ret = lfs_file_open(&internal_storage->lfs, &file, data->key, LFS_O_RDONLY); |  | ||||||
|     if(ret == 0) { |  | ||||||
|         ret = lfs_file_read(&internal_storage->lfs, &file, data->buffer, data->size); |  | ||||||
|         lfs_file_close(&internal_storage->lfs, &file); |  | ||||||
|     } |  | ||||||
|     data->ret = ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int internal_storage_read_key( |  | ||||||
|     InternalStorage* internal_storage, |  | ||||||
|     const char* key, |  | ||||||
|     uint8_t* buffer, |  | ||||||
|     size_t size) { |  | ||||||
|     osThreadId_t caller_thread = osThreadGetId(); |  | ||||||
|     if(caller_thread == 0) { |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0}; |  | ||||||
|     InternalStorageCommand command = { |  | ||||||
|         .thread = caller_thread, |  | ||||||
|         .function = (InternalStorageCommandFunction)_internal_storage_read_key, |  | ||||||
|         .data = &data, |  | ||||||
|     }; |  | ||||||
|     furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK); |  | ||||||
|     osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever); |  | ||||||
|     return data.ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void _internal_storage_write_key( |  | ||||||
|     InternalStorage* internal_storage, |  | ||||||
|     InternalStorageCommandKey* data) { |  | ||||||
|     lfs_file_t file; |  | ||||||
|     int ret = lfs_file_open( |  | ||||||
|         &internal_storage->lfs, &file, data->key, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); |  | ||||||
|     if(ret == 0) { |  | ||||||
|         ret = lfs_file_write(&internal_storage->lfs, &file, data->buffer, data->size); |  | ||||||
|         lfs_file_close(&internal_storage->lfs, &file); |  | ||||||
|     } |  | ||||||
|     data->ret = ret; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| int internal_storage_write_key( |  | ||||||
|     InternalStorage* internal_storage, |  | ||||||
|     const char* key, |  | ||||||
|     uint8_t* buffer, |  | ||||||
|     size_t size) { |  | ||||||
|     osThreadId_t caller_thread = osThreadGetId(); |  | ||||||
|     if(caller_thread == 0) { |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0}; |  | ||||||
|     InternalStorageCommand command = { |  | ||||||
|         .thread = caller_thread, |  | ||||||
|         .function = (InternalStorageCommandFunction)_internal_storage_write_key, |  | ||||||
|         .data = &data, |  | ||||||
|     }; |  | ||||||
|     furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK); |  | ||||||
|     osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever); |  | ||||||
|     return data.ret; |  | ||||||
| } |  | ||||||
| @ -1,40 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <stddef.h> |  | ||||||
| #include <stdint.h> |  | ||||||
| #include <stdbool.h> |  | ||||||
| 
 |  | ||||||
| /* Internal storage state */ |  | ||||||
| typedef enum { |  | ||||||
|     InternalStorageStateInitializing, |  | ||||||
|     InternalStorageStateReady, |  | ||||||
|     InternalStorageStateBroken, |  | ||||||
| } InternalStorageState; |  | ||||||
| 
 |  | ||||||
| typedef struct InternalStorage InternalStorage; |  | ||||||
| 
 |  | ||||||
| /** Read key, blocking api
 |  | ||||||
|  * @param internal_storage - InternalStorage instance |  | ||||||
|  * @param key - file name to read data from |  | ||||||
|  * @param buffer - pointer to data buffer |  | ||||||
|  * @param size - buffer size |  | ||||||
|  * @return negative on error, otherwise data read |  | ||||||
|  */ |  | ||||||
| int internal_storage_read_key( |  | ||||||
|     InternalStorage* internal_storage, |  | ||||||
|     const char* key, |  | ||||||
|     uint8_t* buffer, |  | ||||||
|     size_t size); |  | ||||||
| 
 |  | ||||||
| /** Write key, blocking api
 |  | ||||||
|  * @param internal_storage - InternalStorage instance |  | ||||||
|  * @param key - file name to store data to |  | ||||||
|  * @param buffer - pointer to data buffer |  | ||||||
|  * @param size - buffer size |  | ||||||
|  * @return negative on error, otherwise data written |  | ||||||
|  */ |  | ||||||
| int internal_storage_write_key( |  | ||||||
|     InternalStorage* internal_storage, |  | ||||||
|     const char* key, |  | ||||||
|     uint8_t* buffer, |  | ||||||
|     size_t size); |  | ||||||
| @ -6,7 +6,6 @@ | |||||||
| 
 | 
 | ||||||
| class IrdaAppBruteForce { | class IrdaAppBruteForce { | ||||||
|     const char* universal_db_filename; |     const char* universal_db_filename; | ||||||
|     File file; |  | ||||||
|     std::string current_record; |     std::string current_record; | ||||||
|     std::unique_ptr<IrdaAppFileParser> file_parser; |     std::unique_ptr<IrdaAppFileParser> file_parser; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ | |||||||
| #include <file-worker-cpp.h> | #include <file-worker-cpp.h> | ||||||
| 
 | 
 | ||||||
| uint32_t const IrdaAppFileParser::max_line_length = ((9 + 1) * 512 + 100); | 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"; | const char* IrdaAppFileParser::irda_extension = ".ir"; | ||||||
| uint32_t const IrdaAppFileParser::max_raw_timings_in_signal = 512; | uint32_t const IrdaAppFileParser::max_raw_timings_in_signal = 512; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <file_reader/file_reader.h> |  | ||||||
| #include <irda.h> | #include <irda.h> | ||||||
| #include <file-worker-cpp.h> | #include <file-worker-cpp.h> | ||||||
| #include "irda-app-signal.h" | #include "irda-app-signal.h" | ||||||
|  | #include <memory> | ||||||
| 
 | 
 | ||||||
| class IrdaAppFileParser { | class IrdaAppFileParser { | ||||||
| public: | public: | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #include "irda-app-remote-manager.hpp" | #include "irda-app-remote-manager.hpp" | ||||||
| #include "filesystem-api.h" | #include <storage/storage.h> | ||||||
| #include "furi.h" | #include "furi.h" | ||||||
| #include "furi/check.h" | #include "furi/check.h" | ||||||
| #include "gui/modules/button_menu.h" | #include "gui/modules/button_menu.h" | ||||||
|  | |||||||
| @ -5,8 +5,7 @@ | |||||||
| #include <vector> | #include <vector> | ||||||
| #include <memory> | #include <memory> | ||||||
| #include <irda.h> | #include <irda.h> | ||||||
| #include <sd-card-api.h> | #include <storage/storage.h> | ||||||
| #include <filesystem-api.h> |  | ||||||
| #include "irda-app-signal.h" | #include "irda-app-signal.h" | ||||||
| 
 | 
 | ||||||
| class IrdaAppRemoteButton { | class IrdaAppRemoteButton { | ||||||
|  | |||||||
| @ -148,14 +148,16 @@ protected: | |||||||
| class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon { | class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon { | ||||||
| public: | public: | ||||||
|     void on_enter(IrdaApp* app) final; |     void on_enter(IrdaApp* app) final; | ||||||
|     IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/assets/ext/irda/tv.ir") {} |     IrdaAppSceneUniversalTV() | ||||||
|  |         : IrdaAppSceneUniversalCommon("/ext/irda/universal/tv.ir") { | ||||||
|  |     } | ||||||
|     ~IrdaAppSceneUniversalTV() {} |     ~IrdaAppSceneUniversalTV() {} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon { | class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon { | ||||||
| public: | public: | ||||||
|     void on_enter(IrdaApp* app) final; |     void on_enter(IrdaApp* app) final; | ||||||
|     IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/assets/ext/irda/audio.ir") {} |     IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/ext/irda/universal/audio.ir") {} | ||||||
|     ~IrdaAppSceneUniversalAudio() {} |     ~IrdaAppSceneUniversalAudio() {} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,13 +19,11 @@ | |||||||
| #include <file-worker-cpp.h> | #include <file-worker-cpp.h> | ||||||
| #include <path.h> | #include <path.h> | ||||||
| 
 | 
 | ||||||
| const char* LfRfidApp::app_folder = "lfrfid"; | const char* LfRfidApp::app_folder = "/any/lfrfid"; | ||||||
| const char* LfRfidApp::app_extension = ".rfid"; | const char* LfRfidApp::app_extension = ".rfid"; | ||||||
| 
 | 
 | ||||||
| LfRfidApp::LfRfidApp() | LfRfidApp::LfRfidApp() | ||||||
|     : scene_controller{this} |     : scene_controller{this} | ||||||
|     , fs_api{"sdcard"} |  | ||||||
|     , sd_ex_api{"sdcard-ex"} |  | ||||||
|     , notification{"notification"} |     , notification{"notification"} | ||||||
|     , text_store(40) { |     , text_store(40) { | ||||||
|     api_hal_power_insomnia_enter(); |     api_hal_power_insomnia_enter(); | ||||||
| @ -41,6 +39,8 @@ LfRfidApp::~LfRfidApp() { | |||||||
| void LfRfidApp::run(void* _args) { | void LfRfidApp::run(void* _args) { | ||||||
|     const char* args = reinterpret_cast<const char*>(_args); |     const char* args = reinterpret_cast<const char*>(_args); | ||||||
| 
 | 
 | ||||||
|  |     make_app_folder(); | ||||||
|  | 
 | ||||||
|     if(strlen(args)) { |     if(strlen(args)) { | ||||||
|         load_key_data(args, &worker.key); |         load_key_data(args, &worker.key); | ||||||
|         scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); |         scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); | ||||||
|  | |||||||
| @ -15,8 +15,6 @@ | |||||||
| #include <view-modules/byte-input-vm.h> | #include <view-modules/byte-input-vm.h> | ||||||
| #include "view/container-vm.h" | #include "view/container-vm.h" | ||||||
| 
 | 
 | ||||||
| #include <sd-card-api.h> |  | ||||||
| #include <filesystem-api.h> |  | ||||||
| #include <notification/notification-messages.h> | #include <notification/notification-messages.h> | ||||||
| 
 | 
 | ||||||
| #include "helpers/rfid-worker.h" | #include "helpers/rfid-worker.h" | ||||||
| @ -64,8 +62,6 @@ public: | |||||||
|     ~LfRfidApp(); |     ~LfRfidApp(); | ||||||
|     LfRfidApp(); |     LfRfidApp(); | ||||||
| 
 | 
 | ||||||
|     RecordController<FS_Api> fs_api; |  | ||||||
|     RecordController<SdCard_Api> sd_ex_api; |  | ||||||
|     RecordController<NotificationApp> notification; |     RecordController<NotificationApp> notification; | ||||||
| 
 | 
 | ||||||
|     RfidWorker worker; |     RfidWorker worker; | ||||||
|  | |||||||
| @ -183,7 +183,7 @@ static void loader_build_menu() { | |||||||
|                     menu, |                     menu, | ||||||
|                     menu_item_alloc_function( |                     menu_item_alloc_function( | ||||||
|                         FLIPPER_APPS[i].name, |                         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, |                         loader_menu_callback, | ||||||
|                         (void*)&FLIPPER_APPS[i])); |                         (void*)&FLIPPER_APPS[i])); | ||||||
| 
 | 
 | ||||||
| @ -213,7 +213,8 @@ static void loader_build_menu() { | |||||||
|                     menu_plugins, |                     menu_plugins, | ||||||
|                     menu_item_alloc_function( |                     menu_item_alloc_function( | ||||||
|                         FLIPPER_PLUGINS[i].name, |                         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, |                         loader_menu_callback, | ||||||
|                         (void*)&FLIPPER_PLUGINS[i])); |                         (void*)&FLIPPER_PLUGINS[i])); | ||||||
| 
 | 
 | ||||||
| @ -245,7 +246,9 @@ static void loader_build_menu() { | |||||||
|                     menu_debug, |                     menu_debug, | ||||||
|                     menu_item_alloc_function( |                     menu_item_alloc_function( | ||||||
|                         FLIPPER_DEBUG_APPS[i].name, |                         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, |                         loader_menu_callback, | ||||||
|                         (void*)&FLIPPER_DEBUG_APPS[i])); |                         (void*)&FLIPPER_DEBUG_APPS[i])); | ||||||
| 
 | 
 | ||||||
| @ -277,7 +280,9 @@ static void loader_build_menu() { | |||||||
|                     menu_debug, |                     menu_debug, | ||||||
|                     menu_item_alloc_function( |                     menu_item_alloc_function( | ||||||
|                         FLIPPER_SETTINGS_APPS[i].name, |                         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, |                         loader_menu_callback, | ||||||
|                         (void*)&FLIPPER_SETTINGS_APPS[i])); |                         (void*)&FLIPPER_SETTINGS_APPS[i])); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ | |||||||
| 
 | 
 | ||||||
| #define NFC_DEVICE_MAX_DATA_LEN 14 | #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 const char* nfc_app_extension = ".nfc"; | ||||||
| 
 | 
 | ||||||
| static bool nfc_device_read_hex(string_t str, uint8_t* buff, uint16_t len) { | static bool nfc_device_read_hex(string_t str, uint8_t* buff, uint16_t len) { | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <api-hal.h> | #include <api-hal.h> | ||||||
| #include <internal-storage/internal-storage.h> | #include <storage/storage.h> | ||||||
| #include "notification.h" | #include "notification.h" | ||||||
| #include "notification-messages.h" | #include "notification-messages.h" | ||||||
| #include "notification-app.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; |     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); |     const size_t settings_size = sizeof(NotificationSettings); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I("notification", "Loading state from internal-storage"); |     FURI_LOG_I("notification", "loading settings from \"%s\"", NOTIFICATION_SETTINGS_PATH); | ||||||
|     int ret = internal_storage_read_key( |     bool fs_result = | ||||||
|         internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&settings, settings_size); |         storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
| 
 | 
 | ||||||
|     if(ret != settings_size) { |     if(fs_result) { | ||||||
|         FURI_LOG_E("notification", "Load failed. Storage returned: %d", ret); |         uint16_t bytes_count = storage_file_read(file, &settings, settings_size); | ||||||
|     } else { | 
 | ||||||
|         FURI_LOG_I("notification", "Load success", ret); |         if(bytes_count != settings_size) { | ||||||
|  |             fs_result = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(fs_result) { | ||||||
|  |         FURI_LOG_I("notification", "load success"); | ||||||
| 
 | 
 | ||||||
|         if(settings.version != NOTIFICATION_SETTINGS_VERSION) { |         if(settings.version != NOTIFICATION_SETTINGS_VERSION) { | ||||||
|             FURI_LOG_E( |             FURI_LOG_E( | ||||||
|                 "notification", |                 "notification", | ||||||
|                 "Version(%d != %d) mismatch", |                 "version(%d != %d) mismatch", | ||||||
|                 app->settings.version, |                 app->settings.version, | ||||||
|                 NOTIFICATION_SETTINGS_VERSION); |                 NOTIFICATION_SETTINGS_VERSION); | ||||||
|         } else { |         } else { | ||||||
| @ -334,26 +340,50 @@ static void notification_load_settings(NotificationApp* app) { | |||||||
|             memcpy(&app->settings, &settings, settings_size); |             memcpy(&app->settings, &settings, settings_size); | ||||||
|             osKernelUnlock(); |             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) { | static bool notification_save_settings(NotificationApp* app) { | ||||||
|     InternalStorage* internal_storage = furi_record_open("internal-storage"); |     NotificationSettings settings; | ||||||
|  |     File* file = storage_file_alloc(furi_record_open("storage")); | ||||||
|     const size_t settings_size = sizeof(NotificationSettings); |     const size_t settings_size = sizeof(NotificationSettings); | ||||||
| 
 | 
 | ||||||
|     FURI_LOG_I("notification", "Saving state to internal-storage"); |     FURI_LOG_I("notification", "saving settings to \"%s\"", NOTIFICATION_SETTINGS_PATH); | ||||||
|     int ret = internal_storage_write_key( |  | ||||||
|         internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&app->settings, settings_size); |  | ||||||
| 
 | 
 | ||||||
|     if(ret != settings_size) { |     osKernelLock(); | ||||||
|         FURI_LOG_E("notification", "Save failed. Storage returned: %d", ret); |     memcpy(&settings, &app->settings, settings_size); | ||||||
|     } else { |     osKernelUnlock(); | ||||||
|         FURI_LOG_I("notification", "Saved"); | 
 | ||||||
|  |     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) { | 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) { | int32_t notification_app(void* p) { | ||||||
|     NotificationApp* app = notification_app_alloc(); |     NotificationApp* app = notification_app_alloc(); | ||||||
| 
 | 
 | ||||||
|     notification_load_settings(app); |     if(!notification_load_settings(app)) { | ||||||
|  |         notification_save_settings(app); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     notification_vibro_off(); |     notification_vibro_off(); | ||||||
|     notification_sound_off(); |     notification_sound_off(); | ||||||
|  | |||||||
| @ -31,7 +31,7 @@ typedef struct { | |||||||
| } NotificationLedLayer; | } NotificationLedLayer; | ||||||
| 
 | 
 | ||||||
| #define NOTIFICATION_SETTINGS_VERSION 0x01 | #define NOTIFICATION_SETTINGS_VERSION 0x01 | ||||||
| #define NOTIFICATION_SETTINGS_PATH "notification_settings" | #define NOTIFICATION_SETTINGS_PATH "/int/notification.settings" | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t version; |     uint8_t version; | ||||||
|  | |||||||
| @ -1,892 +0,0 @@ | |||||||
| #include "app-template.h" |  | ||||||
| #include "stm32_adafruit_sd.h" |  | ||||||
| #include "fnv1a-hash.h" |  | ||||||
| #include "filesystem-api.h" |  | ||||||
| #include "cli/cli.h" |  | ||||||
| #include "callback-connector.h" |  | ||||||
| #include <notification/notification-messages.h> |  | ||||||
| 
 |  | ||||||
| // event enumeration type
 |  | ||||||
| typedef uint8_t event_t; |  | ||||||
| 
 |  | ||||||
| class SdTestState { |  | ||||||
| public: |  | ||||||
|     // state data
 |  | ||||||
|     static const uint8_t lines_count = 6; |  | ||||||
|     const char* line[lines_count]; |  | ||||||
| 
 |  | ||||||
|     // state initializer
 |  | ||||||
|     SdTestState() { |  | ||||||
|         for(uint8_t i = 0; i < lines_count; i++) { |  | ||||||
|             line[i] = ""; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // events class
 |  | ||||||
| class SdTestEvent { |  | ||||||
| public: |  | ||||||
|     // events enum
 |  | ||||||
|     static const event_t EventTypeTick = 0; |  | ||||||
|     static const event_t EventTypeKey = 1; |  | ||||||
| 
 |  | ||||||
|     // payload
 |  | ||||||
|     union { |  | ||||||
|         InputEvent input; |  | ||||||
|     } value; |  | ||||||
| 
 |  | ||||||
|     // event type
 |  | ||||||
|     event_t type; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // our app derived from base AppTemplate class
 |  | ||||||
| // with template variables <state, events>
 |  | ||||||
| class SdTest : public AppTemplate<SdTestState, SdTestEvent> { |  | ||||||
| public: |  | ||||||
|     // vars
 |  | ||||||
|     const uint32_t benchmark_data_size = 4096; |  | ||||||
|     uint8_t* benchmark_data; |  | ||||||
|     FS_Api* fs_api; |  | ||||||
|     NotificationApp* notification; |  | ||||||
| 
 |  | ||||||
|     // consts
 |  | ||||||
|     static const uint32_t BENCHMARK_ERROR = UINT_MAX; |  | ||||||
| 
 |  | ||||||
|     // funcs
 |  | ||||||
|     void run(); |  | ||||||
|     void render(Canvas* canvas); |  | ||||||
|     template <class T> void set_text(std::initializer_list<T> list); |  | ||||||
|     template <class T> void set_error(std::initializer_list<T> list); |  | ||||||
|     void wait_for_button(InputKey input_button); |  | ||||||
|     bool ask(InputKey input_button_cancel, InputKey input_button_ok); |  | ||||||
|     void blink_red(); |  | ||||||
|     void blink_green(); |  | ||||||
| 
 |  | ||||||
|     // "tests"
 |  | ||||||
|     void detect_sd_card(); |  | ||||||
|     void show_warning(); |  | ||||||
|     void get_sd_card_info(); |  | ||||||
| 
 |  | ||||||
|     bool prepare_benchmark_data(); |  | ||||||
|     void free_benchmark_data(); |  | ||||||
|     void write_benchmark(); |  | ||||||
|     uint32_t |  | ||||||
|         write_benchmark_internal(const uint32_t size, const uint32_t tcount, bool silent = false); |  | ||||||
| 
 |  | ||||||
|     void read_benchmark(); |  | ||||||
|     uint32_t read_benchmark_internal( |  | ||||||
|         const uint32_t size, |  | ||||||
|         const uint32_t count, |  | ||||||
|         File* file, |  | ||||||
|         bool silent = false); |  | ||||||
| 
 |  | ||||||
|     void hash_benchmark(); |  | ||||||
| 
 |  | ||||||
|     // cli tests
 |  | ||||||
|     void cli_read_benchmark(Cli* cli, string_t args, void* _ctx); |  | ||||||
|     void cli_write_benchmark(Cli* cli, string_t args, void* _ctx); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // start app
 |  | ||||||
| void SdTest::run() { |  | ||||||
|     app_ready(); |  | ||||||
| 
 |  | ||||||
|     fs_api = static_cast<FS_Api*>(furi_record_open("sdcard")); |  | ||||||
|     notification = static_cast<NotificationApp*>(furi_record_open("notification")); |  | ||||||
| 
 |  | ||||||
|     if(fs_api == NULL) { |  | ||||||
|         set_error({"cannot get sdcard api"}); |  | ||||||
|         exit(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Cli* cli = static_cast<Cli*>(furi_record_open("cli")); |  | ||||||
| 
 |  | ||||||
|     // read_benchmark and write_benchmark signatures are same. so we must use tags
 |  | ||||||
|     auto cli_read_cb = cbc::obtain_connector<0>(this, &SdTest::cli_read_benchmark); |  | ||||||
|     cli_add_command(cli, "sd_read_test", CliCommandFlagDefault, cli_read_cb, this); |  | ||||||
| 
 |  | ||||||
|     auto cli_write_cb = cbc::obtain_connector<1>(this, &SdTest::cli_write_benchmark); |  | ||||||
|     cli_add_command(cli, "sd_write_test", CliCommandFlagDefault, cli_write_cb, this); |  | ||||||
| 
 |  | ||||||
|     detect_sd_card(); |  | ||||||
|     get_sd_card_info(); |  | ||||||
|     show_warning(); |  | ||||||
| 
 |  | ||||||
|     set_text({"preparing benchmark data"}); |  | ||||||
|     bool data_prepared = prepare_benchmark_data(); |  | ||||||
|     if(data_prepared) { |  | ||||||
|         set_text({"benchmark data prepared"}); |  | ||||||
|     } else { |  | ||||||
|         set_error({"cannot allocate buffer", "for benchmark data"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     write_benchmark(); |  | ||||||
|     read_benchmark(); |  | ||||||
|     hash_benchmark(); |  | ||||||
|     free_benchmark_data(); |  | ||||||
| 
 |  | ||||||
|     set_text({ |  | ||||||
|         "test complete", |  | ||||||
|         "", |  | ||||||
|         "", |  | ||||||
|         "", |  | ||||||
|         "", |  | ||||||
|         "press BACK to exit", |  | ||||||
|     }); |  | ||||||
|     wait_for_button(InputKeyBack); |  | ||||||
| 
 |  | ||||||
|     furi_record_close("notification"); |  | ||||||
|     exit(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // detect sd card insertion
 |  | ||||||
| void SdTest::detect_sd_card() { |  | ||||||
|     const uint8_t str_buffer_size = 40; |  | ||||||
|     const uint8_t dots_animation_size = 4; |  | ||||||
|     char str_buffer[str_buffer_size]; |  | ||||||
|     const char dots[dots_animation_size][4] = {"", ".", "..", "..."}; |  | ||||||
|     uint8_t i = 0; |  | ||||||
| 
 |  | ||||||
|     // detect sd card pin
 |  | ||||||
|     while(fs_api->common.get_fs_info(NULL, NULL) == FSE_NOT_READY) { |  | ||||||
|         delay(100); |  | ||||||
| 
 |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "Waiting%s", dots[i]); |  | ||||||
|         set_text({static_cast<const char*>(str_buffer), "Please insert sd card"}); |  | ||||||
| 
 |  | ||||||
|         if(i < (dots_animation_size - 1)) { |  | ||||||
|             i++; |  | ||||||
|         } else { |  | ||||||
|             i = 0; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     blink_green(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // show warning about test
 |  | ||||||
| void SdTest::show_warning() { |  | ||||||
|     set_text( |  | ||||||
|         {"!!Warning!!", |  | ||||||
|          "during the tests", |  | ||||||
|          "files can be overwritten", |  | ||||||
|          "or data on card may be lost", |  | ||||||
|          "", |  | ||||||
|          "press UP DOWN OK to continue"}); |  | ||||||
| 
 |  | ||||||
|     wait_for_button(InputKeyUp); |  | ||||||
|     wait_for_button(InputKeyDown); |  | ||||||
|     wait_for_button(InputKeyOk); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // get info about sd card, label, sn
 |  | ||||||
| // sector, cluster, total and free size
 |  | ||||||
| void SdTest::get_sd_card_info() { |  | ||||||
|     const uint8_t str_buffer_size = 26; |  | ||||||
|     char str_buffer[2][str_buffer_size]; |  | ||||||
|     FS_Error result; |  | ||||||
|     uint64_t bytes_total, bytes_free; |  | ||||||
|     int __attribute__((unused)) snprintf_count = 0; |  | ||||||
| 
 |  | ||||||
|     result = fs_api->common.get_fs_info(&bytes_total, &bytes_free); |  | ||||||
|     if(result != FSE_OK) set_error({"get_fs_info error", fs_api->error.get_desc(result)}); |  | ||||||
| 
 |  | ||||||
|     snprintf( |  | ||||||
|         str_buffer[0], str_buffer_size, "%lu KB total", static_cast<uint32_t>(bytes_total / 1024)); |  | ||||||
|     snprintf( |  | ||||||
|         str_buffer[1], str_buffer_size, "%lu KB free", static_cast<uint32_t>(bytes_free / 1024)); |  | ||||||
| 
 |  | ||||||
|     set_text( |  | ||||||
|         {static_cast<const char*>(str_buffer[0]), |  | ||||||
|          static_cast<const char*>(str_buffer[1]), |  | ||||||
|          "", |  | ||||||
|          "", |  | ||||||
|          "", |  | ||||||
|          "press OK to continue"}); |  | ||||||
| 
 |  | ||||||
|     blink_green(); |  | ||||||
| 
 |  | ||||||
|     wait_for_button(InputKeyOk); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // prepare benchmark data (allocate data in ram)
 |  | ||||||
| bool SdTest::prepare_benchmark_data() { |  | ||||||
|     bool result = true; |  | ||||||
|     benchmark_data = static_cast<uint8_t*>(malloc(benchmark_data_size)); |  | ||||||
| 
 |  | ||||||
|     if(benchmark_data == NULL) { |  | ||||||
|         result = false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(size_t i = 0; i < benchmark_data_size; i++) { |  | ||||||
|         benchmark_data[i] = static_cast<uint8_t>(i); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SdTest::free_benchmark_data() { |  | ||||||
|     free(benchmark_data); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // write speed test
 |  | ||||||
| void SdTest::write_benchmark() { |  | ||||||
|     const uint32_t b1_size = 1; |  | ||||||
|     const uint32_t b8_size = 8; |  | ||||||
|     const uint32_t b32_size = 32; |  | ||||||
|     const uint32_t b256_size = 256; |  | ||||||
|     const uint32_t b4096_size = 4096; |  | ||||||
| 
 |  | ||||||
|     const uint32_t benchmark_data_size = 16384 * 4; |  | ||||||
| 
 |  | ||||||
|     uint32_t benchmark_bps = 0; |  | ||||||
| 
 |  | ||||||
|     const uint8_t str_buffer_size = 32; |  | ||||||
|     char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""}; |  | ||||||
|     auto string_list = { |  | ||||||
|         static_cast<const char*>(str_buffer[0]), |  | ||||||
|         static_cast<const char*>(str_buffer[1]), |  | ||||||
|         static_cast<const char*>(str_buffer[2]), |  | ||||||
|         static_cast<const char*>(str_buffer[3]), |  | ||||||
|         static_cast<const char*>(str_buffer[4]), |  | ||||||
|         static_cast<const char*>(str_buffer[5])}; |  | ||||||
| 
 |  | ||||||
|     set_text({"write speed test", "procedure can be lengthy", "please wait"}); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 1b test
 |  | ||||||
|     benchmark_bps = write_benchmark_internal(b1_size, benchmark_data_size / b1_size); |  | ||||||
|     snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 8b test
 |  | ||||||
|     benchmark_bps = write_benchmark_internal(b8_size, benchmark_data_size / b8_size); |  | ||||||
|     snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 32b test
 |  | ||||||
|     benchmark_bps = write_benchmark_internal(b32_size, benchmark_data_size / b32_size); |  | ||||||
|     snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 256b test
 |  | ||||||
|     benchmark_bps = write_benchmark_internal(b256_size, benchmark_data_size / b256_size); |  | ||||||
|     snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 4096b test
 |  | ||||||
|     benchmark_bps = write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size); |  | ||||||
|     snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps); |  | ||||||
|     snprintf(str_buffer[5], str_buffer_size, "press OK to continue"); |  | ||||||
|     set_text(string_list); |  | ||||||
| 
 |  | ||||||
|     blink_green(); |  | ||||||
| 
 |  | ||||||
|     wait_for_button(InputKeyOk); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint32_t SdTest::write_benchmark_internal(const uint32_t size, const uint32_t count, bool silent) { |  | ||||||
|     uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_written; |  | ||||||
|     File file; |  | ||||||
| 
 |  | ||||||
|     const uint8_t str_buffer_size = 32; |  | ||||||
|     char str_buffer[str_buffer_size]; |  | ||||||
| 
 |  | ||||||
|     if(!fs_api->file.open(&file, "write.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { |  | ||||||
|         if(!silent) { |  | ||||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); |  | ||||||
|             set_error({"cannot open file ", static_cast<const char*>(str_buffer)}); |  | ||||||
|         } else { |  | ||||||
|             benchmark_bps = BENCHMARK_ERROR; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     start_tick = osKernelGetTickCount(); |  | ||||||
|     for(size_t i = 0; i < count; i++) { |  | ||||||
|         bytes_written = fs_api->file.write(&file, benchmark_data, size); |  | ||||||
|         if(bytes_written != size || file.error_id != FSE_OK) { |  | ||||||
|             if(!silent) { |  | ||||||
|                 snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); |  | ||||||
|                 set_error({"cannot write to file ", static_cast<const char*>(str_buffer)}); |  | ||||||
|             } else { |  | ||||||
|                 benchmark_bps = BENCHMARK_ERROR; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     stop_tick = osKernelGetTickCount(); |  | ||||||
| 
 |  | ||||||
|     if(!fs_api->file.close(&file)) { |  | ||||||
|         if(!silent) { |  | ||||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); |  | ||||||
|             set_error({"cannot close file ", static_cast<const char*>(str_buffer)}); |  | ||||||
|         } else { |  | ||||||
|             benchmark_bps = BENCHMARK_ERROR; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(benchmark_bps != BENCHMARK_ERROR) { |  | ||||||
|         benchmark_time = stop_tick - start_tick; |  | ||||||
|         benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return benchmark_bps; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // read speed test
 |  | ||||||
| void SdTest::read_benchmark() { |  | ||||||
|     const uint32_t benchmark_data_size = 16384 * 8; |  | ||||||
|     uint32_t bytes_written; |  | ||||||
|     uint32_t benchmark_bps = 0; |  | ||||||
| 
 |  | ||||||
|     const uint8_t str_buffer_size = 32; |  | ||||||
|     char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""}; |  | ||||||
|     auto string_list = { |  | ||||||
|         static_cast<const char*>(str_buffer[0]), |  | ||||||
|         static_cast<const char*>(str_buffer[1]), |  | ||||||
|         static_cast<const char*>(str_buffer[2]), |  | ||||||
|         static_cast<const char*>(str_buffer[3]), |  | ||||||
|         static_cast<const char*>(str_buffer[4]), |  | ||||||
|         static_cast<const char*>(str_buffer[5])}; |  | ||||||
| 
 |  | ||||||
|     File file; |  | ||||||
| 
 |  | ||||||
|     const uint32_t b1_size = 1; |  | ||||||
|     const uint32_t b8_size = 8; |  | ||||||
|     const uint32_t b32_size = 32; |  | ||||||
|     const uint32_t b256_size = 256; |  | ||||||
|     const uint32_t b4096_size = 4096; |  | ||||||
| 
 |  | ||||||
|     // prepare data for read test
 |  | ||||||
|     set_text({"prepare data", "for read speed test", "procedure can be lengthy", "please wait"}); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     if(!fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { |  | ||||||
|         set_error({"cannot open file ", "in prepare read"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { |  | ||||||
|         bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); |  | ||||||
|         if(bytes_written != b4096_size || file.error_id != FSE_OK) { |  | ||||||
|             set_error({"cannot write to file ", "in prepare read"}); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!fs_api->file.close(&file)) { |  | ||||||
|         set_error({"cannot close file ", "in prepare read"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // test start
 |  | ||||||
|     set_text({"read speed test", "procedure can be lengthy", "please wait"}); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // open file
 |  | ||||||
|     if(!fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) { |  | ||||||
|         set_error({"cannot open file ", "in read benchmark"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 1b test
 |  | ||||||
|     benchmark_bps = read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file); |  | ||||||
|     snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 8b test
 |  | ||||||
|     benchmark_bps = read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file); |  | ||||||
|     snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 32b test
 |  | ||||||
|     benchmark_bps = read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file); |  | ||||||
|     snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 256b test
 |  | ||||||
|     benchmark_bps = read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file); |  | ||||||
|     snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps); |  | ||||||
|     set_text(string_list); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // 4096b test
 |  | ||||||
|     benchmark_bps = read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file); |  | ||||||
|     snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps); |  | ||||||
|     snprintf(str_buffer[5], str_buffer_size, "press OK to continue"); |  | ||||||
|     set_text(string_list); |  | ||||||
| 
 |  | ||||||
|     // close file
 |  | ||||||
|     if(!fs_api->file.close(&file)) { |  | ||||||
|         set_error({"cannot close file ", "in read test"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     blink_green(); |  | ||||||
| 
 |  | ||||||
|     wait_for_button(InputKeyOk); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint32_t SdTest::read_benchmark_internal( |  | ||||||
|     const uint32_t size, |  | ||||||
|     const uint32_t count, |  | ||||||
|     File* file, |  | ||||||
|     bool silent) { |  | ||||||
|     uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_readed; |  | ||||||
| 
 |  | ||||||
|     const uint8_t str_buffer_size = 32; |  | ||||||
|     char str_buffer[str_buffer_size]; |  | ||||||
|     uint8_t* read_buffer; |  | ||||||
| 
 |  | ||||||
|     read_buffer = static_cast<uint8_t*>(malloc(size)); |  | ||||||
| 
 |  | ||||||
|     if(read_buffer == NULL) { |  | ||||||
|         if(!silent) { |  | ||||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); |  | ||||||
|             set_error({"cannot allocate memory", static_cast<const char*>(str_buffer)}); |  | ||||||
|         } else { |  | ||||||
|             benchmark_bps = BENCHMARK_ERROR; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fs_api->file.seek(file, 0, true); |  | ||||||
| 
 |  | ||||||
|     start_tick = osKernelGetTickCount(); |  | ||||||
|     for(size_t i = 0; i < count; i++) { |  | ||||||
|         bytes_readed = fs_api->file.read(file, read_buffer, size); |  | ||||||
|         if(bytes_readed != size || file->error_id != FSE_OK) { |  | ||||||
|             if(!silent) { |  | ||||||
|                 snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); |  | ||||||
|                 set_error({"cannot read from file ", static_cast<const char*>(str_buffer)}); |  | ||||||
|             } else { |  | ||||||
|                 benchmark_bps = BENCHMARK_ERROR; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     stop_tick = osKernelGetTickCount(); |  | ||||||
| 
 |  | ||||||
|     free(read_buffer); |  | ||||||
| 
 |  | ||||||
|     if(benchmark_bps != BENCHMARK_ERROR) { |  | ||||||
|         benchmark_time = stop_tick - start_tick; |  | ||||||
|         benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return benchmark_bps; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // hash benchmark, store data to sd with known hash
 |  | ||||||
| // then read, calculate hash and compare both hashes
 |  | ||||||
| void SdTest::hash_benchmark() { |  | ||||||
|     uint32_t mcu_data_hash = FNV_1A_INIT; |  | ||||||
|     uint32_t sdcard_data_hash = FNV_1A_INIT; |  | ||||||
|     uint8_t* read_buffer; |  | ||||||
|     uint32_t bytes_readed; |  | ||||||
| 
 |  | ||||||
|     uint32_t bytes_written; |  | ||||||
| 
 |  | ||||||
|     const uint8_t str_buffer_size = 32; |  | ||||||
|     char str_buffer[3][str_buffer_size] = {"", "", ""}; |  | ||||||
| 
 |  | ||||||
|     File file; |  | ||||||
| 
 |  | ||||||
|     const uint32_t b4096_size = 4096; |  | ||||||
|     const uint32_t benchmark_count = 20; |  | ||||||
| 
 |  | ||||||
|     // prepare data for hash test
 |  | ||||||
|     set_text({"prepare data", "for hash test"}); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // write data to test file and calculate hash
 |  | ||||||
|     if(!fs_api->file.open(&file, "hash.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { |  | ||||||
|         set_error({"cannot open file ", "in prepare hash"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(uint32_t i = 0; i < benchmark_count; i++) { |  | ||||||
|         mcu_data_hash = fnv1a_buffer_hash(benchmark_data, b4096_size, mcu_data_hash); |  | ||||||
|         bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); |  | ||||||
| 
 |  | ||||||
|         if(bytes_written != b4096_size || file.error_id != FSE_OK) { |  | ||||||
|             set_error({"cannot write to file ", "in prepare hash"}); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         snprintf(str_buffer[0], str_buffer_size, "writing %lu of %lu x 4k", i, benchmark_count); |  | ||||||
|         set_text({"prepare data", "for hash test", static_cast<const char*>(str_buffer[0])}); |  | ||||||
|         delay(100); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!fs_api->file.close(&file)) { |  | ||||||
|         set_error({"cannot close file ", "in prepare hash"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // show hash of data located in mcu memory
 |  | ||||||
|     snprintf(str_buffer[0], str_buffer_size, "hash in mcu 0x%lx", mcu_data_hash); |  | ||||||
|     set_text({str_buffer[0]}); |  | ||||||
|     delay(100); |  | ||||||
| 
 |  | ||||||
|     // read data from sd card and calculate hash
 |  | ||||||
|     read_buffer = static_cast<uint8_t*>(malloc(b4096_size)); |  | ||||||
| 
 |  | ||||||
|     if(read_buffer == NULL) { |  | ||||||
|         set_error({"cannot allocate memory", "in hash test"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!fs_api->file.open(&file, "hash.test", FSAM_READ, FSOM_OPEN_EXISTING)) { |  | ||||||
|         set_error({"cannot open file ", "in hash test"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(uint32_t i = 0; i < benchmark_count; i++) { |  | ||||||
|         bytes_readed = fs_api->file.read(&file, read_buffer, b4096_size); |  | ||||||
|         sdcard_data_hash = fnv1a_buffer_hash(read_buffer, b4096_size, sdcard_data_hash); |  | ||||||
| 
 |  | ||||||
|         if(bytes_readed != b4096_size || file.error_id != FSE_OK) { |  | ||||||
|             set_error({"cannot read from file ", "in hash test"}); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         snprintf(str_buffer[1], str_buffer_size, "reading %lu of %lu x 4k", i, benchmark_count); |  | ||||||
|         set_text({str_buffer[0], str_buffer[1]}); |  | ||||||
|         delay(100); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!fs_api->file.close(&file)) { |  | ||||||
|         set_error({"cannot close file ", "in hash test"}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     free(read_buffer); |  | ||||||
| 
 |  | ||||||
|     snprintf(str_buffer[1], str_buffer_size, "hash in sdcard 0x%lx", sdcard_data_hash); |  | ||||||
|     if(mcu_data_hash == sdcard_data_hash) { |  | ||||||
|         snprintf(str_buffer[2], str_buffer_size, "hashes are equal, press OK"); |  | ||||||
|         set_text( |  | ||||||
|             {static_cast<const char*>(str_buffer[0]), |  | ||||||
|              static_cast<const char*>(str_buffer[1]), |  | ||||||
|              "", |  | ||||||
|              "", |  | ||||||
|              "", |  | ||||||
|              static_cast<const char*>(str_buffer[2])}); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer[2], str_buffer_size, "hash error, press BACK to exit"); |  | ||||||
|         set_error( |  | ||||||
|             {static_cast<const char*>(str_buffer[0]), |  | ||||||
|              static_cast<const char*>(str_buffer[1]), |  | ||||||
|              "", |  | ||||||
|              "", |  | ||||||
|              "", |  | ||||||
|              static_cast<const char*>(str_buffer[2])}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     blink_green(); |  | ||||||
| 
 |  | ||||||
|     wait_for_button(InputKeyOk); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SdTest::cli_read_benchmark(Cli* cli, string_t args, void* _ctx) { |  | ||||||
|     SdTest* _this = static_cast<SdTest*>(_ctx); |  | ||||||
| 
 |  | ||||||
|     const uint32_t benchmark_data_size = 16384 * 8; |  | ||||||
|     uint32_t bytes_written; |  | ||||||
|     uint32_t benchmark_bps = 0; |  | ||||||
|     File file; |  | ||||||
| 
 |  | ||||||
|     const uint32_t b1_size = 1; |  | ||||||
|     const uint32_t b8_size = 8; |  | ||||||
|     const uint32_t b32_size = 32; |  | ||||||
|     const uint32_t b256_size = 256; |  | ||||||
|     const uint32_t b4096_size = 4096; |  | ||||||
| 
 |  | ||||||
|     const uint8_t str_buffer_size = 64; |  | ||||||
|     char str_buffer[str_buffer_size]; |  | ||||||
| 
 |  | ||||||
|     printf("preparing benchmark data\r\n"); |  | ||||||
|     bool data_prepared = _this->prepare_benchmark_data(); |  | ||||||
|     if(data_prepared) { |  | ||||||
|         printf("benchmark data prepared\r\n"); |  | ||||||
|     } else { |  | ||||||
|         printf("error: cannot allocate buffer for benchmark data\r\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // prepare data for read test
 |  | ||||||
|     printf("prepare data for read speed test, procedure can be lengthy, please wait\r\n"); |  | ||||||
| 
 |  | ||||||
|     if(!_this->fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { |  | ||||||
|         printf("error: cannot open file in prepare read\r\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { |  | ||||||
|         bytes_written = _this->fs_api->file.write(&file, benchmark_data, b4096_size); |  | ||||||
|         if(bytes_written != b4096_size || file.error_id != FSE_OK) { |  | ||||||
|             printf("error: cannot write to file in prepare read\r\n"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!_this->fs_api->file.close(&file)) { |  | ||||||
|         printf("error: cannot close file in prepare read\r\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // test start
 |  | ||||||
|     printf("read speed test, procedure can be lengthy, please wait\r\n"); |  | ||||||
| 
 |  | ||||||
|     // open file
 |  | ||||||
|     if(!_this->fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) { |  | ||||||
|         printf("error: cannot open file in read benchmark\r\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 1b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 1-byte read test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 8b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 8-byte read test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 32b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 32-byte read test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 256b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 256-byte read test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 4096b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 4096-byte read test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf( |  | ||||||
|             str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // close file
 |  | ||||||
|     if(!_this->fs_api->file.close(&file)) { |  | ||||||
|         printf("error: cannot close file\r\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _this->free_benchmark_data(); |  | ||||||
| 
 |  | ||||||
|     printf("test completed\r\n"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void SdTest::cli_write_benchmark(Cli* cli, string_t args, void* _ctx) { |  | ||||||
|     SdTest* _this = static_cast<SdTest*>(_ctx); |  | ||||||
| 
 |  | ||||||
|     const uint32_t b1_size = 1; |  | ||||||
|     const uint32_t b8_size = 8; |  | ||||||
|     const uint32_t b32_size = 32; |  | ||||||
|     const uint32_t b256_size = 256; |  | ||||||
|     const uint32_t b4096_size = 4096; |  | ||||||
| 
 |  | ||||||
|     const uint32_t benchmark_data_size = 16384 * 4; |  | ||||||
| 
 |  | ||||||
|     uint32_t benchmark_bps = 0; |  | ||||||
| 
 |  | ||||||
|     const uint8_t str_buffer_size = 64; |  | ||||||
|     char str_buffer[str_buffer_size]; |  | ||||||
| 
 |  | ||||||
|     printf("preparing benchmark data\r\n"); |  | ||||||
|     bool data_prepared = _this->prepare_benchmark_data(); |  | ||||||
|     if(data_prepared) { |  | ||||||
|         printf("benchmark data prepared\r\n"); |  | ||||||
|     } else { |  | ||||||
|         printf("error: cannot allocate buffer for benchmark data\r\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     printf("write speed test, procedure can be lengthy, please wait\r\n"); |  | ||||||
| 
 |  | ||||||
|     // 1b test
 |  | ||||||
|     benchmark_bps = _this->write_benchmark_internal(b1_size, benchmark_data_size / b1_size, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 1-byte write test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 8b test
 |  | ||||||
|     benchmark_bps = _this->write_benchmark_internal(b8_size, benchmark_data_size / b8_size, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 8-byte write test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 32b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->write_benchmark_internal(b32_size, benchmark_data_size / b32_size, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 32-byte write test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 256b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->write_benchmark_internal(b256_size, benchmark_data_size / b256_size, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 256-byte write test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // 4096b test
 |  | ||||||
|     benchmark_bps = |  | ||||||
|         _this->write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, true); |  | ||||||
|     if(benchmark_bps == BENCHMARK_ERROR) { |  | ||||||
|         printf("error: in 4096-byte write test\r\n"); |  | ||||||
|     } else { |  | ||||||
|         snprintf( |  | ||||||
|             str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps); |  | ||||||
|         printf(str_buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _this->free_benchmark_data(); |  | ||||||
| 
 |  | ||||||
|     printf("test completed\r\n"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // wait for button press
 |  | ||||||
| void SdTest::wait_for_button(InputKey input_button) { |  | ||||||
|     SdTestEvent event; |  | ||||||
|     osMessageQueueReset(event_queue); |  | ||||||
|     while(1) { |  | ||||||
|         osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); |  | ||||||
| 
 |  | ||||||
|         if(result == osOK && event.type == SdTestEvent::EventTypeKey) { |  | ||||||
|             if(event.value.input.type == InputTypeShort) { |  | ||||||
|                 if(event.value.input.key == InputKeyBack) { |  | ||||||
|                     exit(); |  | ||||||
|                 } else { |  | ||||||
|                     if(event.value.input.key == input_button) { |  | ||||||
|                         blink_green(); |  | ||||||
|                         break; |  | ||||||
|                     } else { |  | ||||||
|                         blink_red(); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     osMessageQueueReset(event_queue); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ask user to proceed or cancel
 |  | ||||||
| bool SdTest::ask(InputKey input_button_cancel, InputKey input_button_ok) { |  | ||||||
|     bool return_result; |  | ||||||
|     SdTestEvent event; |  | ||||||
|     osMessageQueueReset(event_queue); |  | ||||||
|     while(1) { |  | ||||||
|         osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); |  | ||||||
| 
 |  | ||||||
|         if(result == osOK && event.type == SdTestEvent::EventTypeKey) { |  | ||||||
|             if(event.value.input.type == InputTypeShort) { |  | ||||||
|                 if(event.value.input.key == InputKeyBack) { |  | ||||||
|                     exit(); |  | ||||||
|                 } else { |  | ||||||
|                     if(event.value.input.key == input_button_ok) { |  | ||||||
|                         blink_green(); |  | ||||||
|                         return_result = true; |  | ||||||
|                         break; |  | ||||||
|                     } else if(event.value.input.key == input_button_cancel) { |  | ||||||
|                         blink_green(); |  | ||||||
|                         return_result = false; |  | ||||||
|                         break; |  | ||||||
|                     } else { |  | ||||||
|                         blink_red(); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     osMessageQueueReset(event_queue); |  | ||||||
|     return return_result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // blink red led
 |  | ||||||
| void SdTest::blink_red() { |  | ||||||
|     notification_message(notification, &sequence_blink_red_100); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // blink green led
 |  | ||||||
| void SdTest::blink_green() { |  | ||||||
|     notification_message(notification, &sequence_blink_green_100); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // set text, but with infinite loop
 |  | ||||||
| template <class T> void SdTest::set_error(std::initializer_list<T> list) { |  | ||||||
|     set_text(list); |  | ||||||
|     blink_red(); |  | ||||||
|     wait_for_button(InputKeyBack); |  | ||||||
|     exit(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // set text, sort of variadic function
 |  | ||||||
| template <class T> void SdTest::set_text(std::initializer_list<T> list) { |  | ||||||
|     uint8_t line_position = 0; |  | ||||||
|     acquire_state(); |  | ||||||
|     printf("------------------------\r\n"); |  | ||||||
| 
 |  | ||||||
|     // set line strings from args
 |  | ||||||
|     for(auto element : list) { |  | ||||||
|         state.line[line_position] = element; |  | ||||||
|         printf("%s\n", element); |  | ||||||
|         line_position++; |  | ||||||
|         if(line_position == state.lines_count) break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // set empty lines
 |  | ||||||
|     for(; line_position < state.lines_count; line_position++) { |  | ||||||
|         state.line[line_position] = ""; |  | ||||||
|         printf("\r\n"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     printf("------------------------\r\n"); |  | ||||||
|     release_state(); |  | ||||||
|     update_gui(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // render app
 |  | ||||||
| void SdTest::render(Canvas* canvas) { |  | ||||||
|     canvas_set_color(canvas, ColorBlack); |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     for(uint8_t i = 0; i < state.lines_count; i++) { |  | ||||||
|         canvas_draw_str(canvas, 0, (i + 1) * 10, state.line[i]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // app enter function
 |  | ||||||
| extern "C" int32_t sd_card_test(void* p) { |  | ||||||
|     SdTest* app = new SdTest(); |  | ||||||
|     app->run(); |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| @ -1,739 +0,0 @@ | |||||||
| #include "fatfs.h" |  | ||||||
| #include "filesystem-api.h" |  | ||||||
| #include "sd-filesystem.h" |  | ||||||
| 
 |  | ||||||
| /******************* Global vars for api *******************/ |  | ||||||
| 
 |  | ||||||
| static SdFsInfo* fs_info; |  | ||||||
| 
 |  | ||||||
| /******************* Core Functions *******************/ |  | ||||||
| 
 |  | ||||||
| bool _fs_init(SdFsInfo* _fs_info) { |  | ||||||
|     bool result = true; |  | ||||||
|     _fs_info->mutex = osMutexNew(NULL); |  | ||||||
|     if(_fs_info->mutex == NULL) result = false; |  | ||||||
| 
 |  | ||||||
|     for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) { |  | ||||||
|         _fs_info->files[i].thread_id = NULL; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _fs_info->path = "0:/"; |  | ||||||
|     _fs_info->status = SD_NO_CARD; |  | ||||||
| 
 |  | ||||||
|     // store pointer for api fns
 |  | ||||||
|     fs_info = _fs_info; |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool _fs_lock(SdFsInfo* fs_info) { |  | ||||||
|     api_hal_power_insomnia_enter(); |  | ||||||
|     return (osMutexAcquire(fs_info->mutex, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool _fs_unlock(SdFsInfo* fs_info) { |  | ||||||
|     api_hal_power_insomnia_exit(); |  | ||||||
|     return (osMutexRelease(fs_info->mutex) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| SDError _get_filedata(SdFsInfo* fs_info, File* file, FileData** filedata, FiledataFilter filter) { |  | ||||||
|     SDError error = SD_OK; |  | ||||||
|     _fs_lock(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fs_info->status == SD_OK) { |  | ||||||
|         if(file != NULL && file->file_id < SD_FS_MAX_FILES) { |  | ||||||
|             if(fs_info->files[file->file_id].thread_id == osThreadGetId()) { |  | ||||||
|                 if(filter == FDF_ANY) { |  | ||||||
|                     // any type
 |  | ||||||
|                     *filedata = &fs_info->files[file->file_id]; |  | ||||||
|                 } else if(filter == FDF_FILE) { |  | ||||||
|                     // file type
 |  | ||||||
|                     if(!fs_info->files[file->file_id].is_dir) { |  | ||||||
|                         *filedata = &fs_info->files[file->file_id]; |  | ||||||
|                     } else { |  | ||||||
|                         error = SD_NOT_A_FILE; |  | ||||||
|                     } |  | ||||||
|                 } else if(filter == FDF_DIR) { |  | ||||||
|                     // dir type
 |  | ||||||
|                     if(fs_info->files[file->file_id].is_dir) { |  | ||||||
|                         *filedata = &fs_info->files[file->file_id]; |  | ||||||
|                     } else { |  | ||||||
|                         error = SD_NOT_A_DIR; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 error = SD_OTHER_APP; |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             error = SD_INVALID_PARAMETER; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         error = SD_NO_CARD; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _fs_unlock(fs_info); |  | ||||||
|     return error; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| SDError _get_file(SdFsInfo* fs_info, File* file, FileData** filedata) { |  | ||||||
|     return _get_filedata(fs_info, file, filedata, FDF_FILE); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| SDError _get_dir(SdFsInfo* fs_info, File* file, FileData** filedata) { |  | ||||||
|     return _get_filedata(fs_info, file, filedata, FDF_DIR); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| SDError _get_any(SdFsInfo* fs_info, File* file, FileData** filedata) { |  | ||||||
|     return _get_filedata(fs_info, file, filedata, FDF_ANY); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| SDError _fs_status(SdFsInfo* fs_info) { |  | ||||||
|     SDError result; |  | ||||||
| 
 |  | ||||||
|     _fs_lock(fs_info); |  | ||||||
|     result = fs_info->status; |  | ||||||
|     _fs_unlock(fs_info); |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void _fs_on_client_app_exit(SdFsInfo* fs_info) { |  | ||||||
|     _fs_lock(fs_info); |  | ||||||
|     for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) { |  | ||||||
|         if(fs_info->files[i].thread_id == osThreadGetId()) { |  | ||||||
|             if(fs_info->files[i].is_dir) { |  | ||||||
|                 // TODO close dir
 |  | ||||||
|             } else { |  | ||||||
|                 // TODO close file
 |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     _fs_unlock(fs_info); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FS_Error _fs_parse_error(SDError error) { |  | ||||||
|     FS_Error result; |  | ||||||
|     switch(error) { |  | ||||||
|     case SD_OK: |  | ||||||
|         result = FSE_OK; |  | ||||||
|         break; |  | ||||||
|     case SD_INT_ERR: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_NO_FILE: |  | ||||||
|         result = FSE_NOT_EXIST; |  | ||||||
|         break; |  | ||||||
|     case SD_NO_PATH: |  | ||||||
|         result = FSE_NOT_EXIST; |  | ||||||
|         break; |  | ||||||
|     case SD_INVALID_NAME: |  | ||||||
|         result = FSE_INVALID_NAME; |  | ||||||
|         break; |  | ||||||
|     case SD_DENIED: |  | ||||||
|         result = FSE_DENIED; |  | ||||||
|         break; |  | ||||||
|     case SD_EXIST: |  | ||||||
|         result = FSE_EXIST; |  | ||||||
|         break; |  | ||||||
|     case SD_INVALID_OBJECT: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_WRITE_PROTECTED: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_INVALID_DRIVE: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_NOT_ENABLED: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_NO_FILESYSTEM: |  | ||||||
|         result = FSE_NOT_READY; |  | ||||||
|         break; |  | ||||||
|     case SD_MKFS_ABORTED: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_TIMEOUT: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_LOCKED: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_NOT_ENOUGH_CORE: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_TOO_MANY_OPEN_FILES: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     case SD_INVALID_PARAMETER: |  | ||||||
|         result = FSE_INVALID_PARAMETER; |  | ||||||
|         break; |  | ||||||
|     case SD_NO_CARD: |  | ||||||
|         result = FSE_NOT_READY; |  | ||||||
|         break; |  | ||||||
|     case SD_NOT_A_FILE: |  | ||||||
|         result = FSE_INVALID_PARAMETER; |  | ||||||
|         break; |  | ||||||
|     case SD_NOT_A_DIR: |  | ||||||
|         result = FSE_INVALID_PARAMETER; |  | ||||||
|         break; |  | ||||||
|     case SD_OTHER_APP: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
| 
 |  | ||||||
|     default: |  | ||||||
|         result = FSE_INTERNAL; |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* File Functions *******************/ |  | ||||||
| 
 |  | ||||||
| // Open/Create a file
 |  | ||||||
| bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode) { |  | ||||||
|     SDFile* sd_file = NULL; |  | ||||||
| 
 |  | ||||||
|     _fs_lock(fs_info); |  | ||||||
|     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { |  | ||||||
|         FileData* filedata = &fs_info->files[index]; |  | ||||||
| 
 |  | ||||||
|         if(filedata->thread_id == NULL) { |  | ||||||
|             file->file_id = index; |  | ||||||
| 
 |  | ||||||
|             memset(&(filedata->data), 0, sizeof(SDFileDirStorage)); |  | ||||||
|             filedata->thread_id = osThreadGetId(); |  | ||||||
|             filedata->is_dir = false; |  | ||||||
|             sd_file = &(filedata->data.file); |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     _fs_unlock(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(sd_file == NULL) { |  | ||||||
|         file->internal_error_id = SD_TOO_MANY_OPEN_FILES; |  | ||||||
|     } else { |  | ||||||
|         uint8_t _mode = 0; |  | ||||||
| 
 |  | ||||||
|         if(access_mode & FSAM_READ) _mode |= FA_READ; |  | ||||||
|         if(access_mode & FSAM_WRITE) _mode |= FA_WRITE; |  | ||||||
|         if(open_mode & FSOM_OPEN_EXISTING) _mode |= FA_OPEN_EXISTING; |  | ||||||
|         if(open_mode & FSOM_OPEN_ALWAYS) _mode |= FA_OPEN_ALWAYS; |  | ||||||
|         if(open_mode & FSOM_OPEN_APPEND) _mode |= FA_OPEN_APPEND; |  | ||||||
|         if(open_mode & FSOM_CREATE_NEW) _mode |= FA_CREATE_NEW; |  | ||||||
|         if(open_mode & FSOM_CREATE_ALWAYS) _mode |= FA_CREATE_ALWAYS; |  | ||||||
| 
 |  | ||||||
|         file->internal_error_id = f_open(sd_file, path, _mode); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // TODO on exit
 |  | ||||||
|     //furiac_onexit(_fs_on_client_app_exit, fs_info);
 |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Close an opened file
 |  | ||||||
| bool fs_file_close(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         file->internal_error_id = f_close(&filedata->data.file); |  | ||||||
| 
 |  | ||||||
|         _fs_lock(fs_info); |  | ||||||
|         filedata->thread_id = NULL; |  | ||||||
|         _fs_unlock(fs_info); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Read data from the file
 |  | ||||||
| uint16_t fs_file_read(File* file, void* buff, uint16_t const bytes_to_read) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     uint16_t bytes_readed = 0; |  | ||||||
| 
 |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         file->internal_error_id = f_read(&filedata->data.file, buff, bytes_to_read, &bytes_readed); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return bytes_readed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Write data to the file
 |  | ||||||
| uint16_t fs_file_write(File* file, const void* buff, uint16_t const bytes_to_write) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     uint16_t bytes_written = 0; |  | ||||||
| 
 |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         file->internal_error_id = |  | ||||||
|             f_write(&filedata->data.file, buff, bytes_to_write, &bytes_written); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return bytes_written; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Move read/write pointer, expand size
 |  | ||||||
| bool fs_file_seek(File* file, const uint32_t offset, const bool from_start) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
| 
 |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         if(from_start) { |  | ||||||
|             file->internal_error_id = f_lseek(&filedata->data.file, offset); |  | ||||||
|         } else { |  | ||||||
|             uint64_t position = f_tell(&filedata->data.file); |  | ||||||
|             position += offset; |  | ||||||
|             file->internal_error_id = f_lseek(&filedata->data.file, position); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Tell pointer position
 |  | ||||||
| uint64_t fs_file_tell(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     uint64_t position = 0; |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         position = f_tell(&filedata->data.file); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return position; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Truncate file size to current pointer value
 |  | ||||||
| bool fs_file_truncate(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
| 
 |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         file->internal_error_id = f_truncate(&filedata->data.file); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Flush cached data
 |  | ||||||
| bool fs_file_sync(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
| 
 |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         file->internal_error_id = f_sync(&filedata->data.file); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get size
 |  | ||||||
| uint64_t fs_file_size(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     uint64_t size = 0; |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         size = f_size(&filedata->data.file); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return size; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Test EOF
 |  | ||||||
| bool fs_file_eof(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     bool eof = true; |  | ||||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         eof = f_eof(&filedata->data.file); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return eof; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Dir Functions *******************/ |  | ||||||
| 
 |  | ||||||
| // Open directory
 |  | ||||||
| bool fs_dir_open(File* file, const char* path) { |  | ||||||
|     SDDir* sd_dir = NULL; |  | ||||||
| 
 |  | ||||||
|     _fs_lock(fs_info); |  | ||||||
|     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { |  | ||||||
|         FileData* filedata = &fs_info->files[index]; |  | ||||||
| 
 |  | ||||||
|         if(filedata->thread_id == NULL) { |  | ||||||
|             file->file_id = index; |  | ||||||
| 
 |  | ||||||
|             memset(&(filedata->data), 0, sizeof(SDFileDirStorage)); |  | ||||||
|             filedata->thread_id = osThreadGetId(); |  | ||||||
|             filedata->is_dir = true; |  | ||||||
|             sd_dir = &(filedata->data.dir); |  | ||||||
| 
 |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     _fs_unlock(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(sd_dir == NULL) { |  | ||||||
|         file->internal_error_id = SD_TOO_MANY_OPEN_FILES; |  | ||||||
|     } else { |  | ||||||
|         file->internal_error_id = f_opendir(sd_dir, path); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // TODO on exit
 |  | ||||||
|     //furiac_onexit(_fs_on_client_app_exit, fs_info);
 |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Close directory
 |  | ||||||
| bool fs_dir_close(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     file->internal_error_id = _get_dir(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         file->internal_error_id = f_closedir(&filedata->data.dir); |  | ||||||
| 
 |  | ||||||
|         _fs_lock(fs_info); |  | ||||||
|         filedata->thread_id = NULL; |  | ||||||
|         _fs_unlock(fs_info); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Read next file info and name from directory
 |  | ||||||
| bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, const uint16_t name_length) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     file->internal_error_id = _get_dir(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         SDFileInfo _fileinfo; |  | ||||||
|         file->internal_error_id = f_readdir(&filedata->data.dir, &_fileinfo); |  | ||||||
| 
 |  | ||||||
|         if(fileinfo != NULL) { |  | ||||||
|             fileinfo->date.value = _fileinfo.fdate; |  | ||||||
|             fileinfo->time.value = _fileinfo.ftime; |  | ||||||
|             fileinfo->size = _fileinfo.fsize; |  | ||||||
|             fileinfo->flags = 0; |  | ||||||
| 
 |  | ||||||
|             if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY; |  | ||||||
|             if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN; |  | ||||||
|             if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM; |  | ||||||
|             if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; |  | ||||||
|             if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(name != NULL && name_length > 0) { |  | ||||||
|             strlcpy(name, _fileinfo.fname, name_length); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool fs_dir_rewind(File* file) { |  | ||||||
|     FileData* filedata = NULL; |  | ||||||
|     file->internal_error_id = _get_dir(fs_info, file, &filedata); |  | ||||||
| 
 |  | ||||||
|     if(file->internal_error_id == SD_OK) { |  | ||||||
|         file->internal_error_id = f_readdir(&filedata->data.dir, NULL); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     file->error_id = _fs_parse_error(file->internal_error_id); |  | ||||||
|     return (file->internal_error_id == SD_OK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Common FS Functions *******************/ |  | ||||||
| 
 |  | ||||||
| // Get info about file/dir
 |  | ||||||
| FS_Error |  | ||||||
|     fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length) { |  | ||||||
|     SDFileInfo _fileinfo; |  | ||||||
|     SDError fresult = _fs_status(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fresult == SD_OK) { |  | ||||||
|         fresult = f_stat(path, &_fileinfo); |  | ||||||
|         if((FRESULT)fresult == FR_OK) { |  | ||||||
|             if(fileinfo != NULL) { |  | ||||||
|                 fileinfo->date.value = _fileinfo.fdate; |  | ||||||
|                 fileinfo->time.value = _fileinfo.ftime; |  | ||||||
|                 fileinfo->size = _fileinfo.fsize; |  | ||||||
|                 fileinfo->flags = 0; |  | ||||||
| 
 |  | ||||||
|                 if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY; |  | ||||||
|                 if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN; |  | ||||||
|                 if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM; |  | ||||||
|                 if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; |  | ||||||
|                 if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if(name != NULL && name_length > 0) { |  | ||||||
|                 strlcpy(name, _fileinfo.fname, name_length); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return _fs_parse_error(fresult); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Delete file/dir
 |  | ||||||
| // File/dir must not have read-only attribute.
 |  | ||||||
| // File/dir must be empty.
 |  | ||||||
| // File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that.
 |  | ||||||
| FS_Error fs_common_remove(const char* path) { |  | ||||||
|     SDError fresult = _fs_status(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fresult == SD_OK) { |  | ||||||
|         fresult = f_unlink(path); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return _fs_parse_error(fresult); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Rename file/dir
 |  | ||||||
| // File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that.
 |  | ||||||
| FS_Error fs_common_rename(const char* old_path, const char* new_path) { |  | ||||||
|     SDError fresult = _fs_status(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fresult == SD_OK) { |  | ||||||
|         fresult = f_rename(old_path, new_path); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return _fs_parse_error(fresult); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Set attributes of file/dir
 |  | ||||||
| // For example:
 |  | ||||||
| // set "read only" flag and remove "hidden" flag
 |  | ||||||
| // fs_common_set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN);
 |  | ||||||
| FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask) { |  | ||||||
|     SDError fresult = _fs_status(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fresult == SD_OK) { |  | ||||||
|         uint8_t _mask = 0; |  | ||||||
|         uint8_t _attr = 0; |  | ||||||
| 
 |  | ||||||
|         if(mask & FSF_READ_ONLY) _mask |= AM_RDO; |  | ||||||
|         if(mask & FSF_HIDDEN) _mask |= AM_HID; |  | ||||||
|         if(mask & FSF_SYSTEM) _mask |= AM_SYS; |  | ||||||
|         if(mask & FSF_DIRECTORY) _mask |= AM_DIR; |  | ||||||
|         if(mask & FSF_ARCHIVE) _mask |= AM_ARC; |  | ||||||
| 
 |  | ||||||
|         if(attr & FSF_READ_ONLY) _attr |= AM_RDO; |  | ||||||
|         if(attr & FSF_HIDDEN) _attr |= AM_HID; |  | ||||||
|         if(attr & FSF_SYSTEM) _attr |= AM_SYS; |  | ||||||
|         if(attr & FSF_DIRECTORY) _attr |= AM_DIR; |  | ||||||
|         if(attr & FSF_ARCHIVE) _attr |= AM_ARC; |  | ||||||
| 
 |  | ||||||
|         fresult = f_chmod(path, attr, mask); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return _fs_parse_error(fresult); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Set time of file/dir
 |  | ||||||
| FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time) { |  | ||||||
|     SDError fresult = _fs_status(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fresult == SD_OK) { |  | ||||||
|         SDFileInfo _fileinfo; |  | ||||||
| 
 |  | ||||||
|         _fileinfo.fdate = date.value; |  | ||||||
|         _fileinfo.ftime = time.value; |  | ||||||
| 
 |  | ||||||
|         fresult = f_utime(path, &_fileinfo); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return _fs_parse_error(fresult); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Create new directory
 |  | ||||||
| FS_Error fs_common_mkdir(const char* path) { |  | ||||||
|     SDError fresult = _fs_status(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fresult == SD_OK) { |  | ||||||
|         fresult = f_mkdir(path); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return _fs_parse_error(fresult); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get common info about FS
 |  | ||||||
| FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space) { |  | ||||||
|     SDError fresult = _fs_status(fs_info); |  | ||||||
| 
 |  | ||||||
|     if(fresult == SD_OK) { |  | ||||||
|         DWORD free_clusters; |  | ||||||
|         FATFS* fs; |  | ||||||
| 
 |  | ||||||
|         fresult = f_getfree("0:/", &free_clusters, &fs); |  | ||||||
|         if((FRESULT)fresult == FR_OK) { |  | ||||||
|             uint32_t total_sectors = (fs->n_fatent - 2) * fs->csize; |  | ||||||
|             uint32_t free_sectors = free_clusters * fs->csize; |  | ||||||
| 
 |  | ||||||
|             uint16_t sector_size = _MAX_SS; |  | ||||||
| #if _MAX_SS != _MIN_SS |  | ||||||
|             sector_size = fs->ssize; |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|             if(total_space != NULL) { |  | ||||||
|                 *total_space = (uint64_t)total_sectors * (uint64_t)sector_size; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if(free_space != NULL) { |  | ||||||
|                 *free_space = (uint64_t)free_sectors * (uint64_t)sector_size; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return _fs_parse_error(fresult); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Error Reporting Functions *******************/ |  | ||||||
| 
 |  | ||||||
| // Get common error description
 |  | ||||||
| const char* fs_error_get_desc(FS_Error error_id) { |  | ||||||
|     const char* result; |  | ||||||
|     switch(error_id) { |  | ||||||
|     case(FSE_OK): |  | ||||||
|         result = "OK"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_NOT_READY): |  | ||||||
|         result = "filesystem not ready"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_EXIST): |  | ||||||
|         result = "file/dir already exist"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_NOT_EXIST): |  | ||||||
|         result = "file/dir not exist"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_INVALID_PARAMETER): |  | ||||||
|         result = "invalid parameter"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_DENIED): |  | ||||||
|         result = "access denied"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_INVALID_NAME): |  | ||||||
|         result = "invalid name/path"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_INTERNAL): |  | ||||||
|         result = "internal error"; |  | ||||||
|         break; |  | ||||||
|     case(FSE_NOT_IMPLEMENTED): |  | ||||||
|         result = "function not implemented"; |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         result = "unknown error"; |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Get internal error description
 |  | ||||||
| const char* fs_error_get_internal_desc(uint32_t internal_error_id) { |  | ||||||
|     const char* result; |  | ||||||
|     switch(internal_error_id) { |  | ||||||
|     case(SD_OK): |  | ||||||
|         result = "OK"; |  | ||||||
|         break; |  | ||||||
|     case(SD_DISK_ERR): |  | ||||||
|         result = "disk error"; |  | ||||||
|         break; |  | ||||||
|     case(SD_INT_ERR): |  | ||||||
|         result = "internal error"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NO_FILE): |  | ||||||
|         result = "no file"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NO_PATH): |  | ||||||
|         result = "no path"; |  | ||||||
|         break; |  | ||||||
|     case(SD_INVALID_NAME): |  | ||||||
|         result = "invalid name"; |  | ||||||
|         break; |  | ||||||
|     case(SD_DENIED): |  | ||||||
|         result = "access denied"; |  | ||||||
|         break; |  | ||||||
|     case(SD_EXIST): |  | ||||||
|         result = "file/dir exist"; |  | ||||||
|         break; |  | ||||||
|     case(SD_INVALID_OBJECT): |  | ||||||
|         result = "invalid object"; |  | ||||||
|         break; |  | ||||||
|     case(SD_WRITE_PROTECTED): |  | ||||||
|         result = "write protected"; |  | ||||||
|         break; |  | ||||||
|     case(SD_INVALID_DRIVE): |  | ||||||
|         result = "invalid drive"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NOT_ENABLED): |  | ||||||
|         result = "not enabled"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NO_FILESYSTEM): |  | ||||||
|         result = "no filesystem"; |  | ||||||
|         break; |  | ||||||
|     case(SD_MKFS_ABORTED): |  | ||||||
|         result = "aborted"; |  | ||||||
|         break; |  | ||||||
|     case(SD_TIMEOUT): |  | ||||||
|         result = "timeout"; |  | ||||||
|         break; |  | ||||||
|     case(SD_LOCKED): |  | ||||||
|         result = "file locked"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NOT_ENOUGH_CORE): |  | ||||||
|         result = "not enough memory"; |  | ||||||
|         break; |  | ||||||
|     case(SD_TOO_MANY_OPEN_FILES): |  | ||||||
|         result = "too many open files"; |  | ||||||
|         break; |  | ||||||
|     case(SD_INVALID_PARAMETER): |  | ||||||
|         result = "invalid parameter"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NO_CARD): |  | ||||||
|         result = "no SD Card"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NOT_A_FILE): |  | ||||||
|         result = "not a file"; |  | ||||||
|         break; |  | ||||||
|     case(SD_NOT_A_DIR): |  | ||||||
|         result = "not a directory"; |  | ||||||
|         break; |  | ||||||
|     case(SD_OTHER_APP): |  | ||||||
|         result = "opened by other app"; |  | ||||||
|         break; |  | ||||||
|     case(SD_LOW_LEVEL_ERR): |  | ||||||
|         result = "low level error"; |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         result = "unknown error"; |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| @ -1,956 +0,0 @@ | |||||||
| #include "fatfs.h" |  | ||||||
| #include "filesystem-api.h" |  | ||||||
| #include "sd-filesystem.h" |  | ||||||
| #include "menu/menu.h" |  | ||||||
| #include "menu/menu_item.h" |  | ||||||
| #include "cli/cli.h" |  | ||||||
| #include "api-hal-sd.h" |  | ||||||
| 
 |  | ||||||
| #include <gui/modules/dialog_ex.h> |  | ||||||
| #include <gui/modules/file_select.h> |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     FST_FAT12 = FS_FAT12, |  | ||||||
|     FST_FAT16 = FS_FAT16, |  | ||||||
|     FST_FAT32 = FS_FAT32, |  | ||||||
|     FST_EXFAT = FS_EXFAT, |  | ||||||
| } SDFsType; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     SDFsType fs_type; |  | ||||||
|     uint32_t kb_total; |  | ||||||
|     uint32_t kb_free; |  | ||||||
|     uint16_t cluster_size; |  | ||||||
|     uint16_t sector_size; |  | ||||||
|     char label[34]; |  | ||||||
|     SDError error; |  | ||||||
| } SDInfo; |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     SdAppEventTypeBack, |  | ||||||
|     SdAppEventTypeOK, |  | ||||||
|     SdAppEventTypeFormat, |  | ||||||
|     SdAppEventTypeInfo, |  | ||||||
|     SdAppEventTypeEject, |  | ||||||
|     SdAppEventTypeFileSelect, |  | ||||||
|     SdAppEventTypeCheckError, |  | ||||||
|     SdAppEventTypeShowError, |  | ||||||
| } SdAppEventType; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     const char* path; |  | ||||||
|     const char* extension; |  | ||||||
|     char* result; |  | ||||||
|     uint8_t result_size; |  | ||||||
|     const char* selected_filename; |  | ||||||
| } SdAppFileSelectData; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     bool result; |  | ||||||
| } SdAppFileSelectResultEvent; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     SdAppEventType type; |  | ||||||
|     union { |  | ||||||
|         SdAppFileSelectData file_select_data; |  | ||||||
|         const char* error_text; |  | ||||||
|     } payload; |  | ||||||
| } SdAppEvent; |  | ||||||
| 
 |  | ||||||
| static void sd_icon_draw_callback(Canvas* canvas, void* context); |  | ||||||
| bool sd_api_file_select( |  | ||||||
|     SdApp* sd_app, |  | ||||||
|     const char* path, |  | ||||||
|     const char* extension, |  | ||||||
|     char* result, |  | ||||||
|     uint8_t result_size, |  | ||||||
|     const char* selected_filename); |  | ||||||
| void sd_api_check_error(SdApp* sd_app); |  | ||||||
| void sd_api_show_error(SdApp* sd_app, const char* error_text); |  | ||||||
| 
 |  | ||||||
| /******************* Allocators *******************/ |  | ||||||
| 
 |  | ||||||
| FS_Api* fs_api_alloc() { |  | ||||||
|     FS_Api* fs_api = furi_alloc(sizeof(FS_Api)); |  | ||||||
| 
 |  | ||||||
|     // fill file api
 |  | ||||||
|     fs_api->file.open = fs_file_open; |  | ||||||
|     fs_api->file.close = fs_file_close; |  | ||||||
|     fs_api->file.read = fs_file_read; |  | ||||||
|     fs_api->file.write = fs_file_write; |  | ||||||
|     fs_api->file.seek = fs_file_seek; |  | ||||||
|     fs_api->file.tell = fs_file_tell; |  | ||||||
|     fs_api->file.truncate = fs_file_truncate; |  | ||||||
|     fs_api->file.size = fs_file_size; |  | ||||||
|     fs_api->file.sync = fs_file_sync; |  | ||||||
|     fs_api->file.eof = fs_file_eof; |  | ||||||
| 
 |  | ||||||
|     // fill dir api
 |  | ||||||
|     fs_api->dir.open = fs_dir_open; |  | ||||||
|     fs_api->dir.close = fs_dir_close; |  | ||||||
|     fs_api->dir.read = fs_dir_read; |  | ||||||
|     fs_api->dir.rewind = fs_dir_rewind; |  | ||||||
| 
 |  | ||||||
|     // fill common api
 |  | ||||||
|     fs_api->common.info = fs_common_info; |  | ||||||
|     fs_api->common.remove = fs_common_remove; |  | ||||||
|     fs_api->common.rename = fs_common_rename; |  | ||||||
|     fs_api->common.set_attr = fs_common_set_attr; |  | ||||||
|     fs_api->common.mkdir = fs_common_mkdir; |  | ||||||
|     fs_api->common.set_time = fs_common_set_time; |  | ||||||
|     fs_api->common.get_fs_info = fs_get_fs_info; |  | ||||||
| 
 |  | ||||||
|     // fill errors api
 |  | ||||||
|     fs_api->error.get_desc = fs_error_get_desc; |  | ||||||
|     fs_api->error.get_internal_desc = fs_error_get_internal_desc; |  | ||||||
| 
 |  | ||||||
|     return fs_api; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| SdApp* sd_app_alloc() { |  | ||||||
|     SdApp* sd_app = furi_alloc(sizeof(SdApp)); |  | ||||||
| 
 |  | ||||||
|     // init inner fs data
 |  | ||||||
|     furi_check(_fs_init(&sd_app->info)); |  | ||||||
| 
 |  | ||||||
|     sd_app->event_queue = osMessageQueueNew(8, sizeof(SdAppEvent), NULL); |  | ||||||
|     sd_app->result_receiver = osMessageQueueNew(1, sizeof(SdAppFileSelectResultEvent), NULL); |  | ||||||
| 
 |  | ||||||
|     // init icon view_port
 |  | ||||||
|     sd_app->icon.view_port = view_port_alloc(); |  | ||||||
|     sd_app->icon.mounted = &I_SDcardMounted_11x8; |  | ||||||
|     sd_app->icon.fail = &I_SDcardFail_11x8; |  | ||||||
|     view_port_set_width(sd_app->icon.view_port, icon_get_width(sd_app->icon.mounted)); |  | ||||||
|     view_port_draw_callback_set(sd_app->icon.view_port, sd_icon_draw_callback, sd_app); |  | ||||||
|     view_port_enabled_set(sd_app->icon.view_port, false); |  | ||||||
| 
 |  | ||||||
|     // init sd card api
 |  | ||||||
|     sd_app->sd_card_api.context = sd_app; |  | ||||||
|     sd_app->sd_card_api.file_select = sd_api_file_select; |  | ||||||
|     sd_app->sd_card_api.check_error = sd_api_check_error; |  | ||||||
|     sd_app->sd_card_api.show_error = sd_api_show_error; |  | ||||||
| 
 |  | ||||||
|     sd_app->sd_app_state = SdAppStateBackground; |  | ||||||
|     string_init(sd_app->text_holder); |  | ||||||
| 
 |  | ||||||
|     return sd_app; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Internal sd card related fns *******************/ |  | ||||||
| 
 |  | ||||||
| void get_sd_info(SdApp* sd_app, SDInfo* sd_info) { |  | ||||||
|     uint32_t free_clusters, free_sectors, total_sectors; |  | ||||||
|     FATFS* fs; |  | ||||||
| 
 |  | ||||||
|     // clean data
 |  | ||||||
|     memset(sd_info, 0, sizeof(SDInfo)); |  | ||||||
| 
 |  | ||||||
|     // get fs info
 |  | ||||||
|     _fs_lock(&sd_app->info); |  | ||||||
|     sd_info->error = f_getlabel(sd_app->info.path, sd_info->label, NULL); |  | ||||||
|     if(sd_info->error == SD_OK) { |  | ||||||
|         sd_info->error = f_getfree(sd_app->info.path, &free_clusters, &fs); |  | ||||||
|     } |  | ||||||
|     _fs_unlock(&sd_app->info); |  | ||||||
| 
 |  | ||||||
|     if(sd_info->error == SD_OK) { |  | ||||||
|         // calculate size
 |  | ||||||
|         total_sectors = (fs->n_fatent - 2) * fs->csize; |  | ||||||
|         free_sectors = free_clusters * fs->csize; |  | ||||||
| 
 |  | ||||||
|         uint16_t sector_size = _MAX_SS; |  | ||||||
| #if _MAX_SS != _MIN_SS |  | ||||||
|         sector_size = fs->ssize; |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|         sd_info->fs_type = fs->fs_type; |  | ||||||
| 
 |  | ||||||
|         sd_info->kb_total = total_sectors / 1024 * sector_size; |  | ||||||
|         sd_info->kb_free = free_sectors / 1024 * sector_size; |  | ||||||
|         sd_info->cluster_size = fs->csize; |  | ||||||
|         sd_info->sector_size = sector_size; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const char* get_fs_type_text(SDFsType fs_type) { |  | ||||||
|     switch(fs_type) { |  | ||||||
|     case(FST_FAT12): |  | ||||||
|         return "FAT12"; |  | ||||||
|         break; |  | ||||||
|     case(FST_FAT16): |  | ||||||
|         return "FAT16"; |  | ||||||
|         break; |  | ||||||
|     case(FST_FAT32): |  | ||||||
|         return "FAT32"; |  | ||||||
|         break; |  | ||||||
|     case(FST_EXFAT): |  | ||||||
|         return "EXFAT"; |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         return "UNKNOWN"; |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_format_internal(SdApp* sd_app) { |  | ||||||
|     uint8_t* work_area; |  | ||||||
| 
 |  | ||||||
|     _fs_lock(&sd_app->info); |  | ||||||
|     work_area = malloc(_MAX_SS); |  | ||||||
|     if(work_area == NULL) { |  | ||||||
|         sd_app->info.status = SD_NOT_ENOUGH_CORE; |  | ||||||
|     } else { |  | ||||||
|         sd_app->info.status = f_mkfs(sd_app->info.path, FM_ANY, 0, work_area, _MAX_SS); |  | ||||||
|         free(work_area); |  | ||||||
| 
 |  | ||||||
|         if(sd_app->info.status == SD_OK) { |  | ||||||
|             // set label and mount card
 |  | ||||||
|             f_setlabel("Flipper SD"); |  | ||||||
|             sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _fs_unlock(&sd_app->info); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| const NotificationSequence sd_sequence_success = { |  | ||||||
|     &message_green_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_green_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_green_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_green_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_green_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_green_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     NULL, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const NotificationSequence sd_sequence_error = { |  | ||||||
|     &message_red_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_red_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_red_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_red_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_red_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_red_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     NULL, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const NotificationSequence sd_sequence_eject = { |  | ||||||
|     &message_blue_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_blue_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_blue_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_blue_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_blue_255, |  | ||||||
|     &message_delay_50, |  | ||||||
|     &message_blue_0, |  | ||||||
|     &message_delay_50, |  | ||||||
|     NULL, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const NotificationSequence sd_sequence_wait = { |  | ||||||
|     &message_red_255, |  | ||||||
|     &message_blue_255, |  | ||||||
|     &message_do_not_reset, |  | ||||||
|     NULL, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const NotificationSequence sd_sequence_wait_off = { |  | ||||||
|     &message_red_0, |  | ||||||
|     &message_blue_0, |  | ||||||
|     NULL, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void app_sd_notify_wait(SdApp* sd_app) { |  | ||||||
|     notification_message(sd_app->notifications, &sd_sequence_wait); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_notify_wait_off(SdApp* sd_app) { |  | ||||||
|     notification_message(sd_app->notifications, &sd_sequence_wait_off); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_notify_success(SdApp* sd_app) { |  | ||||||
|     notification_message(sd_app->notifications, &sd_sequence_success); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_notify_eject(SdApp* sd_app) { |  | ||||||
|     notification_message(sd_app->notifications, &sd_sequence_eject); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_notify_error(SdApp* sd_app) { |  | ||||||
|     notification_message(sd_app->notifications, &sd_sequence_error); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool app_sd_mount_card(SdApp* sd_app) { |  | ||||||
|     bool result = false; |  | ||||||
|     const uint8_t max_init_counts = 10; |  | ||||||
|     uint8_t counter = max_init_counts; |  | ||||||
|     uint8_t bsp_result; |  | ||||||
| 
 |  | ||||||
|     _fs_lock(&sd_app->info); |  | ||||||
| 
 |  | ||||||
|     while(result == false && counter > 0 && hal_sd_detect()) { |  | ||||||
|         app_sd_notify_wait(sd_app); |  | ||||||
| 
 |  | ||||||
|         if((counter % 10) == 0) { |  | ||||||
|             // power reset sd card
 |  | ||||||
|             bsp_result = BSP_SD_Init(true); |  | ||||||
|         } else { |  | ||||||
|             bsp_result = BSP_SD_Init(false); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(bsp_result) { |  | ||||||
|             // bsp error
 |  | ||||||
|             sd_app->info.status = SD_LOW_LEVEL_ERR; |  | ||||||
|         } else { |  | ||||||
|             sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1); |  | ||||||
| 
 |  | ||||||
|             if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) { |  | ||||||
|                 FATFS* fs; |  | ||||||
|                 uint32_t free_clusters; |  | ||||||
| 
 |  | ||||||
|                 sd_app->info.status = f_getfree(sd_app->info.path, &free_clusters, &fs); |  | ||||||
| 
 |  | ||||||
|                 if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) { |  | ||||||
|                     result = true; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         app_sd_notify_wait_off(sd_app); |  | ||||||
| 
 |  | ||||||
|         if(!result) { |  | ||||||
|             delay(1000); |  | ||||||
|             FURI_LOG_E( |  | ||||||
|                 "SD FILESYSTEM", |  | ||||||
|                 "init(%d), error: %s\r\n", |  | ||||||
|                 counter, |  | ||||||
|                 fs_error_get_internal_desc(sd_app->info.status)); |  | ||||||
| 
 |  | ||||||
|             counter--; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _fs_unlock(&sd_app->info); |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_unmount_card(SdApp* sd_app) { |  | ||||||
|     _fs_lock(&sd_app->info); |  | ||||||
| 
 |  | ||||||
|     // set status
 |  | ||||||
|     sd_app->info.status = SD_NO_CARD; |  | ||||||
|     view_port_enabled_set(sd_app->icon.view_port, false); |  | ||||||
| 
 |  | ||||||
|     // close files
 |  | ||||||
|     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { |  | ||||||
|         FileData* filedata = &sd_app->info.files[index]; |  | ||||||
| 
 |  | ||||||
|         if(filedata->thread_id != NULL) { |  | ||||||
|             if(filedata->is_dir) { |  | ||||||
|                 f_closedir(&filedata->data.dir); |  | ||||||
|             } else { |  | ||||||
|                 f_close(&filedata->data.file); |  | ||||||
|             } |  | ||||||
|             filedata->thread_id = NULL; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // unmount volume
 |  | ||||||
|     f_mount(0, sd_app->info.path, 0); |  | ||||||
| 
 |  | ||||||
|     _fs_unlock(&sd_app->info); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool app_sd_make_path(const char* path) { |  | ||||||
|     furi_assert(path); |  | ||||||
| 
 |  | ||||||
|     if(*path) { |  | ||||||
|         char* file_path = strdup(path); |  | ||||||
|         // Make parent directories
 |  | ||||||
|         for(char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { |  | ||||||
|             *p = '\0'; |  | ||||||
|             SDError result = f_mkdir(file_path); |  | ||||||
| 
 |  | ||||||
|             if(result != SD_OK) { |  | ||||||
|                 if(result != SD_EXIST) { |  | ||||||
|                     *p = '/'; |  | ||||||
|                     free(file_path); |  | ||||||
|                     return false; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             *p = '/'; |  | ||||||
|         } |  | ||||||
|         // Make origin directory
 |  | ||||||
|         SDError result = f_mkdir(file_path); |  | ||||||
|         if(result != SD_OK) { |  | ||||||
|             if(result != SD_EXIST) { |  | ||||||
|                 free(file_path); |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         free(file_path); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Draw callbacks *******************/ |  | ||||||
| 
 |  | ||||||
| static void sd_icon_draw_callback(Canvas* canvas, void* context) { |  | ||||||
|     furi_assert(canvas); |  | ||||||
|     furi_assert(context); |  | ||||||
|     SdApp* sd_app = context; |  | ||||||
| 
 |  | ||||||
|     switch(sd_app->info.status) { |  | ||||||
|     case SD_NO_CARD: |  | ||||||
|         break; |  | ||||||
|     case SD_OK: |  | ||||||
|         canvas_draw_icon(canvas, 0, 0, sd_app->icon.mounted); |  | ||||||
|         break; |  | ||||||
|     default: |  | ||||||
|         canvas_draw_icon(canvas, 0, 0, sd_app->icon.fail); |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* SD-api callbacks *******************/ |  | ||||||
| 
 |  | ||||||
| bool sd_api_file_select( |  | ||||||
|     SdApp* sd_app, |  | ||||||
|     const char* path, |  | ||||||
|     const char* extension, |  | ||||||
|     char* result, |  | ||||||
|     uint8_t result_size, |  | ||||||
|     const char* selected_filename) { |  | ||||||
|     bool retval = false; |  | ||||||
| 
 |  | ||||||
|     SdAppEvent message = { |  | ||||||
|         .type = SdAppEventTypeFileSelect, |  | ||||||
|         .payload = { |  | ||||||
|             .file_select_data = { |  | ||||||
|                 .path = path, |  | ||||||
|                 .extension = extension, |  | ||||||
|                 .result = result, |  | ||||||
|                 .result_size = result_size, |  | ||||||
|                 .selected_filename = selected_filename, |  | ||||||
|             }}}; |  | ||||||
| 
 |  | ||||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
| 
 |  | ||||||
|     SdAppFileSelectResultEvent event; |  | ||||||
|     while(1) { |  | ||||||
|         osStatus_t event_status = |  | ||||||
|             osMessageQueueGet(sd_app->result_receiver, &event, NULL, osWaitForever); |  | ||||||
|         if(event_status == osOK) { |  | ||||||
|             retval = event.result; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(!retval) { |  | ||||||
|         sd_api_check_error(sd_app); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return retval; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void sd_api_check_error(SdApp* sd_app) { |  | ||||||
|     SdAppEvent message = {.type = SdAppEventTypeCheckError}; |  | ||||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void sd_api_show_error(SdApp* sd_app, const char* error_text) { |  | ||||||
|     SdAppEvent message = {.type = SdAppEventTypeShowError, .payload.error_text = error_text}; |  | ||||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* View callbacks *******************/ |  | ||||||
| 
 |  | ||||||
| void app_view_back_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     SdApp* sd_app = context; |  | ||||||
|     SdAppEvent message = {.type = SdAppEventTypeBack}; |  | ||||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_view_dialog_callback(DialogExResult result, void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     SdApp* sd_app = context; |  | ||||||
| 
 |  | ||||||
|     if(result == DialogExResultLeft) { |  | ||||||
|         SdAppEvent message = {.type = SdAppEventTypeBack}; |  | ||||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
|     } else if(result == DialogExResultRight) { |  | ||||||
|         SdAppEvent message = {.type = SdAppEventTypeOK}; |  | ||||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_view_file_select_callback(bool result, void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     SdApp* sd_app = context; |  | ||||||
| 
 |  | ||||||
|     if(result) { |  | ||||||
|         SdAppEvent message = {.type = SdAppEventTypeOK}; |  | ||||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
|     } else { |  | ||||||
|         SdAppEvent message = {.type = SdAppEventTypeBack}; |  | ||||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Menu callbacks *******************/ |  | ||||||
| 
 |  | ||||||
| void app_sd_info_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     SdApp* sd_app = context; |  | ||||||
|     SdAppEvent message = {.type = SdAppEventTypeInfo}; |  | ||||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_format_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     SdApp* sd_app = context; |  | ||||||
|     SdAppEvent message = {.type = SdAppEventTypeFormat}; |  | ||||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_sd_eject_callback(void* context) { |  | ||||||
|     furi_assert(context); |  | ||||||
|     SdApp* sd_app = context; |  | ||||||
|     SdAppEvent message = {.type = SdAppEventTypeEject}; |  | ||||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Cli callbacks *******************/ |  | ||||||
| 
 |  | ||||||
| static void cli_sd_format(Cli* cli, string_t args, void* _ctx) { |  | ||||||
|     SdApp* sd_app = (SdApp*)_ctx; |  | ||||||
| 
 |  | ||||||
|     printf("Formatting SD card, all data will be lost. Are you sure (y/n)?\r\n"); |  | ||||||
|     char c = cli_getc(cli); |  | ||||||
|     if(c == 'y' || c == 'Y') { |  | ||||||
|         printf("Formatting, please wait...\r\n"); |  | ||||||
|         // format card
 |  | ||||||
|         app_sd_format_internal(sd_app); |  | ||||||
| 
 |  | ||||||
|         if(sd_app->info.status != SD_OK) { |  | ||||||
|             printf("SD card format error: "); |  | ||||||
|             printf(fs_error_get_internal_desc(sd_app->info.status)); |  | ||||||
|             printf("\r\n"); |  | ||||||
|         } else { |  | ||||||
|             printf("SD card was successfully formatted.\r\n"); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         printf("Cancelled.\r\n"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static void cli_sd_info(Cli* cli, string_t args, void* _ctx) { |  | ||||||
|     SdApp* sd_app = (SdApp*)_ctx; |  | ||||||
|     SDInfo sd_info; |  | ||||||
| 
 |  | ||||||
|     get_sd_info(sd_app, &sd_info); |  | ||||||
|     printf("SD Status: %s\r\n", fs_error_get_internal_desc(sd_app->info.status)); |  | ||||||
| 
 |  | ||||||
|     if(sd_info.error == SD_OK) { |  | ||||||
|         const char* fs_type = get_fs_type_text(sd_info.fs_type); |  | ||||||
|         printf("Label: %s\r\n", sd_info.label); |  | ||||||
|         printf("Filesystem: %s\r\n", fs_type); |  | ||||||
|         printf("Cluster: %d sectors\r\n", sd_info.cluster_size); |  | ||||||
|         printf("Sector: %d bytes\r\n", sd_info.sector_size); |  | ||||||
|         printf("%lu KB total\r\n", sd_info.kb_total); |  | ||||||
|         printf("%lu KB free\r\n", sd_info.kb_free); |  | ||||||
|     } else { |  | ||||||
|         printf("SD Info error: %s\r\n", fs_error_get_internal_desc(sd_info.error)); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Test *******************/ |  | ||||||
| 
 |  | ||||||
| bool try_to_alloc_view_holder(SdApp* sd_app, Gui* gui) { |  | ||||||
|     bool result = false; |  | ||||||
| 
 |  | ||||||
|     _fs_lock(&sd_app->info); |  | ||||||
| 
 |  | ||||||
|     if(sd_app->view_holder == NULL) { |  | ||||||
|         sd_app->view_holder = view_holder_alloc(); |  | ||||||
|         view_holder_attach_to_gui(sd_app->view_holder, gui); |  | ||||||
|         view_holder_set_back_callback(sd_app->view_holder, app_view_back_callback, sd_app); |  | ||||||
|         result = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _fs_unlock(&sd_app->info); |  | ||||||
| 
 |  | ||||||
|     return result; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| DialogEx* alloc_and_attach_dialog(SdApp* sd_app) { |  | ||||||
|     DialogEx* dialog = dialog_ex_alloc(); |  | ||||||
|     dialog_ex_set_context(dialog, sd_app); |  | ||||||
|     dialog_ex_set_result_callback(dialog, app_view_dialog_callback); |  | ||||||
|     view_holder_set_view(sd_app->view_holder, dialog_ex_get_view(dialog)); |  | ||||||
|     view_holder_set_free_callback(sd_app->view_holder, (FreeCallback)dialog_ex_free, dialog); |  | ||||||
|     return dialog; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FileSelect* alloc_and_attach_file_select(SdApp* sd_app) { |  | ||||||
|     FileSelect* file_select = file_select_alloc(); |  | ||||||
|     file_select_set_callback(file_select, app_view_file_select_callback, sd_app); |  | ||||||
|     view_holder_set_view(sd_app->view_holder, file_select_get_view(file_select)); |  | ||||||
|     view_holder_set_free_callback( |  | ||||||
|         sd_app->view_holder, (FreeCallback)file_select_free, file_select); |  | ||||||
|     return file_select; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void free_view_holder(SdApp* sd_app) { |  | ||||||
|     _fs_lock(&sd_app->info); |  | ||||||
| 
 |  | ||||||
|     if(sd_app->view_holder) { |  | ||||||
|         view_holder_free(sd_app->view_holder); |  | ||||||
|         sd_app->view_holder = NULL; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     _fs_unlock(&sd_app->info); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void app_reset_state(SdApp* sd_app) { |  | ||||||
|     _fs_lock(&sd_app->info); |  | ||||||
|     if(sd_app->view_holder) { |  | ||||||
|         view_holder_stop(sd_app->view_holder); |  | ||||||
|     } |  | ||||||
|     _fs_unlock(&sd_app->info); |  | ||||||
| 
 |  | ||||||
|     free_view_holder(sd_app); |  | ||||||
|     string_set_str(sd_app->text_holder, ""); |  | ||||||
|     sd_app->sd_app_state = SdAppStateBackground; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /******************* Main app *******************/ |  | ||||||
| 
 |  | ||||||
| int32_t sd_filesystem(void* p) { |  | ||||||
|     SdApp* sd_app = sd_app_alloc(); |  | ||||||
|     FS_Api* fs_api = fs_api_alloc(); |  | ||||||
| 
 |  | ||||||
|     Gui* gui = furi_record_open("gui"); |  | ||||||
|     Cli* cli = furi_record_open("cli"); |  | ||||||
|     sd_app->notifications = furi_record_open("notification"); |  | ||||||
|     ValueMutex* menu_vm = furi_record_open("menu"); |  | ||||||
| 
 |  | ||||||
|     gui_add_view_port(gui, sd_app->icon.view_port, GuiLayerStatusBarLeft); |  | ||||||
| 
 |  | ||||||
|     cli_add_command(cli, "sd_format", CliCommandFlagDefault, cli_sd_format, sd_app); |  | ||||||
|     cli_add_command(cli, "sd_info", CliCommandFlagDefault, cli_sd_info, sd_app); |  | ||||||
| 
 |  | ||||||
|     // add api record
 |  | ||||||
|     furi_record_create("sdcard", fs_api); |  | ||||||
| 
 |  | ||||||
|     // init menu
 |  | ||||||
|     // TODO menu icon
 |  | ||||||
|     MenuItem* menu_item; |  | ||||||
|     menu_item = menu_item_alloc_menu("SD Card", icon_animation_alloc(&I_SDcardMounted_11x8)); |  | ||||||
| 
 |  | ||||||
|     menu_item_subitem_add( |  | ||||||
|         menu_item, menu_item_alloc_function("Info", NULL, app_sd_info_callback, sd_app)); |  | ||||||
|     menu_item_subitem_add( |  | ||||||
|         menu_item, menu_item_alloc_function("Format", NULL, app_sd_format_callback, sd_app)); |  | ||||||
|     menu_item_subitem_add( |  | ||||||
|         menu_item, menu_item_alloc_function("Eject", NULL, app_sd_eject_callback, sd_app)); |  | ||||||
| 
 |  | ||||||
|     // add item to menu
 |  | ||||||
|     furi_check(menu_vm); |  | ||||||
|     with_value_mutex( |  | ||||||
|         menu_vm, (Menu * menu) { menu_item_add(menu, menu_item); }); |  | ||||||
| 
 |  | ||||||
|     FURI_LOG_I("SD FILESYSTEM", "start"); |  | ||||||
| 
 |  | ||||||
|     // add api record
 |  | ||||||
|     furi_record_create("sdcard", fs_api); |  | ||||||
|     furi_record_create("sdcard-ex", &sd_app->sd_card_api); |  | ||||||
| 
 |  | ||||||
|     // sd card cycle
 |  | ||||||
|     bool sd_was_present = true; |  | ||||||
| 
 |  | ||||||
|     // init detect pins
 |  | ||||||
|     hal_sd_detect_init(); |  | ||||||
| 
 |  | ||||||
|     while(true) { |  | ||||||
|         if(sd_was_present) { |  | ||||||
|             if(hal_sd_detect()) { |  | ||||||
|                 FURI_LOG_I("SD FILESYSTEM", "Card detected"); |  | ||||||
|                 app_sd_mount_card(sd_app); |  | ||||||
| 
 |  | ||||||
|                 if(sd_app->info.status != SD_OK) { |  | ||||||
|                     FURI_LOG_E( |  | ||||||
|                         "SD FILESYSTEM", |  | ||||||
|                         "sd init error: %s", |  | ||||||
|                         fs_error_get_internal_desc(sd_app->info.status)); |  | ||||||
|                     app_sd_notify_error(sd_app); |  | ||||||
|                 } else { |  | ||||||
|                     FURI_LOG_I("SD FILESYSTEM", "sd init ok"); |  | ||||||
|                     app_sd_notify_success(sd_app); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 view_port_enabled_set(sd_app->icon.view_port, true); |  | ||||||
|                 sd_was_present = false; |  | ||||||
| 
 |  | ||||||
|                 if(!hal_sd_detect()) { |  | ||||||
|                     FURI_LOG_I("SD FILESYSTEM", "card removed"); |  | ||||||
| 
 |  | ||||||
|                     view_port_enabled_set(sd_app->icon.view_port, false); |  | ||||||
|                     app_sd_unmount_card(sd_app); |  | ||||||
|                     sd_was_present = true; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             if(!hal_sd_detect()) { |  | ||||||
|                 FURI_LOG_I("SD FILESYSTEM", "card removed"); |  | ||||||
| 
 |  | ||||||
|                 view_port_enabled_set(sd_app->icon.view_port, false); |  | ||||||
|                 app_sd_unmount_card(sd_app); |  | ||||||
|                 sd_was_present = true; |  | ||||||
|                 app_sd_notify_eject(sd_app); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         SdAppEvent event; |  | ||||||
|         osStatus_t event_status = osMessageQueueGet(sd_app->event_queue, &event, NULL, 1000); |  | ||||||
| 
 |  | ||||||
|         const uint8_t y_1_line = 32; |  | ||||||
|         const uint8_t y_2_line = 32; |  | ||||||
|         const uint8_t y_4_line = 26; |  | ||||||
| 
 |  | ||||||
|         if(event_status == osOK) { |  | ||||||
|             switch(event.type) { |  | ||||||
|             case SdAppEventTypeOK: |  | ||||||
|                 switch(sd_app->sd_app_state) { |  | ||||||
|                 case SdAppStateFormat: { |  | ||||||
|                     DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder); |  | ||||||
|                     dialog_ex_set_left_button_text(dialog, NULL); |  | ||||||
|                     dialog_ex_set_right_button_text(dialog, NULL); |  | ||||||
|                     dialog_ex_set_header( |  | ||||||
|                         dialog, "Formatting...", 64, y_1_line, AlignCenter, AlignCenter); |  | ||||||
|                     dialog_ex_set_text(dialog, NULL, 0, 0, AlignCenter, AlignCenter); |  | ||||||
|                     sd_app->sd_app_state = SdAppStateFormatInProgress; |  | ||||||
|                     delay(100); |  | ||||||
|                     app_sd_format_internal(sd_app); |  | ||||||
|                     app_sd_notify_success(sd_app); |  | ||||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); |  | ||||||
|                     dialog_ex_set_header( |  | ||||||
|                         dialog, "SD card formatted", 64, 10, AlignCenter, AlignCenter); |  | ||||||
|                     dialog_ex_set_text( |  | ||||||
|                         dialog, "Press back to return", 64, y_1_line, AlignCenter, AlignCenter); |  | ||||||
|                     sd_app->sd_app_state = SdAppStateFormatCompleted; |  | ||||||
|                 }; break; |  | ||||||
|                 case SdAppStateEject: { |  | ||||||
|                     DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder); |  | ||||||
|                     dialog_ex_set_right_button_text(dialog, NULL); |  | ||||||
|                     dialog_ex_set_header( |  | ||||||
|                         dialog, "SD card ejected", 64, 10, AlignCenter, AlignCenter); |  | ||||||
|                     dialog_ex_set_text( |  | ||||||
|                         dialog, |  | ||||||
|                         "Now the SD card\ncan be removed.", |  | ||||||
|                         64, |  | ||||||
|                         y_2_line, |  | ||||||
|                         AlignCenter, |  | ||||||
|                         AlignCenter); |  | ||||||
|                     sd_app->sd_app_state = SdAppStateEjected; |  | ||||||
|                     app_sd_unmount_card(sd_app); |  | ||||||
|                     app_sd_notify_eject(sd_app); |  | ||||||
|                 }; break; |  | ||||||
|                 case SdAppStateFileSelect: { |  | ||||||
|                     SdAppFileSelectResultEvent retval = {.result = true}; |  | ||||||
|                     furi_check( |  | ||||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == |  | ||||||
|                         osOK); |  | ||||||
|                     app_reset_state(sd_app); |  | ||||||
|                 }; break; |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             case SdAppEventTypeBack: |  | ||||||
|                 switch(sd_app->sd_app_state) { |  | ||||||
|                 case SdAppStateFormatInProgress: |  | ||||||
|                     break; |  | ||||||
|                 case SdAppStateFileSelect: { |  | ||||||
|                     SdAppFileSelectResultEvent retval = {.result = false}; |  | ||||||
|                     furi_check( |  | ||||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == |  | ||||||
|                         osOK); |  | ||||||
|                     app_reset_state(sd_app); |  | ||||||
|                 }; break; |  | ||||||
| 
 |  | ||||||
|                 default: |  | ||||||
|                     app_reset_state(sd_app); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             case SdAppEventTypeFormat: |  | ||||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { |  | ||||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); |  | ||||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); |  | ||||||
|                     dialog_ex_set_right_button_text(dialog, "Format"); |  | ||||||
|                     dialog_ex_set_header( |  | ||||||
|                         dialog, "Format SD card?", 64, 10, AlignCenter, AlignCenter); |  | ||||||
|                     dialog_ex_set_text( |  | ||||||
|                         dialog, "All data will be lost.", 64, y_1_line, AlignCenter, AlignCenter); |  | ||||||
|                     view_holder_start(sd_app->view_holder); |  | ||||||
|                     sd_app->sd_app_state = SdAppStateFormat; |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             case SdAppEventTypeInfo: |  | ||||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { |  | ||||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); |  | ||||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); |  | ||||||
| 
 |  | ||||||
|                     SDInfo sd_info; |  | ||||||
|                     get_sd_info(sd_app, &sd_info); |  | ||||||
| 
 |  | ||||||
|                     if(sd_info.error == SD_OK) { |  | ||||||
|                         string_printf( |  | ||||||
|                             sd_app->text_holder, |  | ||||||
|                             "Label: %s\nType: %s\n%lu KB total\n%lu KB free", |  | ||||||
|                             sd_info.label, |  | ||||||
|                             get_fs_type_text(sd_info.fs_type), |  | ||||||
|                             sd_info.kb_total, |  | ||||||
|                             sd_info.kb_free); |  | ||||||
|                         dialog_ex_set_text( |  | ||||||
|                             dialog, |  | ||||||
|                             string_get_cstr(sd_app->text_holder), |  | ||||||
|                             4, |  | ||||||
|                             y_4_line, |  | ||||||
|                             AlignLeft, |  | ||||||
|                             AlignCenter); |  | ||||||
|                         view_holder_start(sd_app->view_holder); |  | ||||||
|                     } else { |  | ||||||
|                         string_printf( |  | ||||||
|                             sd_app->text_holder, |  | ||||||
|                             "SD status: %s\n SD info: %s", |  | ||||||
|                             fs_error_get_internal_desc(_fs_status(&sd_app->info)), |  | ||||||
|                             fs_error_get_internal_desc(sd_info.error)); |  | ||||||
|                         dialog_ex_set_header(dialog, "Error", 64, 10, AlignCenter, AlignCenter); |  | ||||||
|                         dialog_ex_set_text( |  | ||||||
|                             dialog, |  | ||||||
|                             string_get_cstr(sd_app->text_holder), |  | ||||||
|                             64, |  | ||||||
|                             y_2_line, |  | ||||||
|                             AlignCenter, |  | ||||||
|                             AlignCenter); |  | ||||||
|                         view_holder_start(sd_app->view_holder); |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     sd_app->sd_app_state = SdAppStateInfo; |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             case SdAppEventTypeEject: |  | ||||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { |  | ||||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); |  | ||||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); |  | ||||||
|                     dialog_ex_set_right_button_text(dialog, "Eject"); |  | ||||||
|                     dialog_ex_set_header( |  | ||||||
|                         dialog, "Eject SD card?", 64, 10, AlignCenter, AlignCenter); |  | ||||||
|                     dialog_ex_set_text( |  | ||||||
|                         dialog, |  | ||||||
|                         "SD card will be\nunavailable", |  | ||||||
|                         64, |  | ||||||
|                         y_2_line, |  | ||||||
|                         AlignCenter, |  | ||||||
|                         AlignCenter); |  | ||||||
|                     view_holder_start(sd_app->view_holder); |  | ||||||
|                     sd_app->sd_app_state = SdAppStateEject; |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             case SdAppEventTypeFileSelect: |  | ||||||
|                 if(!app_sd_make_path(event.payload.file_select_data.path)) { |  | ||||||
|                     SdAppFileSelectResultEvent retval = {.result = false}; |  | ||||||
|                     furi_check( |  | ||||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == |  | ||||||
|                         osOK); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { |  | ||||||
|                     FileSelect* file_select = alloc_and_attach_file_select(sd_app); |  | ||||||
|                     SdAppFileSelectData* file_select_data = &event.payload.file_select_data; |  | ||||||
| 
 |  | ||||||
|                     file_select_set_api(file_select, fs_api); |  | ||||||
|                     file_select_set_filter( |  | ||||||
|                         file_select, file_select_data->path, file_select_data->extension); |  | ||||||
|                     file_select_set_result_buffer( |  | ||||||
|                         file_select, file_select_data->result, file_select_data->result_size); |  | ||||||
|                     if(!file_select_init(file_select)) { |  | ||||||
|                         SdAppFileSelectResultEvent retval = {.result = false}; |  | ||||||
|                         furi_check( |  | ||||||
|                             osMessageQueuePut( |  | ||||||
|                                 sd_app->result_receiver, &retval, 0, osWaitForever) == osOK); |  | ||||||
|                         app_reset_state(sd_app); |  | ||||||
|                     } else { |  | ||||||
|                         sd_app->sd_app_state = SdAppStateFileSelect; |  | ||||||
|                         if(file_select_data->selected_filename != NULL) { |  | ||||||
|                             file_select_set_selected_file( |  | ||||||
|                                 file_select, file_select_data->selected_filename); |  | ||||||
|                         } |  | ||||||
|                         view_holder_start(sd_app->view_holder); |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     SdAppFileSelectResultEvent retval = {.result = false}; |  | ||||||
|                     furi_check( |  | ||||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == |  | ||||||
|                         osOK); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             case SdAppEventTypeCheckError: |  | ||||||
|                 if(sd_app->info.status != SD_OK) { |  | ||||||
|                     if(try_to_alloc_view_holder(sd_app, gui)) { |  | ||||||
|                         DialogEx* dialog = alloc_and_attach_dialog(sd_app); |  | ||||||
|                         dialog_ex_set_left_button_text(dialog, "Back"); |  | ||||||
|                         if(sd_app->info.status == SD_NO_CARD) { |  | ||||||
|                             dialog_ex_set_text( |  | ||||||
|                                 dialog, |  | ||||||
|                                 "SD card\nnot found", |  | ||||||
|                                 88, |  | ||||||
|                                 y_1_line, |  | ||||||
|                                 AlignCenter, |  | ||||||
|                                 AlignCenter); |  | ||||||
|                             dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43); |  | ||||||
|                         } else { |  | ||||||
|                             dialog_ex_set_text( |  | ||||||
|                                 dialog, "SD card\nerror", 88, y_1_line, AlignCenter, AlignCenter); |  | ||||||
|                             dialog_ex_set_icon(dialog, 5, 10, &I_SDError_43x35); |  | ||||||
|                         } |  | ||||||
|                         sd_app->sd_app_state = SdAppStateCheckError; |  | ||||||
|                         view_holder_start(sd_app->view_holder); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             case SdAppEventTypeShowError: |  | ||||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { |  | ||||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); |  | ||||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); |  | ||||||
|                     dialog_ex_set_text( |  | ||||||
|                         dialog, event.payload.error_text, 88, y_1_line, AlignCenter, AlignCenter); |  | ||||||
|                     dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43); |  | ||||||
|                     sd_app->sd_app_state = SdAppStateShowError; |  | ||||||
|                     view_holder_start(sd_app->view_holder); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| @ -1,145 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <furi.h> |  | ||||||
| #include <api-hal.h> |  | ||||||
| #include <gui/gui.h> |  | ||||||
| #include <input/input.h> |  | ||||||
| #include <m-string.h> |  | ||||||
| #include "sd-card-api.h" |  | ||||||
| #include "view_holder.h" |  | ||||||
| #include <notification/notification-messages.h> |  | ||||||
| 
 |  | ||||||
| #define SD_FS_MAX_FILES _FS_LOCK |  | ||||||
| #define SD_STATE_LINES_COUNT 6 |  | ||||||
| 
 |  | ||||||
| /* api data */ |  | ||||||
| typedef FIL SDFile; |  | ||||||
| typedef DIR SDDir; |  | ||||||
| typedef FILINFO SDFileInfo; |  | ||||||
| 
 |  | ||||||
| /* storage for file/directory objects*/ |  | ||||||
| typedef union { |  | ||||||
|     SDFile file; |  | ||||||
|     SDDir dir; |  | ||||||
| } SDFileDirStorage; |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     SD_OK = FR_OK, |  | ||||||
|     SD_DISK_ERR = FR_DISK_ERR, |  | ||||||
|     SD_INT_ERR = FR_INT_ERR, |  | ||||||
|     SD_NO_FILE = FR_NO_FILE, |  | ||||||
|     SD_NO_PATH = FR_NO_PATH, |  | ||||||
|     SD_INVALID_NAME = FR_INVALID_NAME, |  | ||||||
|     SD_DENIED = FR_DENIED, |  | ||||||
|     SD_EXIST = FR_EXIST, |  | ||||||
|     SD_INVALID_OBJECT = FR_INVALID_OBJECT, |  | ||||||
|     SD_WRITE_PROTECTED = FR_WRITE_PROTECTED, |  | ||||||
|     SD_INVALID_DRIVE = FR_INVALID_DRIVE, |  | ||||||
|     SD_NOT_ENABLED = FR_NOT_ENABLED, |  | ||||||
|     SD_NO_FILESYSTEM = FR_NO_FILESYSTEM, |  | ||||||
|     SD_MKFS_ABORTED = FR_MKFS_ABORTED, |  | ||||||
|     SD_TIMEOUT = FR_TIMEOUT, |  | ||||||
|     SD_LOCKED = FR_LOCKED, |  | ||||||
|     SD_NOT_ENOUGH_CORE = FR_NOT_ENOUGH_CORE, |  | ||||||
|     SD_TOO_MANY_OPEN_FILES = FR_TOO_MANY_OPEN_FILES, |  | ||||||
|     SD_INVALID_PARAMETER = FR_INVALID_PARAMETER, |  | ||||||
|     SD_NO_CARD, |  | ||||||
|     SD_NOT_A_FILE, |  | ||||||
|     SD_NOT_A_DIR, |  | ||||||
|     SD_OTHER_APP, |  | ||||||
|     SD_LOW_LEVEL_ERR, |  | ||||||
| } SDError; |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     FDF_DIR, |  | ||||||
|     FDF_FILE, |  | ||||||
|     FDF_ANY, |  | ||||||
| } FiledataFilter; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     osThreadId_t thread_id; |  | ||||||
|     bool is_dir; |  | ||||||
|     SDFileDirStorage data; |  | ||||||
| } FileData; |  | ||||||
| 
 |  | ||||||
| /* application data */ |  | ||||||
| typedef struct { |  | ||||||
|     ViewPort* view_port; |  | ||||||
|     const Icon* mounted; |  | ||||||
|     const Icon* fail; |  | ||||||
| } SdFsIcon; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     osMutexId_t mutex; |  | ||||||
|     FileData files[SD_FS_MAX_FILES]; |  | ||||||
|     SDError status; |  | ||||||
|     char* path; |  | ||||||
|     FATFS fat_fs; |  | ||||||
| } SdFsInfo; |  | ||||||
| 
 |  | ||||||
| typedef enum { |  | ||||||
|     SdAppStateBackground, |  | ||||||
|     SdAppStateFormat, |  | ||||||
|     SdAppStateFormatInProgress, |  | ||||||
|     SdAppStateFormatCompleted, |  | ||||||
|     SdAppStateInfo, |  | ||||||
|     SdAppStateEject, |  | ||||||
|     SdAppStateEjected, |  | ||||||
|     SdAppStateFileSelect, |  | ||||||
|     SdAppStateCheckError, |  | ||||||
|     SdAppStateShowError, |  | ||||||
| } SdAppState; |  | ||||||
| 
 |  | ||||||
| struct SdApp { |  | ||||||
|     SdFsInfo info; |  | ||||||
|     SdFsIcon icon; |  | ||||||
| 
 |  | ||||||
|     SdCard_Api sd_card_api; |  | ||||||
|     SdAppState sd_app_state; |  | ||||||
| 
 |  | ||||||
|     ViewHolder* view_holder; |  | ||||||
|     osMessageQueueId_t result_receiver; |  | ||||||
| 
 |  | ||||||
|     osMessageQueueId_t event_queue; |  | ||||||
|     string_t text_holder; |  | ||||||
| 
 |  | ||||||
|     NotificationApp* notifications; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| /* core api fns */ |  | ||||||
| bool _fs_init(SdFsInfo* _fs_info); |  | ||||||
| bool _fs_lock(SdFsInfo* fs_info); |  | ||||||
| bool _fs_unlock(SdFsInfo* fs_info); |  | ||||||
| SDError _fs_status(SdFsInfo* fs_info); |  | ||||||
| 
 |  | ||||||
| /* sd api fns */ |  | ||||||
| bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode); |  | ||||||
| bool fs_file_close(File* file); |  | ||||||
| uint16_t fs_file_read(File* file, void* buff, uint16_t bytes_to_read); |  | ||||||
| uint16_t fs_file_write(File* file, const void* buff, uint16_t bytes_to_write); |  | ||||||
| bool fs_file_seek(File* file, uint32_t offset, bool from_start); |  | ||||||
| uint64_t fs_file_tell(File* file); |  | ||||||
| bool fs_file_truncate(File* file); |  | ||||||
| uint64_t fs_file_size(File* file); |  | ||||||
| bool fs_file_sync(File* file); |  | ||||||
| bool fs_file_eof(File* file); |  | ||||||
| 
 |  | ||||||
| /* dir api */ |  | ||||||
| bool fs_dir_open(File* file, const char* path); |  | ||||||
| bool fs_dir_close(File* file); |  | ||||||
| bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); |  | ||||||
| bool fs_dir_rewind(File* file); |  | ||||||
| 
 |  | ||||||
| /* common api */ |  | ||||||
| FS_Error |  | ||||||
|     fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length); |  | ||||||
| FS_Error fs_common_remove(const char* path); |  | ||||||
| FS_Error fs_common_rename(const char* old_path, const char* new_path); |  | ||||||
| FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask); |  | ||||||
| FS_Error fs_common_mkdir(const char* path); |  | ||||||
| FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time); |  | ||||||
| FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space); |  | ||||||
| 
 |  | ||||||
| /* errors api */ |  | ||||||
| const char* fs_error_get_desc(FS_Error error_id); |  | ||||||
| const char* fs_error_get_internal_desc(uint32_t internal_error_id); |  | ||||||
| @ -0,0 +1,161 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | 
 | ||||||
|  | #define BENCH_DATA_SIZE 4096 | ||||||
|  | #define BENCH_COUNT 6 | ||||||
|  | #define BENCH_REPEATS 4 | ||||||
|  | #define BENCH_FILE "/ext/rwfiletest.bin" | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     storage_settings_scene_benchmark_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_settings_bench_write( | ||||||
|  |     Storage* api, | ||||||
|  |     uint16_t size, | ||||||
|  |     const uint8_t* data, | ||||||
|  |     uint32_t* speed) { | ||||||
|  |     File* file = storage_file_alloc(api); | ||||||
|  |     bool result = true; | ||||||
|  |     if(storage_file_open(file, BENCH_FILE, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { | ||||||
|  |         uint32_t ticks; | ||||||
|  |         ticks = osKernelGetTickCount(); | ||||||
|  | 
 | ||||||
|  |         for(size_t repeat = 0; repeat < BENCH_REPEATS; repeat++) { | ||||||
|  |             for(size_t i = 0; i < BENCH_DATA_SIZE / size; i++) { | ||||||
|  |                 if(storage_file_write(file, (data + i * size), size) != size) { | ||||||
|  |                     result = false; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         ticks = osKernelGetTickCount() - ticks; | ||||||
|  |         *speed = BENCH_DATA_SIZE * osKernelGetTickFreq() * BENCH_REPEATS; | ||||||
|  |         *speed /= ticks; | ||||||
|  |         *speed /= 1024; | ||||||
|  |     } | ||||||
|  |     storage_file_close(file); | ||||||
|  |     storage_file_free(file); | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  |     storage_settings_bench_read(Storage* api, uint16_t size, uint8_t* data, uint32_t* speed) { | ||||||
|  |     File* file = storage_file_alloc(api); | ||||||
|  |     bool result = true; | ||||||
|  |     *speed = -1; | ||||||
|  | 
 | ||||||
|  |     if(storage_file_open(file, BENCH_FILE, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|  |         uint32_t ticks; | ||||||
|  |         ticks = osKernelGetTickCount(); | ||||||
|  | 
 | ||||||
|  |         for(size_t repeat = 0; repeat < BENCH_REPEATS; repeat++) { | ||||||
|  |             for(size_t i = 0; i < BENCH_DATA_SIZE / size; i++) { | ||||||
|  |                 if(storage_file_read(file, (data + i * size), size) != size) { | ||||||
|  |                     result = false; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         ticks = osKernelGetTickCount() - ticks; | ||||||
|  |         *speed = BENCH_DATA_SIZE * osKernelGetTickFreq() * BENCH_REPEATS; | ||||||
|  |         *speed /= ticks; | ||||||
|  |         *speed /= 1024; | ||||||
|  |     } | ||||||
|  |     storage_file_close(file); | ||||||
|  |     storage_file_free(file); | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void storage_settings_benchmark(StorageSettings* app) { | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  |     uint8_t* bench_data; | ||||||
|  |     dialog_ex_set_header(dialog_ex, "Preparing data...", 64, 32, AlignCenter, AlignCenter); | ||||||
|  | 
 | ||||||
|  |     bench_data = malloc(BENCH_DATA_SIZE); | ||||||
|  |     for(size_t i = 0; i < BENCH_DATA_SIZE; i++) { | ||||||
|  |         bench_data[i] = (uint8_t)i; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     uint16_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 1024, 4096}; | ||||||
|  |     uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; | ||||||
|  |     uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter); | ||||||
|  |     for(size_t i = 0; i < BENCH_COUNT; i++) { | ||||||
|  |         if(!storage_settings_bench_write(app->fs_api, bench_size[i], bench_data, &bench_w_speed[i])) | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         if(i > 0) string_cat_printf(app->text_string, "\n"); | ||||||
|  |         string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); | ||||||
|  |         dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); | ||||||
|  | 
 | ||||||
|  |         if(!storage_settings_bench_read(app->fs_api, bench_size[i], bench_data, &bench_r_speed[i])) | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     free(bench_data); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_benchmark_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_benchmark_dialog_callback); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  | 
 | ||||||
|  |     if(storage_sd_status(app->fs_api) != FSE_OK) { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, | ||||||
|  |             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||||
|  |             64, | ||||||
|  |             32, | ||||||
|  |             AlignCenter, | ||||||
|  |             AlignCenter); | ||||||
|  |         dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|  |     } else { | ||||||
|  |         storage_settings_benchmark(app); | ||||||
|  |         notification_message(app->notification, &sequence_blink_green_100); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DialogExResultLeft: | ||||||
|  |             consumed = scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_benchmark_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_context(dialog_ex, NULL); | ||||||
|  | 
 | ||||||
|  |     string_clean(app->text_string); | ||||||
|  | } | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | ADD_SCENE(storage_settings, start, Start) | ||||||
|  | ADD_SCENE(storage_settings, unmount_confirm, UnmountConfirm) | ||||||
|  | ADD_SCENE(storage_settings, unmounted, Unmounted) | ||||||
|  | ADD_SCENE(storage_settings, format_confirm, FormatConfirm) | ||||||
|  | ADD_SCENE(storage_settings, formatting, Formatting) | ||||||
|  | ADD_SCENE(storage_settings, sd_info, SDInfo) | ||||||
|  | ADD_SCENE(storage_settings, internal_info, InternalInfo) | ||||||
|  | ADD_SCENE(storage_settings, benchmark, Benchmark) | ||||||
| @ -0,0 +1,68 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     storage_settings_scene_unmount_confirm_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_unmount_confirm_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     FS_Error sd_status = storage_sd_status(app->fs_api); | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|  | 
 | ||||||
|  |     if(sd_status == FSE_NOT_READY) { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, | ||||||
|  |             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||||
|  |             64, | ||||||
|  |             32, | ||||||
|  |             AlignCenter, | ||||||
|  |             AlignCenter); | ||||||
|  |     } else { | ||||||
|  |         dialog_ex_set_right_button_text(dialog_ex, "Unmount"); | ||||||
|  |         dialog_ex_set_header(dialog_ex, "Unmount SD card?", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, "SD card will be\nunavailable", 64, 32, AlignCenter, AlignCenter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_set_result_callback( | ||||||
|  |         dialog_ex, storage_settings_scene_unmount_confirm_dialog_callback); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DialogExResultLeft: | ||||||
|  |             consumed = scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             break; | ||||||
|  |         case DialogExResultRight: | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_unmount_confirm_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_context(dialog_ex, NULL); | ||||||
|  | } | ||||||
| @ -0,0 +1,65 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     storage_settings_scene_unmounted_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_unmounted_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     FS_Error error = storage_sd_unmount(app->fs_api); | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|  | 
 | ||||||
|  |     if(error == FSE_OK) { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "SD card unmounted", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, "Now the SD card\ncan be removed.", 64, 32, AlignCenter, AlignCenter); | ||||||
|  |         notification_message(app->notification, &sequence_blink_green_100); | ||||||
|  |     } else { | ||||||
|  |         dialog_ex_set_header( | ||||||
|  |             dialog_ex, "Cannot unmount SD Card", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); | ||||||
|  |         notification_message(app->notification, &sequence_blink_red_100); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_unmounted_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DialogExResultLeft: | ||||||
|  |             consumed = | ||||||
|  |                 scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } else if(event.type == SceneManagerEventTypeNavigation) { | ||||||
|  |         consumed = scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_unmounted_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_context(dialog_ex, NULL); | ||||||
|  | } | ||||||
| @ -0,0 +1,67 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     storage_settings_scene_format_confirm_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_format_confirm_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     FS_Error sd_status = storage_sd_status(app->fs_api); | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|  | 
 | ||||||
|  |     if(sd_status == FSE_NOT_READY) { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, | ||||||
|  |             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||||
|  |             64, | ||||||
|  |             32, | ||||||
|  |             AlignCenter, | ||||||
|  |             AlignCenter); | ||||||
|  |     } else { | ||||||
|  |         dialog_ex_set_right_button_text(dialog_ex, "Format"); | ||||||
|  |         dialog_ex_set_header(dialog_ex, "Format SD card?", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text(dialog_ex, "All data will be lost", 64, 32, AlignCenter, AlignCenter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_set_result_callback( | ||||||
|  |         dialog_ex, storage_settings_scene_format_confirm_dialog_callback); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_format_confirm_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DialogExResultLeft: | ||||||
|  |             consumed = scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             break; | ||||||
|  |         case DialogExResultRight: | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsFormatting); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_format_confirm_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_context(dialog_ex, NULL); | ||||||
|  | } | ||||||
| @ -0,0 +1,85 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | 
 | ||||||
|  | static const NotificationMessage message_green_165 = { | ||||||
|  |     .type = NotificationMessageTypeLedGreen, | ||||||
|  |     .data.led.value = 165, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence sequence_set_formatting_leds = { | ||||||
|  |     &message_red_255, | ||||||
|  |     &message_green_165, | ||||||
|  |     &message_blue_0, | ||||||
|  |     &message_do_not_reset, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence sequence_reset_formatting_leds = { | ||||||
|  |     &message_red_0, | ||||||
|  |     &message_green_0, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     storage_settings_scene_formatting_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_formatting_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     FS_Error error; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, "Formatting...", 64, 32, AlignCenter, AlignCenter); | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  | 
 | ||||||
|  |     notification_message_block(app->notification, &sequence_set_formatting_leds); | ||||||
|  |     error = storage_sd_format(app->fs_api); | ||||||
|  |     notification_message(app->notification, &sequence_reset_formatting_leds); | ||||||
|  |     notification_message(app->notification, &sequence_blink_green_100); | ||||||
|  | 
 | ||||||
|  |     if(error != FSE_OK) { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "Cannot format SD Card", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); | ||||||
|  |     } else { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "SD card formatted", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text(dialog_ex, "Press back to return", 64, 32, AlignCenter, AlignCenter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_formatting_dialog_callback); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DialogExResultLeft: | ||||||
|  |             consumed = | ||||||
|  |                 scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } else if(event.type == SceneManagerEventTypeNavigation) { | ||||||
|  |         consumed = scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_formatting_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_context(dialog_ex, NULL); | ||||||
|  | } | ||||||
| @ -0,0 +1,68 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | #include <api-hal-version.h> | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     storage_settings_scene_internal_info_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_internal_info_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     uint64_t total_space; | ||||||
|  |     uint64_t free_space; | ||||||
|  |     FS_Error error = storage_common_fs_info(app->fs_api, "/int", &total_space, &free_space); | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_internal_info_dialog_callback); | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|  |     if(error != FSE_OK) { | ||||||
|  |         dialog_ex_set_header( | ||||||
|  |             dialog_ex, "Internal storage error", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); | ||||||
|  |     } else { | ||||||
|  |         string_printf( | ||||||
|  |             app->text_string, | ||||||
|  |             "Label: %s\nType: LittleFS\n%lu KB total\n%lu KB free", | ||||||
|  |             api_hal_version_get_name_ptr(), | ||||||
|  |             (uint32_t)(total_space / 1024), | ||||||
|  |             (uint32_t)(free_space / 1024)); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_internal_info_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DialogExResultLeft: | ||||||
|  |             consumed = scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_internal_info_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_context(dialog_ex, NULL); | ||||||
|  | 
 | ||||||
|  |     string_clean(app->text_string); | ||||||
|  | } | ||||||
| @ -0,0 +1,74 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | 
 | ||||||
|  | static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result, void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_sd_info_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     SDInfo sd_info; | ||||||
|  |     FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info); | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_context(dialog_ex, app); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback); | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||||
|  |     if(sd_status != FSE_OK) { | ||||||
|  |         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, | ||||||
|  |             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||||
|  |             64, | ||||||
|  |             32, | ||||||
|  |             AlignCenter, | ||||||
|  |             AlignCenter); | ||||||
|  |     } else { | ||||||
|  |         string_printf( | ||||||
|  |             app->text_string, | ||||||
|  |             "Label: %s\nType: %s\n%lu KB total\n%lu KB free", | ||||||
|  |             sd_info.label, | ||||||
|  |             sd_api_get_fs_type_text(sd_info.fs_type), | ||||||
|  |             sd_info.kb_total, | ||||||
|  |             sd_info.kb_free); | ||||||
|  |         dialog_ex_set_text( | ||||||
|  |             dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_sd_info_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case DialogExResultLeft: | ||||||
|  |             consumed = scene_manager_previous_scene(app->scene_manager); | ||||||
|  |             break; | ||||||
|  |         case DialogExResultRight: | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_sd_info_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     DialogEx* dialog_ex = app->dialog_ex; | ||||||
|  | 
 | ||||||
|  |     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||||
|  |     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||||
|  |     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||||
|  |     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||||
|  |     dialog_ex_set_context(dialog_ex, NULL); | ||||||
|  | 
 | ||||||
|  |     string_clean(app->text_string); | ||||||
|  | } | ||||||
| @ -0,0 +1,104 @@ | |||||||
|  | #include "../storage-settings.h" | ||||||
|  | 
 | ||||||
|  | enum StorageSettingsStartSubmenuIndex { | ||||||
|  |     StorageSettingsStartSubmenuIndexInternalInfo, | ||||||
|  |     StorageSettingsStartSubmenuIndexSDInfo, | ||||||
|  |     StorageSettingsStartSubmenuIndexUnmount, | ||||||
|  |     StorageSettingsStartSubmenuIndexFormat, | ||||||
|  |     StorageSettingsStartSubmenuIndexBenchy, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void storage_settings_scene_start_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_start_on_enter(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "About internal storage", | ||||||
|  |         StorageSettingsStartSubmenuIndexInternalInfo, | ||||||
|  |         storage_settings_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "About SD Card", | ||||||
|  |         StorageSettingsStartSubmenuIndexSDInfo, | ||||||
|  |         storage_settings_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Unmount SD Card", | ||||||
|  |         StorageSettingsStartSubmenuIndexUnmount, | ||||||
|  |         storage_settings_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Format SD Card", | ||||||
|  |         StorageSettingsStartSubmenuIndexFormat, | ||||||
|  |         storage_settings_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Benchmark SD Card", | ||||||
|  |         StorageSettingsStartSubmenuIndexBenchy, | ||||||
|  |         storage_settings_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  | 
 | ||||||
|  |     submenu_set_selected_item( | ||||||
|  |         submenu, scene_manager_get_scene_state(app->scene_manager, StorageSettingsStart)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewSubmenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         switch(event.event) { | ||||||
|  |         case StorageSettingsStartSubmenuIndexSDInfo: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexSDInfo); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsSDInfo); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case StorageSettingsStartSubmenuIndexInternalInfo: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, | ||||||
|  |                 StorageSettingsStart, | ||||||
|  |                 StorageSettingsStartSubmenuIndexInternalInfo); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsInternalInfo); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case StorageSettingsStartSubmenuIndexUnmount: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexUnmount); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsUnmountConfirm); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case StorageSettingsStartSubmenuIndexFormat: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexFormat); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsFormatConfirm); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         case StorageSettingsStartSubmenuIndexBenchy: | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); | ||||||
|  |             scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); | ||||||
|  |             consumed = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_settings_scene_start_on_exit(void* context) { | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     submenu_clean(app->submenu); | ||||||
|  | } | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #include "storage-settings-scene.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const storage_settings_on_enter_handlers[])(void*) = { | ||||||
|  | #include "storage-settings-scene-config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||||
|  | bool (*const storage_settings_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "storage-settings-scene-config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||||
|  | void (*const storage_settings_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "storage-settings-scene-config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers storage_settings_scene_handlers = { | ||||||
|  |     .on_enter_handlers = storage_settings_on_enter_handlers, | ||||||
|  |     .on_event_handlers = storage_settings_on_event_handlers, | ||||||
|  |     .on_exit_handlers = storage_settings_on_exit_handlers, | ||||||
|  |     .scene_num = StorageSettingsSceneNum, | ||||||
|  | }; | ||||||
| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) StorageSettings##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "storage-settings-scene-config.h" | ||||||
|  |     StorageSettingsSceneNum, | ||||||
|  | } StorageSettingsScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers storage_settings_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "storage-settings-scene-config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_event handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) \ | ||||||
|  |     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||||
|  | #include "storage-settings-scene-config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Generate scene on_exit handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||||
|  | #include "storage-settings-scene-config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
							
								
								
									
										75
									
								
								applications/storage-settings/storage-settings.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								applications/storage-settings/storage-settings.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | #include "storage-settings.h" | ||||||
|  | 
 | ||||||
|  | static bool storage_settings_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_settings_navigation_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     StorageSettings* app = context; | ||||||
|  |     return scene_manager_handle_navigation_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static StorageSettings* storage_settings_alloc() { | ||||||
|  |     StorageSettings* app = malloc(sizeof(StorageSettings)); | ||||||
|  | 
 | ||||||
|  |     app->gui = furi_record_open("gui"); | ||||||
|  |     app->fs_api = furi_record_open("storage"); | ||||||
|  |     app->notification = furi_record_open("notification"); | ||||||
|  | 
 | ||||||
|  |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     app->scene_manager = scene_manager_alloc(&storage_settings_scene_handlers, app); | ||||||
|  |     string_init(app->text_string); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_enable_queue(app->view_dispatcher); | ||||||
|  |     view_dispatcher_set_event_callback_context(app->view_dispatcher, app); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_set_custom_event_callback( | ||||||
|  |         app->view_dispatcher, storage_settings_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         app->view_dispatcher, storage_settings_navigation_event_callback); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
|  | 
 | ||||||
|  |     app->submenu = submenu_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, StorageSettingsViewSubmenu, submenu_get_view(app->submenu)); | ||||||
|  | 
 | ||||||
|  |     app->dialog_ex = dialog_ex_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, StorageSettingsViewDialogEx, dialog_ex_get_view(app->dialog_ex)); | ||||||
|  | 
 | ||||||
|  |     scene_manager_next_scene(app->scene_manager, StorageSettingsStart); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void storage_settings_free(StorageSettings* app) { | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewSubmenu); | ||||||
|  |     submenu_free(app->submenu); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||||
|  |     dialog_ex_free(app->dialog_ex); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_free(app->view_dispatcher); | ||||||
|  |     scene_manager_free(app->scene_manager); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("gui"); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  |     furi_record_close("notification"); | ||||||
|  | 
 | ||||||
|  |     string_clear(app->text_string); | ||||||
|  | 
 | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t storage_settings(void* p) { | ||||||
|  |     StorageSettings* app = storage_settings_alloc(); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     storage_settings_free(app); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								applications/storage-settings/storage-settings.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								applications/storage-settings/storage-settings.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <notification/notification-messages.h> | ||||||
|  | 
 | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/dialog_ex.h> | ||||||
|  | #include <gui/modules/popup.h> | ||||||
|  | 
 | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <storage/storage-sd-api.h> | ||||||
|  | 
 | ||||||
|  | #include "scenes/storage-settings-scene.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     // records
 | ||||||
|  |     Gui* gui; | ||||||
|  |     NotificationApp* notification; | ||||||
|  |     Storage* fs_api; | ||||||
|  | 
 | ||||||
|  |     // view managment
 | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  | 
 | ||||||
|  |     // view modules
 | ||||||
|  |     Submenu* submenu; | ||||||
|  |     DialogEx* dialog_ex; | ||||||
|  | 
 | ||||||
|  |     // text
 | ||||||
|  |     string_t text_string; | ||||||
|  | } StorageSettings; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     StorageSettingsViewSubmenu, | ||||||
|  |     StorageSettingsViewDialogEx, | ||||||
|  | } StorageSettingsView; | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										59
									
								
								applications/storage/filesystem-api-defines.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/storage/filesystem-api-defines.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /** Access mode flags */ | ||||||
|  | typedef enum { | ||||||
|  |     FSAM_READ = (1 << 0), /**< Read access */ | ||||||
|  |     FSAM_WRITE = (1 << 1), /**< Write access */ | ||||||
|  | } FS_AccessMode; | ||||||
|  | 
 | ||||||
|  | /** Open mode flags */ | ||||||
|  | typedef enum { | ||||||
|  |     FSOM_OPEN_EXISTING = 1, /**< Open file, fail if file doesn't exist */ | ||||||
|  |     FSOM_OPEN_ALWAYS = 2, /**< Open file. Create new file if not exist */ | ||||||
|  |     FSOM_OPEN_APPEND = 4, /**< Open file. Create new file if not exist. Set R/W pointer to EOF */ | ||||||
|  |     FSOM_CREATE_NEW = 8, /**< Creates a new file. Fails if the file is exist */ | ||||||
|  |     FSOM_CREATE_ALWAYS = 16, /**< Creates a new file. If file exist, truncate to zero size */ | ||||||
|  | } FS_OpenMode; | ||||||
|  | 
 | ||||||
|  | /** API errors enumeration */ | ||||||
|  | typedef enum { | ||||||
|  |     FSE_OK, /**< No error */ | ||||||
|  |     FSE_NOT_READY, /**< FS not ready */ | ||||||
|  |     FSE_EXIST, /**< File/Dir alrady exist */ | ||||||
|  |     FSE_NOT_EXIST, /**< File/Dir does not exist */ | ||||||
|  |     FSE_INVALID_PARAMETER, /**< Invalid API parameter */ | ||||||
|  |     FSE_DENIED, /**< Access denied */ | ||||||
|  |     FSE_INVALID_NAME, /**< Invalid name/path */ | ||||||
|  |     FSE_INTERNAL, /**< Internal error */ | ||||||
|  |     FSE_NOT_IMPLEMENTED, /**< Functon not implemented */ | ||||||
|  |     FSE_ALREADY_OPEN, /**< File/Dir already opened */ | ||||||
|  | } FS_Error; | ||||||
|  | 
 | ||||||
|  | /** FileInfo flags */ | ||||||
|  | typedef enum { | ||||||
|  |     FSF_DIRECTORY = (1 << 0), /**< Directory */ | ||||||
|  | } FS_Flags; | ||||||
|  | 
 | ||||||
|  | /**  Structure that hold file index and returned api errors  */ | ||||||
|  | typedef struct File File; | ||||||
|  | 
 | ||||||
|  | /**  Structure that hold file info */ | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t flags; /**< flags from FS_Flags enum */ | ||||||
|  |     uint64_t size; /**< file size */ | ||||||
|  | } FileInfo; | ||||||
|  | 
 | ||||||
|  | /** Gets the error text from FS_Error
 | ||||||
|  |  * @param error_id error id | ||||||
|  |  * @return const char* error text | ||||||
|  |  */ | ||||||
|  | const char* filesystem_api_error_get_desc(FS_Error error_id); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										192
									
								
								applications/storage/filesystem-api-internal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								applications/storage/filesystem-api-internal.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,192 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "filesystem-api-defines.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /** Structure that hold file index and returned api errors */ | ||||||
|  | struct File { | ||||||
|  |     uint32_t file_id; /**< File ID for internal references */ | ||||||
|  |     FS_Error error_id; /**< Standart API error from FS_Error enum */ | ||||||
|  |     int32_t internal_error_id; /**< Internal API error value */ | ||||||
|  |     void* storage; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** File api structure
 | ||||||
|  |  *  @var FS_File_Api::open | ||||||
|  |  *      @brief Open file | ||||||
|  |  *      @param file pointer to file object, filled by api | ||||||
|  |  *      @param path path to file  | ||||||
|  |  *      @param access_mode access mode from FS_AccessMode  | ||||||
|  |  *      @param open_mode open mode from FS_OpenMode  | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::close  | ||||||
|  |  *      @brief Close file | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::read | ||||||
|  |  *      @brief Read bytes from file to buffer | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param buff pointer to buffer for reading | ||||||
|  |  *      @param bytes_to_read how many bytes to read, must be smaller or equal to buffer size  | ||||||
|  |  *      @return how many bytes actually has been readed | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::write | ||||||
|  |  *      @brief Write bytes from buffer to file | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param buff pointer to buffer for writing | ||||||
|  |  *      @param bytes_to_read how many bytes to write, must be smaller or equal to buffer size  | ||||||
|  |  *      @return how many bytes actually has been writed | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::seek | ||||||
|  |  *      @brief Move r/w pointer  | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param offset offset to move r/w pointer | ||||||
|  |  *      @param from_start set offset from start, or from current position | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::tell | ||||||
|  |  *      @brief Get r/w pointer position | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return current r/w pointer position | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::truncate | ||||||
|  |  *      @brief Truncate file size to current r/w pointer position | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::size | ||||||
|  |  *      @brief Fet file size | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return file size | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::sync | ||||||
|  |  *      @brief Write file cache to storage | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::eof | ||||||
|  |  *      @brief Checks that the r/w pointer is at the end of the file | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return end of file flag | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     bool (*open)( | ||||||
|  |         void* context, | ||||||
|  |         File* file, | ||||||
|  |         const char* path, | ||||||
|  |         FS_AccessMode access_mode, | ||||||
|  |         FS_OpenMode open_mode); | ||||||
|  |     bool (*close)(void* context, File* file); | ||||||
|  |     uint16_t (*read)(void* context, File* file, void* buff, uint16_t bytes_to_read); | ||||||
|  |     uint16_t (*write)(void* context, File* file, const void* buff, uint16_t bytes_to_write); | ||||||
|  |     bool (*seek)(void* context, File* file, uint32_t offset, bool from_start); | ||||||
|  |     uint64_t (*tell)(void* context, File* file); | ||||||
|  |     bool (*truncate)(void* context, File* file); | ||||||
|  |     uint64_t (*size)(void* context, File* file); | ||||||
|  |     bool (*sync)(void* context, File* file); | ||||||
|  |     bool (*eof)(void* context, File* file); | ||||||
|  | } FS_File_Api; | ||||||
|  | 
 | ||||||
|  | /** Dir api structure
 | ||||||
|  |  *  @var FS_Dir_Api::open | ||||||
|  |  *      @brief Open directory to get objects from | ||||||
|  |  *      @param file pointer to file object, filled by api | ||||||
|  |  *      @param path path to directory  | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Dir_Api::close  | ||||||
|  |  *      @brief Close directory | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Dir_Api::read | ||||||
|  |  *      @brief Read next object info in directory | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||||
|  |  *      @param name pointer to name buffer, can be NULL | ||||||
|  |  *      @param name_length name buffer length | ||||||
|  |  *      @return success flag (if next object not exist also returns false and set error_id to FSE_NOT_EXIST) | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Dir_Api::rewind | ||||||
|  |  *      @brief Rewind to first object info in directory | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     bool (*open)(void* context, File* file, const char* path); | ||||||
|  |     bool (*close)(void* context, File* file); | ||||||
|  |     bool (*read)(void* context, File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||||
|  |     bool (*rewind)(void* context, File* file); | ||||||
|  | } FS_Dir_Api; | ||||||
|  | 
 | ||||||
|  | /** Common api structure
 | ||||||
|  |  *  @var FS_Common_Api::stat | ||||||
|  |  *      @brief Open directory to get objects from | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||||
|  |  *      @param name pointer to name buffer, can be NULL | ||||||
|  |  *      @param name_length name buffer length | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::remove | ||||||
|  |  *      @brief Remove file/directory from storage,  | ||||||
|  |  *          directory must be empty, | ||||||
|  |  *          file/directory must not be opened, | ||||||
|  |  *          file/directory must not have FSF_READ_ONLY flag | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::rename | ||||||
|  |  *      @brief Rename file/directory, | ||||||
|  |  *          file/directory must not be opened | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::mkdir | ||||||
|  |  *      @brief Create new directory | ||||||
|  |  *      @param path path to new directory | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::fs_info | ||||||
|  |  *      @brief Get total and free space storage values | ||||||
|  |  *      @param fs_path path of fs | ||||||
|  |  *      @param total_space pointer to total space value | ||||||
|  |  *      @param free_space pointer to free space value | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     FS_Error (*stat)(void* context, const char* path, FileInfo* fileinfo); | ||||||
|  |     FS_Error (*remove)(void* context, const char* path); | ||||||
|  |     FS_Error (*rename)(void* context, const char* old_path, const char* new_path); | ||||||
|  |     FS_Error (*mkdir)(void* context, const char* path); | ||||||
|  |     FS_Error ( | ||||||
|  |         *fs_info)(void* context, const char* fs_path, uint64_t* total_space, uint64_t* free_space); | ||||||
|  | } FS_Common_Api; | ||||||
|  | 
 | ||||||
|  | /** Errors api structure
 | ||||||
|  |  *  @var FS_Error_Api::get_desc | ||||||
|  |  *      @brief Get error description text | ||||||
|  |  *      @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id) | ||||||
|  |  *      @return pointer to description text | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     const char* (*get_desc)(void* context, FS_Error error_id); | ||||||
|  | } FS_Error_Api; | ||||||
|  | 
 | ||||||
|  | /** Full filesystem api structure */ | ||||||
|  | typedef struct { | ||||||
|  |     FS_File_Api file; | ||||||
|  |     FS_Dir_Api dir; | ||||||
|  |     FS_Common_Api common; | ||||||
|  |     FS_Error_Api error; | ||||||
|  |     void* context; | ||||||
|  | } FS_Api; | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										38
									
								
								applications/storage/filesystem-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								applications/storage/filesystem-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | #include "filesystem-api-defines.h" | ||||||
|  | 
 | ||||||
|  | const char* filesystem_api_error_get_desc(FS_Error error_id) { | ||||||
|  |     const char* result = "unknown error"; | ||||||
|  |     switch(error_id) { | ||||||
|  |     case(FSE_OK): | ||||||
|  |         result = "OK"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_NOT_READY): | ||||||
|  |         result = "filesystem not ready"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_EXIST): | ||||||
|  |         result = "file/dir already exist"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_NOT_EXIST): | ||||||
|  |         result = "file/dir not exist"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_INVALID_PARAMETER): | ||||||
|  |         result = "invalid parameter"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_DENIED): | ||||||
|  |         result = "access denied"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_INVALID_NAME): | ||||||
|  |         result = "invalid name/path"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_INTERNAL): | ||||||
|  |         result = "internal error"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_NOT_IMPLEMENTED): | ||||||
|  |         result = "function not implemented"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_ALREADY_OPEN): | ||||||
|  |         result = "file is already open"; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | } | ||||||
							
								
								
									
										350
									
								
								applications/storage/storage-cli.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								applications/storage/storage-cli.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,350 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include <cli/cli.h> | ||||||
|  | #include <args.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | #include <storage/storage-sd-api.h> | ||||||
|  | #include <api-hal-version.h> | ||||||
|  | 
 | ||||||
|  | #define MAX_NAME_LENGTH 255 | ||||||
|  | 
 | ||||||
|  | void storage_cli(Cli* cli, string_t args, void* context); | ||||||
|  | 
 | ||||||
|  | // app cli function
 | ||||||
|  | void storage_cli_init() { | ||||||
|  |     Cli* cli = furi_record_open("cli"); | ||||||
|  |     cli_add_command(cli, "storage", CliCommandFlagDefault, storage_cli, NULL); | ||||||
|  |     furi_record_close("cli"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_print_usage() { | ||||||
|  |     printf("Usage:\r\n"); | ||||||
|  |     printf("storage <cmd> <path> <args>\r\n"); | ||||||
|  |     printf("The path must start with /int or /ext\r\n"); | ||||||
|  |     printf("Cmd list:\r\n"); | ||||||
|  |     printf("\tinfo\t - get FS info\r\n"); | ||||||
|  |     printf("\tformat\t - format filesystem\r\n"); | ||||||
|  |     printf("\tlist\t - list files and dirs\r\n"); | ||||||
|  |     printf("\tremove\t - delete the file or directory\r\n"); | ||||||
|  |     printf("\tread\t - read data from file and print file size and content to cli\r\n"); | ||||||
|  |     printf( | ||||||
|  |         "\twrite\t - read data from cli and append it to file, <args> should contain how many bytes you want to write\r\n"); | ||||||
|  |     printf("\tcopy\t - copy file to new file, <args> must contain new path\r\n"); | ||||||
|  |     printf("\trename\t - move file to new file, <args> must contain new path\r\n"); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void storage_cli_print_error(FS_Error error) { | ||||||
|  |     printf("Storage error: %s\r\n", storage_error_get_desc(error)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_print_path_error(string_t path, FS_Error error) { | ||||||
|  |     printf( | ||||||
|  |         "Storage error for path \"%s\": %s\r\n", | ||||||
|  |         string_get_cstr(path), | ||||||
|  |         storage_error_get_desc(error)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_print_file_error(string_t path, File* file) { | ||||||
|  |     printf( | ||||||
|  |         "Storage error for path \"%s\": %s\r\n", | ||||||
|  |         string_get_cstr(path), | ||||||
|  |         storage_file_get_error_desc(file)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_info(Cli* cli, string_t path) { | ||||||
|  |     Storage* api = furi_record_open("storage"); | ||||||
|  | 
 | ||||||
|  |     if(string_cmp_str(path, "/int") == 0) { | ||||||
|  |         uint64_t total_space; | ||||||
|  |         uint64_t free_space; | ||||||
|  |         FS_Error error = storage_common_fs_info(api, "/int", &total_space, &free_space); | ||||||
|  | 
 | ||||||
|  |         if(error != FSE_OK) { | ||||||
|  |             storage_cli_print_path_error(path, error); | ||||||
|  |         } else { | ||||||
|  |             printf( | ||||||
|  |                 "Label: %s\r\nType: LittleFS\r\n%lu KB total\r\n%lu KB free\r\n", | ||||||
|  |                 api_hal_version_get_name_ptr(), | ||||||
|  |                 (uint32_t)(total_space / 1024), | ||||||
|  |                 (uint32_t)(free_space / 1024)); | ||||||
|  |         } | ||||||
|  |     } else if(string_cmp_str(path, "/ext") == 0) { | ||||||
|  |         SDInfo sd_info; | ||||||
|  |         FS_Error error = storage_sd_info(api, &sd_info); | ||||||
|  | 
 | ||||||
|  |         if(error != FSE_OK) { | ||||||
|  |             storage_cli_print_path_error(path, error); | ||||||
|  |         } else { | ||||||
|  |             printf( | ||||||
|  |                 "Label: %s\r\nType: %s\r\n%lu KB total\r\n%lu KB free\r\n", | ||||||
|  |                 sd_info.label, | ||||||
|  |                 sd_api_get_fs_type_text(sd_info.fs_type), | ||||||
|  |                 sd_info.kb_total, | ||||||
|  |                 sd_info.kb_free); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         storage_cli_print_usage(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void storage_cli_format(Cli* cli, string_t path) { | ||||||
|  |     if(string_cmp_str(path, "/int") == 0) { | ||||||
|  |         storage_cli_print_path_error(path, FSE_NOT_IMPLEMENTED); | ||||||
|  |     } else if(string_cmp_str(path, "/ext") == 0) { | ||||||
|  |         printf("Formatting SD card, all data will be lost. Are you sure (y/n)?\r\n"); | ||||||
|  |         char answer = cli_getc(cli); | ||||||
|  |         if(answer == 'y' || answer == 'Y') { | ||||||
|  |             Storage* api = furi_record_open("storage"); | ||||||
|  |             printf("Formatting, please wait...\r\n"); | ||||||
|  | 
 | ||||||
|  |             FS_Error error = storage_sd_format(api); | ||||||
|  | 
 | ||||||
|  |             if(error != FSE_OK) { | ||||||
|  |                 storage_cli_print_path_error(path, error); | ||||||
|  |             } else { | ||||||
|  |                 printf("SD card was successfully formatted.\r\n"); | ||||||
|  |             } | ||||||
|  |             furi_record_close("storage"); | ||||||
|  |         } else { | ||||||
|  |             printf("Cancelled.\r\n"); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         storage_cli_print_usage(); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void storage_cli_list(Cli* cli, string_t path) { | ||||||
|  |     if(string_cmp_str(path, "/") == 0) { | ||||||
|  |         printf("\t[D] int\r\n"); | ||||||
|  |         printf("\t[D] ext\r\n"); | ||||||
|  |         printf("\t[D] any\r\n"); | ||||||
|  |     } else { | ||||||
|  |         Storage* api = furi_record_open("storage"); | ||||||
|  |         File* file = storage_file_alloc(api); | ||||||
|  | 
 | ||||||
|  |         if(storage_dir_open(file, string_get_cstr(path))) { | ||||||
|  |             FileInfo fileinfo; | ||||||
|  |             char name[MAX_NAME_LENGTH]; | ||||||
|  |             bool readed = false; | ||||||
|  | 
 | ||||||
|  |             while(storage_dir_read(file, &fileinfo, name, MAX_NAME_LENGTH)) { | ||||||
|  |                 readed = true; | ||||||
|  |                 if(fileinfo.flags & FSF_DIRECTORY) { | ||||||
|  |                     printf("\t[D] %s\r\n", name); | ||||||
|  |                 } else { | ||||||
|  |                     printf("\t[F] %s %lub\r\n", name, (uint32_t)(fileinfo.size)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(!readed) { | ||||||
|  |                 printf("\tEmpty\r\n"); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             storage_cli_print_file_error(path, file); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         storage_dir_close(file); | ||||||
|  |         storage_file_free(file); | ||||||
|  |         furi_record_close("storage"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_read(Cli* cli, string_t path) { | ||||||
|  |     Storage* api = furi_record_open("storage"); | ||||||
|  |     File* file = storage_file_alloc(api); | ||||||
|  | 
 | ||||||
|  |     if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|  |         const uint16_t read_size = 128; | ||||||
|  |         uint16_t readed_size = 0; | ||||||
|  |         uint8_t* data = furi_alloc(read_size); | ||||||
|  | 
 | ||||||
|  |         printf("Size: %lu\r\n", (uint32_t)storage_file_size(file)); | ||||||
|  | 
 | ||||||
|  |         do { | ||||||
|  |             readed_size = storage_file_read(file, data, read_size); | ||||||
|  |             for(uint16_t i = 0; i < readed_size; i++) { | ||||||
|  |                 printf("%c", data[i]); | ||||||
|  |             } | ||||||
|  |         } while(readed_size > 0); | ||||||
|  |         printf("\r\n"); | ||||||
|  | 
 | ||||||
|  |         free(data); | ||||||
|  |     } else { | ||||||
|  |         storage_cli_print_file_error(path, file); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage_file_close(file); | ||||||
|  |     storage_file_free(file); | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_write(Cli* cli, string_t path, string_t args) { | ||||||
|  |     Storage* api = furi_record_open("storage"); | ||||||
|  |     File* file = storage_file_alloc(api); | ||||||
|  | 
 | ||||||
|  |     uint32_t size; | ||||||
|  |     int parsed_count = sscanf(string_get_cstr(args), "%lu", &size); | ||||||
|  | 
 | ||||||
|  |     if(parsed_count == EOF || parsed_count != 1) { | ||||||
|  |         storage_cli_print_usage(); | ||||||
|  |     } else { | ||||||
|  |         if(storage_file_open(file, string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { | ||||||
|  |             const uint16_t write_size = 8; | ||||||
|  |             uint32_t readed_index = 0; | ||||||
|  |             uint8_t* data = furi_alloc(write_size); | ||||||
|  | 
 | ||||||
|  |             while(true) { | ||||||
|  |                 data[readed_index % write_size] = cli_getc(cli); | ||||||
|  |                 printf("%c", data[readed_index % write_size]); | ||||||
|  |                 fflush(stdout); | ||||||
|  |                 readed_index++; | ||||||
|  | 
 | ||||||
|  |                 if(((readed_index % write_size) == 0)) { | ||||||
|  |                     uint16_t writed_size = storage_file_write(file, data, write_size); | ||||||
|  | 
 | ||||||
|  |                     if(writed_size != write_size) { | ||||||
|  |                         storage_cli_print_file_error(path, file); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } else if(readed_index == size) { | ||||||
|  |                     uint16_t writed_size = storage_file_write(file, data, size % write_size); | ||||||
|  | 
 | ||||||
|  |                     if(writed_size != (size % write_size)) { | ||||||
|  |                         storage_cli_print_file_error(path, file); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if(readed_index == size) { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             printf("\r\n"); | ||||||
|  | 
 | ||||||
|  |             free(data); | ||||||
|  |         } else { | ||||||
|  |             storage_cli_print_file_error(path, file); | ||||||
|  |         } | ||||||
|  |         storage_file_close(file); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage_file_free(file); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_copy(Cli* cli, string_t old_path, string_t args) { | ||||||
|  |     Storage* api = furi_record_open("storage"); | ||||||
|  |     string_t new_path; | ||||||
|  |     string_init(new_path); | ||||||
|  | 
 | ||||||
|  |     if(!args_read_probably_quoted_string_and_trim(args, new_path)) { | ||||||
|  |         storage_cli_print_usage(); | ||||||
|  |     } else { | ||||||
|  |         FS_Error error = | ||||||
|  |             storage_common_copy(api, string_get_cstr(old_path), string_get_cstr(new_path)); | ||||||
|  | 
 | ||||||
|  |         if(error != FSE_OK) { | ||||||
|  |             storage_cli_print_error(error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(new_path); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_remove(Cli* cli, string_t path) { | ||||||
|  |     Storage* api = furi_record_open("storage"); | ||||||
|  |     FS_Error error = storage_common_remove(api, string_get_cstr(path)); | ||||||
|  | 
 | ||||||
|  |     if(error != FSE_OK) { | ||||||
|  |         storage_cli_print_error(error); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli_rename(Cli* cli, string_t old_path, string_t args) { | ||||||
|  |     Storage* api = furi_record_open("storage"); | ||||||
|  |     string_t new_path; | ||||||
|  |     string_init(new_path); | ||||||
|  | 
 | ||||||
|  |     if(!args_read_probably_quoted_string_and_trim(args, new_path)) { | ||||||
|  |         storage_cli_print_usage(); | ||||||
|  |     } else { | ||||||
|  |         FS_Error error = | ||||||
|  |             storage_common_rename(api, string_get_cstr(old_path), string_get_cstr(new_path)); | ||||||
|  | 
 | ||||||
|  |         if(error != FSE_OK) { | ||||||
|  |             storage_cli_print_error(error); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(new_path); | ||||||
|  |     furi_record_close("storage"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_cli(Cli* cli, string_t args, void* context) { | ||||||
|  |     string_t cmd; | ||||||
|  |     string_t path; | ||||||
|  |     string_init(cmd); | ||||||
|  |     string_init(path); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         if(!args_read_string_and_trim(args, cmd)) { | ||||||
|  |             storage_cli_print_usage(); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(!args_read_probably_quoted_string_and_trim(args, path)) { | ||||||
|  |             storage_cli_print_usage(); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "info") == 0) { | ||||||
|  |             storage_cli_info(cli, path); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "format") == 0) { | ||||||
|  |             storage_cli_format(cli, path); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "list") == 0) { | ||||||
|  |             storage_cli_list(cli, path); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "read") == 0) { | ||||||
|  |             storage_cli_read(cli, path); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "write") == 0) { | ||||||
|  |             storage_cli_write(cli, path, args); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "copy") == 0) { | ||||||
|  |             storage_cli_copy(cli, path, args); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "remove") == 0) { | ||||||
|  |             storage_cli_remove(cli, path); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(string_cmp_str(cmd, "rename") == 0) { | ||||||
|  |             storage_cli_rename(cli, path, args); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         storage_cli_print_usage(); | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     string_clear(path); | ||||||
|  |     string_clear(cmd); | ||||||
|  | } | ||||||
							
								
								
									
										383
									
								
								applications/storage/storage-external-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								applications/storage/storage-external-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,383 @@ | |||||||
|  | #include "storage.h" | ||||||
|  | #include "storage-i.h" | ||||||
|  | #include "storage-message.h" | ||||||
|  | 
 | ||||||
|  | #define S_API_PROLOGUE                                      \ | ||||||
|  |     osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \ | ||||||
|  |     furi_check(semaphore != NULL); | ||||||
|  | 
 | ||||||
|  | #define S_FILE_API_PROLOGUE           \ | ||||||
|  |     Storage* storage = file->storage; \ | ||||||
|  |     furi_assert(storage); | ||||||
|  | 
 | ||||||
|  | #define S_API_EPILOGUE                                                                         \ | ||||||
|  |     furi_check(osMessageQueuePut(storage->message_queue, &message, 0, osWaitForever) == osOK); \ | ||||||
|  |     osSemaphoreAcquire(semaphore, osWaitForever);                                              \ | ||||||
|  |     osSemaphoreDelete(semaphore); | ||||||
|  | 
 | ||||||
|  | #define S_API_MESSAGE(_command)      \ | ||||||
|  |     SAReturn return_data;            \ | ||||||
|  |     StorageMessage message = {       \ | ||||||
|  |         .semaphore = semaphore,      \ | ||||||
|  |         .command = _command,         \ | ||||||
|  |         .data = &data,               \ | ||||||
|  |         .return_data = &return_data, \ | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  | #define S_API_DATA_FILE   \ | ||||||
|  |     SAData data = {       \ | ||||||
|  |         .file = {         \ | ||||||
|  |             .file = file, \ | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  | #define S_API_DATA_PATH   \ | ||||||
|  |     SAData data = {       \ | ||||||
|  |         .path = {         \ | ||||||
|  |             .path = path, \ | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  | #define S_RETURN_BOOL (return_data.bool_value); | ||||||
|  | #define S_RETURN_UINT16 (return_data.uint16_value); | ||||||
|  | #define S_RETURN_UINT64 (return_data.uint64_value); | ||||||
|  | #define S_RETURN_ERROR (return_data.error_value); | ||||||
|  | #define S_RETURN_CSTRING (return_data.cstring_value); | ||||||
|  | 
 | ||||||
|  | #define FILE_OPENED 1 | ||||||
|  | #define FILE_CLOSED 0 | ||||||
|  | 
 | ||||||
|  | /****************** FILE ******************/ | ||||||
|  | 
 | ||||||
|  | bool storage_file_open( | ||||||
|  |     File* file, | ||||||
|  |     const char* path, | ||||||
|  |     FS_AccessMode access_mode, | ||||||
|  |     FS_OpenMode open_mode) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .fopen = { | ||||||
|  |             .file = file, | ||||||
|  |             .path = path, | ||||||
|  |             .access_mode = access_mode, | ||||||
|  |             .open_mode = open_mode, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     file->file_id = FILE_OPENED; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandFileOpen); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  | 
 | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_file_close(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandFileClose); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  | 
 | ||||||
|  |     file->file_id = FILE_CLOSED; | ||||||
|  | 
 | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .fread = { | ||||||
|  |             .file = file, | ||||||
|  |             .buff = buff, | ||||||
|  |             .bytes_to_read = bytes_to_read, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandFileRead); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_UINT16; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .fwrite = { | ||||||
|  |             .file = file, | ||||||
|  |             .buff = buff, | ||||||
|  |             .bytes_to_write = bytes_to_write, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandFileWrite); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_UINT16; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_file_seek(File* file, uint32_t offset, bool from_start) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .fseek = { | ||||||
|  |             .file = file, | ||||||
|  |             .offset = offset, | ||||||
|  |             .from_start = from_start, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandFileSeek); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint64_t storage_file_tell(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandFileTell); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_UINT64; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_file_truncate(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandFileTruncate); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint64_t storage_file_size(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandFileSize); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_UINT64; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_file_sync(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandFileSync); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_file_eof(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandFileEof); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** DIR ******************/ | ||||||
|  | 
 | ||||||
|  | bool storage_dir_open(File* file, const char* path) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .dopen = { | ||||||
|  |             .file = file, | ||||||
|  |             .path = path, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     file->file_id = FILE_OPENED; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandDirOpen); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_dir_close(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandDirClose); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  | 
 | ||||||
|  |     file->file_id = FILE_CLOSED; | ||||||
|  | 
 | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .dread = { | ||||||
|  |             .file = file, | ||||||
|  |             .fileinfo = fileinfo, | ||||||
|  |             .name = name, | ||||||
|  |             .name_length = name_length, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandDirRead); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_dir_rewind(File* file) { | ||||||
|  |     S_FILE_API_PROLOGUE; | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_FILE; | ||||||
|  |     S_API_MESSAGE(StorageCommandDirRewind); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_BOOL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** COMMON ******************/ | ||||||
|  | 
 | ||||||
|  | FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = {.cstat = {.path = path, .fileinfo = fileinfo}}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandCommonStat); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_common_remove(Storage* storage, const char* path) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_PATH; | ||||||
|  |     S_API_MESSAGE(StorageCommandCommonRemove); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .cpaths = { | ||||||
|  |             .old = old_path, | ||||||
|  |             .new = new_path, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandCommonRename); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .cpaths = { | ||||||
|  |             .old = old_path, | ||||||
|  |             .new = new_path, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandCommonCopy); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_common_mkdir(Storage* storage, const char* path) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     S_API_DATA_PATH; | ||||||
|  |     S_API_MESSAGE(StorageCommandCommonMkDir); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_common_fs_info( | ||||||
|  |     Storage* storage, | ||||||
|  |     const char* fs_path, | ||||||
|  |     uint64_t* total_space, | ||||||
|  |     uint64_t* free_space) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  | 
 | ||||||
|  |     SAData data = { | ||||||
|  |         .cfsinfo = { | ||||||
|  |             .fs_path = fs_path, | ||||||
|  |             .total_space = total_space, | ||||||
|  |             .free_space = free_space, | ||||||
|  |         }}; | ||||||
|  | 
 | ||||||
|  |     S_API_MESSAGE(StorageCommandCommonFSInfo); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** ERROR ******************/ | ||||||
|  | 
 | ||||||
|  | const char* storage_error_get_desc(FS_Error error_id) { | ||||||
|  |     return filesystem_api_error_get_desc(error_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_file_get_error(File* file) { | ||||||
|  |     furi_check(file != NULL); | ||||||
|  |     return file->error_id; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char* storage_file_get_error_desc(File* file) { | ||||||
|  |     furi_check(file != NULL); | ||||||
|  |     return filesystem_api_error_get_desc(file->error_id); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** Raw SD API ******************/ | ||||||
|  | 
 | ||||||
|  | FS_Error storage_sd_format(Storage* storage) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     SAData data = {}; | ||||||
|  |     S_API_MESSAGE(StorageCommandSDFormat); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_sd_unmount(Storage* storage) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     SAData data = {}; | ||||||
|  |     S_API_MESSAGE(StorageCommandSDUnmount); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_sd_info(Storage* storage, SDInfo* info) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     SAData data = { | ||||||
|  |         .sdinfo = { | ||||||
|  |             .info = info, | ||||||
|  |         }}; | ||||||
|  |     S_API_MESSAGE(StorageCommandSDInfo); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error storage_sd_status(Storage* storage) { | ||||||
|  |     S_API_PROLOGUE; | ||||||
|  |     SAData data = {}; | ||||||
|  |     S_API_MESSAGE(StorageCommandSDStatus); | ||||||
|  |     S_API_EPILOGUE; | ||||||
|  |     return S_RETURN_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | File* storage_file_alloc(Storage* storage) { | ||||||
|  |     File* file = furi_alloc(sizeof(File)); | ||||||
|  |     file->file_id = FILE_CLOSED; | ||||||
|  |     file->storage = storage; | ||||||
|  | 
 | ||||||
|  |     return file; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_file_is_open(File* file) { | ||||||
|  |     return (file->file_id != FILE_CLOSED); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_file_free(File* file) { | ||||||
|  |     if(storage_file_is_open(file)) { | ||||||
|  |         storage_file_close(file); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     free(file); | ||||||
|  | } | ||||||
							
								
								
									
										212
									
								
								applications/storage/storage-glue.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								applications/storage/storage-glue.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,212 @@ | |||||||
|  | #include "storage-glue.h" | ||||||
|  | #include <api-hal.h> | ||||||
|  | 
 | ||||||
|  | /****************** storage file ******************/ | ||||||
|  | 
 | ||||||
|  | void storage_file_init(StorageFile* obj) { | ||||||
|  |     obj->file = NULL; | ||||||
|  |     obj->type = ST_ERROR; | ||||||
|  |     obj->file_data = NULL; | ||||||
|  |     string_init(obj->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_file_init_set(StorageFile* obj, const StorageFile* src) { | ||||||
|  |     obj->file = src->file; | ||||||
|  |     obj->type = src->type; | ||||||
|  |     obj->file_data = src->file_data; | ||||||
|  |     string_init_set(obj->path, src->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_file_set(StorageFile* obj, const StorageFile* src) { | ||||||
|  |     obj->file = src->file; | ||||||
|  |     obj->type = src->type; | ||||||
|  |     obj->file_data = src->file_data; | ||||||
|  |     string_set(obj->path, src->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_file_clear(StorageFile* obj) { | ||||||
|  |     string_clear(obj->path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** storage data ******************/ | ||||||
|  | 
 | ||||||
|  | void storage_data_init(StorageData* storage) { | ||||||
|  |     storage->mutex = osMutexNew(NULL); | ||||||
|  |     furi_check(storage->mutex != NULL); | ||||||
|  |     storage->data = NULL; | ||||||
|  |     storage->status = StorageStatusNotReady; | ||||||
|  |     StorageFileList_init(storage->files); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_data_lock(StorageData* storage) { | ||||||
|  |     api_hal_power_insomnia_enter(); | ||||||
|  |     return (osMutexAcquire(storage->mutex, osWaitForever) == osOK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_data_unlock(StorageData* storage) { | ||||||
|  |     api_hal_power_insomnia_exit(); | ||||||
|  |     return (osMutexRelease(storage->mutex) == osOK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | StorageStatus storage_data_status(StorageData* storage) { | ||||||
|  |     StorageStatus status; | ||||||
|  | 
 | ||||||
|  |     storage_data_lock(storage); | ||||||
|  |     status = storage->status; | ||||||
|  |     storage_data_unlock(storage); | ||||||
|  | 
 | ||||||
|  |     return status; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char* storage_data_status_text(StorageData* storage) { | ||||||
|  |     const char* result = "unknown"; | ||||||
|  |     switch(storage->status) { | ||||||
|  |     case StorageStatusOK: | ||||||
|  |         result = "ok"; | ||||||
|  |         break; | ||||||
|  |     case StorageStatusNotReady: | ||||||
|  |         result = "not ready"; | ||||||
|  |         break; | ||||||
|  |     case StorageStatusNotMounted: | ||||||
|  |         result = "not mounted"; | ||||||
|  |         break; | ||||||
|  |     case StorageStatusNoFS: | ||||||
|  |         result = "no filesystem"; | ||||||
|  |         break; | ||||||
|  |     case StorageStatusNotAccessible: | ||||||
|  |         result = "not accessible"; | ||||||
|  |         break; | ||||||
|  |     case StorageStatusErrorInternal: | ||||||
|  |         result = "internal"; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** storage glue ******************/ | ||||||
|  | 
 | ||||||
|  | bool storage_has_file(const File* file, StorageData* storage_data) { | ||||||
|  |     bool result = false; | ||||||
|  | 
 | ||||||
|  |     StorageFileList_it_t it; | ||||||
|  |     for(StorageFileList_it(it, storage_data->files); !StorageFileList_end_p(it); | ||||||
|  |         StorageFileList_next(it)) { | ||||||
|  |         const StorageFile* storage_file = StorageFileList_cref(it); | ||||||
|  | 
 | ||||||
|  |         if(storage_file->file->file_id == file->file_id) { | ||||||
|  |             result = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | StorageType storage_get_type_by_path(const char* path) { | ||||||
|  |     StorageType type = ST_ERROR; | ||||||
|  | 
 | ||||||
|  |     const char* ext_path = "/ext"; | ||||||
|  |     const char* int_path = "/int"; | ||||||
|  |     const char* any_path = "/any"; | ||||||
|  | 
 | ||||||
|  |     if(strlen(path) >= strlen(ext_path) && memcmp(path, ext_path, strlen(ext_path)) == 0) { | ||||||
|  |         type = ST_EXT; | ||||||
|  |     } else if(strlen(path) >= strlen(int_path) && memcmp(path, int_path, strlen(int_path)) == 0) { | ||||||
|  |         type = ST_INT; | ||||||
|  |     } else if(strlen(path) >= strlen(any_path) && memcmp(path, any_path, strlen(any_path)) == 0) { | ||||||
|  |         type = ST_ANY; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return type; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_path_already_open(const char* path, StorageFileList_t array) { | ||||||
|  |     bool open = false; | ||||||
|  | 
 | ||||||
|  |     StorageFileList_it_t it; | ||||||
|  | 
 | ||||||
|  |     for(StorageFileList_it(it, array); !StorageFileList_end_p(it); StorageFileList_next(it)) { | ||||||
|  |         const StorageFile* storage_file = StorageFileList_cref(it); | ||||||
|  | 
 | ||||||
|  |         if(string_cmp(storage_file->path, path) == 0) { | ||||||
|  |             open = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return open; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage) { | ||||||
|  |     StorageFile* founded_file = NULL; | ||||||
|  | 
 | ||||||
|  |     StorageFileList_it_t it; | ||||||
|  | 
 | ||||||
|  |     for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); | ||||||
|  |         StorageFileList_next(it)) { | ||||||
|  |         StorageFile* storage_file = StorageFileList_ref(it); | ||||||
|  | 
 | ||||||
|  |         if(storage_file->file->file_id == file->file_id) { | ||||||
|  |             founded_file = storage_file; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_check(founded_file != NULL); | ||||||
|  | 
 | ||||||
|  |     founded_file->file_data = file_data; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void* storage_get_storage_file_data(const File* file, StorageData* storage) { | ||||||
|  |     const StorageFile* founded_file = NULL; | ||||||
|  | 
 | ||||||
|  |     StorageFileList_it_t it; | ||||||
|  | 
 | ||||||
|  |     for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); | ||||||
|  |         StorageFileList_next(it)) { | ||||||
|  |         const StorageFile* storage_file = StorageFileList_cref(it); | ||||||
|  | 
 | ||||||
|  |         if(storage_file->file->file_id == file->file_id) { | ||||||
|  |             founded_file = storage_file; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     furi_check(founded_file != NULL); | ||||||
|  | 
 | ||||||
|  |     return founded_file->file_data; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_push_storage_file( | ||||||
|  |     File* file, | ||||||
|  |     const char* path, | ||||||
|  |     StorageType type, | ||||||
|  |     StorageData* storage) { | ||||||
|  |     StorageFile* storage_file = StorageFileList_push_new(storage->files); | ||||||
|  |     furi_check(storage_file != NULL); | ||||||
|  | 
 | ||||||
|  |     file->file_id = (uint32_t)storage_file; | ||||||
|  |     storage_file->file = file; | ||||||
|  |     storage_file->type = type; | ||||||
|  |     string_set(storage_file->path, path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_pop_storage_file(File* file, StorageData* storage) { | ||||||
|  |     StorageFileList_it_t it; | ||||||
|  |     bool result = false; | ||||||
|  | 
 | ||||||
|  |     for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); | ||||||
|  |         StorageFileList_next(it)) { | ||||||
|  |         if(StorageFileList_cref(it)->file->file_id == file->file_id) { | ||||||
|  |             result = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(result) { | ||||||
|  |         StorageFileList_remove(storage->files, it); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								applications/storage/storage-glue.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								applications/storage/storage-glue.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "filesystem-api-internal.h" | ||||||
|  | #include <m-string.h> | ||||||
|  | #include <m-array.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef enum { ST_EXT = 0, ST_INT = 1, ST_ANY, ST_ERROR } StorageType; | ||||||
|  | 
 | ||||||
|  | typedef struct StorageData StorageData; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     void (*tick)(StorageData* storage); | ||||||
|  | } StorageApi; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  |     StorageType type; | ||||||
|  |     void* file_data; | ||||||
|  |     string_t path; | ||||||
|  | } StorageFile; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     StorageStatusOK, /**< storage ok */ | ||||||
|  |     StorageStatusNotReady, /**< storage not ready (not initialized or waiting for data storage to appear) */ | ||||||
|  |     StorageStatusNotMounted, /**< datastore appeared, but we cannot mount it */ | ||||||
|  |     StorageStatusNoFS, /**< datastore appeared and mounted, but does not have a file system */ | ||||||
|  |     StorageStatusNotAccessible, /**< datastore appeared and mounted, but not available */ | ||||||
|  |     StorageStatusErrorInternal, /**< any other internal error */ | ||||||
|  | } StorageStatus; | ||||||
|  | 
 | ||||||
|  | void storage_file_init(StorageFile* obj); | ||||||
|  | void storage_file_init_set(StorageFile* obj, const StorageFile* src); | ||||||
|  | void storage_file_set(StorageFile* obj, const StorageFile* src); | ||||||
|  | void storage_file_clear(StorageFile* obj); | ||||||
|  | 
 | ||||||
|  | void storage_data_init(StorageData* storage); | ||||||
|  | bool storage_data_lock(StorageData* storage); | ||||||
|  | bool storage_data_unlock(StorageData* storage); | ||||||
|  | StorageStatus storage_data_status(StorageData* storage); | ||||||
|  | const char* storage_data_status_text(StorageData* storage); | ||||||
|  | 
 | ||||||
|  | LIST_DEF( | ||||||
|  |     StorageFileList, | ||||||
|  |     StorageFile, | ||||||
|  |     (INIT(API_2(storage_file_init)), | ||||||
|  |      SET(API_6(storage_file_init_set)), | ||||||
|  |      INIT_SET(API_6(storage_file_set)), | ||||||
|  |      CLEAR(API_2(storage_file_clear)))) | ||||||
|  | 
 | ||||||
|  | struct StorageData { | ||||||
|  |     FS_Api fs_api; | ||||||
|  |     StorageApi api; | ||||||
|  |     void* data; | ||||||
|  |     osMutexId_t mutex; | ||||||
|  |     StorageStatus status; | ||||||
|  |     StorageFileList_t files; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool storage_has_file(const File* file, StorageData* storage_data); | ||||||
|  | StorageType storage_get_type_by_path(const char* path); | ||||||
|  | bool storage_path_already_open(const char* path, StorageFileList_t files); | ||||||
|  | 
 | ||||||
|  | void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); | ||||||
|  | void* storage_get_storage_file_data(const File* file, StorageData* storage); | ||||||
|  | 
 | ||||||
|  | void storage_push_storage_file( | ||||||
|  |     File* file, | ||||||
|  |     const char* path, | ||||||
|  |     StorageType type, | ||||||
|  |     StorageData* storage); | ||||||
|  | bool storage_pop_storage_file(File* file, StorageData* storage); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										27
									
								
								applications/storage/storage-i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								applications/storage/storage-i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include "storage-glue.h" | ||||||
|  | #include "storage-sd-api.h" | ||||||
|  | #include "filesystem-api-internal.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #define STORAGE_COUNT (ST_INT + 1) | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     ViewPort* view_port; | ||||||
|  |     bool enabled; | ||||||
|  | } StorageSDGui; | ||||||
|  | 
 | ||||||
|  | struct Storage { | ||||||
|  |     osMessageQueueId_t message_queue; | ||||||
|  |     StorageData storage[STORAGE_COUNT]; | ||||||
|  |     StorageSDGui sd_gui; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										142
									
								
								applications/storage/storage-message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								applications/storage/storage-message.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,142 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  |     const char* path; | ||||||
|  |     FS_AccessMode access_mode; | ||||||
|  |     FS_OpenMode open_mode; | ||||||
|  | } SADataFOpen; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  |     void* buff; | ||||||
|  |     uint16_t bytes_to_read; | ||||||
|  | } SADataFRead; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  |     const void* buff; | ||||||
|  |     uint16_t bytes_to_write; | ||||||
|  | } SADataFWrite; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  |     uint32_t offset; | ||||||
|  |     bool from_start; | ||||||
|  | } SADataFSeek; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  |     const char* path; | ||||||
|  | } SADataDOpen; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  |     FileInfo* fileinfo; | ||||||
|  |     char* name; | ||||||
|  |     uint16_t name_length; | ||||||
|  | } SADataDRead; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const char* path; | ||||||
|  |     FileInfo* fileinfo; | ||||||
|  | } SADataCStat; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const char* old; | ||||||
|  |     const char* new; | ||||||
|  | } SADataCPaths; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const char* fs_path; | ||||||
|  |     uint64_t* total_space; | ||||||
|  |     uint64_t* free_space; | ||||||
|  | } SADataCFSInfo; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t id; | ||||||
|  | } SADataError; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const char* path; | ||||||
|  | } SADataPath; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     File* file; | ||||||
|  | } SADataFile; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     SDInfo* info; | ||||||
|  | } SAInfo; | ||||||
|  | 
 | ||||||
|  | typedef union { | ||||||
|  |     SADataFOpen fopen; | ||||||
|  |     SADataFRead fread; | ||||||
|  |     SADataFWrite fwrite; | ||||||
|  |     SADataFSeek fseek; | ||||||
|  | 
 | ||||||
|  |     SADataDOpen dopen; | ||||||
|  |     SADataDRead dread; | ||||||
|  | 
 | ||||||
|  |     SADataCStat cstat; | ||||||
|  |     SADataCPaths cpaths; | ||||||
|  |     SADataCFSInfo cfsinfo; | ||||||
|  | 
 | ||||||
|  |     SADataError error; | ||||||
|  | 
 | ||||||
|  |     SADataFile file; | ||||||
|  |     SADataPath path; | ||||||
|  | 
 | ||||||
|  |     SAInfo sdinfo; | ||||||
|  | } SAData; | ||||||
|  | 
 | ||||||
|  | typedef union { | ||||||
|  |     bool bool_value; | ||||||
|  |     uint16_t uint16_value; | ||||||
|  |     uint64_t uint64_value; | ||||||
|  |     FS_Error error_value; | ||||||
|  |     const char* cstring_value; | ||||||
|  | } SAReturn; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     StorageCommandFileOpen, | ||||||
|  |     StorageCommandFileClose, | ||||||
|  |     StorageCommandFileRead, | ||||||
|  |     StorageCommandFileWrite, | ||||||
|  |     StorageCommandFileSeek, | ||||||
|  |     StorageCommandFileTell, | ||||||
|  |     StorageCommandFileTruncate, | ||||||
|  |     StorageCommandFileSize, | ||||||
|  |     StorageCommandFileSync, | ||||||
|  |     StorageCommandFileEof, | ||||||
|  |     StorageCommandDirOpen, | ||||||
|  |     StorageCommandDirClose, | ||||||
|  |     StorageCommandDirRead, | ||||||
|  |     StorageCommandDirRewind, | ||||||
|  |     StorageCommandCommonStat, | ||||||
|  |     StorageCommandCommonRemove, | ||||||
|  |     StorageCommandCommonRename, | ||||||
|  |     StorageCommandCommonCopy, | ||||||
|  |     StorageCommandCommonMkDir, | ||||||
|  |     StorageCommandCommonFSInfo, | ||||||
|  |     StorageCommandSDFormat, | ||||||
|  |     StorageCommandSDUnmount, | ||||||
|  |     StorageCommandSDInfo, | ||||||
|  |     StorageCommandSDStatus, | ||||||
|  | } StorageCommand; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     osSemaphoreId_t semaphore; | ||||||
|  |     StorageCommand command; | ||||||
|  |     SAData* data; | ||||||
|  |     SAReturn* return_data; | ||||||
|  | } StorageMessage; | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										584
									
								
								applications/storage/storage-processing.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										584
									
								
								applications/storage/storage-processing.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,584 @@ | |||||||
|  | #include "storage-processing.h" | ||||||
|  | 
 | ||||||
|  | #define FS_CALL(_storage, _fn)   \ | ||||||
|  |     storage_data_lock(_storage); \ | ||||||
|  |     ret = _storage->fs_api._fn;  \ | ||||||
|  |     storage_data_unlock(_storage); | ||||||
|  | 
 | ||||||
|  | #define ST_CALL(_storage, _fn)   \ | ||||||
|  |     storage_data_lock(_storage); \ | ||||||
|  |     ret = _storage->api._fn;     \ | ||||||
|  |     storage_data_unlock(_storage); | ||||||
|  | 
 | ||||||
|  | static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { | ||||||
|  |     StorageData* storage; | ||||||
|  | 
 | ||||||
|  |     if(type == ST_ANY) { | ||||||
|  |         type = ST_INT; | ||||||
|  |         StorageData* ext_storage = &app->storage[ST_EXT]; | ||||||
|  | 
 | ||||||
|  |         if(storage_data_status(ext_storage) == StorageStatusOK) { | ||||||
|  |             type = ST_EXT; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     storage = &app->storage[type]; | ||||||
|  | 
 | ||||||
|  |     return storage; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_type_is_not_valid(StorageType type) { | ||||||
|  |     return type >= ST_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static StorageData* get_storage_by_file(File* file, StorageData* storages) { | ||||||
|  |     StorageData* storage_data = NULL; | ||||||
|  | 
 | ||||||
|  |     for(uint8_t i = 0; i < STORAGE_COUNT; i++) { | ||||||
|  |         if(storage_has_file(file, &storages[i])) { | ||||||
|  |             storage_data = &storages[i]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return storage_data; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char* remove_vfs(const char* path) { | ||||||
|  |     return path + MIN(4, strlen(path)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* File Functions *******************/ | ||||||
|  | 
 | ||||||
|  | bool storage_process_file_open( | ||||||
|  |     Storage* app, | ||||||
|  |     File* file, | ||||||
|  |     const char* path, | ||||||
|  |     FS_AccessMode access_mode, | ||||||
|  |     FS_OpenMode open_mode) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageType type = storage_get_type_by_path(path); | ||||||
|  |     StorageData* storage; | ||||||
|  |     file->error_id = FSE_OK; | ||||||
|  | 
 | ||||||
|  |     if(storage_type_is_not_valid(type)) { | ||||||
|  |         file->error_id = FSE_INVALID_NAME; | ||||||
|  |     } else { | ||||||
|  |         storage = storage_get_storage_by_type(app, type); | ||||||
|  |         if(storage_path_already_open(path, storage->files)) { | ||||||
|  |             file->error_id = FSE_ALREADY_OPEN; | ||||||
|  |         } else { | ||||||
|  |             storage_push_storage_file(file, path, type, storage); | ||||||
|  |             FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_process_file_close(Storage* app, File* file) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.close(storage, file)); | ||||||
|  |         storage_pop_storage_file(file, storage); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint16_t | ||||||
|  |     storage_process_file_read(Storage* app, File* file, void* buff, uint16_t const bytes_to_read) { | ||||||
|  |     uint16_t ret = 0; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.read(storage, file, buff, bytes_to_read)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint16_t storage_process_file_write( | ||||||
|  |     Storage* app, | ||||||
|  |     File* file, | ||||||
|  |     const void* buff, | ||||||
|  |     uint16_t const bytes_to_write) { | ||||||
|  |     uint16_t ret = 0; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.write(storage, file, buff, bytes_to_write)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_process_file_seek( | ||||||
|  |     Storage* app, | ||||||
|  |     File* file, | ||||||
|  |     const uint32_t offset, | ||||||
|  |     const bool from_start) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.seek(storage, file, offset, from_start)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint64_t storage_process_file_tell(Storage* app, File* file) { | ||||||
|  |     uint64_t ret = 0; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.tell(storage, file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_process_file_truncate(Storage* app, File* file) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.truncate(storage, file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_process_file_sync(Storage* app, File* file) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.sync(storage, file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint64_t storage_process_file_size(Storage* app, File* file) { | ||||||
|  |     uint64_t ret = 0; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.size(storage, file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_process_file_eof(Storage* app, File* file) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, file.eof(storage, file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Dir Functions *******************/ | ||||||
|  | 
 | ||||||
|  | bool storage_process_dir_open(Storage* app, File* file, const char* path) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageType type = storage_get_type_by_path(path); | ||||||
|  |     StorageData* storage; | ||||||
|  |     file->error_id = FSE_OK; | ||||||
|  | 
 | ||||||
|  |     if(storage_type_is_not_valid(type)) { | ||||||
|  |         file->error_id = FSE_INVALID_NAME; | ||||||
|  |     } else { | ||||||
|  |         storage = storage_get_storage_by_type(app, type); | ||||||
|  |         if(storage_path_already_open(path, storage->files)) { | ||||||
|  |             file->error_id = FSE_ALREADY_OPEN; | ||||||
|  |         } else { | ||||||
|  |             storage_push_storage_file(file, path, type, storage); | ||||||
|  |             FS_CALL(storage, dir.open(storage, file, remove_vfs(path))); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_process_dir_close(Storage* app, File* file) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, dir.close(storage, file)); | ||||||
|  |         storage_pop_storage_file(file, storage); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_process_dir_read( | ||||||
|  |     Storage* app, | ||||||
|  |     File* file, | ||||||
|  |     FileInfo* fileinfo, | ||||||
|  |     char* name, | ||||||
|  |     const uint16_t name_length) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool storage_process_dir_rewind(Storage* app, File* file) { | ||||||
|  |     bool ret = false; | ||||||
|  |     StorageData* storage = get_storage_by_file(file, app->storage); | ||||||
|  | 
 | ||||||
|  |     if(storage == NULL) { | ||||||
|  |         file->error_id = FSE_INVALID_PARAMETER; | ||||||
|  |     } else { | ||||||
|  |         FS_CALL(storage, dir.rewind(storage, file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Common FS Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) { | ||||||
|  |     FS_Error ret = FSE_OK; | ||||||
|  |     StorageType type = storage_get_type_by_path(path); | ||||||
|  | 
 | ||||||
|  |     if(storage_type_is_not_valid(type)) { | ||||||
|  |         ret = FSE_INVALID_NAME; | ||||||
|  |     } else { | ||||||
|  |         StorageData* storage = storage_get_storage_by_type(app, type); | ||||||
|  |         FS_CALL(storage, common.stat(storage, remove_vfs(path), fileinfo)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_common_remove(Storage* app, const char* path) { | ||||||
|  |     FS_Error ret = FSE_OK; | ||||||
|  |     StorageType type = storage_get_type_by_path(path); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         if(storage_type_is_not_valid(type)) { | ||||||
|  |             ret = FSE_INVALID_NAME; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         StorageData* storage = storage_get_storage_by_type(app, type); | ||||||
|  |         if(storage_path_already_open(path, storage->files)) { | ||||||
|  |             ret = FSE_ALREADY_OPEN; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         FS_CALL(storage, common.remove(storage, remove_vfs(path))); | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_common_copy(Storage* app, const char* old, const char* new) { | ||||||
|  |     FS_Error ret = FSE_INTERNAL; | ||||||
|  |     File file_old; | ||||||
|  |     File file_new; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         if(!storage_process_file_open(app, &file_old, old, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|  |             ret = storage_file_get_error(&file_old); | ||||||
|  |             storage_process_file_close(app, &file_old); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(!storage_process_file_open(app, &file_new, new, FSAM_WRITE, FSOM_CREATE_NEW)) { | ||||||
|  |             ret = storage_file_get_error(&file_new); | ||||||
|  |             storage_process_file_close(app, &file_new); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const uint16_t buffer_size = 64; | ||||||
|  |         uint8_t* buffer = malloc(buffer_size); | ||||||
|  |         uint16_t readed_size = 0; | ||||||
|  |         uint16_t writed_size = 0; | ||||||
|  | 
 | ||||||
|  |         while(true) { | ||||||
|  |             readed_size = storage_process_file_read(app, &file_old, buffer, buffer_size); | ||||||
|  |             ret = storage_file_get_error(&file_old); | ||||||
|  |             if(readed_size == 0) break; | ||||||
|  | 
 | ||||||
|  |             writed_size = storage_process_file_write(app, &file_new, buffer, readed_size); | ||||||
|  |             ret = storage_file_get_error(&file_new); | ||||||
|  |             if(writed_size < readed_size) break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         free(buffer); | ||||||
|  |         storage_process_file_close(app, &file_old); | ||||||
|  |         storage_process_file_close(app, &file_new); | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_common_rename(Storage* app, const char* old, const char* new) { | ||||||
|  |     FS_Error ret = FSE_INTERNAL; | ||||||
|  |     StorageType type_old = storage_get_type_by_path(old); | ||||||
|  |     StorageType type_new = storage_get_type_by_path(new); | ||||||
|  | 
 | ||||||
|  |     if(storage_type_is_not_valid(type_old) || storage_type_is_not_valid(type_old)) { | ||||||
|  |         ret = FSE_INVALID_NAME; | ||||||
|  |     } else { | ||||||
|  |         if(type_old != type_new) { | ||||||
|  |             ret = storage_process_common_copy(app, old, new); | ||||||
|  |             if(ret == FSE_OK) { | ||||||
|  |                 ret = storage_process_common_remove(app, old); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             StorageData* storage = storage_get_storage_by_type(app, type_old); | ||||||
|  |             FS_CALL(storage, common.rename(storage, remove_vfs(old), remove_vfs(new))); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { | ||||||
|  |     FS_Error ret = FSE_OK; | ||||||
|  |     StorageType type = storage_get_type_by_path(path); | ||||||
|  | 
 | ||||||
|  |     if(storage_type_is_not_valid(type)) { | ||||||
|  |         ret = FSE_INVALID_NAME; | ||||||
|  |     } else { | ||||||
|  |         StorageData* storage = storage_get_storage_by_type(app, type); | ||||||
|  |         FS_CALL(storage, common.mkdir(storage, remove_vfs(path))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_common_fs_info( | ||||||
|  |     Storage* app, | ||||||
|  |     const char* fs_path, | ||||||
|  |     uint64_t* total_space, | ||||||
|  |     uint64_t* free_space) { | ||||||
|  |     FS_Error ret = FSE_OK; | ||||||
|  |     StorageType type = storage_get_type_by_path(fs_path); | ||||||
|  | 
 | ||||||
|  |     if(storage_type_is_not_valid(type)) { | ||||||
|  |         ret = FSE_INVALID_NAME; | ||||||
|  |     } else { | ||||||
|  |         StorageData* storage = storage_get_storage_by_type(app, type); | ||||||
|  |         FS_CALL(storage, common.fs_info(storage, remove_vfs(fs_path), total_space, free_space)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** Raw SD API ******************/ | ||||||
|  | // TODO think about implementing a custom storage API to split that kind of api linkage
 | ||||||
|  | #include "storages/storage-ext.h" | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_sd_format(Storage* app) { | ||||||
|  |     FS_Error ret = FSE_OK; | ||||||
|  | 
 | ||||||
|  |     if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) { | ||||||
|  |         ret = FSE_NOT_READY; | ||||||
|  |     } else { | ||||||
|  |         ret = sd_format_card(&app->storage[ST_EXT]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_sd_unmount(Storage* app) { | ||||||
|  |     FS_Error ret = FSE_OK; | ||||||
|  | 
 | ||||||
|  |     if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) { | ||||||
|  |         ret = FSE_NOT_READY; | ||||||
|  |     } else { | ||||||
|  |         sd_unmount_card(&app->storage[ST_EXT]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_sd_info(Storage* app, SDInfo* info) { | ||||||
|  |     FS_Error ret = FSE_OK; | ||||||
|  | 
 | ||||||
|  |     if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) { | ||||||
|  |         ret = FSE_NOT_READY; | ||||||
|  |     } else { | ||||||
|  |         ret = sd_card_info(&app->storage[ST_EXT], info); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_process_sd_status(Storage* app) { | ||||||
|  |     FS_Error ret; | ||||||
|  |     StorageStatus status = storage_data_status(&app->storage[ST_EXT]); | ||||||
|  | 
 | ||||||
|  |     switch(status) { | ||||||
|  |     case StorageStatusOK: | ||||||
|  |         ret = FSE_OK; | ||||||
|  |         break; | ||||||
|  |     case StorageStatusNotReady: | ||||||
|  |         ret = FSE_NOT_READY; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         ret = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** API calls processing ******************/ | ||||||
|  | 
 | ||||||
|  | void storage_process_message(Storage* app, StorageMessage* message) { | ||||||
|  |     switch(message->command) { | ||||||
|  |     case StorageCommandFileOpen: | ||||||
|  |         message->return_data->bool_value = storage_process_file_open( | ||||||
|  |             app, | ||||||
|  |             message->data->fopen.file, | ||||||
|  |             message->data->fopen.path, | ||||||
|  |             message->data->fopen.access_mode, | ||||||
|  |             message->data->fopen.open_mode); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileClose: | ||||||
|  |         message->return_data->bool_value = | ||||||
|  |             storage_process_file_close(app, message->data->fopen.file); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileRead: | ||||||
|  |         message->return_data->uint16_value = storage_process_file_read( | ||||||
|  |             app, | ||||||
|  |             message->data->fread.file, | ||||||
|  |             message->data->fread.buff, | ||||||
|  |             message->data->fread.bytes_to_read); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileWrite: | ||||||
|  |         message->return_data->uint16_value = storage_process_file_write( | ||||||
|  |             app, | ||||||
|  |             message->data->fwrite.file, | ||||||
|  |             message->data->fwrite.buff, | ||||||
|  |             message->data->fwrite.bytes_to_write); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileSeek: | ||||||
|  |         message->return_data->bool_value = storage_process_file_seek( | ||||||
|  |             app, | ||||||
|  |             message->data->fseek.file, | ||||||
|  |             message->data->fseek.offset, | ||||||
|  |             message->data->fseek.from_start); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileTell: | ||||||
|  |         message->return_data->uint64_value = | ||||||
|  |             storage_process_file_tell(app, message->data->file.file); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileTruncate: | ||||||
|  |         message->return_data->bool_value = | ||||||
|  |             storage_process_file_truncate(app, message->data->file.file); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileSync: | ||||||
|  |         message->return_data->bool_value = | ||||||
|  |             storage_process_file_sync(app, message->data->file.file); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileSize: | ||||||
|  |         message->return_data->uint64_value = | ||||||
|  |             storage_process_file_size(app, message->data->file.file); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandFileEof: | ||||||
|  |         message->return_data->bool_value = storage_process_file_eof(app, message->data->file.file); | ||||||
|  |         break; | ||||||
|  | 
 | ||||||
|  |     case StorageCommandDirOpen: | ||||||
|  |         message->return_data->bool_value = | ||||||
|  |             storage_process_dir_open(app, message->data->dopen.file, message->data->dopen.path); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandDirClose: | ||||||
|  |         message->return_data->bool_value = | ||||||
|  |             storage_process_dir_close(app, message->data->file.file); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandDirRead: | ||||||
|  |         message->return_data->bool_value = storage_process_dir_read( | ||||||
|  |             app, | ||||||
|  |             message->data->dread.file, | ||||||
|  |             message->data->dread.fileinfo, | ||||||
|  |             message->data->dread.name, | ||||||
|  |             message->data->dread.name_length); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandDirRewind: | ||||||
|  |         message->return_data->bool_value = | ||||||
|  |             storage_process_dir_rewind(app, message->data->file.file); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandCommonStat: | ||||||
|  |         message->return_data->error_value = storage_process_common_stat( | ||||||
|  |             app, message->data->cstat.path, message->data->cstat.fileinfo); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandCommonRemove: | ||||||
|  |         message->return_data->error_value = | ||||||
|  |             storage_process_common_remove(app, message->data->path.path); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandCommonRename: | ||||||
|  |         message->return_data->error_value = storage_process_common_rename( | ||||||
|  |             app, message->data->cpaths.old, message->data->cpaths.new); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandCommonCopy: | ||||||
|  |         message->return_data->error_value = | ||||||
|  |             storage_process_common_copy(app, message->data->cpaths.old, message->data->cpaths.new); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandCommonMkDir: | ||||||
|  |         message->return_data->error_value = | ||||||
|  |             storage_process_common_mkdir(app, message->data->path.path); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandCommonFSInfo: | ||||||
|  |         message->return_data->error_value = storage_process_common_fs_info( | ||||||
|  |             app, | ||||||
|  |             message->data->cfsinfo.fs_path, | ||||||
|  |             message->data->cfsinfo.total_space, | ||||||
|  |             message->data->cfsinfo.free_space); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandSDFormat: | ||||||
|  |         message->return_data->error_value = storage_process_sd_format(app); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandSDUnmount: | ||||||
|  |         message->return_data->error_value = storage_process_sd_unmount(app); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandSDInfo: | ||||||
|  |         message->return_data->error_value = | ||||||
|  |             storage_process_sd_info(app, message->data->sdinfo.info); | ||||||
|  |         break; | ||||||
|  |     case StorageCommandSDStatus: | ||||||
|  |         message->return_data->error_value = storage_process_sd_status(app); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     osSemaphoreRelease(message->semaphore); | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								applications/storage/storage-processing.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								applications/storage/storage-processing.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "storage.h" | ||||||
|  | #include "storage-i.h" | ||||||
|  | #include "storage-message.h" | ||||||
|  | #include "storage-glue.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | void storage_process_message(Storage* app, StorageMessage* message); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										21
									
								
								applications/storage/storage-sd-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								applications/storage/storage-sd-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | #include "storage-sd-api.h" | ||||||
|  | 
 | ||||||
|  | const char* sd_api_get_fs_type_text(SDFsType fs_type) { | ||||||
|  |     switch(fs_type) { | ||||||
|  |     case(FST_FAT12): | ||||||
|  |         return "FAT12"; | ||||||
|  |         break; | ||||||
|  |     case(FST_FAT16): | ||||||
|  |         return "FAT16"; | ||||||
|  |         break; | ||||||
|  |     case(FST_FAT32): | ||||||
|  |         return "FAT32"; | ||||||
|  |         break; | ||||||
|  |     case(FST_EXFAT): | ||||||
|  |         return "EXFAT"; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         return "UNKNOWN"; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								applications/storage/storage-sd-api.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								applications/storage/storage-sd-api.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "filesystem-api-defines.h" | ||||||
|  | #include <fatfs.h> | ||||||
|  | #include "storage-glue.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #define SD_LABEL_LENGTH 34 | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     FST_FAT12 = FS_FAT12, | ||||||
|  |     FST_FAT16 = FS_FAT16, | ||||||
|  |     FST_FAT32 = FS_FAT32, | ||||||
|  |     FST_EXFAT = FS_EXFAT, | ||||||
|  | } SDFsType; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     SDFsType fs_type; | ||||||
|  |     uint32_t kb_total; | ||||||
|  |     uint32_t kb_free; | ||||||
|  |     uint16_t cluster_size; | ||||||
|  |     uint16_t sector_size; | ||||||
|  |     char label[SD_LABEL_LENGTH]; | ||||||
|  |     FS_Error error; | ||||||
|  | } SDInfo; | ||||||
|  | 
 | ||||||
|  | const char* sd_api_get_fs_type_text(SDFsType fs_type); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										341
									
								
								applications/storage/storage-test-app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								applications/storage/storage-test-app.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,341 @@ | |||||||
|  | #include <furi.h> | ||||||
|  | #include <api-hal.h> | ||||||
|  | #include <storage/storage.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "storage-test" | ||||||
|  | #define BYTES_COUNT 16 | ||||||
|  | #define TEST_STRING "TestDataStringProvidedByDiceRoll" | ||||||
|  | #define SEEK_OFFSET_FROM_START 10 | ||||||
|  | #define SEEK_OFFSET_INCREASE 12 | ||||||
|  | #define SEEK_OFFSET_SUM (SEEK_OFFSET_FROM_START + SEEK_OFFSET_INCREASE) | ||||||
|  | 
 | ||||||
|  | static void do_file_test(Storage* api, const char* path) { | ||||||
|  |     File* file = storage_file_alloc(api); | ||||||
|  |     bool result; | ||||||
|  |     uint8_t bytes[BYTES_COUNT + 1]; | ||||||
|  |     uint8_t bytes_count; | ||||||
|  |     uint64_t position; | ||||||
|  |     uint64_t size; | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(TAG, "--------- FILE \"%s\" ---------", path); | ||||||
|  | 
 | ||||||
|  |     // open
 | ||||||
|  |     result = storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "open"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // write
 | ||||||
|  |     bytes_count = storage_file_write(file, TEST_STRING, strlen(TEST_STRING)); | ||||||
|  |     if(bytes_count == 0) { | ||||||
|  |         FURI_LOG_E(TAG, "write, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_I(TAG, "write"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // sync
 | ||||||
|  |     result = storage_file_sync(file); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "sync"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "sync, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // eof #1
 | ||||||
|  |     result = storage_file_eof(file); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "eof #1"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "eof #1, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // seek from start and tell
 | ||||||
|  |     result = storage_file_seek(file, SEEK_OFFSET_FROM_START, true); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "seek #1"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "seek #1, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  |     position = storage_file_tell(file); | ||||||
|  |     if(position != SEEK_OFFSET_FROM_START) { | ||||||
|  |         FURI_LOG_E(TAG, "tell #1, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_I(TAG, "tell #1"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // size
 | ||||||
|  |     size = storage_file_size(file); | ||||||
|  |     if(size != strlen(TEST_STRING)) { | ||||||
|  |         FURI_LOG_E(TAG, "size #1, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_I(TAG, "size #1"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // seek and tell
 | ||||||
|  |     result = storage_file_seek(file, SEEK_OFFSET_INCREASE, false); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "seek #2"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "seek #2, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  |     position = storage_file_tell(file); | ||||||
|  |     if(position != SEEK_OFFSET_SUM) { | ||||||
|  |         FURI_LOG_E(TAG, "tell #2, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_I(TAG, "tell #2"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // eof #2
 | ||||||
|  |     result = storage_file_eof(file); | ||||||
|  |     if(!result) { | ||||||
|  |         FURI_LOG_I(TAG, "eof #2"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "eof #2, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // truncate
 | ||||||
|  |     result = storage_file_truncate(file); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "truncate"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "truncate, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  |     size = storage_file_size(file); | ||||||
|  |     if(size != SEEK_OFFSET_SUM) { | ||||||
|  |         FURI_LOG_E(TAG, "size #2, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_I(TAG, "size #2"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // close
 | ||||||
|  |     result = storage_file_close(file); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "close"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "close, error"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // open
 | ||||||
|  |     result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "open"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // read
 | ||||||
|  |     memset(bytes, 0, BYTES_COUNT + 1); | ||||||
|  |     bytes_count = storage_file_read(file, bytes, BYTES_COUNT); | ||||||
|  |     if(bytes_count == 0) { | ||||||
|  |         FURI_LOG_E(TAG, "read, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } else { | ||||||
|  |         if(memcmp(TEST_STRING, bytes, bytes_count) == 0) { | ||||||
|  |             FURI_LOG_I(TAG, "read"); | ||||||
|  |         } else { | ||||||
|  |             FURI_LOG_E(TAG, "read, garbage"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // close
 | ||||||
|  |     result = storage_file_close(file); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "close"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "close, error"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage_file_free(file); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void do_dir_test(Storage* api, const char* path) { | ||||||
|  |     File* file = storage_file_alloc(api); | ||||||
|  |     bool result; | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(TAG, "--------- DIR \"%s\" ---------", path); | ||||||
|  | 
 | ||||||
|  |     // open
 | ||||||
|  |     result = storage_dir_open(file, path); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "open"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // read
 | ||||||
|  |     const uint8_t filename_size = 100; | ||||||
|  |     char* filename = malloc(filename_size); | ||||||
|  |     FileInfo fileinfo; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         result = storage_dir_read(file, &fileinfo, filename, filename_size); | ||||||
|  |         if(result) { | ||||||
|  |             if(strlen(filename)) { | ||||||
|  |                 FURI_LOG_I( | ||||||
|  |                     TAG, | ||||||
|  |                     "read #1, [%s]%s", | ||||||
|  |                     ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), | ||||||
|  |                     filename); | ||||||
|  |             } | ||||||
|  |         } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { | ||||||
|  |             FURI_LOG_E(TAG, "read #1, %s", storage_file_get_error_desc(file)); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } while(result); | ||||||
|  | 
 | ||||||
|  |     // rewind
 | ||||||
|  |     result = storage_dir_rewind(file); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "rewind"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "rewind, %s", storage_file_get_error_desc(file)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // read
 | ||||||
|  |     do { | ||||||
|  |         result = storage_dir_read(file, &fileinfo, filename, filename_size); | ||||||
|  |         if(result) { | ||||||
|  |             if(strlen(filename)) { | ||||||
|  |                 FURI_LOG_I( | ||||||
|  |                     TAG, | ||||||
|  |                     "read #2, [%s]%s", | ||||||
|  |                     ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), | ||||||
|  |                     filename); | ||||||
|  |             } | ||||||
|  |         } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { | ||||||
|  |             FURI_LOG_E(TAG, "read #2, %s", storage_file_get_error_desc(file)); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } while((strlen(filename))); | ||||||
|  | 
 | ||||||
|  |     // close
 | ||||||
|  |     result = storage_dir_close(file); | ||||||
|  |     if(result) { | ||||||
|  |         FURI_LOG_I(TAG, "close"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "close, error"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage_file_free(file); | ||||||
|  |     free(filename); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void do_test_start(Storage* api, const char* path) { | ||||||
|  |     string_t str_path; | ||||||
|  |     string_init_printf(str_path, "%s/test-folder", path); | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(TAG, "--------- START \"%s\" ---------", path); | ||||||
|  | 
 | ||||||
|  |     // mkdir
 | ||||||
|  |     FS_Error result = storage_common_mkdir(api, string_get_cstr(str_path)); | ||||||
|  | 
 | ||||||
|  |     if(result == FSE_OK) { | ||||||
|  |         FURI_LOG_I(TAG, "mkdir ok"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "mkdir, %s", storage_error_get_desc(result)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // stat
 | ||||||
|  |     FileInfo fileinfo; | ||||||
|  |     result = storage_common_stat(api, string_get_cstr(str_path), &fileinfo); | ||||||
|  | 
 | ||||||
|  |     if(result == FSE_OK) { | ||||||
|  |         if(fileinfo.flags & FSF_DIRECTORY) { | ||||||
|  |             FURI_LOG_I(TAG, "stat #1 ok"); | ||||||
|  |         } else { | ||||||
|  |             FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(str_path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void do_test_end(Storage* api, const char* path) { | ||||||
|  |     uint64_t total_space; | ||||||
|  |     uint64_t free_space; | ||||||
|  |     string_t str_path_1; | ||||||
|  |     string_t str_path_2; | ||||||
|  |     string_init_printf(str_path_1, "%s/test-folder", path); | ||||||
|  |     string_init_printf(str_path_2, "%s/test-folder2", path); | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_I(TAG, "--------- END \"%s\" ---------", path); | ||||||
|  | 
 | ||||||
|  |     // fs stat
 | ||||||
|  |     FS_Error result = storage_common_fs_info(api, path, &total_space, &free_space); | ||||||
|  | 
 | ||||||
|  |     if(result == FSE_OK) { | ||||||
|  |         uint32_t total_kb = total_space / 1024; | ||||||
|  |         uint32_t free_kb = free_space / 1024; | ||||||
|  |         FURI_LOG_I(TAG, "fs_info: total %luk, free %luk", total_kb, free_kb); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "fs_info, %s", storage_error_get_desc(result)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // rename #1
 | ||||||
|  |     result = storage_common_rename(api, string_get_cstr(str_path_1), string_get_cstr(str_path_2)); | ||||||
|  |     if(result == FSE_OK) { | ||||||
|  |         FURI_LOG_I(TAG, "rename #1 ok"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "rename #1, %s", storage_error_get_desc(result)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // remove #1
 | ||||||
|  |     result = storage_common_remove(api, string_get_cstr(str_path_2)); | ||||||
|  |     if(result == FSE_OK) { | ||||||
|  |         FURI_LOG_I(TAG, "remove #1 ok"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "remove #1, %s", storage_error_get_desc(result)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // rename #2
 | ||||||
|  |     string_printf(str_path_1, "%s/test.txt", path); | ||||||
|  |     string_printf(str_path_2, "%s/test2.txt", path); | ||||||
|  | 
 | ||||||
|  |     result = storage_common_rename(api, string_get_cstr(str_path_1), string_get_cstr(str_path_2)); | ||||||
|  |     if(result == FSE_OK) { | ||||||
|  |         FURI_LOG_I(TAG, "rename #2 ok"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "rename #2, %s", storage_error_get_desc(result)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // remove #2
 | ||||||
|  |     result = storage_common_remove(api, string_get_cstr(str_path_2)); | ||||||
|  |     if(result == FSE_OK) { | ||||||
|  |         FURI_LOG_I(TAG, "remove #2 ok"); | ||||||
|  |     } else { | ||||||
|  |         FURI_LOG_E(TAG, "remove #2, %s", storage_error_get_desc(result)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     string_clear(str_path_1); | ||||||
|  |     string_clear(str_path_2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t storage_app_test(void* p) { | ||||||
|  |     Storage* api = furi_record_open("storage"); | ||||||
|  |     do_test_start(api, "/int"); | ||||||
|  |     do_test_start(api, "/any"); | ||||||
|  |     do_test_start(api, "/ext"); | ||||||
|  | 
 | ||||||
|  |     do_file_test(api, "/int/test.txt"); | ||||||
|  |     do_file_test(api, "/any/test.txt"); | ||||||
|  |     do_file_test(api, "/ext/test.txt"); | ||||||
|  | 
 | ||||||
|  |     do_dir_test(api, "/int"); | ||||||
|  |     do_dir_test(api, "/any"); | ||||||
|  |     do_dir_test(api, "/ext"); | ||||||
|  | 
 | ||||||
|  |     do_test_end(api, "/int"); | ||||||
|  |     do_test_end(api, "/any"); | ||||||
|  |     do_test_end(api, "/ext"); | ||||||
|  | 
 | ||||||
|  |     while(true) { | ||||||
|  |         delay(1000); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								applications/storage/storage.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								applications/storage/storage.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | #include "storage.h" | ||||||
|  | #include "storage-i.h" | ||||||
|  | #include "storage-message.h" | ||||||
|  | #include "storage-processing.h" | ||||||
|  | #include "storages/storage-int.h" | ||||||
|  | #include "storages/storage-ext.h" | ||||||
|  | 
 | ||||||
|  | #define STORAGE_TICK 1000 | ||||||
|  | 
 | ||||||
|  | #define ICON_SD_MOUNTED &I_SDcardMounted_11x8 | ||||||
|  | #define ICON_SD_ERROR &I_SDcardFail_11x8 | ||||||
|  | 
 | ||||||
|  | static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     furi_assert(context); | ||||||
|  |     Storage* app = context; | ||||||
|  | 
 | ||||||
|  |     // here we don't care about thread race when reading / writing status
 | ||||||
|  |     switch(app->storage[ST_EXT].status) { | ||||||
|  |     case StorageStatusNotReady: | ||||||
|  |         break; | ||||||
|  |     case StorageStatusOK: | ||||||
|  |         canvas_draw_icon(canvas, 0, 0, ICON_SD_MOUNTED); | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         canvas_draw_icon(canvas, 0, 0, ICON_SD_ERROR); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Storage* storage_app_alloc() { | ||||||
|  |     Storage* app = malloc(sizeof(Storage)); | ||||||
|  |     app->message_queue = osMessageQueueNew(8, sizeof(StorageMessage), NULL); | ||||||
|  | 
 | ||||||
|  |     for(uint8_t i = 0; i < STORAGE_COUNT; i++) { | ||||||
|  |         storage_data_init(&app->storage[i]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage_int_init(&app->storage[ST_INT]); | ||||||
|  |     storage_ext_init(&app->storage[ST_EXT]); | ||||||
|  | 
 | ||||||
|  |     // sd icon gui
 | ||||||
|  |     app->sd_gui.enabled = false; | ||||||
|  |     app->sd_gui.view_port = view_port_alloc(); | ||||||
|  |     view_port_set_width(app->sd_gui.view_port, icon_get_width(ICON_SD_MOUNTED)); | ||||||
|  |     view_port_draw_callback_set(app->sd_gui.view_port, storage_app_sd_icon_draw_callback, app); | ||||||
|  |     view_port_enabled_set(app->sd_gui.view_port, false); | ||||||
|  | 
 | ||||||
|  |     Gui* gui = furi_record_open("gui"); | ||||||
|  |     gui_add_view_port(gui, app->sd_gui.view_port, GuiLayerStatusBarLeft); | ||||||
|  |     furi_record_close("gui"); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void storage_tick(Storage* app) { | ||||||
|  |     for(uint8_t i = 0; i < STORAGE_COUNT; i++) { | ||||||
|  |         StorageApi api = app->storage[i].api; | ||||||
|  |         if(api.tick != NULL) { | ||||||
|  |             api.tick(&app->storage[i]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // storage not enabled but was enabled (sd card unmount)
 | ||||||
|  |     if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) { | ||||||
|  |         app->sd_gui.enabled = false; | ||||||
|  |         view_port_enabled_set(app->sd_gui.view_port, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // storage enabled (or in error state) but was not enabled (sd card mount)
 | ||||||
|  |     if((app->storage[ST_EXT].status == StorageStatusOK || | ||||||
|  |         app->storage[ST_EXT].status == StorageStatusNotMounted || | ||||||
|  |         app->storage[ST_EXT].status == StorageStatusNoFS || | ||||||
|  |         app->storage[ST_EXT].status == StorageStatusNotAccessible || | ||||||
|  |         app->storage[ST_EXT].status == StorageStatusErrorInternal) && | ||||||
|  |        app->sd_gui.enabled == false) { | ||||||
|  |         app->sd_gui.enabled = true; | ||||||
|  |         view_port_enabled_set(app->sd_gui.view_port, true); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t storage_app(void* p) { | ||||||
|  |     Storage* app = storage_app_alloc(); | ||||||
|  |     furi_record_create("storage", app); | ||||||
|  | 
 | ||||||
|  |     StorageMessage message; | ||||||
|  |     while(1) { | ||||||
|  |         if(osMessageQueueGet(app->message_queue, &message, NULL, STORAGE_TICK) == osOK) { | ||||||
|  |             storage_process_message(app, &message); | ||||||
|  |         } else { | ||||||
|  |             storage_tick(app); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										235
									
								
								applications/storage/storage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								applications/storage/storage.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,235 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "filesystem-api-defines.h" | ||||||
|  | #include "storage-sd-api.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | typedef struct Storage Storage; | ||||||
|  | 
 | ||||||
|  | /** Allocates and initializes a file descriptor
 | ||||||
|  |  * @return File* | ||||||
|  |  */ | ||||||
|  | File* storage_file_alloc(Storage* storage); | ||||||
|  | 
 | ||||||
|  | /** Frees the file descriptor. Closes the file if it was open.
 | ||||||
|  |  */ | ||||||
|  | void storage_file_free(File* file); | ||||||
|  | 
 | ||||||
|  | /******************* File Functions *******************/ | ||||||
|  | 
 | ||||||
|  | /** Opens an existing file or create a new one.
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @param path path to file  | ||||||
|  |  * @param access_mode access mode from FS_AccessMode  | ||||||
|  |  * @param open_mode open mode from FS_OpenMode  | ||||||
|  |  * @return success flag. You need to close the file even if the open operation failed. | ||||||
|  |  */ | ||||||
|  | bool storage_file_open( | ||||||
|  |     File* file, | ||||||
|  |     const char* path, | ||||||
|  |     FS_AccessMode access_mode, | ||||||
|  |     FS_OpenMode open_mode); | ||||||
|  | 
 | ||||||
|  | /** Close the file.
 | ||||||
|  |  * @param file pointer to a file object, the file object will be freed. | ||||||
|  |  * @return success flag | ||||||
|  |  */ | ||||||
|  | bool storage_file_close(File* file); | ||||||
|  | 
 | ||||||
|  | /** Tells if the file is open
 | ||||||
|  |  * @param file pointer to a file object | ||||||
|  |  * @return bool true if file is open | ||||||
|  |  */ | ||||||
|  | bool storage_file_is_open(File* file); | ||||||
|  | 
 | ||||||
|  | /** Reads bytes from a file into a buffer
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @param buff pointer to a buffer, for reading | ||||||
|  |  * @param bytes_to_read how many bytes to read. Must be less than or equal to the size of the buffer. | ||||||
|  |  * @return uint16_t how many bytes were actually readed | ||||||
|  |  */ | ||||||
|  | uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read); | ||||||
|  | 
 | ||||||
|  | /** Writes bytes from a buffer to a file
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @param buff pointer to buffer, for writing | ||||||
|  |  * @param bytes_to_write how many bytes to write. Must be less than or equal to the size of the buffer. | ||||||
|  |  * @return uint16_t how many bytes were actually written | ||||||
|  |  */ | ||||||
|  | uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write); | ||||||
|  | 
 | ||||||
|  | /** Moves the r/w pointer 
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @param offset offset to move the r/w pointer | ||||||
|  |  * @param from_start set an offset from the start or from the current position | ||||||
|  |  * @return success flag | ||||||
|  |  */ | ||||||
|  | bool storage_file_seek(File* file, uint32_t offset, bool from_start); | ||||||
|  | 
 | ||||||
|  | /** Gets the position of the r/w pointer 
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @return uint64_t position of the r/w pointer  | ||||||
|  |  */ | ||||||
|  | uint64_t storage_file_tell(File* file); | ||||||
|  | 
 | ||||||
|  | /** Truncates the file size to the current position of the r/w pointer
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @return bool success flag | ||||||
|  |  */ | ||||||
|  | bool storage_file_truncate(File* file); | ||||||
|  | 
 | ||||||
|  | /** Gets the size of the file
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @return uint64_t size of the file | ||||||
|  |  */ | ||||||
|  | uint64_t storage_file_size(File* file); | ||||||
|  | 
 | ||||||
|  | /** Writes file cache to storage
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @return bool success flag | ||||||
|  |  */ | ||||||
|  | bool storage_file_sync(File* file); | ||||||
|  | 
 | ||||||
|  | /** Checks that the r/w pointer is at the end of the file
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @return bool success flag | ||||||
|  |  */ | ||||||
|  | bool storage_file_eof(File* file); | ||||||
|  | 
 | ||||||
|  | /******************* Dir Functions *******************/ | ||||||
|  | 
 | ||||||
|  | /** Opens a directory to get objects from it
 | ||||||
|  |  * @param app pointer to the api | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @param path path to directory | ||||||
|  |  * @return bool success flag. You need to close the directory even if the open operation failed. | ||||||
|  |  */ | ||||||
|  | bool storage_dir_open(File* file, const char* path); | ||||||
|  | 
 | ||||||
|  | /** Close the directory. Also free file handle structure and point it to the NULL.
 | ||||||
|  |  * @param file pointer to a file object. | ||||||
|  |  * @return bool success flag | ||||||
|  |  */ | ||||||
|  | bool storage_dir_close(File* file); | ||||||
|  | 
 | ||||||
|  | /** Reads the next object in the directory
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @param fileinfo pointer to the readed FileInfo, may be NULL | ||||||
|  |  * @param name pointer to name buffer, may be NULL | ||||||
|  |  * @param name_length name buffer length | ||||||
|  |  * @return success flag (if the next object does not exist, it also returns false and sets the file error id to FSE_NOT_EXIST) | ||||||
|  |  */ | ||||||
|  | bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||||
|  | 
 | ||||||
|  | /** Rewinds the read pointer to first item in the directory
 | ||||||
|  |  * @param file pointer to file object. | ||||||
|  |  * @return bool success flag | ||||||
|  |  */ | ||||||
|  | bool storage_dir_rewind(File* file); | ||||||
|  | 
 | ||||||
|  | /******************* Common Functions *******************/ | ||||||
|  | 
 | ||||||
|  | /** Retrieves information about a file/directory
 | ||||||
|  |  * @param app pointer to the api | ||||||
|  |  * @param path path to file/directory | ||||||
|  |  * @param fileinfo pointer to the readed FileInfo, may be NULL | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo); | ||||||
|  | 
 | ||||||
|  | /** Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open
 | ||||||
|  |  * @param app pointer to the api | ||||||
|  |  * @param path  | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_common_remove(Storage* storage, const char* path); | ||||||
|  | 
 | ||||||
|  | /** Renames file/directory, file/directory must not be open
 | ||||||
|  |  * @param app pointer to the api | ||||||
|  |  * @param old_path old path | ||||||
|  |  * @param new_path new path | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path); | ||||||
|  | 
 | ||||||
|  | /** Copy file, file must not be open
 | ||||||
|  |  * @param app pointer to the api | ||||||
|  |  * @param old_path old path | ||||||
|  |  * @param new_path new path | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path); | ||||||
|  | 
 | ||||||
|  | /** Creates a directory
 | ||||||
|  |  * @param app pointer to the api | ||||||
|  |  * @param path directory path | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_common_mkdir(Storage* storage, const char* path); | ||||||
|  | 
 | ||||||
|  | /** Gets general information about the storage
 | ||||||
|  |  * @param app pointer to the api | ||||||
|  |  * @param fs_path the path to the storage of interest | ||||||
|  |  * @param total_space pointer to total space record, will be filled | ||||||
|  |  * @param free_space pointer to free space record, will be filled | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_common_fs_info( | ||||||
|  |     Storage* storage, | ||||||
|  |     const char* fs_path, | ||||||
|  |     uint64_t* total_space, | ||||||
|  |     uint64_t* free_space); | ||||||
|  | 
 | ||||||
|  | /******************* Error Functions *******************/ | ||||||
|  | 
 | ||||||
|  | /** Retrieves the error text from the error id
 | ||||||
|  |  * @param error_id error id | ||||||
|  |  * @return const char* error text | ||||||
|  |  */ | ||||||
|  | const char* storage_error_get_desc(FS_Error error_id); | ||||||
|  | 
 | ||||||
|  | /** Retrieves the error id from the file object
 | ||||||
|  |  * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR ID IF THE FILE HAS BEEN CLOSED | ||||||
|  |  * @return FS_Error error id | ||||||
|  |  */ | ||||||
|  | FS_Error storage_file_get_error(File* file); | ||||||
|  | 
 | ||||||
|  | /** Retrieves the error text from the file object
 | ||||||
|  |  * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED | ||||||
|  |  * @return const char* error text | ||||||
|  |  */ | ||||||
|  | const char* storage_file_get_error_desc(File* file); | ||||||
|  | 
 | ||||||
|  | /******************* SD Card Functions *******************/ | ||||||
|  | 
 | ||||||
|  | /** Formats SD Card
 | ||||||
|  |  * @param api pointer to the api | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_sd_format(Storage* api); | ||||||
|  | 
 | ||||||
|  | /** Will unmount the SD card
 | ||||||
|  |  * @param api pointer to the api | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_sd_unmount(Storage* api); | ||||||
|  | 
 | ||||||
|  | /** Retrieves SD card information
 | ||||||
|  |  * @param api pointer to the api | ||||||
|  |  * @param info pointer to the info | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_sd_info(Storage* api, SDInfo* info); | ||||||
|  | 
 | ||||||
|  | /** Retrieves SD card status
 | ||||||
|  |  * @param api pointer to the api | ||||||
|  |  * @return FS_Error operation result | ||||||
|  |  */ | ||||||
|  | FS_Error storage_sd_status(Storage* api); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										82
									
								
								applications/storage/storages/sd-notify.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								applications/storage/storages/sd-notify.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | |||||||
|  | #include "sd-notify.h" | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence sd_sequence_success = { | ||||||
|  |     &message_green_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_green_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_green_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_green_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_green_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_green_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence sd_sequence_error = { | ||||||
|  |     &message_red_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_red_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_red_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_red_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_red_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_red_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence sd_sequence_eject = { | ||||||
|  |     &message_blue_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_blue_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_blue_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_blue_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_blue_255, | ||||||
|  |     &message_delay_50, | ||||||
|  |     &message_blue_0, | ||||||
|  |     &message_delay_50, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence sd_sequence_wait = { | ||||||
|  |     &message_red_255, | ||||||
|  |     &message_blue_255, | ||||||
|  |     &message_do_not_reset, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const NotificationSequence sd_sequence_wait_off = { | ||||||
|  |     &message_red_0, | ||||||
|  |     &message_blue_0, | ||||||
|  |     NULL, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void sd_notify_wait(NotificationApp* notifications) { | ||||||
|  |     notification_message(notifications, &sd_sequence_wait); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void sd_notify_wait_off(NotificationApp* notifications) { | ||||||
|  |     notification_message(notifications, &sd_sequence_wait_off); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void sd_notify_success(NotificationApp* notifications) { | ||||||
|  |     notification_message(notifications, &sd_sequence_success); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void sd_notify_eject(NotificationApp* notifications) { | ||||||
|  |     notification_message(notifications, &sd_sequence_eject); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void sd_notify_error(NotificationApp* notifications) { | ||||||
|  |     notification_message(notifications, &sd_sequence_error); | ||||||
|  | } | ||||||
							
								
								
									
										17
									
								
								applications/storage/storages/sd-notify.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								applications/storage/storages/sd-notify.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include <notification/notification-messages.h> | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | void sd_notify_wait(NotificationApp* notifications); | ||||||
|  | void sd_notify_wait_off(NotificationApp* notifications); | ||||||
|  | void sd_notify_success(NotificationApp* notifications); | ||||||
|  | void sd_notify_eject(NotificationApp* notifications); | ||||||
|  | void sd_notify_error(NotificationApp* notifications); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										547
									
								
								applications/storage/storages/storage-ext.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										547
									
								
								applications/storage/storages/storage-ext.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,547 @@ | |||||||
|  | #include "fatfs.h" | ||||||
|  | #include "../filesystem-api-internal.h" | ||||||
|  | #include "storage-ext.h" | ||||||
|  | #include <api-hal.h> | ||||||
|  | #include "sd-notify.h" | ||||||
|  | #include <api-hal-sd.h> | ||||||
|  | 
 | ||||||
|  | typedef FIL SDFile; | ||||||
|  | typedef DIR SDDir; | ||||||
|  | typedef FILINFO SDFileInfo; | ||||||
|  | typedef FRESULT SDError; | ||||||
|  | 
 | ||||||
|  | #define TAG "storage-ext" | ||||||
|  | #define STORAGE_PATH "/ext" | ||||||
|  | /********************* Definitions ********************/ | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     FATFS* fs; | ||||||
|  |     const char* path; | ||||||
|  |     bool sd_was_present; | ||||||
|  | } SDData; | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_ext_parse_error(SDError error); | ||||||
|  | 
 | ||||||
|  | /******************* Core Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static bool sd_mount_card(StorageData* storage, bool notify) { | ||||||
|  |     bool result = false; | ||||||
|  |     const uint8_t max_init_counts = 10; | ||||||
|  |     uint8_t counter = max_init_counts; | ||||||
|  |     uint8_t bsp_result; | ||||||
|  |     SDData* sd_data = storage->data; | ||||||
|  | 
 | ||||||
|  |     storage_data_lock(storage); | ||||||
|  | 
 | ||||||
|  |     while(result == false && counter > 0 && hal_sd_detect()) { | ||||||
|  |         if(notify) { | ||||||
|  |             NotificationApp* notification = furi_record_open("notification"); | ||||||
|  |             sd_notify_wait(notification); | ||||||
|  |             furi_record_close("notification"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if((counter % 2) == 0) { | ||||||
|  |             // power reset sd card
 | ||||||
|  |             bsp_result = BSP_SD_Init(true); | ||||||
|  |         } else { | ||||||
|  |             bsp_result = BSP_SD_Init(false); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(bsp_result) { | ||||||
|  |             // bsp error
 | ||||||
|  |             storage->status = StorageStatusErrorInternal; | ||||||
|  |         } else { | ||||||
|  |             SDError status = f_mount(sd_data->fs, sd_data->path, 1); | ||||||
|  | 
 | ||||||
|  |             if(status == FR_OK || status == FR_NO_FILESYSTEM) { | ||||||
|  |                 FATFS* fs; | ||||||
|  |                 uint32_t free_clusters; | ||||||
|  | 
 | ||||||
|  |                 status = f_getfree(sd_data->path, &free_clusters, &fs); | ||||||
|  | 
 | ||||||
|  |                 if(status == FR_OK || status == FR_NO_FILESYSTEM) { | ||||||
|  |                     result = true; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if(status == FR_OK) { | ||||||
|  |                     storage->status = StorageStatusOK; | ||||||
|  |                 } else if(status == FR_NO_FILESYSTEM) { | ||||||
|  |                     storage->status = StorageStatusNoFS; | ||||||
|  |                 } else { | ||||||
|  |                     storage->status = StorageStatusNotAccessible; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 storage->status = StorageStatusNotMounted; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(notify) { | ||||||
|  |             NotificationApp* notification = furi_record_open("notification"); | ||||||
|  |             sd_notify_wait_off(notification); | ||||||
|  |             furi_record_close("notification"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(!result) { | ||||||
|  |             delay(1000); | ||||||
|  |             FURI_LOG_E( | ||||||
|  |                 TAG, "init cycle %d, error: %s", counter, storage_data_status_text(storage)); | ||||||
|  |             counter--; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     storage_data_unlock(storage); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error sd_unmount_card(StorageData* storage) { | ||||||
|  |     SDData* sd_data = storage->data; | ||||||
|  |     SDError error; | ||||||
|  | 
 | ||||||
|  |     storage_data_lock(storage); | ||||||
|  |     error = storage->status = StorageStatusNotReady; | ||||||
|  | 
 | ||||||
|  |     // TODO do i need to close the files?
 | ||||||
|  | 
 | ||||||
|  |     f_mount(0, sd_data->path, 0); | ||||||
|  |     storage_data_unlock(storage); | ||||||
|  |     return storage_ext_parse_error(error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error sd_format_card(StorageData* storage) { | ||||||
|  |     uint8_t* work_area; | ||||||
|  |     SDData* sd_data = storage->data; | ||||||
|  |     SDError error; | ||||||
|  | 
 | ||||||
|  |     storage_data_lock(storage); | ||||||
|  | 
 | ||||||
|  |     work_area = malloc(_MAX_SS); | ||||||
|  |     error = f_mkfs(sd_data->path, FM_ANY, 0, work_area, _MAX_SS); | ||||||
|  |     free(work_area); | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         storage->status = StorageStatusNotAccessible; | ||||||
|  |         if(error != FR_OK) break; | ||||||
|  |         storage->status = StorageStatusNoFS; | ||||||
|  |         error = f_setlabel("Flipper SD"); | ||||||
|  |         if(error != FR_OK) break; | ||||||
|  |         storage->status = StorageStatusNotMounted; | ||||||
|  |         error = f_mount(sd_data->fs, sd_data->path, 1); | ||||||
|  |         if(error != FR_OK) break; | ||||||
|  |         storage->status = StorageStatusOK; | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     storage_data_unlock(storage); | ||||||
|  | 
 | ||||||
|  |     return storage_ext_parse_error(error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { | ||||||
|  |     uint32_t free_clusters, free_sectors, total_sectors; | ||||||
|  |     FATFS* fs; | ||||||
|  |     SDData* sd_data = storage->data; | ||||||
|  |     SDError error; | ||||||
|  | 
 | ||||||
|  |     // clean data
 | ||||||
|  |     memset(sd_info, 0, sizeof(SDInfo)); | ||||||
|  | 
 | ||||||
|  |     // get fs info
 | ||||||
|  |     storage_data_lock(storage); | ||||||
|  |     error = f_getlabel(sd_data->path, sd_info->label, NULL); | ||||||
|  |     if(error == FR_OK) { | ||||||
|  |         error = f_getfree(sd_data->path, &free_clusters, &fs); | ||||||
|  |     } | ||||||
|  |     storage_data_unlock(storage); | ||||||
|  | 
 | ||||||
|  |     if(error == FR_OK) { | ||||||
|  |         // calculate size
 | ||||||
|  |         total_sectors = (fs->n_fatent - 2) * fs->csize; | ||||||
|  |         free_sectors = free_clusters * fs->csize; | ||||||
|  | 
 | ||||||
|  |         uint16_t sector_size = _MAX_SS; | ||||||
|  | #if _MAX_SS != _MIN_SS | ||||||
|  |         sector_size = fs->ssize; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  |         sd_info->fs_type = fs->fs_type; | ||||||
|  | 
 | ||||||
|  |         sd_info->kb_total = total_sectors / 1024 * sector_size; | ||||||
|  |         sd_info->kb_free = free_sectors / 1024 * sector_size; | ||||||
|  |         sd_info->cluster_size = fs->csize; | ||||||
|  |         sd_info->sector_size = sector_size; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return storage_ext_parse_error(error); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void storage_ext_tick_internal(StorageData* storage, bool notify) { | ||||||
|  |     SDData* sd_data = storage->data; | ||||||
|  | 
 | ||||||
|  |     if(sd_data->sd_was_present) { | ||||||
|  |         if(hal_sd_detect()) { | ||||||
|  |             FURI_LOG_I(TAG, "card detected"); | ||||||
|  |             sd_mount_card(storage, notify); | ||||||
|  | 
 | ||||||
|  |             if(storage->status != StorageStatusOK) { | ||||||
|  |                 FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage)); | ||||||
|  |                 if(notify) { | ||||||
|  |                     NotificationApp* notification = furi_record_open("notification"); | ||||||
|  |                     sd_notify_error(notification); | ||||||
|  |                     furi_record_close("notification"); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_I(TAG, "card mounted"); | ||||||
|  |                 if(notify) { | ||||||
|  |                     NotificationApp* notification = furi_record_open("notification"); | ||||||
|  |                     sd_notify_success(notification); | ||||||
|  |                     furi_record_close("notification"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             sd_data->sd_was_present = false; | ||||||
|  | 
 | ||||||
|  |             if(!hal_sd_detect()) { | ||||||
|  |                 FURI_LOG_I(TAG, "card removed while mounting"); | ||||||
|  |                 sd_unmount_card(storage); | ||||||
|  |                 sd_data->sd_was_present = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         if(!hal_sd_detect()) { | ||||||
|  |             FURI_LOG_I(TAG, "card removed"); | ||||||
|  |             sd_data->sd_was_present = true; | ||||||
|  | 
 | ||||||
|  |             sd_unmount_card(storage); | ||||||
|  |             if(notify) { | ||||||
|  |                 NotificationApp* notification = furi_record_open("notification"); | ||||||
|  |                 sd_notify_eject(notification); | ||||||
|  |                 furi_record_close("notification"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void storage_ext_tick(StorageData* storage) { | ||||||
|  |     storage_ext_tick_internal(storage, true); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** Common Functions ******************/ | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_ext_parse_error(SDError error) { | ||||||
|  |     FS_Error result; | ||||||
|  |     switch(error) { | ||||||
|  |     case FR_OK: | ||||||
|  |         result = FSE_OK; | ||||||
|  |         break; | ||||||
|  |     case FR_NOT_READY: | ||||||
|  |         result = FSE_NOT_READY; | ||||||
|  |         break; | ||||||
|  |     case FR_NO_FILE: | ||||||
|  |     case FR_NO_PATH: | ||||||
|  |     case FR_NO_FILESYSTEM: | ||||||
|  |         result = FSE_NOT_EXIST; | ||||||
|  |         break; | ||||||
|  |     case FR_EXIST: | ||||||
|  |         result = FSE_EXIST; | ||||||
|  |         break; | ||||||
|  |     case FR_INVALID_NAME: | ||||||
|  |         result = FSE_INVALID_NAME; | ||||||
|  |         break; | ||||||
|  |     case FR_INVALID_OBJECT: | ||||||
|  |     case FR_INVALID_PARAMETER: | ||||||
|  |         result = FSE_INVALID_PARAMETER; | ||||||
|  |         break; | ||||||
|  |     case FR_DENIED: | ||||||
|  |         result = FSE_DENIED; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* File Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_file_open( | ||||||
|  |     void* ctx, | ||||||
|  |     File* file, | ||||||
|  |     const char* path, | ||||||
|  |     FS_AccessMode access_mode, | ||||||
|  |     FS_OpenMode open_mode) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     uint8_t _mode = 0; | ||||||
|  | 
 | ||||||
|  |     if(access_mode & FSAM_READ) _mode |= FA_READ; | ||||||
|  |     if(access_mode & FSAM_WRITE) _mode |= FA_WRITE; | ||||||
|  |     if(open_mode & FSOM_OPEN_EXISTING) _mode |= FA_OPEN_EXISTING; | ||||||
|  |     if(open_mode & FSOM_OPEN_ALWAYS) _mode |= FA_OPEN_ALWAYS; | ||||||
|  |     if(open_mode & FSOM_OPEN_APPEND) _mode |= FA_OPEN_APPEND; | ||||||
|  |     if(open_mode & FSOM_CREATE_NEW) _mode |= FA_CREATE_NEW; | ||||||
|  |     if(open_mode & FSOM_CREATE_ALWAYS) _mode |= FA_CREATE_ALWAYS; | ||||||
|  | 
 | ||||||
|  |     SDFile* file_data = malloc(sizeof(SDFile)); | ||||||
|  |     storage_set_storage_file_data(file, file_data, storage); | ||||||
|  | 
 | ||||||
|  |     file->internal_error_id = f_open(file_data, path, _mode); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_file_close(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  |     file->internal_error_id = f_close(file_data); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     free(file_data); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint16_t | ||||||
|  |     storage_ext_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  |     uint16_t bytes_readed = 0; | ||||||
|  |     file->internal_error_id = f_read(file_data, buff, bytes_to_read, &bytes_readed); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return bytes_readed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint16_t | ||||||
|  |     storage_ext_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  |     uint16_t bytes_written = 0; | ||||||
|  |     file->internal_error_id = f_write(file_data, buff, bytes_to_write, &bytes_written); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return bytes_written; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  |     storage_ext_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(from_start) { | ||||||
|  |         file->internal_error_id = f_lseek(file_data, offset); | ||||||
|  |     } else { | ||||||
|  |         uint64_t position = f_tell(file_data); | ||||||
|  |         position += offset; | ||||||
|  |         file->internal_error_id = f_lseek(file_data, position); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint64_t storage_ext_file_tell(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     uint64_t position = 0; | ||||||
|  |     position = f_tell(file_data); | ||||||
|  |     file->error_id = FSE_OK; | ||||||
|  |     return position; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_file_truncate(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     file->internal_error_id = f_truncate(file_data); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_file_sync(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     file->internal_error_id = f_sync(file_data); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint64_t storage_ext_file_size(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     uint64_t size = 0; | ||||||
|  |     size = f_size(file_data); | ||||||
|  |     file->error_id = FSE_OK; | ||||||
|  |     return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_file_eof(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     bool eof = f_eof(file_data); | ||||||
|  |     file->internal_error_id = 0; | ||||||
|  |     file->error_id = FSE_OK; | ||||||
|  |     return eof; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Dir Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_dir_open(void* ctx, File* file, const char* path) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  | 
 | ||||||
|  |     SDDir* file_data = malloc(sizeof(SDDir)); | ||||||
|  |     storage_set_storage_file_data(file, file_data, storage); | ||||||
|  |     file->internal_error_id = f_opendir(file_data, path); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_dir_close(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDDir* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     file->internal_error_id = f_closedir(file_data); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     free(file_data); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_dir_read( | ||||||
|  |     void* ctx, | ||||||
|  |     File* file, | ||||||
|  |     FileInfo* fileinfo, | ||||||
|  |     char* name, | ||||||
|  |     const uint16_t name_length) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDDir* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     SDFileInfo _fileinfo; | ||||||
|  |     file->internal_error_id = f_readdir(file_data, &_fileinfo); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  | 
 | ||||||
|  |     if(fileinfo != NULL) { | ||||||
|  |         fileinfo->size = _fileinfo.fsize; | ||||||
|  |         fileinfo->flags = 0; | ||||||
|  | 
 | ||||||
|  |         if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(name != NULL) { | ||||||
|  |         snprintf(name, name_length, "%s", _fileinfo.fname); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(_fileinfo.fname[0] == 0) { | ||||||
|  |         file->error_id = FSE_NOT_EXIST; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_ext_dir_rewind(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDDir* file_data = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     file->internal_error_id = f_readdir(file_data, NULL); | ||||||
|  |     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | /******************* Common FS Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_ext_common_stat(void* ctx, const char* path, FileInfo* fileinfo) { | ||||||
|  |     SDFileInfo _fileinfo; | ||||||
|  |     SDError result = f_stat(path, &_fileinfo); | ||||||
|  | 
 | ||||||
|  |     if(fileinfo != NULL) { | ||||||
|  |         fileinfo->size = _fileinfo.fsize; | ||||||
|  |         fileinfo->flags = 0; | ||||||
|  | 
 | ||||||
|  |         if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return storage_ext_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_ext_common_remove(void* ctx, const char* path) { | ||||||
|  |     SDError result = f_unlink(path); | ||||||
|  |     return storage_ext_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_ext_common_rename(void* ctx, const char* old_path, const char* new_path) { | ||||||
|  |     SDError result = f_rename(old_path, new_path); | ||||||
|  |     return storage_ext_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_ext_common_mkdir(void* ctx, const char* path) { | ||||||
|  |     SDError result = f_mkdir(path); | ||||||
|  |     return storage_ext_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_ext_common_fs_info( | ||||||
|  |     void* ctx, | ||||||
|  |     const char* fs_path, | ||||||
|  |     uint64_t* total_space, | ||||||
|  |     uint64_t* free_space) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     SDData* sd_data = storage->data; | ||||||
|  | 
 | ||||||
|  |     DWORD free_clusters; | ||||||
|  |     FATFS* fs; | ||||||
|  | 
 | ||||||
|  |     SDError fresult = f_getfree(sd_data->path, &free_clusters, &fs); | ||||||
|  |     if((FRESULT)fresult == FR_OK) { | ||||||
|  |         uint32_t total_sectors = (fs->n_fatent - 2) * fs->csize; | ||||||
|  |         uint32_t free_sectors = free_clusters * fs->csize; | ||||||
|  | 
 | ||||||
|  |         uint16_t sector_size = _MAX_SS; | ||||||
|  | #if _MAX_SS != _MIN_SS | ||||||
|  |         sector_size = fs->ssize; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  |         if(total_space != NULL) { | ||||||
|  |             *total_space = (uint64_t)total_sectors * (uint64_t)sector_size; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(free_space != NULL) { | ||||||
|  |             *free_space = (uint64_t)free_sectors * (uint64_t)sector_size; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return storage_ext_parse_error(fresult); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Init Storage *******************/ | ||||||
|  | 
 | ||||||
|  | void storage_ext_init(StorageData* storage) { | ||||||
|  |     SDData* sd_data = malloc(sizeof(SDData)); | ||||||
|  |     sd_data->fs = &USERFatFS; | ||||||
|  |     sd_data->path = "0:/"; | ||||||
|  |     sd_data->sd_was_present = true; | ||||||
|  | 
 | ||||||
|  |     storage->data = sd_data; | ||||||
|  |     storage->api.tick = storage_ext_tick; | ||||||
|  |     storage->fs_api.file.open = storage_ext_file_open; | ||||||
|  |     storage->fs_api.file.close = storage_ext_file_close; | ||||||
|  |     storage->fs_api.file.read = storage_ext_file_read; | ||||||
|  |     storage->fs_api.file.write = storage_ext_file_write; | ||||||
|  |     storage->fs_api.file.seek = storage_ext_file_seek; | ||||||
|  |     storage->fs_api.file.tell = storage_ext_file_tell; | ||||||
|  |     storage->fs_api.file.truncate = storage_ext_file_truncate; | ||||||
|  |     storage->fs_api.file.size = storage_ext_file_size; | ||||||
|  |     storage->fs_api.file.sync = storage_ext_file_sync; | ||||||
|  |     storage->fs_api.file.eof = storage_ext_file_eof; | ||||||
|  | 
 | ||||||
|  |     storage->fs_api.dir.open = storage_ext_dir_open; | ||||||
|  |     storage->fs_api.dir.close = storage_ext_dir_close; | ||||||
|  |     storage->fs_api.dir.read = storage_ext_dir_read; | ||||||
|  |     storage->fs_api.dir.rewind = storage_ext_dir_rewind; | ||||||
|  | 
 | ||||||
|  |     storage->fs_api.common.stat = storage_ext_common_stat; | ||||||
|  |     storage->fs_api.common.mkdir = storage_ext_common_mkdir; | ||||||
|  |     storage->fs_api.common.rename = storage_ext_common_rename; | ||||||
|  |     storage->fs_api.common.remove = storage_ext_common_remove; | ||||||
|  |     storage->fs_api.common.fs_info = storage_ext_common_fs_info; | ||||||
|  | 
 | ||||||
|  |     hal_sd_detect_init(); | ||||||
|  | 
 | ||||||
|  |     // do not notify on first launch, notifications app is waiting for our thread to read settings
 | ||||||
|  |     storage_ext_tick_internal(storage, false); | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								applications/storage/storages/storage-ext.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								applications/storage/storages/storage-ext.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "../storage-glue.h" | ||||||
|  | #include "../storage-sd-api.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | void storage_ext_init(StorageData* storage); | ||||||
|  | FS_Error sd_unmount_card(StorageData* storage); | ||||||
|  | FS_Error sd_format_card(StorageData* storage); | ||||||
|  | FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info); | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
							
								
								
									
										690
									
								
								applications/storage/storages/storage-int.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										690
									
								
								applications/storage/storages/storage-int.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,690 @@ | |||||||
|  | #include "storage-int.h" | ||||||
|  | #include <lfs.h> | ||||||
|  | #include <api-hal.h> | ||||||
|  | 
 | ||||||
|  | #define TAG "storage-int" | ||||||
|  | #define STORAGE_PATH "/int" | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     const size_t start_address; | ||||||
|  |     const size_t start_page; | ||||||
|  |     struct lfs_config config; | ||||||
|  |     lfs_t lfs; | ||||||
|  | } LFSData; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     void* data; | ||||||
|  |     bool open; | ||||||
|  | } LFSHandle; | ||||||
|  | 
 | ||||||
|  | static LFSHandle* lfs_handle_alloc_file() { | ||||||
|  |     LFSHandle* handle = furi_alloc(sizeof(LFSHandle)); | ||||||
|  |     handle->data = furi_alloc(sizeof(lfs_file_t)); | ||||||
|  |     return handle; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static LFSHandle* lfs_handle_alloc_dir() { | ||||||
|  |     LFSHandle* handle = furi_alloc(sizeof(LFSHandle)); | ||||||
|  |     handle->data = furi_alloc(sizeof(lfs_dir_t)); | ||||||
|  |     return handle; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* INTERNALS */ | ||||||
|  | 
 | ||||||
|  | static lfs_dir_t* lfs_handle_get_dir(LFSHandle* handle) { | ||||||
|  |     return handle->data; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static lfs_file_t* lfs_handle_get_file(LFSHandle* handle) { | ||||||
|  |     return handle->data; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void lfs_handle_free(LFSHandle* handle) { | ||||||
|  |     free(handle->data); | ||||||
|  |     free(handle); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void lfs_handle_set_open(LFSHandle* handle) { | ||||||
|  |     handle->open = true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool lfs_handle_is_open(LFSHandle* handle) { | ||||||
|  |     return handle->open; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static lfs_t* lfs_get_from_storage(StorageData* storage) { | ||||||
|  |     return &((LFSData*)storage->data)->lfs; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static LFSData* lfs_data_get_from_storage(StorageData* storage) { | ||||||
|  |     return (LFSData*)storage->data; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int storage_int_device_read( | ||||||
|  |     const struct lfs_config* c, | ||||||
|  |     lfs_block_t block, | ||||||
|  |     lfs_off_t off, | ||||||
|  |     void* buffer, | ||||||
|  |     lfs_size_t size) { | ||||||
|  |     LFSData* lfs_data = c->context; | ||||||
|  |     size_t address = lfs_data->start_address + block * c->block_size + off; | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_D( | ||||||
|  |         TAG, | ||||||
|  |         "Device read: block %d, off %d, buffer: %p, size %d, translated address: %p", | ||||||
|  |         block, | ||||||
|  |         off, | ||||||
|  |         buffer, | ||||||
|  |         size, | ||||||
|  |         address); | ||||||
|  | 
 | ||||||
|  |     memcpy(buffer, (void*)address, size); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int storage_int_device_prog( | ||||||
|  |     const struct lfs_config* c, | ||||||
|  |     lfs_block_t block, | ||||||
|  |     lfs_off_t off, | ||||||
|  |     const void* buffer, | ||||||
|  |     lfs_size_t size) { | ||||||
|  |     LFSData* lfs_data = c->context; | ||||||
|  |     size_t address = lfs_data->start_address + block * c->block_size + off; | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_D( | ||||||
|  |         TAG, | ||||||
|  |         "Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p", | ||||||
|  |         block, | ||||||
|  |         off, | ||||||
|  |         buffer, | ||||||
|  |         size, | ||||||
|  |         address); | ||||||
|  | 
 | ||||||
|  |     int ret = 0; | ||||||
|  |     while(size > 0) { | ||||||
|  |         if(!api_hal_flash_write_dword(address, *(uint64_t*)buffer)) { | ||||||
|  |             ret = -1; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         address += c->prog_size; | ||||||
|  |         buffer += c->prog_size; | ||||||
|  |         size -= c->prog_size; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t block) { | ||||||
|  |     LFSData* lfs_data = c->context; | ||||||
|  |     size_t page = lfs_data->start_page + block; | ||||||
|  | 
 | ||||||
|  |     FURI_LOG_D(TAG, "Device erase: page %d, translated page: %d", block, page); | ||||||
|  | 
 | ||||||
|  |     if(api_hal_flash_erase(page, 1)) { | ||||||
|  |         return 0; | ||||||
|  |     } else { | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int storage_int_device_sync(const struct lfs_config* c) { | ||||||
|  |     FURI_LOG_D(TAG, "Device sync: skipping, cause "); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static LFSData* storage_int_lfs_data_alloc() { | ||||||
|  |     LFSData* lfs_data = furi_alloc(sizeof(LFSData)); | ||||||
|  | 
 | ||||||
|  |     // Internal storage start address
 | ||||||
|  |     *(size_t*)(&lfs_data->start_address) = api_hal_flash_get_free_page_start_address(); | ||||||
|  |     *(size_t*)(&lfs_data->start_page) = | ||||||
|  |         (lfs_data->start_address - api_hal_flash_get_base()) / api_hal_flash_get_page_size(); | ||||||
|  | 
 | ||||||
|  |     // LFS configuration
 | ||||||
|  |     // Glue and context
 | ||||||
|  |     lfs_data->config.context = lfs_data; | ||||||
|  |     lfs_data->config.read = storage_int_device_read; | ||||||
|  |     lfs_data->config.prog = storage_int_device_prog; | ||||||
|  |     lfs_data->config.erase = storage_int_device_erase; | ||||||
|  |     lfs_data->config.sync = storage_int_device_sync; | ||||||
|  | 
 | ||||||
|  |     // Block device description
 | ||||||
|  |     lfs_data->config.read_size = api_hal_flash_get_read_block_size(); | ||||||
|  |     lfs_data->config.prog_size = api_hal_flash_get_write_block_size(); | ||||||
|  |     lfs_data->config.block_size = api_hal_flash_get_page_size(); | ||||||
|  |     lfs_data->config.block_count = api_hal_flash_get_free_page_count(); | ||||||
|  |     lfs_data->config.block_cycles = api_hal_flash_get_cycles_count(); | ||||||
|  |     lfs_data->config.cache_size = 16; | ||||||
|  |     lfs_data->config.lookahead_size = 16; | ||||||
|  | 
 | ||||||
|  |     return lfs_data; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) { | ||||||
|  |     int err; | ||||||
|  |     ApiHalBootFlag boot_flags = api_hal_boot_get_flags(); | ||||||
|  |     lfs_t* lfs = &lfs_data->lfs; | ||||||
|  | 
 | ||||||
|  |     if(boot_flags & ApiHalBootFlagFactoryReset) { | ||||||
|  |         // Factory reset
 | ||||||
|  |         err = lfs_format(lfs, &lfs_data->config); | ||||||
|  |         if(err == 0) { | ||||||
|  |             FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount"); | ||||||
|  |             api_hal_boot_set_flags(boot_flags & ~ApiHalBootFlagFactoryReset); | ||||||
|  |             err = lfs_mount(lfs, &lfs_data->config); | ||||||
|  |             if(err == 0) { | ||||||
|  |                 FURI_LOG_I(TAG, "Factory reset: Mounted"); | ||||||
|  |                 storage->status = StorageStatusOK; | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_E(TAG, "Factory reset: Mount after format failed"); | ||||||
|  |                 storage->status = StorageStatusNotMounted; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             FURI_LOG_E(TAG, "Factory reset: Format failed"); | ||||||
|  |             storage->status = StorageStatusNoFS; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         // Normal
 | ||||||
|  |         err = lfs_mount(lfs, &lfs_data->config); | ||||||
|  |         if(err == 0) { | ||||||
|  |             FURI_LOG_I(TAG, "Mounted"); | ||||||
|  |             storage->status = StorageStatusOK; | ||||||
|  |         } else { | ||||||
|  |             FURI_LOG_E(TAG, "Mount failed, formatting"); | ||||||
|  |             err = lfs_format(lfs, &lfs_data->config); | ||||||
|  |             if(err == 0) { | ||||||
|  |                 FURI_LOG_I(TAG, "Format successful, trying to mount"); | ||||||
|  |                 err = lfs_mount(lfs, &lfs_data->config); | ||||||
|  |                 if(err == 0) { | ||||||
|  |                     FURI_LOG_I(TAG, "Mounted"); | ||||||
|  |                     storage->status = StorageStatusOK; | ||||||
|  |                 } else { | ||||||
|  |                     FURI_LOG_E(TAG, "Mount after format failed"); | ||||||
|  |                     storage->status = StorageStatusNotMounted; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_E(TAG, "Format failed"); | ||||||
|  |                 storage->status = StorageStatusNoFS; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /****************** Common Functions ******************/ | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_int_parse_error(int error) { | ||||||
|  |     FS_Error result = FSE_INTERNAL; | ||||||
|  | 
 | ||||||
|  |     if(error >= LFS_ERR_OK) { | ||||||
|  |         result = FSE_OK; | ||||||
|  |     } else { | ||||||
|  |         switch(error) { | ||||||
|  |         case LFS_ERR_IO: | ||||||
|  |             result = FSE_INTERNAL; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_CORRUPT: | ||||||
|  |             result = FSE_INTERNAL; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_NOENT: | ||||||
|  |             result = FSE_NOT_EXIST; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_EXIST: | ||||||
|  |             result = FSE_EXIST; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_NOTDIR: | ||||||
|  |             result = FSE_INVALID_NAME; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_ISDIR: | ||||||
|  |             result = FSE_INVALID_NAME; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_NOTEMPTY: | ||||||
|  |             result = FSE_DENIED; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_BADF: | ||||||
|  |             result = FSE_INVALID_NAME; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_FBIG: | ||||||
|  |             result = FSE_INTERNAL; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_INVAL: | ||||||
|  |             result = FSE_INVALID_PARAMETER; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_NOSPC: | ||||||
|  |             result = FSE_INTERNAL; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_NOMEM: | ||||||
|  |             result = FSE_INTERNAL; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_NOATTR: | ||||||
|  |             result = FSE_INVALID_PARAMETER; | ||||||
|  |             break; | ||||||
|  |         case LFS_ERR_NAMETOOLONG: | ||||||
|  |             result = FSE_INVALID_NAME; | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* File Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static bool storage_int_file_open( | ||||||
|  |     void* ctx, | ||||||
|  |     File* file, | ||||||
|  |     const char* path, | ||||||
|  |     FS_AccessMode access_mode, | ||||||
|  |     FS_OpenMode open_mode) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  | 
 | ||||||
|  |     int flags = 0; | ||||||
|  | 
 | ||||||
|  |     if(access_mode & FSAM_READ) flags |= LFS_O_RDONLY; | ||||||
|  |     if(access_mode & FSAM_WRITE) flags |= LFS_O_WRONLY; | ||||||
|  | 
 | ||||||
|  |     if(open_mode & FSOM_OPEN_EXISTING) flags = flags; | ||||||
|  |     if(open_mode & FSOM_OPEN_ALWAYS) flags |= LFS_O_CREAT; | ||||||
|  |     if(open_mode & FSOM_OPEN_APPEND) flags |= LFS_O_CREAT | LFS_O_APPEND; | ||||||
|  |     if(open_mode & FSOM_CREATE_NEW) flags |= LFS_O_CREAT | LFS_O_EXCL; | ||||||
|  |     if(open_mode & FSOM_CREATE_ALWAYS) flags |= LFS_O_CREAT | LFS_O_TRUNC; | ||||||
|  | 
 | ||||||
|  |     LFSHandle* handle = lfs_handle_alloc_file(); | ||||||
|  |     storage_set_storage_file_data(file, handle, storage); | ||||||
|  |     file->internal_error_id = lfs_file_open(lfs, lfs_handle_get_file(handle), path, flags); | ||||||
|  | 
 | ||||||
|  |     if(file->internal_error_id >= LFS_ERR_OK) { | ||||||
|  |         lfs_handle_set_open(handle); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_int_file_close(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = lfs_file_close(lfs, lfs_handle_get_file(handle)); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     lfs_handle_free(handle); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint16_t | ||||||
|  |     storage_int_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     uint16_t bytes_readed = 0; | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = | ||||||
|  |             lfs_file_read(lfs, lfs_handle_get_file(handle), buff, bytes_to_read); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  | 
 | ||||||
|  |     if(file->error_id == FSE_OK) { | ||||||
|  |         bytes_readed = file->internal_error_id; | ||||||
|  |         file->internal_error_id = 0; | ||||||
|  |     } | ||||||
|  |     return bytes_readed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint16_t | ||||||
|  |     storage_int_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     uint16_t bytes_written = 0; | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = | ||||||
|  |             lfs_file_write(lfs, lfs_handle_get_file(handle), buff, bytes_to_write); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  | 
 | ||||||
|  |     if(file->error_id == FSE_OK) { | ||||||
|  |         bytes_written = file->internal_error_id; | ||||||
|  |         file->internal_error_id = 0; | ||||||
|  |     } | ||||||
|  |     return bytes_written; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  |     storage_int_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         if(from_start) { | ||||||
|  |             file->internal_error_id = | ||||||
|  |                 lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_SET); | ||||||
|  |         } else { | ||||||
|  |             file->internal_error_id = | ||||||
|  |                 lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_CUR); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint64_t storage_int_file_tell(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle)); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  | 
 | ||||||
|  |     int32_t position = 0; | ||||||
|  |     if(file->error_id == FSE_OK) { | ||||||
|  |         position = file->internal_error_id; | ||||||
|  |         file->internal_error_id = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return position; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_int_file_truncate(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle)); | ||||||
|  |         file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  | 
 | ||||||
|  |         if(file->error_id == FSE_OK) { | ||||||
|  |             uint32_t position = file->internal_error_id; | ||||||
|  |             file->internal_error_id = | ||||||
|  |                 lfs_file_truncate(lfs, lfs_handle_get_file(handle), position); | ||||||
|  |             file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |         file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_int_file_sync(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = lfs_file_sync(lfs, lfs_handle_get_file(handle)); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static uint64_t storage_int_file_size(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = lfs_file_size(lfs, lfs_handle_get_file(handle)); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  | 
 | ||||||
|  |     uint32_t size = 0; | ||||||
|  |     if(file->error_id == FSE_OK) { | ||||||
|  |         size = file->internal_error_id; | ||||||
|  |         file->internal_error_id = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_int_file_eof(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     bool eof = true; | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         int32_t position = lfs_file_tell(lfs, lfs_handle_get_file(handle)); | ||||||
|  |         int32_t size = lfs_file_size(lfs, lfs_handle_get_file(handle)); | ||||||
|  | 
 | ||||||
|  |         if(position < 0) { | ||||||
|  |             file->internal_error_id = position; | ||||||
|  |         } else if(size < 0) { | ||||||
|  |             file->internal_error_id = size; | ||||||
|  |         } else { | ||||||
|  |             file->internal_error_id = LFS_ERR_OK; | ||||||
|  |             eof = (position >= size); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     return eof; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Dir Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static bool storage_int_dir_open(void* ctx, File* file, const char* path) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  | 
 | ||||||
|  |     LFSHandle* handle = lfs_handle_alloc_dir(); | ||||||
|  |     storage_set_storage_file_data(file, handle, storage); | ||||||
|  | 
 | ||||||
|  |     file->internal_error_id = lfs_dir_open(lfs, lfs_handle_get_dir(handle), path); | ||||||
|  |     if(file->internal_error_id >= LFS_ERR_OK) { | ||||||
|  |         lfs_handle_set_open(handle); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_int_dir_close(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = lfs_dir_close(lfs, lfs_handle_get_dir(handle)); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     lfs_handle_free(handle); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_int_dir_read( | ||||||
|  |     void* ctx, | ||||||
|  |     File* file, | ||||||
|  |     FileInfo* fileinfo, | ||||||
|  |     char* name, | ||||||
|  |     const uint16_t name_length) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         struct lfs_info _fileinfo; | ||||||
|  | 
 | ||||||
|  |         // LFS returns virtual directories "." and "..", so we read until we get something meaningful or an empty string
 | ||||||
|  |         do { | ||||||
|  |             file->internal_error_id = lfs_dir_read(lfs, lfs_handle_get_dir(handle), &_fileinfo); | ||||||
|  |             file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |         } while(strcmp(_fileinfo.name, ".") == 0 || strcmp(_fileinfo.name, "..") == 0); | ||||||
|  | 
 | ||||||
|  |         if(fileinfo != NULL) { | ||||||
|  |             fileinfo->size = _fileinfo.size; | ||||||
|  |             fileinfo->flags = 0; | ||||||
|  |             if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if(name != NULL) { | ||||||
|  |             snprintf(name, name_length, "%s", _fileinfo.name); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // set FSE_NOT_EXIST error on end of directory
 | ||||||
|  |         if(file->internal_error_id == 0) { | ||||||
|  |             file->error_id = FSE_NOT_EXIST; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |         file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool storage_int_dir_rewind(void* ctx, File* file) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||||
|  | 
 | ||||||
|  |     if(lfs_handle_is_open(handle)) { | ||||||
|  |         file->internal_error_id = lfs_dir_rewind(lfs, lfs_handle_get_dir(handle)); | ||||||
|  |     } else { | ||||||
|  |         file->internal_error_id = LFS_ERR_BADF; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||||
|  |     return (file->error_id == FSE_OK); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Common FS Functions *******************/ | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_int_common_stat(void* ctx, const char* path, FileInfo* fileinfo) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     struct lfs_info _fileinfo; | ||||||
|  |     int result = lfs_stat(lfs, path, &_fileinfo); | ||||||
|  | 
 | ||||||
|  |     if(fileinfo != NULL) { | ||||||
|  |         fileinfo->size = _fileinfo.size; | ||||||
|  |         fileinfo->flags = 0; | ||||||
|  |         if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return storage_int_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_int_common_remove(void* ctx, const char* path) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     int result = lfs_remove(lfs, path); | ||||||
|  |     return storage_int_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_int_common_rename(void* ctx, const char* old_path, const char* new_path) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     int result = lfs_rename(lfs, old_path, new_path); | ||||||
|  |     return storage_int_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_int_common_mkdir(void* ctx, const char* path) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     int result = lfs_mkdir(lfs, path); | ||||||
|  |     return storage_int_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static FS_Error storage_int_common_fs_info( | ||||||
|  |     void* ctx, | ||||||
|  |     const char* fs_path, | ||||||
|  |     uint64_t* total_space, | ||||||
|  |     uint64_t* free_space) { | ||||||
|  |     StorageData* storage = ctx; | ||||||
|  | 
 | ||||||
|  |     lfs_t* lfs = lfs_get_from_storage(storage); | ||||||
|  |     LFSData* lfs_data = lfs_data_get_from_storage(storage); | ||||||
|  | 
 | ||||||
|  |     *total_space = lfs_data->config.block_size * lfs_data->config.block_count; | ||||||
|  | 
 | ||||||
|  |     lfs_ssize_t result = lfs_fs_size(lfs); | ||||||
|  |     if(result >= 0) { | ||||||
|  |         *free_space = *total_space - (result * lfs_data->config.block_size); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return storage_int_parse_error(result); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Init Storage *******************/ | ||||||
|  | 
 | ||||||
|  | void storage_int_init(StorageData* storage) { | ||||||
|  |     FURI_LOG_I(TAG, "Starting"); | ||||||
|  |     LFSData* lfs_data = storage_int_lfs_data_alloc(); | ||||||
|  |     FURI_LOG_I( | ||||||
|  |         TAG, | ||||||
|  |         "Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d", | ||||||
|  |         lfs_data->start_address, | ||||||
|  |         lfs_data->config.read_size, | ||||||
|  |         lfs_data->config.prog_size, | ||||||
|  |         lfs_data->config.block_size, | ||||||
|  |         lfs_data->config.block_count, | ||||||
|  |         lfs_data->config.block_cycles); | ||||||
|  | 
 | ||||||
|  |     storage_int_lfs_mount(lfs_data, storage); | ||||||
|  | 
 | ||||||
|  |     storage->data = lfs_data; | ||||||
|  |     storage->api.tick = NULL; | ||||||
|  |     storage->fs_api.file.open = storage_int_file_open; | ||||||
|  |     storage->fs_api.file.close = storage_int_file_close; | ||||||
|  |     storage->fs_api.file.read = storage_int_file_read; | ||||||
|  |     storage->fs_api.file.write = storage_int_file_write; | ||||||
|  |     storage->fs_api.file.seek = storage_int_file_seek; | ||||||
|  |     storage->fs_api.file.tell = storage_int_file_tell; | ||||||
|  |     storage->fs_api.file.truncate = storage_int_file_truncate; | ||||||
|  |     storage->fs_api.file.size = storage_int_file_size; | ||||||
|  |     storage->fs_api.file.sync = storage_int_file_sync; | ||||||
|  |     storage->fs_api.file.eof = storage_int_file_eof; | ||||||
|  | 
 | ||||||
|  |     storage->fs_api.dir.open = storage_int_dir_open; | ||||||
|  |     storage->fs_api.dir.close = storage_int_dir_close; | ||||||
|  |     storage->fs_api.dir.read = storage_int_dir_read; | ||||||
|  |     storage->fs_api.dir.rewind = storage_int_dir_rewind; | ||||||
|  | 
 | ||||||
|  |     storage->fs_api.common.stat = storage_int_common_stat; | ||||||
|  |     storage->fs_api.common.mkdir = storage_int_common_mkdir; | ||||||
|  |     storage->fs_api.common.rename = storage_int_common_rename; | ||||||
|  |     storage->fs_api.common.remove = storage_int_common_remove; | ||||||
|  |     storage->fs_api.common.fs_info = storage_int_common_fs_info; | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								applications/storage/storages/storage-int.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/storage/storages/storage-int.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <furi.h> | ||||||
|  | #include "../storage-glue.h" | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | void storage_int_init(StorageData* storage); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @ -223,8 +223,8 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { | |||||||
|     furi_check(instance->stream); |     furi_check(instance->stream); | ||||||
| 
 | 
 | ||||||
|     SubGhzProtocol* protocol = subghz_protocol_alloc(); |     SubGhzProtocol* protocol = subghz_protocol_alloc(); | ||||||
|     subghz_protocol_load_keeloq_file(protocol, "/assets/subghz/keeloq_mfcodes"); |     subghz_protocol_load_keeloq_file(protocol, "/ext/assets/subghz/keeloq_mfcodes"); | ||||||
|     subghz_protocol_load_nice_flor_s_file(protocol, "/assets/subghz/nice_floor_s_rx"); |     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); |     subghz_protocol_enable_dump_text(protocol, subghz_cli_command_rx_text_callback, instance); | ||||||
| 
 | 
 | ||||||
|     // Configure radio
 |     // Configure radio
 | ||||||
|  | |||||||
| @ -207,9 +207,10 @@ SubghzCapture* subghz_capture_alloc() { | |||||||
|         subghz_capture->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse); |         subghz_capture->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse); | ||||||
|     subghz_worker_set_context(subghz_capture->worker, subghz_capture->protocol); |     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_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_protocol_enable_dump_text( | ||||||
|         subghz_capture->protocol, subghz_capture_text_callback, subghz_capture); |         subghz_capture->protocol, subghz_capture_text_callback, subghz_capture); | ||||||
| 
 | 
 | ||||||
|  | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -10,67 +10,67 @@ extern const Icon A_MDWRB_32x32; | |||||||
| extern const Icon A_MDWR_32x32; | extern const Icon A_MDWR_32x32; | ||||||
| extern const Icon A_WatchingTV_128x64; | extern const Icon A_WatchingTV_128x64; | ||||||
| extern const Icon A_Wink_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_dir_10px; | ||||||
|  | extern const Icon I_ibutt_10px; | ||||||
|  | extern const Icon I_ir_10px; | ||||||
| extern const Icon I_Nfc_10px; | extern const Icon I_Nfc_10px; | ||||||
| extern const Icon I_sub1_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_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_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_BigGames_24x24; | ||||||
| extern const Icon I_BigProfile_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_DolphinFirstStart0_70x53; | ||||||
| extern const Icon I_DolphinFirstStart6_58x54; |  | ||||||
| extern const Icon I_DolphinFirstStart1_59x53; | extern const Icon I_DolphinFirstStart1_59x53; | ||||||
| extern const Icon I_DolphinFirstStart8_56x51; | extern const Icon I_DolphinFirstStart2_59x51; | ||||||
| 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_DolphinFirstStart3_57x48; | 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_DoorLeft_8x56; | ||||||
| extern const Icon I_DoorLocked_10x56; | 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_70x55; | ||||||
|  | extern const Icon I_DoorRight_8x56; | ||||||
| extern const Icon I_LockPopup_100x49; | extern const Icon I_LockPopup_100x49; | ||||||
| extern const Icon I_Mute_25x27; | extern const Icon I_PassportBottom_128x17; | ||||||
| extern const Icon I_IrdaArrowUp_4x8; | extern const Icon I_PassportLeft_6x47; | ||||||
| extern const Icon I_Up_hvr_25x27; | extern const Icon I_Back_15x10; | ||||||
| extern const Icon I_Mute_hvr_25x27; |  | ||||||
| extern const Icon I_Vol_down_25x27; |  | ||||||
| extern const Icon I_Down_25x27; | 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_Down_hvr_25x27; | ||||||
| extern const Icon I_Fill_marker_7x7; | extern const Icon I_Fill_marker_7x7; | ||||||
| extern const Icon I_Power_25x27; | extern const Icon I_IrdaArrowDown_4x8; | ||||||
| extern const Icon I_Vol_up_25x27; | extern const Icon I_IrdaArrowUp_4x8; | ||||||
| extern const Icon I_Up_25x27; | extern const Icon I_IrdaLearnShort_128x31; | ||||||
| extern const Icon I_Back_15x10; | extern const Icon I_IrdaLearn_128x64; | ||||||
| extern const Icon I_IrdaSend_128x64; |  | ||||||
| extern const Icon I_IrdaSendShort_128x34; | 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_Vol_up_hvr_25x27; | ||||||
| extern const Icon I_KeySave_24x11; |  | ||||||
| extern const Icon I_KeyBackspaceSelected_16x9; | extern const Icon I_KeyBackspaceSelected_16x9; | ||||||
| extern const Icon I_KeySaveSelected_24x11; |  | ||||||
| extern const Icon I_KeyBackspace_16x9; | 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_125khz_14; | ||||||
| extern const Icon A_Bluetooth_14; | extern const Icon A_Bluetooth_14; | ||||||
| extern const Icon A_FileManager_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_Tamagotchi_14; | ||||||
| extern const Icon A_U2F_14; | extern const Icon A_U2F_14; | ||||||
| extern const Icon A_iButton_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_EMV_Chip_14x11; | ||||||
| extern const Icon I_passport_happy1_43x45; | extern const Icon I_Medium_chip_22x21; | ||||||
| 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_passport_bad1_43x45; | 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_happy2_43x45; | ||||||
|  | extern const Icon I_passport_happy3_43x45; | ||||||
| extern const Icon I_passport_okay1_43x45; | extern const Icon I_passport_okay1_43x45; | ||||||
| extern const Icon I_Health_16x16; | extern const Icon I_passport_okay2_43x45; | ||||||
| extern const Icon I_FaceCharging_29x14; | extern const Icon I_passport_okay3_43x45; | ||||||
| extern const Icon I_BatteryBody_52x28; | extern const Icon I_BatteryBody_52x28; | ||||||
| extern const Icon I_Voltage_16x16; | extern const Icon I_Battery_16x16; | ||||||
| extern const Icon I_Temperature_16x16; | extern const Icon I_FaceCharging_29x14; | ||||||
|  | extern const Icon I_FaceConfused_29x14; | ||||||
| extern const Icon I_FaceNopower_29x14; | extern const Icon I_FaceNopower_29x14; | ||||||
| extern const Icon I_FaceNormal_29x14; | extern const Icon I_FaceNormal_29x14; | ||||||
| extern const Icon I_Battery_16x16; | extern const Icon I_Health_16x16; | ||||||
| extern const Icon I_FaceConfused_29x14; | extern const Icon I_Temperature_16x16; | ||||||
| extern const Icon I_RFIDDolphinSuccess_108x57; | extern const Icon I_Voltage_16x16; | ||||||
| extern const Icon I_RFIDBigChip_37x36; | extern const Icon I_RFIDBigChip_37x36; | ||||||
| extern const Icon I_RFIDDolphinSend_97x61; |  | ||||||
| extern const Icon I_RFIDDolphinReceive_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_SDError_43x35; | ||||||
| extern const Icon I_WalkR2_32x32; | extern const Icon I_SDQuestion_35x43; | ||||||
| extern const Icon I_WalkL2_32x32; |  | ||||||
| extern const Icon I_WalkRB1_32x32; |  | ||||||
| extern const Icon I_Home_painting_17x20; | 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_PC_22x29; | ||||||
| extern const Icon I_WalkL1_32x32; | extern const Icon I_Sofa_40x13; | ||||||
| extern const Icon I_TV_20x20; | 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_TV_20x24; | ||||||
| extern const Icon I_BadUsb_9x8; | extern const Icon I_WalkL1_32x32; | ||||||
| extern const Icon I_PlaceholderR_30x13; | extern const Icon I_WalkL2_32x32; | ||||||
| extern const Icon I_Background_128x8; | extern const Icon I_WalkLB1_32x32; | ||||||
| extern const Icon I_Lock_8x8; | extern const Icon I_WalkLB2_32x32; | ||||||
| extern const Icon I_Battery_26x8; | extern const Icon I_WalkR1_32x32; | ||||||
| extern const Icon I_PlaceholderL_11x13; | extern const Icon I_WalkR2_32x32; | ||||||
| extern const Icon I_Battery_19x8; | extern const Icon I_WalkRB1_32x32; | ||||||
| extern const Icon I_SDcardMounted_11x8; | extern const Icon I_WalkRB2_32x32; | ||||||
| extern const Icon I_SDcardFail_11x8; |  | ||||||
| extern const Icon I_USBConnected_15x8; |  | ||||||
| extern const Icon I_Bluetooth_5x8; |  | ||||||
| extern const Icon I_Background_128x11; | 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_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_iButtonDolphinSuccess_109x60; | ||||||
| extern const Icon I_iButtonDolphinVerySuccess_108x52; | extern const Icon I_iButtonDolphinVerySuccess_108x52; | ||||||
| extern const Icon I_iButtonKey_49x44; | extern const Icon I_iButtonKey_49x44; | ||||||
| extern const Icon I_DolphinNice_96x59; |  | ||||||
| extern const Icon I_DolphinWait_61x59; |  | ||||||
|  | |||||||
| @ -223,6 +223,20 @@ size_t memmgr_heap_get_max_free_block() { | |||||||
|     osKernelUnlock(); |     osKernelUnlock(); | ||||||
|     return max_free_size; |     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) { | void* pvPortMalloc(size_t xWantedSize) { | ||||||
|  | |||||||
| @ -28,6 +28,11 @@ size_t memmgr_heap_get_thread_memory(osThreadId_t thread_id); | |||||||
|  */ |  */ | ||||||
| size_t memmgr_heap_get_max_free_block(); | 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 | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -68,7 +68,7 @@ | |||||||
| #define	_USE_EXPAND		0 | #define	_USE_EXPAND		0 | ||||||
| /* This option switches f_expand function. (0:Disable or 1:Enable) */ | /* 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().
 | /* 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. */ | /  (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. | /  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. */ | /  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
 | /* 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(). | /  be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf(). | ||||||
| / | / | ||||||
| @ -205,7 +205,7 @@ | |||||||
| / System Configurations | / 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)
 | /* 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. | /  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 | /  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) | /  When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1) | ||||||
| /  Note that enabling exFAT discards C89 compatibility. */ | /  Note that enabling exFAT discards C89 compatibility. */ | ||||||
| 
 | 
 | ||||||
| #define _FS_NORTC	0 | #define _FS_NORTC	1 | ||||||
| #define _NORTC_MON	6 | #define _NORTC_MON	7 | ||||||
| #define _NORTC_MDAY	4 | #define _NORTC_MDAY	20 | ||||||
| #define _NORTC_YEAR	2015 | #define _NORTC_YEAR	2021 | ||||||
| /* The option _FS_NORTC switches timestamp functiton. If the system does not have
 | /* 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 | /  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 | /  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.  | /  _NORTC_MDAY and _NORTC_YEAR have no effect.  | ||||||
| /  These options have no effect at read-only configuration (_FS_READONLY = 1). */ | /  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
 | /* 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 | /  and illegal operation to open objects. This option must be 0 when _FS_READONLY | ||||||
| /  is 1. | /  is 1. | ||||||
| @ -240,7 +240,7 @@ | |||||||
| /      can be opened simultaneously under file lock control. Note that the file | /      can be opened simultaneously under file lock control. Note that the file | ||||||
| /      lock control is independent of re-entrancy. */ | /      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 _FS_TIMEOUT      1000 /* Timeout period in unit of time ticks */ | ||||||
| #define _SYNC_t          osMutexId_t | #define _SYNC_t          osMutexId_t | ||||||
| /* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
 | /* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
 | ||||||
|  | |||||||
| @ -1,16 +1,13 @@ | |||||||
| #include "file-worker.h" | #include "file-worker.h" | ||||||
| #include "m-string.h" | #include "m-string.h" | ||||||
| #include <hex.h> | #include <hex.h> | ||||||
| #include <sd-card-api.h> | #include <dialogs/dialogs.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| struct FileWorker { | struct FileWorker { | ||||||
|     FS_Api* fs_api; |     Storage* api; | ||||||
|     SdCard_Api* sd_ex_api; |  | ||||||
|     bool silent; |     bool silent; | ||||||
|     File file; |     File* file; | ||||||
|     char file_buf[48]; |  | ||||||
|     size_t file_buf_cnt; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| bool file_worker_check_common_errors(FileWorker* file_worker); | 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_alloc(bool _silent) { | ||||||
|     FileWorker* file_worker = malloc(sizeof(FileWorker)); |     FileWorker* file_worker = malloc(sizeof(FileWorker)); | ||||||
|     file_worker->silent = _silent; |     file_worker->silent = _silent; | ||||||
|     file_worker->fs_api = furi_record_open("sdcard"); |     file_worker->api = furi_record_open("storage"); | ||||||
|     file_worker->sd_ex_api = furi_record_open("sdcard-ex"); |     file_worker->file = storage_file_alloc(file_worker->api); | ||||||
|     file_worker->file_buf_cnt = 0; |  | ||||||
| 
 | 
 | ||||||
|     return file_worker; |     return file_worker; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void file_worker_free(FileWorker* file_worker) { | void file_worker_free(FileWorker* file_worker) { | ||||||
|     furi_record_close("sdcard"); |     storage_file_free(file_worker->file); | ||||||
|     furi_record_close("sdcard-ex"); |     furi_record_close("storage"); | ||||||
|     free(file_worker); |     free(file_worker); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -44,8 +40,7 @@ bool file_worker_open( | |||||||
|     const char* filename, |     const char* filename, | ||||||
|     FS_AccessMode access_mode, |     FS_AccessMode access_mode, | ||||||
|     FS_OpenMode open_mode) { |     FS_OpenMode open_mode) { | ||||||
|     bool result = |     bool result = storage_file_open(file_worker->file, filename, access_mode, open_mode); | ||||||
|         file_worker->fs_api->file.open(&file_worker->file, filename, access_mode, open_mode); |  | ||||||
| 
 | 
 | ||||||
|     if(!result) { |     if(!result) { | ||||||
|         file_worker_show_error_internal(file_worker, "Cannot open\nfile"); |         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) { | 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); |     return file_worker_check_common_errors(file_worker); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool file_worker_mkdir(FileWorker* file_worker, const char* dirname) { | 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) { |     if(fs_result != FSE_OK && fs_result != FSE_EXIST) { | ||||||
|         file_worker_show_error_internal(file_worker, "Cannot create\nfolder"); |         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) { | 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) { |     if(fs_result != FSE_OK && fs_result != FSE_NOT_EXIST) { | ||||||
|         file_worker_show_error_internal(file_worker, "Cannot remove\nold file"); |         file_worker_show_error_internal(file_worker, "Cannot remove\nold file"); | ||||||
|         return false; |         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]; |     uint8_t buffer[buffer_size]; | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         uint16_t read_count = |         uint16_t read_count = storage_file_read(file_worker->file, buffer, buffer_size); | ||||||
|             file_worker->fs_api->file.read(&file_worker->file, buffer, buffer_size); |         if(storage_file_get_error(file_worker->file) != FSE_OK) { | ||||||
|         if(file_worker->file.error_id != FSE_OK) { |  | ||||||
|             file_worker_show_error_internal(file_worker, "Cannot read\nfile"); |             file_worker_show_error_internal(file_worker, "Cannot read\nfile"); | ||||||
|             return false; |             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) { | 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( | bool file_worker_file_select( | ||||||
| @ -220,16 +225,17 @@ bool file_worker_file_select( | |||||||
|     char* result, |     char* result, | ||||||
|     uint8_t result_size, |     uint8_t result_size, | ||||||
|     const char* selected_filename) { |     const char* selected_filename) { | ||||||
|     return file_worker->sd_ex_api->file_select( |     DialogsApp* dialogs = furi_record_open("dialogs"); | ||||||
|         file_worker->sd_ex_api->context, path, extension, result, result_size, selected_filename); |     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) { | 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(). */ |     /* 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); |     return true; | ||||||
|     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; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text) { | 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) { | bool file_worker_read_internal(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) { | ||||||
|     uint16_t read_count = |     uint16_t read_count = storage_file_read(file_worker->file, buffer, bytes_to_read); | ||||||
|         file_worker->fs_api->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"); |         file_worker_show_error_internal(file_worker, "Cannot read\nfile"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -254,10 +259,9 @@ bool file_worker_write_internal( | |||||||
|     FileWorker* file_worker, |     FileWorker* file_worker, | ||||||
|     const void* buffer, |     const void* buffer, | ||||||
|     uint16_t bytes_to_write) { |     uint16_t bytes_to_write) { | ||||||
|     uint16_t write_count = |     uint16_t write_count = storage_file_write(file_worker->file, buffer, bytes_to_write); | ||||||
|         file_worker->fs_api->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"); |         file_worker_show_error_internal(file_worker, "Cannot write\nto file"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -266,9 +270,9 @@ bool file_worker_write_internal( | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position) { | 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"); |         file_worker_show_error_internal(file_worker, "Cannot tell\nfile offset"); | ||||||
|         return false; |         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) { | 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); |     storage_file_seek(file_worker->file, position, from_start); | ||||||
|     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 seek\nfile"); |         file_worker_show_error_internal(file_worker, "Cannot seek\nfile"); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @ -286,9 +290,17 @@ bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool | |||||||
|     return true; |     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(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); |     string_clean(str_result); | ||||||
|     size_t newline_index = 0; |     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); |                 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; |                 max_length_exceeded = true; | ||||||
| 
 | 
 | ||||||
|             if (!max_length_exceeded) { |             if(!max_length_exceeded) { | ||||||
|                 for (size_t i = 0; i < end_index; ++i) { |                 for(size_t i = 0; i < end_index; ++i) { | ||||||
|                     string_push_back(str_result, file_buf[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; |             if(found_eol) break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         *file_buf_cnt += |         *file_buf_cnt += storage_file_read( | ||||||
|             file_worker->fs_api->file.read(&file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); |             file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); | ||||||
|         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 read\nfile"); |             file_worker_show_error_internal(file_worker, "Cannot read\nfile"); | ||||||
|             string_clear(str_result); |             string_clear(str_result); | ||||||
|             *file_buf_cnt = 0; |             *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) |     if(max_length_exceeded) string_clear(str_result); | ||||||
|         string_clear(str_result); |  | ||||||
| 
 | 
 | ||||||
|     return string_size(str_result) || *file_buf_cnt; |     return string_size(str_result) || *file_buf_cnt; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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) { | ||||||
|     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) { |     if(fs_result != FSE_OK && fs_result != FSE_EXIST) { | ||||||
|         file_worker_show_error_internal(file_worker, "Cannot rename\n file/directory"); |         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); |     return file_worker_check_common_errors(file_worker); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool file_worker_is_file_exist( | bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist) { | ||||||
|     FileWorker* file_worker, |     File* file = storage_file_alloc(file_worker->api); | ||||||
|     const char* filename, |  | ||||||
|     bool* exist) { |  | ||||||
| 
 | 
 | ||||||
|     File file; |     *exist = storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|     *exist = file_worker->fs_api->file.open(&file, filename, FSAM_READ, FSOM_OPEN_EXISTING); |     storage_file_close(file); | ||||||
|     if (*exist) |     storage_file_free(file); | ||||||
|         file_worker->fs_api->file.close(&file); |  | ||||||
| 
 | 
 | ||||||
|     return file_worker_check_common_errors(file_worker); |     return file_worker_check_common_errors(file_worker); | ||||||
| } | } | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
| #include <filesystem-api.h> | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
| @ -150,20 +150,20 @@ void file_worker_show_error(FileWorker* file_worker, const char* error_text); | |||||||
|  * @brief Show system file select widget |  * @brief Show system file select widget | ||||||
|  *  |  *  | ||||||
|  * @param file_worker FileWorker instance  |  * @param file_worker FileWorker instance  | ||||||
|  * @param path  |  * @param path path to directory | ||||||
|  * @param extension  |  * @param extension file extension to be offered for selection | ||||||
|  * @param result  |  * @param selected_filename buffer where the selected filename will be saved | ||||||
|  * @param result_size  |  * @param selected_filename_size and the size of this buffer | ||||||
|  * @param selected_filename  |  * @param preselected_filename filename to be preselected | ||||||
|  * @return true if file was selected |  * @return bool whether a file was selected | ||||||
|  */ |  */ | ||||||
| bool file_worker_file_select( | bool file_worker_file_select( | ||||||
|     FileWorker* file_worker, |     FileWorker* file_worker, | ||||||
|     const char* path, |     const char* path, | ||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     char* selected_filename, | ||||||
|     uint8_t result_size, |     uint8_t selected_filename_size, | ||||||
|     const char* selected_filename); |     const char* preselected_filename); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * @brief Reads data from a file until separator or EOF is found. |  * @brief Reads data from a file until separator or EOF is found. | ||||||
| @ -177,7 +177,13 @@ bool file_worker_file_select( | |||||||
|  * @param separator |  * @param separator | ||||||
|  * @return true on success |  * @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 |  * @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 |  * @param exist - flag to show file exist | ||||||
|  * @return true on success |  * @return true on success | ||||||
|  */ |  */ | ||||||
| bool file_worker_is_file_exist( | bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist); | ||||||
|     FileWorker* file_worker, |  | ||||||
|     const char* filename, |  | ||||||
|     bool* exist); |  | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * @brief Rename file or directory |  * @brief Rename file or directory | ||||||
| @ -200,9 +203,7 @@ bool file_worker_is_file_exist( | |||||||
|  * @param new_filename |  * @param new_filename | ||||||
|  * @return true on success |  * @return true on success | ||||||
|  */ |  */ | ||||||
| bool file_worker_rename(FileWorker* file_worker, | bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path); | ||||||
|     const char* old_path, |  | ||||||
|     const char* new_path); |  | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * @brief Check errors |  * @brief Check errors | ||||||
|  | |||||||
| @ -28,6 +28,23 @@ bool args_read_string_and_trim(string_t args, string_t word) { | |||||||
|     return true; |     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) { | bool args_char_to_hex(char hi_nibble, char low_nibble, uint8_t* byte) { | ||||||
|     uint8_t hi_nibble_value = 0; |     uint8_t hi_nibble_value = 0; | ||||||
|     uint8_t low_nibble_value = 0; |     uint8_t low_nibble_value = 0; | ||||||
|  | |||||||
| @ -8,15 +8,25 @@ extern "C" { | |||||||
| #endif | #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 args arguments string | ||||||
|  * @param word first word, output |  * @param word first argument, output | ||||||
|  * @return true - success |  * @return true - success | ||||||
|  * @return false - arguments string does not contain anything |  * @return false - arguments string does not contain anything | ||||||
|  */ |  */ | ||||||
| bool args_read_string_and_trim(string_t args, string_t word); | 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 |  * @brief Convert hex ASCII values to byte array | ||||||
|  *  |  *  | ||||||
|  | |||||||
| @ -1,329 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| #include <furi.h> |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  *  @brief Access mode flags |  | ||||||
|  */ |  | ||||||
| typedef enum { |  | ||||||
|     FSAM_READ = (1 << 0), /**< Read access */ |  | ||||||
|     FSAM_WRITE = (1 << 1), /**< Write access */ |  | ||||||
| } FS_AccessMode; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  *  @brief Open mode flags |  | ||||||
|  */ |  | ||||||
| typedef enum { |  | ||||||
|     FSOM_OPEN_EXISTING = 1, /**< Open file, fail if file doesn't exist */ |  | ||||||
|     FSOM_OPEN_ALWAYS = 2, /**< Open file. Create new file if not exist */ |  | ||||||
|     FSOM_OPEN_APPEND = 4, /**< Open file. Create new file if not exist. Set R/W pointer to EOF */ |  | ||||||
|     FSOM_CREATE_NEW = 8, /**< Creates a new file. Fails if the file is exist */ |  | ||||||
|     FSOM_CREATE_ALWAYS = 16, /**< Creates a new file. If file exist, truncate to zero size */ |  | ||||||
| } FS_OpenMode; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  *  @brief API errors enumeration |  | ||||||
|  */ |  | ||||||
| typedef enum { |  | ||||||
|     FSE_OK, /**< No error */ |  | ||||||
|     FSE_NOT_READY, /**< FS not ready */ |  | ||||||
|     FSE_EXIST, /**< File/Dir alrady exist */ |  | ||||||
|     FSE_NOT_EXIST, /**< File/Dir does not exist */ |  | ||||||
|     FSE_INVALID_PARAMETER, /**< Invalid API parameter */ |  | ||||||
|     FSE_DENIED, /**< Access denied */ |  | ||||||
|     FSE_INVALID_NAME, /**< Invalid name/path */ |  | ||||||
|     FSE_INTERNAL, /**< Internal error */ |  | ||||||
|     FSE_NOT_IMPLEMENTED, /**< Functon not implemented */ |  | ||||||
| } FS_Error; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  *  @brief FileInfo flags |  | ||||||
|  */ |  | ||||||
| typedef enum { |  | ||||||
|     FSF_READ_ONLY = (1 << 0), /**< Readonly */ |  | ||||||
|     FSF_HIDDEN = (1 << 1), /**< Hidden */ |  | ||||||
|     FSF_SYSTEM = (1 << 2), /**< System */ |  | ||||||
|     FSF_DIRECTORY = (1 << 3), /**< Directory */ |  | ||||||
|     FSF_ARCHIVE = (1 << 4), /**< Archive */ |  | ||||||
| } FS_Flags; |  | ||||||
| 
 |  | ||||||
| /** 
 |  | ||||||
|  *  @brief Structure that hold file index and returned api errors  |  | ||||||
|  */ |  | ||||||
| typedef struct { |  | ||||||
|     uint32_t file_id; /**< File ID for internal references */ |  | ||||||
|     FS_Error error_id; /**< Standart API error from FS_Error enum */ |  | ||||||
|     uint32_t internal_error_id; /**< Internal API error value */ |  | ||||||
| } File; |  | ||||||
| 
 |  | ||||||
| // TODO: solve year 2107 problem
 |  | ||||||
| /** 
 |  | ||||||
|  *  @brief Structure that hold packed date values  |  | ||||||
|  */ |  | ||||||
| typedef struct __attribute__((packed)) { |  | ||||||
|     uint16_t month_day : 5; /**< month day */ |  | ||||||
|     uint16_t month : 4; /**< month index */ |  | ||||||
|     uint16_t year : 7; /**< year, year + 1980 to get actual value */ |  | ||||||
| } FileDate; |  | ||||||
| 
 |  | ||||||
| /** 
 |  | ||||||
|  *  @brief Structure that hold packed time values  |  | ||||||
|  */ |  | ||||||
| typedef struct __attribute__((packed)) { |  | ||||||
|     uint16_t second : 5; /**< second, second * 2 to get actual value  */ |  | ||||||
|     uint16_t minute : 6; /**< minute */ |  | ||||||
|     uint16_t hour : 5; /**< hour */ |  | ||||||
| } FileTime; |  | ||||||
| 
 |  | ||||||
| /** 
 |  | ||||||
|  *  @brief Union of simple date and real value  |  | ||||||
|  */ |  | ||||||
| typedef union { |  | ||||||
|     FileDate simple; /**< simple access to date */ |  | ||||||
|     uint16_t value; /**< real date value */ |  | ||||||
| } FileDateUnion; |  | ||||||
| 
 |  | ||||||
| /** 
 |  | ||||||
|  *  @brief Union of simple time and real value  |  | ||||||
|  */ |  | ||||||
| typedef union { |  | ||||||
|     FileTime simple; /**< simple access to time */ |  | ||||||
|     uint16_t value; /**< real time value */ |  | ||||||
| } FileTimeUnion; |  | ||||||
| 
 |  | ||||||
| /** 
 |  | ||||||
|  *  @brief Structure that hold file info |  | ||||||
|  */ |  | ||||||
| typedef struct { |  | ||||||
|     uint8_t flags; /**< flags from FS_Flags enum */ |  | ||||||
|     uint64_t size; /**< file size */ |  | ||||||
|     FileDateUnion date; /**< file date */ |  | ||||||
|     FileTimeUnion time; /**< file time */ |  | ||||||
| } FileInfo; |  | ||||||
| 
 |  | ||||||
| /** @struct FS_File_Api
 |  | ||||||
|  *  @brief File api structure |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::open |  | ||||||
|  *      @brief Open file |  | ||||||
|  *      @param file pointer to file object, filled by api |  | ||||||
|  *      @param path path to file  |  | ||||||
|  *      @param access_mode access mode from FS_AccessMode  |  | ||||||
|  *      @param open_mode open mode from FS_OpenMode  |  | ||||||
|  *      @return success flag |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::close  |  | ||||||
|  *      @brief Close file |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return success flag |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::read |  | ||||||
|  *      @brief Read bytes from file to buffer |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @param buff pointer to buffer for reading |  | ||||||
|  *      @param bytes_to_read how many bytes to read, must be smaller or equal to buffer size  |  | ||||||
|  *      @return how many bytes actually has been readed |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::write |  | ||||||
|  *      @brief Write bytes from buffer to file |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @param buff pointer to buffer for writing |  | ||||||
|  *      @param bytes_to_read how many bytes to write, must be smaller or equal to buffer size  |  | ||||||
|  *      @return how many bytes actually has been writed |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::seek |  | ||||||
|  *      @brief Move r/w pointer  |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @param offset offset to move r/w pointer |  | ||||||
|  *      @param from_start set offset from start, or from current position |  | ||||||
|  *      @return success flag |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::tell |  | ||||||
|  *      @brief Get r/w pointer position |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return current r/w pointer position |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::truncate |  | ||||||
|  *      @brief Truncate file size to current r/w pointer position |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return success flag |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::size |  | ||||||
|  *      @brief Fet file size |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return file size |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::sync |  | ||||||
|  *      @brief Write file cache to storage |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return success flag |  | ||||||
|  *  |  | ||||||
|  *  @var FS_File_Api::eof |  | ||||||
|  *      @brief Checks that the r/w pointer is at the end of the file |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return end of file flag |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief File api structure |  | ||||||
|  */ |  | ||||||
| typedef struct { |  | ||||||
|     bool (*open)(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode); |  | ||||||
|     bool (*close)(File* file); |  | ||||||
|     uint16_t (*read)(File* file, void* buff, uint16_t bytes_to_read); |  | ||||||
|     uint16_t (*write)(File* file, const void* buff, uint16_t bytes_to_write); |  | ||||||
|     bool (*seek)(File* file, uint32_t offset, bool from_start); |  | ||||||
|     uint64_t (*tell)(File* file); |  | ||||||
|     bool (*truncate)(File* file); |  | ||||||
|     uint64_t (*size)(File* file); |  | ||||||
|     bool (*sync)(File* file); |  | ||||||
|     bool (*eof)(File* file); |  | ||||||
| } FS_File_Api; |  | ||||||
| 
 |  | ||||||
| /** @struct FS_Dir_Api
 |  | ||||||
|  *  @brief Dir api structure |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Dir_Api::open |  | ||||||
|  *      @brief Open directory to get objects from |  | ||||||
|  *      @param file pointer to file object, filled by api |  | ||||||
|  *      @param path path to directory  |  | ||||||
|  *      @return success flag |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Dir_Api::close  |  | ||||||
|  *      @brief Close directory |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return success flag |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Dir_Api::read |  | ||||||
|  *      @brief Read next object info in directory |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @param fileinfo pointer to readed FileInfo, can be NULL |  | ||||||
|  *      @param name pointer to name buffer, can be NULL |  | ||||||
|  *      @param name_length name buffer length |  | ||||||
|  *      @return success flag (if next object not exist also returns false and set error_id to FSE_NOT_EXIST) |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Dir_Api::rewind |  | ||||||
|  *      @brief Rewind to first object info in directory |  | ||||||
|  *      @param file pointer to file object |  | ||||||
|  *      @return success flag |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Dir api structure |  | ||||||
|  */ |  | ||||||
| typedef struct { |  | ||||||
|     bool (*open)(File* file, const char* path); |  | ||||||
|     bool (*close)(File* file); |  | ||||||
|     bool (*read)(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); |  | ||||||
|     bool (*rewind)(File* file); |  | ||||||
| } FS_Dir_Api; |  | ||||||
| 
 |  | ||||||
| /** @struct FS_Common_Api
 |  | ||||||
|  *  @brief Common api structure |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Common_Api::info |  | ||||||
|  *      @brief Open directory to get objects from |  | ||||||
|  *      @param path path to file/directory |  | ||||||
|  *      @param fileinfo pointer to readed FileInfo, can be NULL |  | ||||||
|  *      @param name pointer to name buffer, can be NULL |  | ||||||
|  *      @param name_length name buffer length |  | ||||||
|  *      @return FS_Error error info |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Common_Api::remove |  | ||||||
|  *      @brief Remove file/directory from storage,  |  | ||||||
|  *          directory must be empty, |  | ||||||
|  *          file/directory must not be opened, |  | ||||||
|  *          file/directory must not have FSF_READ_ONLY flag |  | ||||||
|  *      @param path path to file/directory |  | ||||||
|  *      @return FS_Error error info |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Common_Api::rename |  | ||||||
|  *      @brief Rename file/directory, |  | ||||||
|  *          file/directory must not be opened |  | ||||||
|  *      @param path path to file/directory |  | ||||||
|  *      @return FS_Error error info |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Common_Api::set_attr |  | ||||||
|  *      @brief Set attributes of file/directory,  |  | ||||||
|  *          for example: |  | ||||||
|  *          @code |  | ||||||
|  *          set "read only" flag and remove "hidden" flag |  | ||||||
|  *          set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN); |  | ||||||
|  *          @endcode |  | ||||||
|  *      @param path path to file/directory |  | ||||||
|  *      @param attr attribute values consist of FS_Flags |  | ||||||
|  *      @param mask attribute mask consist of FS_Flags |  | ||||||
|  *      @return FS_Error error info |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Common_Api::mkdir |  | ||||||
|  *      @brief Create new directory |  | ||||||
|  *      @param path path to new directory |  | ||||||
|  *      @return FS_Error error info |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Common_Api::set_time |  | ||||||
|  *      @brief Set file/directory modification time |  | ||||||
|  *      @param path path to file/directory |  | ||||||
|  *      @param date modification date  |  | ||||||
|  *      @param time modification time |  | ||||||
|  *      @see FileDateUnion |  | ||||||
|  *      @see FileTimeUnion |  | ||||||
|  *      @return FS_Error error info |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Common_Api::get_fs_info |  | ||||||
|  *      @brief Get total and free space storage values |  | ||||||
|  *      @param total_space pointer to total space value |  | ||||||
|  *      @param free_space pointer to free space value |  | ||||||
|  *      @return FS_Error error info |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Common api structure |  | ||||||
|  */ |  | ||||||
| typedef struct { |  | ||||||
|     FS_Error (*info)(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length); |  | ||||||
|     FS_Error (*remove)(const char* path); |  | ||||||
|     FS_Error (*rename)(const char* old_path, const char* new_path); |  | ||||||
|     FS_Error (*set_attr)(const char* path, uint8_t attr, uint8_t mask); |  | ||||||
|     FS_Error (*mkdir)(const char* path); |  | ||||||
|     FS_Error (*set_time)(const char* path, FileDateUnion date, FileTimeUnion time); |  | ||||||
|     FS_Error (*get_fs_info)(uint64_t* total_space, uint64_t* free_space); |  | ||||||
| } FS_Common_Api; |  | ||||||
| 
 |  | ||||||
| /** @struct FS_Error_Api
 |  | ||||||
|  *  @brief Errors api structure |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Error_Api::get_desc |  | ||||||
|  *      @brief Get error description text |  | ||||||
|  *      @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id) |  | ||||||
|  *      @return pointer to description text |  | ||||||
|  *  |  | ||||||
|  *  @var FS_Error_Api::get_internal_desc |  | ||||||
|  *      @brief Get internal error description text |  | ||||||
|  *      @param internal_error_id error id (for fire/dir functions result can be obtained from File.internal_error_id) |  | ||||||
|  *      @return pointer to description text |  | ||||||
|  */ |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Errors api structure |  | ||||||
|  */ |  | ||||||
| typedef struct { |  | ||||||
|     const char* (*get_desc)(FS_Error error_id); |  | ||||||
|     const char* (*get_internal_desc)(uint32_t internal_error_id); |  | ||||||
| } FS_Error_Api; |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * @brief Full filesystem api structure |  | ||||||
|  */ |  | ||||||
| typedef struct { |  | ||||||
|     FS_File_Api file; |  | ||||||
|     FS_Dir_Api dir; |  | ||||||
|     FS_Common_Api common; |  | ||||||
|     FS_Error_Api error; |  | ||||||
| } FS_Api; |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| @ -1,25 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| #include <furi.h> |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| extern "C" { |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| typedef struct SdApp SdApp; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     SdApp* context; |  | ||||||
|     bool (*file_select)( |  | ||||||
|         SdApp* context, |  | ||||||
|         const char* path, |  | ||||||
|         const char* extension, |  | ||||||
|         char* result, |  | ||||||
|         uint8_t result_size, |  | ||||||
|         const char* selected_filename); |  | ||||||
|     void (*check_error)(SdApp* context); |  | ||||||
|     void (*show_error)(SdApp* context, const char* error_text); |  | ||||||
| } SdCard_Api; |  | ||||||
| 
 |  | ||||||
| #ifdef __cplusplus |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| @ -1,48 +0,0 @@ | |||||||
| #include <file_reader.h> |  | ||||||
| 
 |  | ||||||
| std::string FileReader::getline(File* file) { |  | ||||||
|     std::string str; |  | ||||||
|     size_t newline_index = 0; |  | ||||||
|     bool found_eol = false; |  | ||||||
|     bool max_length_exceeded = false; |  | ||||||
| 
 |  | ||||||
|     while(1) { |  | ||||||
|         if(file_buf_cnt > 0) { |  | ||||||
|             size_t end_index = 0; |  | ||||||
|             char* endline_ptr = (char*)memchr(file_buf, '\n', file_buf_cnt); |  | ||||||
|             newline_index = endline_ptr - file_buf; |  | ||||||
| 
 |  | ||||||
|             if(endline_ptr == 0) { |  | ||||||
|                 end_index = file_buf_cnt; |  | ||||||
|             } else if(newline_index < file_buf_cnt) { |  | ||||||
|                 end_index = newline_index + 1; |  | ||||||
|                 found_eol = true; |  | ||||||
|             } else { |  | ||||||
|                 furi_assert(0); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (max_line_length && (str.size() + end_index > max_line_length)) |  | ||||||
|                 max_length_exceeded = true; |  | ||||||
| 
 |  | ||||||
|             if (!max_length_exceeded) |  | ||||||
|                 str.append(file_buf, end_index); |  | ||||||
| 
 |  | ||||||
|             memmove(file_buf, &file_buf[end_index], file_buf_cnt - end_index); |  | ||||||
|             file_buf_cnt = file_buf_cnt - end_index; |  | ||||||
|             if(found_eol) break; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         file_buf_cnt += |  | ||||||
|             fs_api->file.read(file, &file_buf[file_buf_cnt], sizeof(file_buf) - file_buf_cnt); |  | ||||||
|         if(file_buf_cnt == 0) { |  | ||||||
|             break; // end of reading
 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (max_length_exceeded) |  | ||||||
|         str.clear(); |  | ||||||
| 
 |  | ||||||
|     return str; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @ -1,44 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| #include <string> |  | ||||||
| #include <memory> |  | ||||||
| #include "sd-card-api.h" |  | ||||||
| #include "filesystem-api.h" |  | ||||||
| 
 |  | ||||||
| class FileReader { |  | ||||||
| private: |  | ||||||
|     char file_buf[48]; |  | ||||||
|     size_t file_buf_cnt = 0; |  | ||||||
|     size_t max_line_length = 0; |  | ||||||
|     SdCard_Api* sd_ex_api; |  | ||||||
|     FS_Api* fs_api; |  | ||||||
| 
 |  | ||||||
| public: |  | ||||||
|     FileReader() { |  | ||||||
|         sd_ex_api = static_cast<SdCard_Api*>(furi_record_open("sdcard-ex")); |  | ||||||
|         fs_api = static_cast<FS_Api*>(furi_record_open("sdcard")); |  | ||||||
|         reset(); |  | ||||||
|     } |  | ||||||
|     ~FileReader() { |  | ||||||
|         furi_record_close("sdcard"); |  | ||||||
|         furi_record_close("sdcard-ex"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::string getline(File* file); |  | ||||||
| 
 |  | ||||||
|     void reset(void) { |  | ||||||
|         file_buf_cnt = 0; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     SdCard_Api& get_sd_api() { |  | ||||||
|         return *sd_ex_api; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     FS_Api& get_fs_api() { |  | ||||||
|         return *fs_api; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void set_max_line_length(size_t value) { |  | ||||||
|         max_line_length = value; |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| @ -1,7 +1,7 @@ | |||||||
| #include "subghz_keystore.h" | #include "subghz_keystore.h" | ||||||
| 
 | 
 | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <filesystem-api.h> | #include <storage/storage.h> | ||||||
| 
 | 
 | ||||||
| #define FILE_BUFFER_SIZE 64 | #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) { | void subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { | ||||||
|     File manufacture_keys_file; |     File* manufacture_keys_file = storage_file_alloc(furi_record_open("storage")); | ||||||
|     FS_Api* fs_api = furi_record_open("sdcard"); |  | ||||||
|     fs_api->file.open(&manufacture_keys_file, file_name, FSAM_READ, FSOM_OPEN_EXISTING); |  | ||||||
|     string_t line; |     string_t line; | ||||||
|     string_init(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); |         printf("Loading manufacture keys file %s\r\n", file_name); | ||||||
|         char buffer[FILE_BUFFER_SIZE]; |         char buffer[FILE_BUFFER_SIZE]; | ||||||
|         uint16_t ret; |         uint16_t ret; | ||||||
|         do { |         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++) { |             for (uint16_t i=0; i < ret; i++) { | ||||||
|                 if (buffer[i] == '\n' && string_size(line) > 0) { |                 if (buffer[i] == '\n' && string_size(line) > 0) { | ||||||
|                     subghz_keystore_process_line(instance, line); |                     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); |         printf("Manufacture keys file is not found: %s\r\n", file_name); | ||||||
|     } |     } | ||||||
|     string_clear(line); |     string_clear(line); | ||||||
|     fs_api->file.close(&manufacture_keys_file); |     storage_file_close(manufacture_keys_file); | ||||||
|     furi_record_close("sdcard"); |     storage_file_free(manufacture_keys_file); | ||||||
|  |     furi_record_close("storage"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance) { | SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 SG
						SG