[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 power_task(void* p); | ||||
| int32_t bt_task(void* p); | ||||
| int32_t sd_card_test(void* p); | ||||
| int32_t application_vibro(void* p); | ||||
| int32_t app_gpio_test(void* p); | ||||
| int32_t app_ibutton(void* p); | ||||
| int32_t cli_task(void* p); | ||||
| int32_t music_player(void* p); | ||||
| int32_t sdnfc(void* p); | ||||
| int32_t sd_filesystem(void* p); | ||||
| int32_t subghz_app(void* p); | ||||
| int32_t gui_test(void* p); | ||||
| int32_t keypad_test(void* p); | ||||
| int32_t scene_app(void* p); | ||||
| int32_t passport(void* p); | ||||
| int32_t app_accessor(void* p); | ||||
| int32_t internal_storage_task(void* p); | ||||
| int32_t app_archive(void* p); | ||||
| int32_t notification_app(void* p); | ||||
| int32_t scened_app(void* p); | ||||
| int32_t lfrfid_app(void* p); | ||||
| int32_t lfrfid_debug_app(void* p); | ||||
| int32_t storage_app(void* p); | ||||
| int32_t storage_app_test(void* p); | ||||
| int32_t dialogs_app(void* p); | ||||
| 
 | ||||
| // On system start hooks declaration
 | ||||
| void irda_cli_init(); | ||||
| @ -47,9 +47,11 @@ void subghz_cli_init(); | ||||
| void bt_cli_init(); | ||||
| void lfrfid_cli_init(); | ||||
| void ibutton_cli_init(); | ||||
| void storage_cli_init(); | ||||
| 
 | ||||
| // Settings
 | ||||
| int32_t notification_app_settings(void* p); | ||||
| int32_t storage_settings(void* p); | ||||
| 
 | ||||
| const FlipperApplication FLIPPER_SERVICES[] = { | ||||
| #ifdef SRV_CLI | ||||
| @ -81,17 +83,6 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
|     {.app = loader, .name = "loader", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_SD_FILESYSTEM | ||||
|     {.app = sd_filesystem, .name = "sd_filesystem", .stack_size = 4096, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_INTERNAL_STORAGE | ||||
|     {.app = internal_storage_task, | ||||
|      .name = "internal_storage", | ||||
|      .stack_size = 2048, | ||||
|      .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DOLPHIN | ||||
|     {.app = dolphin_task, .name = "dolphin_task", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| @ -105,8 +96,7 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_LF_RFID | ||||
|     // TODO: fix stack size when sd api will be in separate thread
 | ||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 4096, .icon = &A_Plugins_14}, | ||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_IRDA | ||||
| @ -128,16 +118,12 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
|      .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_SD_TEST | ||||
|     {.app = sd_card_test, .name = "sd_card_test", .stack_size = 4096, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_MUSIC_PLAYER | ||||
|     {.app = music_player, .name = "music player", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_IBUTTON | ||||
|     {.app = app_ibutton, .name = "ibutton", .stack_size = 4096, .icon = &A_Plugins_14}, | ||||
|     {.app = app_ibutton, .name = "ibutton", .stack_size = 2048, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_GPIO_DEMO | ||||
| @ -164,6 +150,17 @@ const FlipperApplication FLIPPER_SERVICES[] = { | ||||
|     {.app = notification_app, .name = "notification", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_app, .name = "storage", .stack_size = 4096, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE_TEST | ||||
|     {.app = storage_app_test, .name = "storage test", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_DIALOGS | ||||
|     {.app = dialogs_app, .name = "dialogs", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication); | ||||
| @ -172,7 +169,7 @@ const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperA | ||||
| const FlipperApplication FLIPPER_APPS[] = { | ||||
| 
 | ||||
| #ifdef APP_IBUTTON | ||||
|     {.app = app_ibutton, .name = "iButton", .stack_size = 4096, .icon = &A_iButton_14}, | ||||
|     {.app = app_ibutton, .name = "iButton", .stack_size = 2048, .icon = &A_iButton_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_NFC | ||||
| @ -186,7 +183,7 @@ const FlipperApplication FLIPPER_APPS[] = { | ||||
| 
 | ||||
| #ifdef APP_LF_RFID | ||||
|     // TODO: fix stack size when sd api will be in separate thread
 | ||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 4096, .icon = &A_125khz_14}, | ||||
|     {.app = lfrfid_app, .name = "125 kHz RFID", .stack_size = 2048, .icon = &A_125khz_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_IRDA | ||||
| @ -219,6 +216,9 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { | ||||
| #ifdef SRV_BT | ||||
|     bt_cli_init, | ||||
| #endif | ||||
| #ifdef SRV_STORAGE | ||||
|     storage_cli_init, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| const size_t FLIPPER_ON_SYSTEM_START_COUNT = | ||||
| @ -255,10 +255,6 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { | ||||
|      .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_SD_TEST | ||||
|     {.app = sd_card_test, .name = "sd_card_test", .stack_size = 4096, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef APP_VIBRO_DEMO | ||||
|     {.app = application_vibro, .name = "vibro", .stack_size = 1024, .icon = &A_Plugins_14}, | ||||
| #endif | ||||
| @ -326,10 +322,11 @@ const size_t FLIPPER_SCENE_APPS_COUNT = sizeof(FLIPPER_SCENE_APPS) / sizeof(Flip | ||||
| // Settings menu
 | ||||
| const FlipperApplication FLIPPER_SETTINGS_APPS[] = { | ||||
| #ifdef SRV_NOTIFICATION | ||||
|     {.app = notification_app_settings, | ||||
|      .name = "Notification", | ||||
|      .stack_size = 1024, | ||||
|      .icon = &A_Plugins_14}, | ||||
|     {.app = notification_app_settings, .name = "Notification", .stack_size = 1024, .icon = NULL}, | ||||
| #endif | ||||
| 
 | ||||
| #ifdef SRV_STORAGE | ||||
|     {.app = storage_settings, .name = "Storage", .stack_size = 2048, .icon = NULL}, | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -16,10 +16,10 @@ SRV_MENU = 1 | ||||
| SRV_POWER = 1 | ||||
| SRV_BT = 1 | ||||
| SRV_CLI = 1 | ||||
| SRV_SD_FILESYSTEM = 1 | ||||
| SRV_INTERNAL_STORAGE = 1 | ||||
| SRV_DOLPHIN = 1 | ||||
| SRV_NOTIFICATION = 1 | ||||
| SRV_STORAGE = 1 | ||||
| SRV_DIALOGS = 1 | ||||
| 
 | ||||
| # Main Apps
 | ||||
| APP_IRDA  = 1 | ||||
| @ -189,19 +189,6 @@ SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_SD_TEST ?= 0 | ||||
| ifeq ($(SRV_SD_TEST), 1) | ||||
| CFLAGS		+= -DSRV_SD_TEST | ||||
| APP_SD_TEST = 1 | ||||
| endif | ||||
| APP_SD_TEST ?= 0 | ||||
| ifeq ($(APP_SD_TEST), 1) | ||||
| CFLAGS		+= -DAPP_SD_TEST | ||||
| SRV_INPUT = 1 | ||||
| SRV_GUI = 1 | ||||
| SRV_SD_FILESYSTEM = 1 | ||||
| endif | ||||
| 
 | ||||
| SRV_SPEAKER_DEMO ?= 0 | ||||
| ifeq ($(SRV_SPEAKER_DEMO), 1) | ||||
| CFLAGS		+= -DSRV_SPEAKER_DEMO | ||||
| @ -282,15 +269,6 @@ ifeq ($(APP_GUI_TEST), 1) | ||||
| CFLAGS		+= -DAPP_GUI_TEST | ||||
| endif | ||||
| 
 | ||||
| SRV_SDNFC ?= 0 | ||||
| ifeq ($(SRV_SDNFC), 1) | ||||
| CFLAGS		+= -DSRV_SDNFC | ||||
| APP_SDNFC = 1 | ||||
| endif | ||||
| APP_SDNFC ?= 0 | ||||
| ifeq ($(APP_SDNFC), 1) | ||||
| CFLAGS		+= -DAPP_SDNFC | ||||
| endif | ||||
| # device drivers
 | ||||
| 
 | ||||
| SRV_GUI	?= 0 | ||||
| @ -298,16 +276,6 @@ ifeq ($(SRV_GUI), 1) | ||||
| CFLAGS		+= -DSRV_GUI | ||||
| endif | ||||
| 
 | ||||
| SRV_SD_FILESYSTEM ?= 0 | ||||
| ifeq ($(SRV_SD_FILESYSTEM), 1) | ||||
| CFLAGS		+= -DSRV_SD_FILESYSTEM | ||||
| endif | ||||
| 
 | ||||
| SRV_INTERNAL_STORAGE ?= 0 | ||||
| ifeq ($(SRV_INTERNAL_STORAGE), 1) | ||||
| CFLAGS		+= -DSRV_INTERNAL_STORAGE | ||||
| endif | ||||
| 
 | ||||
| SRV_INPUT	?= 0 | ||||
| ifeq ($(SRV_INPUT), 1) | ||||
| CFLAGS		+= -DSRV_INPUT | ||||
| @ -323,3 +291,13 @@ SRV_NOTIFICATION ?= 0 | ||||
| ifeq ($(SRV_NOTIFICATION), 1) | ||||
| CFLAGS		+= -DSRV_NOTIFICATION | ||||
| endif | ||||
| 
 | ||||
| SRV_STORAGE ?= 0 | ||||
| ifeq ($(SRV_STORAGE), 1) | ||||
| CFLAGS		+= -DSRV_STORAGE | ||||
| endif | ||||
| 
 | ||||
| SRV_DIALOGS ?= 0 | ||||
| ifeq ($(SRV_DIALOGS), 1) | ||||
| CFLAGS		+= -DSRV_DIALOGS | ||||
| endif | ||||
| @ -3,18 +3,17 @@ | ||||
| static bool archive_get_filenames(ArchiveApp* archive); | ||||
| 
 | ||||
| static bool is_favorite(ArchiveApp* archive, ArchiveFile_t* file) { | ||||
|     FS_Common_Api* common_api = &archive->fs_api->common; | ||||
|     FileInfo file_info; | ||||
|     FS_Error fr; | ||||
|     string_t path; | ||||
| 
 | ||||
|     string_init_printf(path, "favorites/%s", string_get_cstr(file->name)); | ||||
|     string_init_printf(path, "%s/%s", get_favorites_path(), string_get_cstr(file->name)); | ||||
| 
 | ||||
|     fr = common_api->info(string_get_cstr(path), &file_info, NULL, 0); | ||||
|     fr = storage_common_stat(archive->api, string_get_cstr(path), &file_info); | ||||
|     FURI_LOG_I("FAV", "%d", fr); | ||||
| 
 | ||||
|     string_clear(path); | ||||
|     return fr == 0 || fr == 2; | ||||
|     return (fr == FSE_OK || fr == FSE_EXIST); | ||||
| } | ||||
| 
 | ||||
| static void update_offset(ArchiveApp* archive) { | ||||
| @ -147,14 +146,11 @@ static void set_file_type(ArchiveFile_t* file, FileInfo* file_info) { | ||||
| 
 | ||||
| static bool archive_get_filenames(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
|     FS_Dir_Api* dir_api = &archive->fs_api->dir; | ||||
| 
 | ||||
|     ArchiveFile_t item; | ||||
|     FileInfo file_info; | ||||
|     File directory; | ||||
|     File* directory = storage_file_alloc(archive->api); | ||||
|     char name[MAX_NAME_LEN]; | ||||
|     bool result; | ||||
| 
 | ||||
|     result = dir_api->open(&directory, string_get_cstr(archive->browser.path)); | ||||
| 
 | ||||
|     with_view_model( | ||||
|         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
| @ -162,51 +158,50 @@ static bool archive_get_filenames(ArchiveApp* archive) { | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         dir_api->close(&directory); | ||||
|     if(!storage_dir_open(directory, string_get_cstr(archive->browser.path))) { | ||||
|         storage_dir_close(directory); | ||||
|         storage_file_free(directory); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     while(1) { | ||||
|         result = dir_api->read(&directory, &file_info, name, MAX_NAME_LEN); | ||||
| 
 | ||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { | ||||
|         if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(result) { | ||||
|             uint16_t files_cnt; | ||||
|             with_view_model( | ||||
|                 archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                     files_cnt = files_array_size(model->files); | ||||
|         uint16_t files_cnt; | ||||
|         with_view_model( | ||||
|             archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                 files_cnt = files_array_size(model->files); | ||||
| 
 | ||||
|                     return true; | ||||
|                 }); | ||||
|                 return true; | ||||
|             }); | ||||
| 
 | ||||
|             if(files_cnt > MAX_FILES) { | ||||
|                 break; | ||||
|             } else if(directory.error_id == FSE_OK) { | ||||
|                 if(filter_by_extension(archive, &file_info, name)) { | ||||
|                     ArchiveFile_t_init(&item); | ||||
|                     string_init_set(item.name, name); | ||||
|                     set_file_type(&item, &file_info); | ||||
|         if(files_cnt > MAX_FILES) { | ||||
|             break; | ||||
|         } else if(storage_file_get_error(directory) == FSE_OK) { | ||||
|             if(filter_by_extension(archive, &file_info, name)) { | ||||
|                 ArchiveFile_t_init(&item); | ||||
|                 string_init_set(item.name, name); | ||||
|                 set_file_type(&item, &file_info); | ||||
| 
 | ||||
|                     with_view_model( | ||||
|                         archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                             files_array_push_back(model->files, item); | ||||
|                             return true; | ||||
|                         }); | ||||
|                 with_view_model( | ||||
|                     archive->view_archive_main, (ArchiveViewModel * model) { | ||||
|                         files_array_push_back(model->files, item); | ||||
|                         return true; | ||||
|                     }); | ||||
| 
 | ||||
|                     ArchiveFile_t_clear(&item); | ||||
|                 } | ||||
|             } else { | ||||
|                 dir_api->close(&directory); | ||||
|                 return false; | ||||
|                 ArchiveFile_t_clear(&item); | ||||
|             } | ||||
|         } else { | ||||
|             storage_dir_close(directory); | ||||
|             storage_file_free(directory); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     dir_api->close(&directory); | ||||
|     storage_dir_close(directory); | ||||
|     storage_file_free(directory); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| @ -226,17 +221,7 @@ static uint32_t archive_previous_callback(void* context) { | ||||
| static void archive_add_to_favorites(ArchiveApp* archive) { | ||||
|     furi_assert(archive); | ||||
| 
 | ||||
|     FS_Common_Api* common_api = &archive->fs_api->common; | ||||
|     common_api->mkdir("favorites"); | ||||
| 
 | ||||
|     FS_File_Api* file_api = &archive->fs_api->file; | ||||
|     File src; | ||||
|     File dst; | ||||
| 
 | ||||
|     bool fr; | ||||
|     uint16_t buffer[MAX_FILE_SIZE]; | ||||
|     uint16_t bw = 0; | ||||
|     uint16_t br = 0; | ||||
|     storage_common_mkdir(archive->api, get_favorites_path()); | ||||
| 
 | ||||
|     string_t buffer_src; | ||||
|     string_t buffer_dst; | ||||
| @ -246,22 +231,10 @@ static void archive_add_to_favorites(ArchiveApp* archive) { | ||||
|         "%s/%s", | ||||
|         string_get_cstr(archive->browser.path), | ||||
|         string_get_cstr(archive->browser.name)); | ||||
|     string_init_printf(buffer_dst, "/favorites/%s", string_get_cstr(archive->browser.name)); | ||||
|     string_init_printf( | ||||
|         buffer_dst, "%s/%s", get_favorites_path(), string_get_cstr(archive->browser.name)); | ||||
| 
 | ||||
|     fr = file_api->open(&src, string_get_cstr(buffer_src), FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     FURI_LOG_I("FATFS", "OPEN: %d", fr); | ||||
|     fr = file_api->open(&dst, string_get_cstr(buffer_dst), FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||
|     FURI_LOG_I("FATFS", "CREATE: %d", fr); | ||||
| 
 | ||||
|     for(;;) { | ||||
|         br = file_api->read(&src, &buffer, sizeof(buffer)); | ||||
|         if(br == 0) break; | ||||
|         bw = file_api->write(&dst, &buffer, sizeof(buffer)); | ||||
|         if(bw < br) break; | ||||
|     } | ||||
| 
 | ||||
|     file_api->close(&src); | ||||
|     file_api->close(&dst); | ||||
|     storage_common_copy(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); | ||||
| 
 | ||||
|     string_clear(buffer_src); | ||||
|     string_clear(buffer_dst); | ||||
| @ -271,7 +244,6 @@ static void archive_text_input_callback(void* context) { | ||||
|     furi_assert(context); | ||||
| 
 | ||||
|     ArchiveApp* archive = (ArchiveApp*)context; | ||||
|     FS_Common_Api* common_api = &archive->fs_api->common; | ||||
| 
 | ||||
|     string_t buffer_src; | ||||
|     string_t buffer_dst; | ||||
| @ -299,7 +271,7 @@ static void archive_text_input_callback(void* context) { | ||||
|         }); | ||||
| 
 | ||||
|     string_cat(buffer_dst, known_ext[file->type]); | ||||
|     common_api->rename(string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); | ||||
|     storage_common_rename(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst)); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewMain); | ||||
| 
 | ||||
| @ -371,23 +343,22 @@ static void archive_delete_file(ArchiveApp* archive, ArchiveFile_t* file, bool f | ||||
|     furi_assert(archive); | ||||
|     furi_assert(file); | ||||
| 
 | ||||
|     FS_Common_Api* common_api = &archive->fs_api->common; | ||||
|     string_t path; | ||||
|     string_init(path); | ||||
| 
 | ||||
|     if(!fav && !orig) { | ||||
|         string_printf( | ||||
|             path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(file->name)); | ||||
|         common_api->remove(string_get_cstr(path)); | ||||
|         storage_common_remove(archive->api, string_get_cstr(path)); | ||||
| 
 | ||||
|     } else { // remove from favorites
 | ||||
|         string_printf(path, "favorites/%s", string_get_cstr(file->name)); | ||||
|         common_api->remove(string_get_cstr(path)); | ||||
|         string_printf(path, "%s/%s", get_favorites_path(), string_get_cstr(file->name)); | ||||
|         storage_common_remove(archive->api, string_get_cstr(path)); | ||||
| 
 | ||||
|         if(orig) { // remove original file
 | ||||
|             string_printf( | ||||
|                 path, "%s/%s", get_default_path(file->type), string_get_cstr(file->name)); | ||||
|             common_api->remove(string_get_cstr(path)); | ||||
|             storage_common_remove(archive->api, string_get_cstr(path)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -604,8 +575,8 @@ void archive_free(ArchiveApp* archive) { | ||||
| 
 | ||||
|     text_input_free(archive->text_input); | ||||
| 
 | ||||
|     furi_record_close("sdcard"); | ||||
|     archive->fs_api = NULL; | ||||
|     furi_record_close("storage"); | ||||
|     archive->api = NULL; | ||||
|     furi_record_close("gui"); | ||||
|     archive->gui = NULL; | ||||
|     furi_record_close("loader"); | ||||
| @ -623,7 +594,7 @@ ArchiveApp* archive_alloc() { | ||||
|     archive->app_thread = furi_thread_alloc(); | ||||
|     archive->gui = furi_record_open("gui"); | ||||
|     archive->loader = furi_record_open("loader"); | ||||
|     archive->fs_api = furi_record_open("sdcard"); | ||||
|     archive->api = furi_record_open("storage"); | ||||
|     archive->text_input = text_input_alloc(); | ||||
|     archive->view_archive_main = view_alloc(); | ||||
| 
 | ||||
|  | ||||
| @ -10,7 +10,7 @@ | ||||
| 
 | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| #include <filesystem-api.h> | ||||
| #include <storage/storage.h> | ||||
| #include "archive_views.h" | ||||
| #include "applications.h" | ||||
| 
 | ||||
| @ -41,13 +41,13 @@ static const char* known_ext[] = { | ||||
| }; | ||||
| 
 | ||||
| static const char* tab_default_paths[] = { | ||||
|     [ArchiveTabFavorites] = "favorites", | ||||
|     [ArchiveTabIButton] = "ibutton", | ||||
|     [ArchiveTabNFC] = "nfc", | ||||
|     [ArchiveTabSubOne] = "subone", | ||||
|     [ArchiveTabLFRFID] = "lfrfid", | ||||
|     [ArchiveTabIrda] = "irda", | ||||
|     [ArchiveTabBrowser] = "/", | ||||
|     [ArchiveTabFavorites] = "/any/favorites", | ||||
|     [ArchiveTabIButton] = "/any/ibutton", | ||||
|     [ArchiveTabNFC] = "/any/nfc", | ||||
|     [ArchiveTabSubOne] = "/any/subone", | ||||
|     [ArchiveTabLFRFID] = "/any/lfrfid", | ||||
|     [ArchiveTabIrda] = "/any/irda", | ||||
|     [ArchiveTabBrowser] = "/any", | ||||
| }; | ||||
| 
 | ||||
| static inline const char* get_tab_ext(ArchiveTabEnum tab) { | ||||
| @ -84,6 +84,10 @@ static inline const char* get_default_path(ArchiveFileTypeEnum type) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static inline const char* get_favorites_path() { | ||||
|     return tab_default_paths[ArchiveTabFavorites]; | ||||
| } | ||||
| 
 | ||||
| typedef enum { | ||||
|     EventTypeTick, | ||||
|     EventTypeKey, | ||||
| @ -118,6 +122,6 @@ struct ArchiveApp { | ||||
|     View* view_archive_main; | ||||
|     TextInput* text_input; | ||||
| 
 | ||||
|     FS_Api* fs_api; | ||||
|     Storage* api; | ||||
|     ArchiveBrowser browser; | ||||
| }; | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| #include <gui/canvas.h> | ||||
| #include <gui/elements.h> | ||||
| #include <furi.h> | ||||
| #include <filesystem-api.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #define MAX_LEN_PX 100 | ||||
| #define MAX_NAME_LEN 255 | ||||
|  | ||||
| @ -375,6 +375,10 @@ void cli_command_free(Cli* cli, string_t args, void* context) { | ||||
|     printf("Maximum heap block: %d\r\n", memmgr_heap_get_max_free_block()); | ||||
| } | ||||
| 
 | ||||
| void cli_command_free_blocks(Cli* cli, string_t args, void* context) { | ||||
|     memmgr_heap_printf_free_blocks(); | ||||
| } | ||||
| 
 | ||||
| void cli_commands_init(Cli* cli) { | ||||
|     cli_add_command(cli, "!", CliCommandFlagParallelSafe, cli_command_device_info, NULL); | ||||
|     cli_add_command(cli, "device_info", CliCommandFlagParallelSafe, cli_command_device_info, NULL); | ||||
| @ -386,6 +390,7 @@ void cli_commands_init(Cli* cli) { | ||||
|     cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL); | ||||
|     cli_add_command(cli, "ps", CliCommandFlagParallelSafe, cli_command_ps, NULL); | ||||
|     cli_add_command(cli, "free", CliCommandFlagParallelSafe, cli_command_free, NULL); | ||||
|     cli_add_command(cli, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL); | ||||
| 
 | ||||
|     cli_add_command(cli, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL); | ||||
|     cli_add_command(cli, "led", CliCommandFlagDefault, cli_command_led, NULL); | ||||
|  | ||||
							
								
								
									
										8
									
								
								applications/dialogs/dialogs-api-lock.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								applications/dialogs/dialogs-api-lock.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| #pragma once | ||||
| #define API_LOCK_INIT_LOCKED() osSemaphoreNew(1, 0, NULL); | ||||
| 
 | ||||
| #define API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(_lock) \ | ||||
|     osSemaphoreAcquire(_lock, osWaitForever);      \ | ||||
|     osSemaphoreDelete(_lock); | ||||
| 
 | ||||
| #define API_LOCK_UNLOCK(_lock) osSemaphoreRelease(_lock); | ||||
							
								
								
									
										62
									
								
								applications/dialogs/dialogs-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								applications/dialogs/dialogs-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| #include "dialogs-i.h" | ||||
| #include "dialogs-api-lock.h" | ||||
| 
 | ||||
| /****************** File select ******************/ | ||||
| 
 | ||||
| bool dialog_file_select_show( | ||||
|     DialogsApp* context, | ||||
|     const char* path, | ||||
|     const char* extension, | ||||
|     char* result, | ||||
|     uint8_t result_size, | ||||
|     const char* preselected_filename) { | ||||
|     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); | ||||
|     furi_check(semaphore != NULL); | ||||
| 
 | ||||
|     DialogsAppData data = { | ||||
|         .file_select = { | ||||
|             .path = path, | ||||
|             .extension = extension, | ||||
|             .result = result, | ||||
|             .result_size = result_size, | ||||
|             .preselected_filename = preselected_filename, | ||||
|         }}; | ||||
| 
 | ||||
|     DialogsAppReturn return_data; | ||||
|     DialogsAppMessage message = { | ||||
|         .semaphore = semaphore, | ||||
|         .command = DialogsAppCommandFileOpen, | ||||
|         .data = &data, | ||||
|         .return_data = &return_data, | ||||
|     }; | ||||
| 
 | ||||
|     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore); | ||||
| 
 | ||||
|     return return_data.bool_value; | ||||
| } | ||||
| 
 | ||||
| /****************** Message ******************/ | ||||
| 
 | ||||
| DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* dialog_message) { | ||||
|     osSemaphoreId_t semaphore = API_LOCK_INIT_LOCKED(); | ||||
|     furi_check(semaphore != NULL); | ||||
| 
 | ||||
|     DialogsAppData data = { | ||||
|         .dialog = { | ||||
|             .message = dialog_message, | ||||
|         }}; | ||||
| 
 | ||||
|     DialogsAppReturn return_data; | ||||
|     DialogsAppMessage message = { | ||||
|         .semaphore = semaphore, | ||||
|         .command = DialogsAppCommandDialog, | ||||
|         .data = &data, | ||||
|         .return_data = &return_data, | ||||
|     }; | ||||
| 
 | ||||
|     furi_check(osMessageQueuePut(context->message_queue, &message, 0, osWaitForever) == osOK); | ||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(semaphore); | ||||
| 
 | ||||
|     return return_data.dialog_value; | ||||
| } | ||||
							
								
								
									
										15
									
								
								applications/dialogs/dialogs-i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								applications/dialogs/dialogs-i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| #pragma once | ||||
| #include "dialogs.h" | ||||
| #include "dialogs-message.h" | ||||
| #include "view-holder.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| struct DialogsApp { | ||||
|     osMessageQueueId_t message_queue; | ||||
| }; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										45
									
								
								applications/dialogs/dialogs-message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								applications/dialogs/dialogs-message.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "dialogs-i.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* path; | ||||
|     const char* extension; | ||||
|     char* result; | ||||
|     uint8_t result_size; | ||||
|     const char* preselected_filename; | ||||
| } DialogsAppMessageDataFileSelect; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const DialogMessage* message; | ||||
| } DialogsAppMessageDataDialog; | ||||
| 
 | ||||
| typedef union { | ||||
|     DialogsAppMessageDataFileSelect file_select; | ||||
|     DialogsAppMessageDataDialog dialog; | ||||
| } DialogsAppData; | ||||
| 
 | ||||
| typedef union { | ||||
|     bool bool_value; | ||||
|     DialogMessageButton dialog_value; | ||||
| } DialogsAppReturn; | ||||
| 
 | ||||
| typedef enum { | ||||
|     DialogsAppCommandFileOpen, | ||||
|     DialogsAppCommandDialog, | ||||
| } DialogsAppCommand; | ||||
| 
 | ||||
| typedef struct { | ||||
|     osSemaphoreId_t semaphore; | ||||
|     DialogsAppCommand command; | ||||
|     DialogsAppData* data; | ||||
|     DialogsAppReturn* return_data; | ||||
| } DialogsAppMessage; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										59
									
								
								applications/dialogs/dialogs-module-file-select.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/dialogs/dialogs-module-file-select.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| #include "dialogs-i.h" | ||||
| #include "dialogs-api-lock.h" | ||||
| #include <gui/modules/file_select.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     osSemaphoreId_t semaphore; | ||||
|     bool result; | ||||
| } DialogsAppFileSelectContext; | ||||
| 
 | ||||
| static void dialogs_app_file_select_back_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     DialogsAppFileSelectContext* file_select_context = context; | ||||
|     file_select_context->result = false; | ||||
|     API_LOCK_UNLOCK(file_select_context->semaphore); | ||||
| } | ||||
| 
 | ||||
| static void dialogs_app_file_select_callback(bool result, void* context) { | ||||
|     furi_assert(context); | ||||
|     DialogsAppFileSelectContext* file_select_context = context; | ||||
|     file_select_context->result = result; | ||||
|     API_LOCK_UNLOCK(file_select_context->semaphore); | ||||
| } | ||||
| 
 | ||||
| bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data) { | ||||
|     bool ret = false; | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
| 
 | ||||
|     DialogsAppFileSelectContext* file_select_context = | ||||
|         furi_alloc(sizeof(DialogsAppFileSelectContext)); | ||||
|     file_select_context->semaphore = API_LOCK_INIT_LOCKED(); | ||||
| 
 | ||||
|     ViewHolder* view_holder = view_holder_alloc(); | ||||
|     view_holder_attach_to_gui(view_holder, gui); | ||||
|     view_holder_set_back_callback( | ||||
|         view_holder, dialogs_app_file_select_back_callback, file_select_context); | ||||
| 
 | ||||
|     FileSelect* file_select = file_select_alloc(); | ||||
|     file_select_set_callback(file_select, dialogs_app_file_select_callback, file_select_context); | ||||
|     file_select_set_filter(file_select, data->path, data->extension); | ||||
|     file_select_set_result_buffer(file_select, data->result, data->result_size); | ||||
|     file_select_init(file_select); | ||||
|     if(data->preselected_filename != NULL) { | ||||
|         file_select_set_selected_file(file_select, data->preselected_filename); | ||||
|     } | ||||
| 
 | ||||
|     view_holder_set_view(view_holder, file_select_get_view(file_select)); | ||||
|     view_holder_start(view_holder); | ||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(file_select_context->semaphore); | ||||
| 
 | ||||
|     ret = file_select_context->result; | ||||
| 
 | ||||
|     free(file_select_context); | ||||
|     view_holder_stop(view_holder); | ||||
|     view_holder_free(view_holder); | ||||
|     file_select_free(file_select); | ||||
|     furi_record_close("gui"); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
							
								
								
									
										12
									
								
								applications/dialogs/dialogs-module-file-select.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/dialogs/dialogs-module-file-select.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| #pragma once | ||||
| #include "dialogs-message.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| bool dialogs_app_process_module_file_select(const DialogsAppMessageDataFileSelect* data); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										152
									
								
								applications/dialogs/dialogs-module-message.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								applications/dialogs/dialogs-module-message.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,152 @@ | ||||
| #include "dialogs-i.h" | ||||
| #include "dialogs-api-lock.h" | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| 
 | ||||
| typedef struct { | ||||
|     osSemaphoreId_t semaphore; | ||||
|     DialogMessageButton result; | ||||
| } DialogsAppMessageContext; | ||||
| 
 | ||||
| struct DialogMessage { | ||||
|     const char* header_text; | ||||
|     uint8_t header_text_x; | ||||
|     uint8_t header_text_y; | ||||
|     Align header_horizontal; | ||||
|     Align header_vertical; | ||||
|     const char* dialog_text; | ||||
|     uint8_t dialog_text_x; | ||||
|     uint8_t dialog_text_y; | ||||
|     Align dialog_text_horizontal; | ||||
|     Align dialog_text_vertical; | ||||
|     const Icon* icon; | ||||
|     uint8_t icon_x; | ||||
|     uint8_t icon_y; | ||||
|     const char* left_button_text; | ||||
|     const char* center_button_text; | ||||
|     const char* right_button_text; | ||||
| }; | ||||
| 
 | ||||
| static void dialogs_app_message_back_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     DialogsAppMessageContext* message_context = context; | ||||
|     message_context->result = DialogMessageButtonBack; | ||||
|     API_LOCK_UNLOCK(message_context->semaphore); | ||||
| } | ||||
| 
 | ||||
| static void dialogs_app_message_callback(DialogExResult result, void* context) { | ||||
|     furi_assert(context); | ||||
|     DialogsAppMessageContext* message_context = context; | ||||
|     switch(result) { | ||||
|     case DialogExResultLeft: | ||||
|         message_context->result = DialogMessageButtonLeft; | ||||
|         break; | ||||
|     case DialogExResultRight: | ||||
|         message_context->result = DialogMessageButtonRight; | ||||
|         break; | ||||
|     case DialogExResultCenter: | ||||
|         message_context->result = DialogMessageButtonCenter; | ||||
|         break; | ||||
|     } | ||||
|     API_LOCK_UNLOCK(message_context->semaphore); | ||||
| } | ||||
| 
 | ||||
| DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data) { | ||||
|     DialogMessageButton ret = DialogMessageButtonBack; | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     const DialogMessage* message = data->message; | ||||
|     DialogsAppMessageContext* message_context = furi_alloc(sizeof(DialogsAppMessageContext)); | ||||
|     message_context->semaphore = API_LOCK_INIT_LOCKED(); | ||||
| 
 | ||||
|     ViewHolder* view_holder = view_holder_alloc(); | ||||
|     view_holder_attach_to_gui(view_holder, gui); | ||||
|     view_holder_set_back_callback(view_holder, dialogs_app_message_back_callback, message_context); | ||||
| 
 | ||||
|     DialogEx* dialog_ex = dialog_ex_alloc(); | ||||
|     dialog_ex_set_result_callback(dialog_ex, dialogs_app_message_callback); | ||||
|     dialog_ex_set_context(dialog_ex, message_context); | ||||
|     dialog_ex_set_header( | ||||
|         dialog_ex, | ||||
|         message->header_text, | ||||
|         message->header_text_x, | ||||
|         message->header_text_y, | ||||
|         message->header_horizontal, | ||||
|         message->header_vertical); | ||||
|     dialog_ex_set_text( | ||||
|         dialog_ex, | ||||
|         message->dialog_text, | ||||
|         message->dialog_text_x, | ||||
|         message->dialog_text_y, | ||||
|         message->dialog_text_horizontal, | ||||
|         message->dialog_text_vertical); | ||||
|     dialog_ex_set_icon(dialog_ex, message->icon_x, message->icon_y, message->icon); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, message->left_button_text); | ||||
|     dialog_ex_set_center_button_text(dialog_ex, message->center_button_text); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, message->right_button_text); | ||||
| 
 | ||||
|     view_holder_set_view(view_holder, dialog_ex_get_view(dialog_ex)); | ||||
|     view_holder_start(view_holder); | ||||
|     API_LOCK_WAIT_UNTIL_UNLOCK_AND_FREE(message_context->semaphore); | ||||
| 
 | ||||
|     ret = message_context->result; | ||||
| 
 | ||||
|     free(message_context); | ||||
|     view_holder_stop(view_holder); | ||||
|     view_holder_free(view_holder); | ||||
|     dialog_ex_free(dialog_ex); | ||||
|     furi_record_close("gui"); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| DialogMessage* dialog_message_alloc() { | ||||
|     DialogMessage* message = furi_alloc(sizeof(DialogMessage)); | ||||
|     return message; | ||||
| } | ||||
| 
 | ||||
| void dialog_message_free(DialogMessage* message) { | ||||
|     free(message); | ||||
| } | ||||
| 
 | ||||
| void dialog_message_set_text( | ||||
|     DialogMessage* message, | ||||
|     const char* text, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical) { | ||||
|     message->dialog_text = text; | ||||
|     message->dialog_text_x = x; | ||||
|     message->dialog_text_y = y; | ||||
|     message->dialog_text_horizontal = horizontal; | ||||
|     message->dialog_text_vertical = vertical; | ||||
| } | ||||
| 
 | ||||
| void dialog_message_set_header( | ||||
|     DialogMessage* message, | ||||
|     const char* text, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical) { | ||||
|     message->header_text = text; | ||||
|     message->header_text_x = x; | ||||
|     message->header_text_y = y; | ||||
|     message->header_horizontal = horizontal; | ||||
|     message->header_vertical = vertical; | ||||
| } | ||||
| 
 | ||||
| void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y) { | ||||
|     message->icon = icon; | ||||
|     message->icon_x = x; | ||||
|     message->icon_y = y; | ||||
| } | ||||
| 
 | ||||
| void dialog_message_set_buttons( | ||||
|     DialogMessage* message, | ||||
|     const char* left, | ||||
|     const char* center, | ||||
|     const char* right) { | ||||
|     message->left_button_text = left; | ||||
|     message->center_button_text = center; | ||||
|     message->right_button_text = right; | ||||
| } | ||||
							
								
								
									
										12
									
								
								applications/dialogs/dialogs-module-message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/dialogs/dialogs-module-message.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| #pragma once | ||||
| #include "dialogs-message.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| DialogMessageButton dialogs_app_process_module_message(const DialogsAppMessageDataDialog* data); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										39
									
								
								applications/dialogs/dialogs.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								applications/dialogs/dialogs.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| #include "dialogs-i.h" | ||||
| #include "dialogs-api-lock.h" | ||||
| #include "dialogs-module-file-select.h" | ||||
| #include "dialogs-module-message.h" | ||||
| 
 | ||||
| static DialogsApp* dialogs_app_alloc() { | ||||
|     DialogsApp* app = malloc(sizeof(DialogsApp)); | ||||
|     app->message_queue = osMessageQueueNew(8, sizeof(DialogsAppMessage), NULL); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
| 
 | ||||
| static void dialogs_app_process_message(DialogsApp* app, DialogsAppMessage* message) { | ||||
|     switch(message->command) { | ||||
|     case DialogsAppCommandFileOpen: | ||||
|         message->return_data->bool_value = | ||||
|             dialogs_app_process_module_file_select(&message->data->file_select); | ||||
|         break; | ||||
|     case DialogsAppCommandDialog: | ||||
|         message->return_data->dialog_value = | ||||
|             dialogs_app_process_module_message(&message->data->dialog); | ||||
|         break; | ||||
|     } | ||||
|     API_LOCK_UNLOCK(message->semaphore); | ||||
| } | ||||
| 
 | ||||
| int32_t dialogs_app(void* p) { | ||||
|     DialogsApp* app = dialogs_app_alloc(); | ||||
|     furi_record_create("dialogs", app); | ||||
| 
 | ||||
|     DialogsAppMessage message; | ||||
|     while(1) { | ||||
|         if(osMessageQueueGet(app->message_queue, &message, NULL, osWaitForever) == osOK) { | ||||
|             dialogs_app_process_message(app, &message); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										128
									
								
								applications/dialogs/dialogs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								applications/dialogs/dialogs.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <gui/canvas.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /****************** COMMON ******************/ | ||||
| 
 | ||||
| typedef struct DialogsApp DialogsApp; | ||||
| 
 | ||||
| /****************** FILE SELECT ******************/ | ||||
| 
 | ||||
| /**
 | ||||
|  * Shows and processes the file selection dialog | ||||
|  * @param context api pointer | ||||
|  * @param path path to directory | ||||
|  * @param extension file extension to be offered for selection | ||||
|  * @param selected_filename buffer where the selected filename will be saved | ||||
|  * @param selected_filename_size and the size of this buffer | ||||
|  * @param preselected_filename filename to be preselected | ||||
|  * @return bool whether a file was selected | ||||
|  */ | ||||
| bool dialog_file_select_show( | ||||
|     DialogsApp* context, | ||||
|     const char* path, | ||||
|     const char* extension, | ||||
|     char* result, | ||||
|     uint8_t result_size, | ||||
|     const char* preselected_filename); | ||||
| 
 | ||||
| /****************** MESSAGE ******************/ | ||||
| 
 | ||||
| /**
 | ||||
|  * Message result type | ||||
|  */ | ||||
| typedef enum { | ||||
|     DialogMessageButtonBack, | ||||
|     DialogMessageButtonLeft, | ||||
|     DialogMessageButtonCenter, | ||||
|     DialogMessageButtonRight, | ||||
| } DialogMessageButton; | ||||
| 
 | ||||
| /**
 | ||||
|  * Message struct | ||||
|  */ | ||||
| typedef struct DialogMessage DialogMessage; | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate and fill message | ||||
|  * @return DialogMessage*  | ||||
|  */ | ||||
| DialogMessage* dialog_message_alloc(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Free message struct | ||||
|  * @param message message pointer | ||||
|  */ | ||||
| void dialog_message_free(DialogMessage* message); | ||||
| 
 | ||||
| /**
 | ||||
|  * Set message text | ||||
|  * @param message message pointer | ||||
|  * @param text text, can be NULL if you don't want to display the text | ||||
|  * @param x x position | ||||
|  * @param y y position | ||||
|  * @param horizontal horizontal alignment | ||||
|  * @param vertical vertical alignment | ||||
|  */ | ||||
| void dialog_message_set_text( | ||||
|     DialogMessage* message, | ||||
|     const char* text, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical); | ||||
| 
 | ||||
| /**
 | ||||
|  * Set message header | ||||
|  * @param message message pointer | ||||
|  * @param text text, can be NULL if you don't want to display the header | ||||
|  * @param x x position | ||||
|  * @param y y position | ||||
|  * @param horizontal horizontal alignment | ||||
|  * @param vertical vertical alignment | ||||
|  */ | ||||
| void dialog_message_set_header( | ||||
|     DialogMessage* message, | ||||
|     const char* text, | ||||
|     uint8_t x, | ||||
|     uint8_t y, | ||||
|     Align horizontal, | ||||
|     Align vertical); | ||||
| 
 | ||||
| /**
 | ||||
|  * Set message icon | ||||
|  * @param message message pointer | ||||
|  * @param icon icon pointer, can be NULL if you don't want to display the icon | ||||
|  * @param x x position | ||||
|  * @param y y position | ||||
|  */ | ||||
| void dialog_message_set_icon(DialogMessage* message, const Icon* icon, uint8_t x, uint8_t y); | ||||
| 
 | ||||
| /**
 | ||||
|  * Set message buttons text, button text can be NULL if you don't want to display and process some buttons | ||||
|  * @param message message pointer | ||||
|  * @param left left button text, can be NULL if you don't want to display the left button | ||||
|  * @param center center button text, can be NULL if you don't want to display the center button | ||||
|  * @param right right button text, can be NULL if you don't want to display the right button | ||||
|  */ | ||||
| void dialog_message_set_buttons( | ||||
|     DialogMessage* message, | ||||
|     const char* left, | ||||
|     const char* center, | ||||
|     const char* right); | ||||
| 
 | ||||
| /**
 | ||||
|  * Show message from filled struct | ||||
|  * @param context api pointer | ||||
|  * @param message message struct pointer to be shown | ||||
|  * @return DialogMessageButton type | ||||
|  */ | ||||
| DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* message); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -1,4 +1,4 @@ | ||||
| #include "view_holder.h" | ||||
| #include "view-holder.h" | ||||
| #include <gui/view_i.h> | ||||
| 
 | ||||
| struct ViewHolder { | ||||
| @ -1,10 +1,9 @@ | ||||
| #include "dolphin_state.h" | ||||
| 
 | ||||
| #include <internal-storage/internal-storage.h> | ||||
| #include <storage/storage.h> | ||||
| #include <furi.h> | ||||
| #include <math.h> | ||||
| 
 | ||||
| #define DOLPHIN_STORE_KEY "dolphin_state" | ||||
| #define DOLPHIN_STORE_KEY "/int/dolphin.state" | ||||
| #define DOLPHIN_STORE_HEADER_MAGIC 0xD0 | ||||
| #define DOLPHIN_STORE_HEADER_VERSION 0x01 | ||||
| #define DOLPHIN_LVL_THRESHOLD 20.0f | ||||
| @ -34,24 +33,24 @@ typedef struct { | ||||
| } DolphinStore; | ||||
| 
 | ||||
| struct DolphinState { | ||||
|     InternalStorage* internal_storage; | ||||
|     Storage* fs_api; | ||||
|     DolphinStoreData data; | ||||
| }; | ||||
| 
 | ||||
| DolphinState* dolphin_state_alloc() { | ||||
|     DolphinState* dolphin_state = furi_alloc(sizeof(DolphinState)); | ||||
|     dolphin_state->internal_storage = furi_record_open("internal-storage"); | ||||
|     dolphin_state->fs_api = furi_record_open("storage"); | ||||
|     return dolphin_state; | ||||
| } | ||||
| 
 | ||||
| void dolphin_state_free(DolphinState* dolphin_state) { | ||||
|     furi_record_close("internal-storage"); | ||||
|     furi_record_close("storage"); | ||||
|     free(dolphin_state); | ||||
| } | ||||
| 
 | ||||
| bool dolphin_state_save(DolphinState* dolphin_state) { | ||||
|     DolphinStore store; | ||||
|     FURI_LOG_I("dolphin-state", "Saving state to internal-storage"); | ||||
|     FURI_LOG_I("dolphin-state", "Saving state to \"%s\"", DOLPHIN_STORE_KEY); | ||||
|     // Calculate checksum
 | ||||
|     uint8_t* source = (uint8_t*)&dolphin_state->data; | ||||
|     uint8_t checksum = 0; | ||||
| @ -66,60 +65,95 @@ bool dolphin_state_save(DolphinState* dolphin_state) { | ||||
|     store.header.timestamp = 0; | ||||
|     // Set data
 | ||||
|     store.data = dolphin_state->data; | ||||
| 
 | ||||
|     // Store
 | ||||
|     int ret = internal_storage_write_key( | ||||
|         dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore)); | ||||
|     if(ret != sizeof(DolphinStore)) { | ||||
|         FURI_LOG_E("dolphin-state", "Save failed. Storage returned: %d", ret); | ||||
|         return false; | ||||
|     File* file = storage_file_alloc(dolphin_state->fs_api); | ||||
|     bool save_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||
| 
 | ||||
|     if(save_result) { | ||||
|         uint16_t bytes_count = storage_file_write(file, &store, sizeof(DolphinStore)); | ||||
| 
 | ||||
|         if(bytes_count != sizeof(DolphinStore)) { | ||||
|             save_result = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!save_result) { | ||||
|         FURI_LOG_E( | ||||
|             "dolphin-state", | ||||
|             "Save failed. Storage returned: %s", | ||||
|             storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
| 
 | ||||
|     FURI_LOG_I("dolphin-state", "Saved"); | ||||
|     return true; | ||||
|     return save_result; | ||||
| } | ||||
| 
 | ||||
| bool dolphin_state_load(DolphinState* dolphin_state) { | ||||
|     DolphinStore store; | ||||
|     // Read Dolphin State Store
 | ||||
|     FURI_LOG_I("dolphin-state", "Loading state from internal-storage"); | ||||
|     int ret = internal_storage_read_key( | ||||
|         dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore)); | ||||
|     if(ret != sizeof(DolphinStore)) { | ||||
|         FURI_LOG_E("dolphin-state", "Load failed. Storage returned: %d", ret); | ||||
|         return false; | ||||
|     FURI_LOG_I("dolphin-state", "Loading state from \"%s\"", DOLPHIN_STORE_KEY); | ||||
| 
 | ||||
|     File* file = storage_file_alloc(dolphin_state->fs_api); | ||||
|     bool load_result = storage_file_open(file, DOLPHIN_STORE_KEY, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(load_result) { | ||||
|         uint16_t bytes_count = storage_file_read(file, &store, sizeof(DolphinStore)); | ||||
| 
 | ||||
|         if(bytes_count != sizeof(DolphinStore)) { | ||||
|             load_result = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_I("dolphin-state", "State loaded, verifying header"); | ||||
|     if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC && | ||||
|        store.header.version == DOLPHIN_STORE_HEADER_VERSION) { | ||||
|         FURI_LOG_I( | ||||
|             "dolphin-state", | ||||
|             "Magic(%d) and Version(%d) match", | ||||
|             store.header.magic, | ||||
|             store.header.version); | ||||
|         uint8_t checksum = 0; | ||||
|         const uint8_t* source = (const uint8_t*)&store.data; | ||||
|         for(size_t i = 0; i < sizeof(DolphinStoreData); i++) { | ||||
|             checksum += source[i]; | ||||
|         } | ||||
|         if(store.header.checksum == checksum) { | ||||
|             FURI_LOG_I("dolphin-state", "Checksum(%d) match", store.header.checksum); | ||||
|             dolphin_state->data = store.data; | ||||
|             return true; | ||||
|         } else { | ||||
|             FURI_LOG_E( | ||||
|                 "dolphin-state", "Checksum(%d != %d) mismatch", store.header.checksum, checksum); | ||||
|         } | ||||
|     } else { | ||||
|     if(!load_result) { | ||||
|         FURI_LOG_E( | ||||
|             "dolphin-state", | ||||
|             "Magic(%d != %d) and Version(%d != %d) mismatch", | ||||
|             store.header.magic, | ||||
|             DOLPHIN_STORE_HEADER_MAGIC, | ||||
|             store.header.version, | ||||
|             DOLPHIN_STORE_HEADER_VERSION); | ||||
|             "Load failed. Storage returned: %s", | ||||
|             storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         FURI_LOG_I("dolphin-state", "State loaded, verifying header"); | ||||
|         if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC && | ||||
|            store.header.version == DOLPHIN_STORE_HEADER_VERSION) { | ||||
|             FURI_LOG_I( | ||||
|                 "dolphin-state", | ||||
|                 "Magic(%d) and Version(%d) match", | ||||
|                 store.header.magic, | ||||
|                 store.header.version); | ||||
|             uint8_t checksum = 0; | ||||
|             const uint8_t* source = (const uint8_t*)&store.data; | ||||
|             for(size_t i = 0; i < sizeof(DolphinStoreData); i++) { | ||||
|                 checksum += source[i]; | ||||
|             } | ||||
| 
 | ||||
|             if(store.header.checksum == checksum) { | ||||
|                 FURI_LOG_I("dolphin-state", "Checksum(%d) match", store.header.checksum); | ||||
|                 dolphin_state->data = store.data; | ||||
|             } else { | ||||
|                 FURI_LOG_E( | ||||
|                     "dolphin-state", | ||||
|                     "Checksum(%d != %d) mismatch", | ||||
|                     store.header.checksum, | ||||
|                     checksum); | ||||
|                 load_result = false; | ||||
|             } | ||||
|         } else { | ||||
|             FURI_LOG_E( | ||||
|                 "dolphin-state", | ||||
|                 "Magic(%d != %d) and Version(%d != %d) mismatch", | ||||
|                 store.header.magic, | ||||
|                 DOLPHIN_STORE_HEADER_MAGIC, | ||||
|                 store.header.version, | ||||
|                 DOLPHIN_STORE_HEADER_VERSION); | ||||
|             load_result = false; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| 
 | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
|     return load_result; | ||||
| } | ||||
| 
 | ||||
| void dolphin_state_clear(DolphinState* dolphin_state) { | ||||
|  | ||||
| @ -2,13 +2,14 @@ | ||||
| #include <gui/elements.h> | ||||
| #include <m-string.h> | ||||
| #include <sys/param.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #define FILENAME_COUNT 4 | ||||
| 
 | ||||
| struct FileSelect { | ||||
|     // public
 | ||||
|     View* view; | ||||
|     FS_Api* fs_api; | ||||
|     Storage* fs_api; | ||||
|     const char* path; | ||||
|     const char* extension; | ||||
| 
 | ||||
| @ -180,6 +181,8 @@ static bool file_select_init_inner(FileSelect* file_select) { | ||||
| FileSelect* file_select_alloc() { | ||||
|     FileSelect* file_select = furi_alloc(sizeof(FileSelect)); | ||||
|     file_select->view = view_alloc(); | ||||
|     file_select->fs_api = furi_record_open("storage"); | ||||
| 
 | ||||
|     view_set_context(file_select->view, file_select); | ||||
|     view_allocate_model(file_select->view, ViewModelTypeLockFree, sizeof(FileSelectModel)); | ||||
|     view_set_draw_callback(file_select->view, file_select_draw_callback); | ||||
| @ -210,6 +213,7 @@ void file_select_free(FileSelect* file_select) { | ||||
|         }); | ||||
|     view_free(file_select->view); | ||||
|     free(file_select); | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| View* file_select_get_view(FileSelect* file_select) { | ||||
| @ -217,11 +221,6 @@ View* file_select_get_view(FileSelect* file_select) { | ||||
|     return file_select->view; | ||||
| } | ||||
| 
 | ||||
| void file_select_set_api(FileSelect* file_select, FS_Api* fs_api) { | ||||
|     furi_assert(file_select); | ||||
|     file_select->fs_api = fs_api; | ||||
| } | ||||
| 
 | ||||
| void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) { | ||||
|     file_select->context = context; | ||||
|     file_select->callback = callback; | ||||
| @ -272,13 +271,12 @@ bool file_select_fill_strings(FileSelect* file_select) { | ||||
|     furi_assert(file_select->extension); | ||||
| 
 | ||||
|     FileInfo file_info; | ||||
|     File directory; | ||||
|     bool result; | ||||
|     FS_Dir_Api* dir_api = &file_select->fs_api->dir; | ||||
|     File* directory = storage_file_alloc(file_select->fs_api); | ||||
| 
 | ||||
|     uint8_t string_counter = 0; | ||||
|     uint16_t file_counter = 0; | ||||
|     const uint8_t name_length = 100; | ||||
|     char* name = calloc(name_length, sizeof(char)); | ||||
|     char* name = furi_alloc(name_length); | ||||
|     uint16_t first_file_index = 0; | ||||
| 
 | ||||
|     with_view_model( | ||||
| @ -287,59 +285,50 @@ bool file_select_fill_strings(FileSelect* file_select) { | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     if(name == NULL) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     result = dir_api->open(&directory, file_select->path); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         dir_api->close(&directory); | ||||
|     if(!storage_dir_open(directory, file_select->path)) { | ||||
|         storage_dir_close(directory); | ||||
|         storage_file_free(directory); | ||||
|         free(name); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     while(1) { | ||||
|         result = dir_api->read(&directory, &file_info, name, name_length); | ||||
| 
 | ||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { | ||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(result) { | ||||
|             if(directory.error_id == FSE_OK) { | ||||
|                 if(filter_file(file_select, &file_info, name)) { | ||||
|                     if(file_counter >= first_file_index) { | ||||
|                         with_view_model( | ||||
|                             file_select->view, (FileSelectModel * model) { | ||||
|                                 string_set_str(model->filename[string_counter], name); | ||||
|         if(storage_file_get_error(directory) == FSE_OK) { | ||||
|             if(filter_file(file_select, &file_info, name)) { | ||||
|                 if(file_counter >= first_file_index) { | ||||
|                     with_view_model( | ||||
|                         file_select->view, (FileSelectModel * model) { | ||||
|                             string_set_str(model->filename[string_counter], name); | ||||
| 
 | ||||
|                                 if(strcmp(file_select->extension, "*") != 0) { | ||||
|                                     string_replace_all_str( | ||||
|                                         model->filename[string_counter], | ||||
|                                         file_select->extension, | ||||
|                                         ""); | ||||
|                                 } | ||||
|                             if(strcmp(file_select->extension, "*") != 0) { | ||||
|                                 string_replace_all_str( | ||||
|                                     model->filename[string_counter], file_select->extension, ""); | ||||
|                             } | ||||
| 
 | ||||
|                                 return true; | ||||
|                             }); | ||||
|                         string_counter++; | ||||
|                             return true; | ||||
|                         }); | ||||
|                     string_counter++; | ||||
| 
 | ||||
|                         if(string_counter >= FILENAME_COUNT) { | ||||
|                             break; | ||||
|                         } | ||||
|                     if(string_counter >= FILENAME_COUNT) { | ||||
|                         break; | ||||
|                     } | ||||
|                     file_counter++; | ||||
|                 } | ||||
|             } else { | ||||
|                 dir_api->close(&directory); | ||||
|                 free(name); | ||||
|                 return false; | ||||
|                 file_counter++; | ||||
|             } | ||||
|         } else { | ||||
|             storage_dir_close(directory); | ||||
|             storage_file_free(directory); | ||||
|             free(name); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     dir_api->close(&directory); | ||||
|     storage_dir_close(directory); | ||||
|     storage_file_free(directory); | ||||
|     free(name); | ||||
|     return true; | ||||
| } | ||||
| @ -351,42 +340,33 @@ bool file_select_fill_count(FileSelect* file_select) { | ||||
|     furi_assert(file_select->extension); | ||||
| 
 | ||||
|     FileInfo file_info; | ||||
|     File directory; | ||||
|     bool result; | ||||
|     FS_Dir_Api* dir_api = &file_select->fs_api->dir; | ||||
|     File* directory = storage_file_alloc(file_select->fs_api); | ||||
| 
 | ||||
|     uint16_t file_counter = 0; | ||||
|     const uint8_t name_length = 100; | ||||
|     char* name = calloc(name_length, sizeof(char)); | ||||
|     char* name = furi_alloc(name_length); | ||||
| 
 | ||||
|     if(name == NULL) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     result = dir_api->open(&directory, file_select->path); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         dir_api->close(&directory); | ||||
|     if(!storage_dir_open(directory, file_select->path)) { | ||||
|         storage_dir_close(directory); | ||||
|         storage_file_free(directory); | ||||
|         free(name); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     while(1) { | ||||
|         result = dir_api->read(&directory, &file_info, name, name_length); | ||||
| 
 | ||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { | ||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(result) { | ||||
|             if(directory.error_id == FSE_OK) { | ||||
|                 if(filter_file(file_select, &file_info, name)) { | ||||
|                     file_counter++; | ||||
|                 } | ||||
|             } else { | ||||
|                 dir_api->close(&directory); | ||||
|                 free(name); | ||||
|                 return false; | ||||
|         if(storage_file_get_error(directory) == FSE_OK) { | ||||
|             if(filter_file(file_select, &file_info, name)) { | ||||
|                 file_counter++; | ||||
|             } | ||||
|         } else { | ||||
|             storage_dir_close(directory); | ||||
|             storage_file_free(directory); | ||||
|             free(name); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -396,7 +376,8 @@ bool file_select_fill_count(FileSelect* file_select) { | ||||
|             return false; | ||||
|         }); | ||||
| 
 | ||||
|     dir_api->close(&directory); | ||||
|     storage_dir_close(directory); | ||||
|     storage_file_free(directory); | ||||
|     free(name); | ||||
|     return true; | ||||
| } | ||||
| @ -411,16 +392,10 @@ void file_select_set_selected_file_internal(FileSelect* file_select, const char* | ||||
|     if(strlen(filename) == 0) return; | ||||
| 
 | ||||
|     FileInfo file_info; | ||||
|     File directory; | ||||
|     bool result; | ||||
|     FS_Dir_Api* dir_api = &file_select->fs_api->dir; | ||||
|     File* directory = storage_file_alloc(file_select->fs_api); | ||||
| 
 | ||||
|     const uint8_t name_length = 100; | ||||
|     char* name = calloc(name_length, sizeof(char)); | ||||
| 
 | ||||
|     if(name == NULL) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     char* name = furi_alloc(name_length); | ||||
|     uint16_t file_position = 0; | ||||
|     bool file_found = false; | ||||
| 
 | ||||
| @ -430,38 +405,34 @@ void file_select_set_selected_file_internal(FileSelect* file_select, const char* | ||||
|         string_cat_str(filename_str, file_select->extension); | ||||
|     } | ||||
| 
 | ||||
|     result = dir_api->open(&directory, file_select->path); | ||||
| 
 | ||||
|     if(!result) { | ||||
|     if(!storage_dir_open(directory, file_select->path)) { | ||||
|         string_clear(filename_str); | ||||
|         dir_api->close(&directory); | ||||
|         storage_dir_close(directory); | ||||
|         storage_file_free(directory); | ||||
|         free(name); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     while(1) { | ||||
|         result = dir_api->read(&directory, &file_info, name, name_length); | ||||
| 
 | ||||
|         if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) { | ||||
|         if(!storage_dir_read(directory, &file_info, name, name_length)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(result) { | ||||
|             if(directory.error_id == FSE_OK) { | ||||
|                 if(filter_file(file_select, &file_info, name)) { | ||||
|                     if(strcmp(string_get_cstr(filename_str), name) == 0) { | ||||
|                         file_found = true; | ||||
|                         break; | ||||
|                     } | ||||
| 
 | ||||
|                     file_position++; | ||||
|         if(storage_file_get_error(directory) == FSE_OK) { | ||||
|             if(filter_file(file_select, &file_info, name)) { | ||||
|                 if(strcmp(string_get_cstr(filename_str), name) == 0) { | ||||
|                     file_found = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } else { | ||||
|                 string_clear(filename_str); | ||||
|                 dir_api->close(&directory); | ||||
|                 free(name); | ||||
|                 return; | ||||
| 
 | ||||
|                 file_position++; | ||||
|             } | ||||
|         } else { | ||||
|             string_clear(filename_str); | ||||
|             storage_dir_close(directory); | ||||
|             storage_file_free(directory); | ||||
|             free(name); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -488,7 +459,8 @@ void file_select_set_selected_file_internal(FileSelect* file_select, const char* | ||||
|     } | ||||
| 
 | ||||
|     string_clear(filename_str); | ||||
|     dir_api->close(&directory); | ||||
|     storage_dir_close(directory); | ||||
|     storage_file_free(directory); | ||||
|     free(name); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| #pragma once | ||||
| #include <gui/view.h> | ||||
| #include <filesystem-api.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| @ -15,7 +14,6 @@ FileSelect* file_select_alloc(); | ||||
| void file_select_free(FileSelect* file_select); | ||||
| View* file_select_get_view(FileSelect* file_select); | ||||
| 
 | ||||
| void file_select_set_api(FileSelect* file_select, FS_Api* fs_api); | ||||
| void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context); | ||||
| void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension); | ||||
| void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size); | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| #include <file-worker-cpp.h> | ||||
| #include <path.h> | ||||
| 
 | ||||
| const char* iButtonApp::app_folder = "ibutton"; | ||||
| const char* iButtonApp::app_folder = "/any/ibutton"; | ||||
| const char* iButtonApp::app_extension = ".ibtn"; | ||||
| 
 | ||||
| void iButtonApp::run(void* args) { | ||||
| @ -13,6 +13,8 @@ void iButtonApp::run(void* args) { | ||||
|     bool consumed; | ||||
|     bool exit = false; | ||||
| 
 | ||||
|     make_app_folder(); | ||||
| 
 | ||||
|     if(args && load_key((const char*)args)) { | ||||
|         current_scene = Scene::SceneEmulate; | ||||
|     } | ||||
| @ -218,15 +220,13 @@ void iButtonApp::generate_random_name(char* name, uint8_t max_name_size) { | ||||
| 
 | ||||
| // file managment
 | ||||
| bool iButtonApp::save_key(const char* key_name) { | ||||
|     // Create ibutton directory if necessary
 | ||||
|     make_app_folder(); | ||||
| 
 | ||||
|     FileWorkerCpp file_worker; | ||||
|     string_t key_file_name; | ||||
|     bool result = false; | ||||
| 
 | ||||
|     // Create ibutton directory if necessary
 | ||||
|     if(!file_worker.mkdir(app_folder)) { | ||||
|         return false; | ||||
|     }; | ||||
| 
 | ||||
|     // First remove key if it was saved
 | ||||
|     string_init_printf(key_file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension); | ||||
|     if(!file_worker.remove(string_get_cstr(key_file_name))) { | ||||
| @ -371,3 +371,8 @@ bool iButtonApp::delete_key() { | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void iButtonApp::make_app_folder() { | ||||
|     FileWorkerCpp file_worker; | ||||
|     file_worker.mkdir(app_folder); | ||||
| } | ||||
| @ -25,9 +25,6 @@ | ||||
| 
 | ||||
| #include "helpers/key-worker.h" | ||||
| 
 | ||||
| #include <sd-card-api.h> | ||||
| #include <filesystem-api.h> | ||||
| 
 | ||||
| #include "one_wire_master.h" | ||||
| #include "maxim_crc.h" | ||||
| #include "ibutton-key.h" | ||||
| @ -142,4 +139,5 @@ private: | ||||
|     static const char* app_extension; | ||||
| 
 | ||||
|     bool load_key_data(string_t key_path); | ||||
|     void make_app_folder(); | ||||
| }; | ||||
| @ -4,7 +4,6 @@ | ||||
| #include "../ibutton-event.h" | ||||
| #include "../ibutton-key.h" | ||||
| #include <callback-connector.h> | ||||
| #include <filesystem-api.h> | ||||
| 
 | ||||
| void iButtonSceneSaveName::on_enter(iButtonApp* app) { | ||||
|     iButtonAppViewManager* view_manager = app->get_view_manager(); | ||||
|  | ||||
| @ -3,7 +3,6 @@ | ||||
| #include "../ibutton-view-manager.h" | ||||
| #include "../ibutton-event.h" | ||||
| #include <callback-connector.h> | ||||
| #include <filesystem-api.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     SubmenuIndexRead, | ||||
|  | ||||
| @ -1,62 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "internal-storage.h" | ||||
| #include <furi.h> | ||||
| #include <api-hal.h> | ||||
| #include <lfs.h> | ||||
| 
 | ||||
| #define INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE (1) | ||||
| 
 | ||||
| struct InternalStorage { | ||||
|     osMessageQueueId_t queue; | ||||
|     InternalStorageState state; | ||||
|     const size_t start_address; | ||||
|     const size_t start_page; | ||||
|     struct lfs_config config; | ||||
|     lfs_t lfs; | ||||
| }; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* key; | ||||
|     uint8_t* buffer; | ||||
|     size_t size; | ||||
|     int ret; | ||||
| } InternalStorageCommandKey; | ||||
| 
 | ||||
| typedef void (*InternalStorageCommandFunction)(InternalStorage* internal_storage, void* data); | ||||
| 
 | ||||
| typedef struct { | ||||
|     osThreadId thread; | ||||
|     InternalStorageCommandFunction function; | ||||
|     void* data; | ||||
| } InternalStorageCommand; | ||||
| 
 | ||||
| int internal_storage_device_read( | ||||
|     const struct lfs_config* c, | ||||
|     lfs_block_t block, | ||||
|     lfs_off_t off, | ||||
|     void* buffer, | ||||
|     lfs_size_t size); | ||||
| 
 | ||||
| int internal_storage_device_prog( | ||||
|     const struct lfs_config* c, | ||||
|     lfs_block_t block, | ||||
|     lfs_off_t off, | ||||
|     const void* buffer, | ||||
|     lfs_size_t size); | ||||
| 
 | ||||
| int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block); | ||||
| 
 | ||||
| int internal_storage_device_sync(const struct lfs_config* c); | ||||
| 
 | ||||
| InternalStorage* internal_storage_alloc(); | ||||
| 
 | ||||
| void internal_storage_free(InternalStorage* internal_storage); | ||||
| 
 | ||||
| int32_t internal_storage_task(void* p); | ||||
| 
 | ||||
| void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data); | ||||
| 
 | ||||
| void _internal_storage_write_key( | ||||
|     InternalStorage* internal_storage, | ||||
|     InternalStorageCommandKey* data); | ||||
| @ -1,252 +0,0 @@ | ||||
| #include "internal-storage-i.h" | ||||
| 
 | ||||
| int internal_storage_device_read( | ||||
|     const struct lfs_config* c, | ||||
|     lfs_block_t block, | ||||
|     lfs_off_t off, | ||||
|     void* buffer, | ||||
|     lfs_size_t size) { | ||||
|     InternalStorage* internal_storage = c->context; | ||||
|     size_t address = internal_storage->start_address + block * c->block_size + off; | ||||
| 
 | ||||
|     FURI_LOG_D( | ||||
|         "internal-storage", | ||||
|         "Device read: block %d, off %d, buffer: %p, size %d, translated address: %p", | ||||
|         block, | ||||
|         off, | ||||
|         buffer, | ||||
|         size, | ||||
|         address); | ||||
| 
 | ||||
|     memcpy(buffer, (void*)address, size); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| int internal_storage_device_prog( | ||||
|     const struct lfs_config* c, | ||||
|     lfs_block_t block, | ||||
|     lfs_off_t off, | ||||
|     const void* buffer, | ||||
|     lfs_size_t size) { | ||||
|     InternalStorage* internal_storage = c->context; | ||||
|     size_t address = internal_storage->start_address + block * c->block_size + off; | ||||
| 
 | ||||
|     FURI_LOG_D( | ||||
|         "internal-storage", | ||||
|         "Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p", | ||||
|         block, | ||||
|         off, | ||||
|         buffer, | ||||
|         size, | ||||
|         address); | ||||
| 
 | ||||
|     int ret = 0; | ||||
|     while(size > 0) { | ||||
|         if(!api_hal_flash_write_dword(address, *(uint64_t*)buffer)) { | ||||
|             ret = -1; | ||||
|             break; | ||||
|         } | ||||
|         address += c->prog_size; | ||||
|         buffer += c->prog_size; | ||||
|         size -= c->prog_size; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block) { | ||||
|     InternalStorage* internal_storage = c->context; | ||||
|     size_t page = internal_storage->start_page + block; | ||||
| 
 | ||||
|     FURI_LOG_D("internal-storage", "Device erase: page %d, translated page: %d", block, page); | ||||
| 
 | ||||
|     if(api_hal_flash_erase(page, 1)) { | ||||
|         return 0; | ||||
|     } else { | ||||
|         return -1; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| int internal_storage_device_sync(const struct lfs_config* c) { | ||||
|     FURI_LOG_D("internal-storage", "Device sync: skipping, cause "); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| InternalStorage* internal_storage_alloc() { | ||||
|     InternalStorage* internal_storage = furi_alloc(sizeof(InternalStorage)); | ||||
| 
 | ||||
|     internal_storage->queue = osMessageQueueNew(8, sizeof(InternalStorageCommand), NULL); | ||||
| 
 | ||||
|     // Internal storage start address
 | ||||
|     internal_storage->state = InternalStorageStateInitializing; | ||||
| 
 | ||||
|     // Internal storage start address
 | ||||
|     *(size_t*)(&internal_storage->start_address) = api_hal_flash_get_free_page_start_address(); | ||||
|     *(size_t*)(&internal_storage->start_page) = | ||||
|         (internal_storage->start_address - api_hal_flash_get_base()) / | ||||
|         api_hal_flash_get_page_size(); | ||||
| 
 | ||||
|     // LFS configuration
 | ||||
|     // Glue and context
 | ||||
|     internal_storage->config.context = internal_storage; | ||||
|     internal_storage->config.read = internal_storage_device_read; | ||||
|     internal_storage->config.prog = internal_storage_device_prog; | ||||
|     internal_storage->config.erase = internal_storage_device_erase; | ||||
|     internal_storage->config.sync = internal_storage_device_sync; | ||||
|     // Block device description
 | ||||
|     internal_storage->config.read_size = api_hal_flash_get_read_block_size(); | ||||
|     internal_storage->config.prog_size = api_hal_flash_get_write_block_size(); | ||||
|     internal_storage->config.block_size = api_hal_flash_get_page_size(); | ||||
|     internal_storage->config.block_count = api_hal_flash_get_free_page_count(); | ||||
|     internal_storage->config.block_cycles = api_hal_flash_get_cycles_count(); | ||||
|     internal_storage->config.cache_size = 16; | ||||
|     internal_storage->config.lookahead_size = 16; | ||||
| 
 | ||||
|     return internal_storage; | ||||
| } | ||||
| 
 | ||||
| void internal_storage_free(InternalStorage* internal_storage) { | ||||
|     furi_assert(internal_storage); | ||||
|     free(internal_storage); | ||||
| } | ||||
| 
 | ||||
| int32_t internal_storage_task(void* p) { | ||||
|     FURI_LOG_I("internal-storage", "Starting"); | ||||
|     InternalStorage* internal_storage = internal_storage_alloc(); | ||||
|     FURI_LOG_I( | ||||
|         "internal-storage", | ||||
|         "Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d", | ||||
|         internal_storage->start_address, | ||||
|         internal_storage->config.read_size, | ||||
|         internal_storage->config.prog_size, | ||||
|         internal_storage->config.block_size, | ||||
|         internal_storage->config.block_count, | ||||
|         internal_storage->config.block_cycles); | ||||
| 
 | ||||
|     int err; | ||||
|     ApiHalBootFlag boot_flags = api_hal_boot_get_flags(); | ||||
|     if(boot_flags & ApiHalBootFlagFactoryReset) { | ||||
|         // Factory reset
 | ||||
|         err = lfs_format(&internal_storage->lfs, &internal_storage->config); | ||||
|         if(err == 0) { | ||||
|             FURI_LOG_I("internal-storage", "Factory reset: Format successful, trying to mount"); | ||||
|             api_hal_boot_set_flags(boot_flags & ~ApiHalBootFlagFactoryReset); | ||||
|             err = lfs_mount(&internal_storage->lfs, &internal_storage->config); | ||||
|             if(err == 0) { | ||||
|                 FURI_LOG_I("internal-storage", "Factory reset: Mounted"); | ||||
|                 internal_storage->state = InternalStorageStateReady; | ||||
|             } else { | ||||
|                 FURI_LOG_E("internal-storage", "Factory reset: Mount after format failed"); | ||||
|                 internal_storage->state = InternalStorageStateBroken; | ||||
|             } | ||||
|         } else { | ||||
|             FURI_LOG_E("internal-storage", "Factory reset: Format failed"); | ||||
|             internal_storage->state = InternalStorageStateBroken; | ||||
|         } | ||||
|     } else { | ||||
|         // Normal
 | ||||
|         err = lfs_mount(&internal_storage->lfs, &internal_storage->config); | ||||
|         if(err == 0) { | ||||
|             FURI_LOG_I("internal-storage", "Mounted"); | ||||
|             internal_storage->state = InternalStorageStateReady; | ||||
|         } else { | ||||
|             FURI_LOG_E("internal-storage", "Mount failed, formatting"); | ||||
|             err = lfs_format(&internal_storage->lfs, &internal_storage->config); | ||||
|             if(err == 0) { | ||||
|                 FURI_LOG_I("internal-storage", "Format successful, trying to mount"); | ||||
|                 err = lfs_mount(&internal_storage->lfs, &internal_storage->config); | ||||
|                 if(err == 0) { | ||||
|                     FURI_LOG_I("internal-storage", "Mounted"); | ||||
|                     internal_storage->state = InternalStorageStateReady; | ||||
|                 } else { | ||||
|                     FURI_LOG_E("internal-storage", "Mount after format failed"); | ||||
|                     internal_storage->state = InternalStorageStateBroken; | ||||
|                 } | ||||
|             } else { | ||||
|                 FURI_LOG_E("internal-storage", "Format failed"); | ||||
|                 internal_storage->state = InternalStorageStateBroken; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_record_create("internal-storage", internal_storage); | ||||
| 
 | ||||
|     InternalStorageCommand command; | ||||
|     while(1) { | ||||
|         furi_check( | ||||
|             osMessageQueueGet(internal_storage->queue, &command, NULL, osWaitForever) == osOK); | ||||
|         command.function(internal_storage, command.data); | ||||
|         osThreadFlagsSet(command.thread, INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE); | ||||
|     } | ||||
| 
 | ||||
|     lfs_unmount(&internal_storage->lfs); | ||||
|     internal_storage_free(internal_storage); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data) { | ||||
|     lfs_file_t file; | ||||
|     int ret = lfs_file_open(&internal_storage->lfs, &file, data->key, LFS_O_RDONLY); | ||||
|     if(ret == 0) { | ||||
|         ret = lfs_file_read(&internal_storage->lfs, &file, data->buffer, data->size); | ||||
|         lfs_file_close(&internal_storage->lfs, &file); | ||||
|     } | ||||
|     data->ret = ret; | ||||
| } | ||||
| 
 | ||||
| int internal_storage_read_key( | ||||
|     InternalStorage* internal_storage, | ||||
|     const char* key, | ||||
|     uint8_t* buffer, | ||||
|     size_t size) { | ||||
|     osThreadId_t caller_thread = osThreadGetId(); | ||||
|     if(caller_thread == 0) { | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0}; | ||||
|     InternalStorageCommand command = { | ||||
|         .thread = caller_thread, | ||||
|         .function = (InternalStorageCommandFunction)_internal_storage_read_key, | ||||
|         .data = &data, | ||||
|     }; | ||||
|     furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK); | ||||
|     osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever); | ||||
|     return data.ret; | ||||
| } | ||||
| 
 | ||||
| void _internal_storage_write_key( | ||||
|     InternalStorage* internal_storage, | ||||
|     InternalStorageCommandKey* data) { | ||||
|     lfs_file_t file; | ||||
|     int ret = lfs_file_open( | ||||
|         &internal_storage->lfs, &file, data->key, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC); | ||||
|     if(ret == 0) { | ||||
|         ret = lfs_file_write(&internal_storage->lfs, &file, data->buffer, data->size); | ||||
|         lfs_file_close(&internal_storage->lfs, &file); | ||||
|     } | ||||
|     data->ret = ret; | ||||
| } | ||||
| 
 | ||||
| int internal_storage_write_key( | ||||
|     InternalStorage* internal_storage, | ||||
|     const char* key, | ||||
|     uint8_t* buffer, | ||||
|     size_t size) { | ||||
|     osThreadId_t caller_thread = osThreadGetId(); | ||||
|     if(caller_thread == 0) { | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0}; | ||||
|     InternalStorageCommand command = { | ||||
|         .thread = caller_thread, | ||||
|         .function = (InternalStorageCommandFunction)_internal_storage_write_key, | ||||
|         .data = &data, | ||||
|     }; | ||||
|     furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK); | ||||
|     osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever); | ||||
|     return data.ret; | ||||
| } | ||||
| @ -1,40 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| /* Internal storage state */ | ||||
| typedef enum { | ||||
|     InternalStorageStateInitializing, | ||||
|     InternalStorageStateReady, | ||||
|     InternalStorageStateBroken, | ||||
| } InternalStorageState; | ||||
| 
 | ||||
| typedef struct InternalStorage InternalStorage; | ||||
| 
 | ||||
| /** Read key, blocking api
 | ||||
|  * @param internal_storage - InternalStorage instance | ||||
|  * @param key - file name to read data from | ||||
|  * @param buffer - pointer to data buffer | ||||
|  * @param size - buffer size | ||||
|  * @return negative on error, otherwise data read | ||||
|  */ | ||||
| int internal_storage_read_key( | ||||
|     InternalStorage* internal_storage, | ||||
|     const char* key, | ||||
|     uint8_t* buffer, | ||||
|     size_t size); | ||||
| 
 | ||||
| /** Write key, blocking api
 | ||||
|  * @param internal_storage - InternalStorage instance | ||||
|  * @param key - file name to store data to | ||||
|  * @param buffer - pointer to data buffer | ||||
|  * @param size - buffer size | ||||
|  * @return negative on error, otherwise data written | ||||
|  */ | ||||
| int internal_storage_write_key( | ||||
|     InternalStorage* internal_storage, | ||||
|     const char* key, | ||||
|     uint8_t* buffer, | ||||
|     size_t size); | ||||
| @ -6,7 +6,6 @@ | ||||
| 
 | ||||
| class IrdaAppBruteForce { | ||||
|     const char* universal_db_filename; | ||||
|     File file; | ||||
|     std::string current_record; | ||||
|     std::unique_ptr<IrdaAppFileParser> file_parser; | ||||
| 
 | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
| #include <file-worker-cpp.h> | ||||
| 
 | ||||
| uint32_t const IrdaAppFileParser::max_line_length = ((9 + 1) * 512 + 100); | ||||
| const char* IrdaAppFileParser::irda_directory = "/irda"; | ||||
| const char* IrdaAppFileParser::irda_directory = "/any/irda"; | ||||
| const char* IrdaAppFileParser::irda_extension = ".ir"; | ||||
| uint32_t const IrdaAppFileParser::max_raw_timings_in_signal = 512; | ||||
| 
 | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| #pragma once | ||||
| #include <file_reader/file_reader.h> | ||||
| #include <irda.h> | ||||
| #include <file-worker-cpp.h> | ||||
| #include "irda-app-signal.h" | ||||
| #include <memory> | ||||
| 
 | ||||
| class IrdaAppFileParser { | ||||
| public: | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| #include "irda-app-remote-manager.hpp" | ||||
| #include "filesystem-api.h" | ||||
| #include <storage/storage.h> | ||||
| #include "furi.h" | ||||
| #include "furi/check.h" | ||||
| #include "gui/modules/button_menu.h" | ||||
|  | ||||
| @ -5,8 +5,7 @@ | ||||
| #include <vector> | ||||
| #include <memory> | ||||
| #include <irda.h> | ||||
| #include <sd-card-api.h> | ||||
| #include <filesystem-api.h> | ||||
| #include <storage/storage.h> | ||||
| #include "irda-app-signal.h" | ||||
| 
 | ||||
| class IrdaAppRemoteButton { | ||||
|  | ||||
| @ -148,14 +148,16 @@ protected: | ||||
| class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon { | ||||
| public: | ||||
|     void on_enter(IrdaApp* app) final; | ||||
|     IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/assets/ext/irda/tv.ir") {} | ||||
|     IrdaAppSceneUniversalTV() | ||||
|         : IrdaAppSceneUniversalCommon("/ext/irda/universal/tv.ir") { | ||||
|     } | ||||
|     ~IrdaAppSceneUniversalTV() {} | ||||
| }; | ||||
| 
 | ||||
| class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon { | ||||
| public: | ||||
|     void on_enter(IrdaApp* app) final; | ||||
|     IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/assets/ext/irda/audio.ir") {} | ||||
|     IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/ext/irda/universal/audio.ir") {} | ||||
|     ~IrdaAppSceneUniversalAudio() {} | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -19,13 +19,11 @@ | ||||
| #include <file-worker-cpp.h> | ||||
| #include <path.h> | ||||
| 
 | ||||
| const char* LfRfidApp::app_folder = "lfrfid"; | ||||
| const char* LfRfidApp::app_folder = "/any/lfrfid"; | ||||
| const char* LfRfidApp::app_extension = ".rfid"; | ||||
| 
 | ||||
| LfRfidApp::LfRfidApp() | ||||
|     : scene_controller{this} | ||||
|     , fs_api{"sdcard"} | ||||
|     , sd_ex_api{"sdcard-ex"} | ||||
|     , notification{"notification"} | ||||
|     , text_store(40) { | ||||
|     api_hal_power_insomnia_enter(); | ||||
| @ -41,6 +39,8 @@ LfRfidApp::~LfRfidApp() { | ||||
| void LfRfidApp::run(void* _args) { | ||||
|     const char* args = reinterpret_cast<const char*>(_args); | ||||
| 
 | ||||
|     make_app_folder(); | ||||
| 
 | ||||
|     if(strlen(args)) { | ||||
|         load_key_data(args, &worker.key); | ||||
|         scene_controller.add_scene(SceneType::Emulate, new LfRfidAppSceneEmulate()); | ||||
|  | ||||
| @ -15,8 +15,6 @@ | ||||
| #include <view-modules/byte-input-vm.h> | ||||
| #include "view/container-vm.h" | ||||
| 
 | ||||
| #include <sd-card-api.h> | ||||
| #include <filesystem-api.h> | ||||
| #include <notification/notification-messages.h> | ||||
| 
 | ||||
| #include "helpers/rfid-worker.h" | ||||
| @ -64,8 +62,6 @@ public: | ||||
|     ~LfRfidApp(); | ||||
|     LfRfidApp(); | ||||
| 
 | ||||
|     RecordController<FS_Api> fs_api; | ||||
|     RecordController<SdCard_Api> sd_ex_api; | ||||
|     RecordController<NotificationApp> notification; | ||||
| 
 | ||||
|     RfidWorker worker; | ||||
|  | ||||
| @ -183,7 +183,7 @@ static void loader_build_menu() { | ||||
|                     menu, | ||||
|                     menu_item_alloc_function( | ||||
|                         FLIPPER_APPS[i].name, | ||||
|                         icon_animation_alloc(FLIPPER_APPS[i].icon), | ||||
|                         FLIPPER_APPS[i].icon ? icon_animation_alloc(FLIPPER_APPS[i].icon) : NULL, | ||||
|                         loader_menu_callback, | ||||
|                         (void*)&FLIPPER_APPS[i])); | ||||
| 
 | ||||
| @ -213,7 +213,8 @@ static void loader_build_menu() { | ||||
|                     menu_plugins, | ||||
|                     menu_item_alloc_function( | ||||
|                         FLIPPER_PLUGINS[i].name, | ||||
|                         icon_animation_alloc(FLIPPER_PLUGINS[i].icon), | ||||
|                         FLIPPER_PLUGINS[i].icon ? icon_animation_alloc(FLIPPER_PLUGINS[i].icon) : | ||||
|                                                   NULL, | ||||
|                         loader_menu_callback, | ||||
|                         (void*)&FLIPPER_PLUGINS[i])); | ||||
| 
 | ||||
| @ -245,7 +246,9 @@ static void loader_build_menu() { | ||||
|                     menu_debug, | ||||
|                     menu_item_alloc_function( | ||||
|                         FLIPPER_DEBUG_APPS[i].name, | ||||
|                         icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon), | ||||
|                         FLIPPER_DEBUG_APPS[i].icon ? | ||||
|                             icon_animation_alloc(FLIPPER_DEBUG_APPS[i].icon) : | ||||
|                             NULL, | ||||
|                         loader_menu_callback, | ||||
|                         (void*)&FLIPPER_DEBUG_APPS[i])); | ||||
| 
 | ||||
| @ -277,7 +280,9 @@ static void loader_build_menu() { | ||||
|                     menu_debug, | ||||
|                     menu_item_alloc_function( | ||||
|                         FLIPPER_SETTINGS_APPS[i].name, | ||||
|                         icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon), | ||||
|                         FLIPPER_SETTINGS_APPS[i].icon ? | ||||
|                             icon_animation_alloc(FLIPPER_SETTINGS_APPS[i].icon) : | ||||
|                             NULL, | ||||
|                         loader_menu_callback, | ||||
|                         (void*)&FLIPPER_SETTINGS_APPS[i])); | ||||
|             } | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
| 
 | ||||
| #define NFC_DEVICE_MAX_DATA_LEN 14 | ||||
| 
 | ||||
| static const char* nfc_app_folder = "nfc"; | ||||
| static const char* nfc_app_folder = "/any/nfc"; | ||||
| static const char* nfc_app_extension = ".nfc"; | ||||
| 
 | ||||
| static bool nfc_device_read_hex(string_t str, uint8_t* buff, uint16_t len) { | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| #include <furi.h> | ||||
| #include <api-hal.h> | ||||
| #include <internal-storage/internal-storage.h> | ||||
| #include <storage/storage.h> | ||||
| #include "notification.h" | ||||
| #include "notification-messages.h" | ||||
| #include "notification-app.h" | ||||
| @ -309,24 +309,30 @@ void notification_process_internal_message(NotificationApp* app, NotificationApp | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void notification_load_settings(NotificationApp* app) { | ||||
| static bool notification_load_settings(NotificationApp* app) { | ||||
|     NotificationSettings settings; | ||||
|     InternalStorage* internal_storage = furi_record_open("internal-storage"); | ||||
|     File* file = storage_file_alloc(furi_record_open("storage")); | ||||
|     const size_t settings_size = sizeof(NotificationSettings); | ||||
| 
 | ||||
|     FURI_LOG_I("notification", "Loading state from internal-storage"); | ||||
|     int ret = internal_storage_read_key( | ||||
|         internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&settings, settings_size); | ||||
|     FURI_LOG_I("notification", "loading settings from \"%s\"", NOTIFICATION_SETTINGS_PATH); | ||||
|     bool fs_result = | ||||
|         storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
| 
 | ||||
|     if(ret != settings_size) { | ||||
|         FURI_LOG_E("notification", "Load failed. Storage returned: %d", ret); | ||||
|     } else { | ||||
|         FURI_LOG_I("notification", "Load success", ret); | ||||
|     if(fs_result) { | ||||
|         uint16_t bytes_count = storage_file_read(file, &settings, settings_size); | ||||
| 
 | ||||
|         if(bytes_count != settings_size) { | ||||
|             fs_result = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(fs_result) { | ||||
|         FURI_LOG_I("notification", "load success"); | ||||
| 
 | ||||
|         if(settings.version != NOTIFICATION_SETTINGS_VERSION) { | ||||
|             FURI_LOG_E( | ||||
|                 "notification", | ||||
|                 "Version(%d != %d) mismatch", | ||||
|                 "version(%d != %d) mismatch", | ||||
|                 app->settings.version, | ||||
|                 NOTIFICATION_SETTINGS_VERSION); | ||||
|         } else { | ||||
| @ -334,26 +340,50 @@ static void notification_load_settings(NotificationApp* app) { | ||||
|             memcpy(&app->settings, &settings, settings_size); | ||||
|             osKernelUnlock(); | ||||
|         } | ||||
|     } else { | ||||
|         FURI_LOG_E("notification", "load failed, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     furi_record_close("internal-storage"); | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
|     furi_record_close("storage"); | ||||
| 
 | ||||
|     return fs_result; | ||||
| }; | ||||
| 
 | ||||
| static void notification_save_settings(NotificationApp* app) { | ||||
|     InternalStorage* internal_storage = furi_record_open("internal-storage"); | ||||
| static bool notification_save_settings(NotificationApp* app) { | ||||
|     NotificationSettings settings; | ||||
|     File* file = storage_file_alloc(furi_record_open("storage")); | ||||
|     const size_t settings_size = sizeof(NotificationSettings); | ||||
| 
 | ||||
|     FURI_LOG_I("notification", "Saving state to internal-storage"); | ||||
|     int ret = internal_storage_write_key( | ||||
|         internal_storage, NOTIFICATION_SETTINGS_PATH, (uint8_t*)&app->settings, settings_size); | ||||
|     FURI_LOG_I("notification", "saving settings to \"%s\"", NOTIFICATION_SETTINGS_PATH); | ||||
| 
 | ||||
|     if(ret != settings_size) { | ||||
|         FURI_LOG_E("notification", "Save failed. Storage returned: %d", ret); | ||||
|     } else { | ||||
|         FURI_LOG_I("notification", "Saved"); | ||||
|     osKernelLock(); | ||||
|     memcpy(&settings, &app->settings, settings_size); | ||||
|     osKernelUnlock(); | ||||
| 
 | ||||
|     bool fs_result = | ||||
|         storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||
| 
 | ||||
|     if(fs_result) { | ||||
|         uint16_t bytes_count = storage_file_write(file, &settings, settings_size); | ||||
| 
 | ||||
|         if(bytes_count != settings_size) { | ||||
|             fs_result = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_record_close("internal-storage"); | ||||
|     if(fs_result) { | ||||
|         FURI_LOG_I("notification", "save success"); | ||||
|     } else { | ||||
|         FURI_LOG_E("notification", "save failed, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
|     furi_record_close("storage"); | ||||
| 
 | ||||
|     return fs_result; | ||||
| }; | ||||
| 
 | ||||
| static void input_event_callback(const void* value, void* context) { | ||||
| @ -407,7 +437,9 @@ static NotificationApp* notification_app_alloc() { | ||||
| int32_t notification_app(void* p) { | ||||
|     NotificationApp* app = notification_app_alloc(); | ||||
| 
 | ||||
|     notification_load_settings(app); | ||||
|     if(!notification_load_settings(app)) { | ||||
|         notification_save_settings(app); | ||||
|     } | ||||
| 
 | ||||
|     notification_vibro_off(); | ||||
|     notification_sound_off(); | ||||
|  | ||||
| @ -31,7 +31,7 @@ typedef struct { | ||||
| } NotificationLedLayer; | ||||
| 
 | ||||
| #define NOTIFICATION_SETTINGS_VERSION 0x01 | ||||
| #define NOTIFICATION_SETTINGS_PATH "notification_settings" | ||||
| #define NOTIFICATION_SETTINGS_PATH "/int/notification.settings" | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint8_t version; | ||||
|  | ||||
| @ -1,892 +0,0 @@ | ||||
| #include "app-template.h" | ||||
| #include "stm32_adafruit_sd.h" | ||||
| #include "fnv1a-hash.h" | ||||
| #include "filesystem-api.h" | ||||
| #include "cli/cli.h" | ||||
| #include "callback-connector.h" | ||||
| #include <notification/notification-messages.h> | ||||
| 
 | ||||
| // event enumeration type
 | ||||
| typedef uint8_t event_t; | ||||
| 
 | ||||
| class SdTestState { | ||||
| public: | ||||
|     // state data
 | ||||
|     static const uint8_t lines_count = 6; | ||||
|     const char* line[lines_count]; | ||||
| 
 | ||||
|     // state initializer
 | ||||
|     SdTestState() { | ||||
|         for(uint8_t i = 0; i < lines_count; i++) { | ||||
|             line[i] = ""; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| // events class
 | ||||
| class SdTestEvent { | ||||
| public: | ||||
|     // events enum
 | ||||
|     static const event_t EventTypeTick = 0; | ||||
|     static const event_t EventTypeKey = 1; | ||||
| 
 | ||||
|     // payload
 | ||||
|     union { | ||||
|         InputEvent input; | ||||
|     } value; | ||||
| 
 | ||||
|     // event type
 | ||||
|     event_t type; | ||||
| }; | ||||
| 
 | ||||
| // our app derived from base AppTemplate class
 | ||||
| // with template variables <state, events>
 | ||||
| class SdTest : public AppTemplate<SdTestState, SdTestEvent> { | ||||
| public: | ||||
|     // vars
 | ||||
|     const uint32_t benchmark_data_size = 4096; | ||||
|     uint8_t* benchmark_data; | ||||
|     FS_Api* fs_api; | ||||
|     NotificationApp* notification; | ||||
| 
 | ||||
|     // consts
 | ||||
|     static const uint32_t BENCHMARK_ERROR = UINT_MAX; | ||||
| 
 | ||||
|     // funcs
 | ||||
|     void run(); | ||||
|     void render(Canvas* canvas); | ||||
|     template <class T> void set_text(std::initializer_list<T> list); | ||||
|     template <class T> void set_error(std::initializer_list<T> list); | ||||
|     void wait_for_button(InputKey input_button); | ||||
|     bool ask(InputKey input_button_cancel, InputKey input_button_ok); | ||||
|     void blink_red(); | ||||
|     void blink_green(); | ||||
| 
 | ||||
|     // "tests"
 | ||||
|     void detect_sd_card(); | ||||
|     void show_warning(); | ||||
|     void get_sd_card_info(); | ||||
| 
 | ||||
|     bool prepare_benchmark_data(); | ||||
|     void free_benchmark_data(); | ||||
|     void write_benchmark(); | ||||
|     uint32_t | ||||
|         write_benchmark_internal(const uint32_t size, const uint32_t tcount, bool silent = false); | ||||
| 
 | ||||
|     void read_benchmark(); | ||||
|     uint32_t read_benchmark_internal( | ||||
|         const uint32_t size, | ||||
|         const uint32_t count, | ||||
|         File* file, | ||||
|         bool silent = false); | ||||
| 
 | ||||
|     void hash_benchmark(); | ||||
| 
 | ||||
|     // cli tests
 | ||||
|     void cli_read_benchmark(Cli* cli, string_t args, void* _ctx); | ||||
|     void cli_write_benchmark(Cli* cli, string_t args, void* _ctx); | ||||
| }; | ||||
| 
 | ||||
| // start app
 | ||||
| void SdTest::run() { | ||||
|     app_ready(); | ||||
| 
 | ||||
|     fs_api = static_cast<FS_Api*>(furi_record_open("sdcard")); | ||||
|     notification = static_cast<NotificationApp*>(furi_record_open("notification")); | ||||
| 
 | ||||
|     if(fs_api == NULL) { | ||||
|         set_error({"cannot get sdcard api"}); | ||||
|         exit(); | ||||
|     } | ||||
| 
 | ||||
|     Cli* cli = static_cast<Cli*>(furi_record_open("cli")); | ||||
| 
 | ||||
|     // read_benchmark and write_benchmark signatures are same. so we must use tags
 | ||||
|     auto cli_read_cb = cbc::obtain_connector<0>(this, &SdTest::cli_read_benchmark); | ||||
|     cli_add_command(cli, "sd_read_test", CliCommandFlagDefault, cli_read_cb, this); | ||||
| 
 | ||||
|     auto cli_write_cb = cbc::obtain_connector<1>(this, &SdTest::cli_write_benchmark); | ||||
|     cli_add_command(cli, "sd_write_test", CliCommandFlagDefault, cli_write_cb, this); | ||||
| 
 | ||||
|     detect_sd_card(); | ||||
|     get_sd_card_info(); | ||||
|     show_warning(); | ||||
| 
 | ||||
|     set_text({"preparing benchmark data"}); | ||||
|     bool data_prepared = prepare_benchmark_data(); | ||||
|     if(data_prepared) { | ||||
|         set_text({"benchmark data prepared"}); | ||||
|     } else { | ||||
|         set_error({"cannot allocate buffer", "for benchmark data"}); | ||||
|     } | ||||
| 
 | ||||
|     write_benchmark(); | ||||
|     read_benchmark(); | ||||
|     hash_benchmark(); | ||||
|     free_benchmark_data(); | ||||
| 
 | ||||
|     set_text({ | ||||
|         "test complete", | ||||
|         "", | ||||
|         "", | ||||
|         "", | ||||
|         "", | ||||
|         "press BACK to exit", | ||||
|     }); | ||||
|     wait_for_button(InputKeyBack); | ||||
| 
 | ||||
|     furi_record_close("notification"); | ||||
|     exit(); | ||||
| } | ||||
| 
 | ||||
| // detect sd card insertion
 | ||||
| void SdTest::detect_sd_card() { | ||||
|     const uint8_t str_buffer_size = 40; | ||||
|     const uint8_t dots_animation_size = 4; | ||||
|     char str_buffer[str_buffer_size]; | ||||
|     const char dots[dots_animation_size][4] = {"", ".", "..", "..."}; | ||||
|     uint8_t i = 0; | ||||
| 
 | ||||
|     // detect sd card pin
 | ||||
|     while(fs_api->common.get_fs_info(NULL, NULL) == FSE_NOT_READY) { | ||||
|         delay(100); | ||||
| 
 | ||||
|         snprintf(str_buffer, str_buffer_size, "Waiting%s", dots[i]); | ||||
|         set_text({static_cast<const char*>(str_buffer), "Please insert sd card"}); | ||||
| 
 | ||||
|         if(i < (dots_animation_size - 1)) { | ||||
|             i++; | ||||
|         } else { | ||||
|             i = 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     blink_green(); | ||||
| } | ||||
| 
 | ||||
| // show warning about test
 | ||||
| void SdTest::show_warning() { | ||||
|     set_text( | ||||
|         {"!!Warning!!", | ||||
|          "during the tests", | ||||
|          "files can be overwritten", | ||||
|          "or data on card may be lost", | ||||
|          "", | ||||
|          "press UP DOWN OK to continue"}); | ||||
| 
 | ||||
|     wait_for_button(InputKeyUp); | ||||
|     wait_for_button(InputKeyDown); | ||||
|     wait_for_button(InputKeyOk); | ||||
| } | ||||
| 
 | ||||
| // get info about sd card, label, sn
 | ||||
| // sector, cluster, total and free size
 | ||||
| void SdTest::get_sd_card_info() { | ||||
|     const uint8_t str_buffer_size = 26; | ||||
|     char str_buffer[2][str_buffer_size]; | ||||
|     FS_Error result; | ||||
|     uint64_t bytes_total, bytes_free; | ||||
|     int __attribute__((unused)) snprintf_count = 0; | ||||
| 
 | ||||
|     result = fs_api->common.get_fs_info(&bytes_total, &bytes_free); | ||||
|     if(result != FSE_OK) set_error({"get_fs_info error", fs_api->error.get_desc(result)}); | ||||
| 
 | ||||
|     snprintf( | ||||
|         str_buffer[0], str_buffer_size, "%lu KB total", static_cast<uint32_t>(bytes_total / 1024)); | ||||
|     snprintf( | ||||
|         str_buffer[1], str_buffer_size, "%lu KB free", static_cast<uint32_t>(bytes_free / 1024)); | ||||
| 
 | ||||
|     set_text( | ||||
|         {static_cast<const char*>(str_buffer[0]), | ||||
|          static_cast<const char*>(str_buffer[1]), | ||||
|          "", | ||||
|          "", | ||||
|          "", | ||||
|          "press OK to continue"}); | ||||
| 
 | ||||
|     blink_green(); | ||||
| 
 | ||||
|     wait_for_button(InputKeyOk); | ||||
| } | ||||
| 
 | ||||
| // prepare benchmark data (allocate data in ram)
 | ||||
| bool SdTest::prepare_benchmark_data() { | ||||
|     bool result = true; | ||||
|     benchmark_data = static_cast<uint8_t*>(malloc(benchmark_data_size)); | ||||
| 
 | ||||
|     if(benchmark_data == NULL) { | ||||
|         result = false; | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < benchmark_data_size; i++) { | ||||
|         benchmark_data[i] = static_cast<uint8_t>(i); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void SdTest::free_benchmark_data() { | ||||
|     free(benchmark_data); | ||||
| } | ||||
| 
 | ||||
| // write speed test
 | ||||
| void SdTest::write_benchmark() { | ||||
|     const uint32_t b1_size = 1; | ||||
|     const uint32_t b8_size = 8; | ||||
|     const uint32_t b32_size = 32; | ||||
|     const uint32_t b256_size = 256; | ||||
|     const uint32_t b4096_size = 4096; | ||||
| 
 | ||||
|     const uint32_t benchmark_data_size = 16384 * 4; | ||||
| 
 | ||||
|     uint32_t benchmark_bps = 0; | ||||
| 
 | ||||
|     const uint8_t str_buffer_size = 32; | ||||
|     char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""}; | ||||
|     auto string_list = { | ||||
|         static_cast<const char*>(str_buffer[0]), | ||||
|         static_cast<const char*>(str_buffer[1]), | ||||
|         static_cast<const char*>(str_buffer[2]), | ||||
|         static_cast<const char*>(str_buffer[3]), | ||||
|         static_cast<const char*>(str_buffer[4]), | ||||
|         static_cast<const char*>(str_buffer[5])}; | ||||
| 
 | ||||
|     set_text({"write speed test", "procedure can be lengthy", "please wait"}); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 1b test
 | ||||
|     benchmark_bps = write_benchmark_internal(b1_size, benchmark_data_size / b1_size); | ||||
|     snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 8b test
 | ||||
|     benchmark_bps = write_benchmark_internal(b8_size, benchmark_data_size / b8_size); | ||||
|     snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 32b test
 | ||||
|     benchmark_bps = write_benchmark_internal(b32_size, benchmark_data_size / b32_size); | ||||
|     snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 256b test
 | ||||
|     benchmark_bps = write_benchmark_internal(b256_size, benchmark_data_size / b256_size); | ||||
|     snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 4096b test
 | ||||
|     benchmark_bps = write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size); | ||||
|     snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps); | ||||
|     snprintf(str_buffer[5], str_buffer_size, "press OK to continue"); | ||||
|     set_text(string_list); | ||||
| 
 | ||||
|     blink_green(); | ||||
| 
 | ||||
|     wait_for_button(InputKeyOk); | ||||
| } | ||||
| 
 | ||||
| uint32_t SdTest::write_benchmark_internal(const uint32_t size, const uint32_t count, bool silent) { | ||||
|     uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_written; | ||||
|     File file; | ||||
| 
 | ||||
|     const uint8_t str_buffer_size = 32; | ||||
|     char str_buffer[str_buffer_size]; | ||||
| 
 | ||||
|     if(!fs_api->file.open(&file, "write.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||
|         if(!silent) { | ||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); | ||||
|             set_error({"cannot open file ", static_cast<const char*>(str_buffer)}); | ||||
|         } else { | ||||
|             benchmark_bps = BENCHMARK_ERROR; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     start_tick = osKernelGetTickCount(); | ||||
|     for(size_t i = 0; i < count; i++) { | ||||
|         bytes_written = fs_api->file.write(&file, benchmark_data, size); | ||||
|         if(bytes_written != size || file.error_id != FSE_OK) { | ||||
|             if(!silent) { | ||||
|                 snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); | ||||
|                 set_error({"cannot write to file ", static_cast<const char*>(str_buffer)}); | ||||
|             } else { | ||||
|                 benchmark_bps = BENCHMARK_ERROR; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     stop_tick = osKernelGetTickCount(); | ||||
| 
 | ||||
|     if(!fs_api->file.close(&file)) { | ||||
|         if(!silent) { | ||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); | ||||
|             set_error({"cannot close file ", static_cast<const char*>(str_buffer)}); | ||||
|         } else { | ||||
|             benchmark_bps = BENCHMARK_ERROR; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(benchmark_bps != BENCHMARK_ERROR) { | ||||
|         benchmark_time = stop_tick - start_tick; | ||||
|         benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; | ||||
|     } | ||||
| 
 | ||||
|     return benchmark_bps; | ||||
| } | ||||
| 
 | ||||
| // read speed test
 | ||||
| void SdTest::read_benchmark() { | ||||
|     const uint32_t benchmark_data_size = 16384 * 8; | ||||
|     uint32_t bytes_written; | ||||
|     uint32_t benchmark_bps = 0; | ||||
| 
 | ||||
|     const uint8_t str_buffer_size = 32; | ||||
|     char str_buffer[6][str_buffer_size] = {"", "", "", "", "", ""}; | ||||
|     auto string_list = { | ||||
|         static_cast<const char*>(str_buffer[0]), | ||||
|         static_cast<const char*>(str_buffer[1]), | ||||
|         static_cast<const char*>(str_buffer[2]), | ||||
|         static_cast<const char*>(str_buffer[3]), | ||||
|         static_cast<const char*>(str_buffer[4]), | ||||
|         static_cast<const char*>(str_buffer[5])}; | ||||
| 
 | ||||
|     File file; | ||||
| 
 | ||||
|     const uint32_t b1_size = 1; | ||||
|     const uint32_t b8_size = 8; | ||||
|     const uint32_t b32_size = 32; | ||||
|     const uint32_t b256_size = 256; | ||||
|     const uint32_t b4096_size = 4096; | ||||
| 
 | ||||
|     // prepare data for read test
 | ||||
|     set_text({"prepare data", "for read speed test", "procedure can be lengthy", "please wait"}); | ||||
|     delay(100); | ||||
| 
 | ||||
|     if(!fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||
|         set_error({"cannot open file ", "in prepare read"}); | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { | ||||
|         bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); | ||||
|         if(bytes_written != b4096_size || file.error_id != FSE_OK) { | ||||
|             set_error({"cannot write to file ", "in prepare read"}); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!fs_api->file.close(&file)) { | ||||
|         set_error({"cannot close file ", "in prepare read"}); | ||||
|     } | ||||
| 
 | ||||
|     // test start
 | ||||
|     set_text({"read speed test", "procedure can be lengthy", "please wait"}); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // open file
 | ||||
|     if(!fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         set_error({"cannot open file ", "in read benchmark"}); | ||||
|     } | ||||
| 
 | ||||
|     // 1b test
 | ||||
|     benchmark_bps = read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file); | ||||
|     snprintf(str_buffer[0], str_buffer_size, "1-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 8b test
 | ||||
|     benchmark_bps = read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file); | ||||
|     snprintf(str_buffer[1], str_buffer_size, "8-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 32b test
 | ||||
|     benchmark_bps = read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file); | ||||
|     snprintf(str_buffer[2], str_buffer_size, "32-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 256b test
 | ||||
|     benchmark_bps = read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file); | ||||
|     snprintf(str_buffer[3], str_buffer_size, "256-byte: %lu bps", benchmark_bps); | ||||
|     set_text(string_list); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // 4096b test
 | ||||
|     benchmark_bps = read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file); | ||||
|     snprintf(str_buffer[4], str_buffer_size, "4096-byte: %lu bps", benchmark_bps); | ||||
|     snprintf(str_buffer[5], str_buffer_size, "press OK to continue"); | ||||
|     set_text(string_list); | ||||
| 
 | ||||
|     // close file
 | ||||
|     if(!fs_api->file.close(&file)) { | ||||
|         set_error({"cannot close file ", "in read test"}); | ||||
|     } | ||||
| 
 | ||||
|     blink_green(); | ||||
| 
 | ||||
|     wait_for_button(InputKeyOk); | ||||
| } | ||||
| 
 | ||||
| uint32_t SdTest::read_benchmark_internal( | ||||
|     const uint32_t size, | ||||
|     const uint32_t count, | ||||
|     File* file, | ||||
|     bool silent) { | ||||
|     uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_readed; | ||||
| 
 | ||||
|     const uint8_t str_buffer_size = 32; | ||||
|     char str_buffer[str_buffer_size]; | ||||
|     uint8_t* read_buffer; | ||||
| 
 | ||||
|     read_buffer = static_cast<uint8_t*>(malloc(size)); | ||||
| 
 | ||||
|     if(read_buffer == NULL) { | ||||
|         if(!silent) { | ||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); | ||||
|             set_error({"cannot allocate memory", static_cast<const char*>(str_buffer)}); | ||||
|         } else { | ||||
|             benchmark_bps = BENCHMARK_ERROR; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fs_api->file.seek(file, 0, true); | ||||
| 
 | ||||
|     start_tick = osKernelGetTickCount(); | ||||
|     for(size_t i = 0; i < count; i++) { | ||||
|         bytes_readed = fs_api->file.read(file, read_buffer, size); | ||||
|         if(bytes_readed != size || file->error_id != FSE_OK) { | ||||
|             if(!silent) { | ||||
|                 snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); | ||||
|                 set_error({"cannot read from file ", static_cast<const char*>(str_buffer)}); | ||||
|             } else { | ||||
|                 benchmark_bps = BENCHMARK_ERROR; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     stop_tick = osKernelGetTickCount(); | ||||
| 
 | ||||
|     free(read_buffer); | ||||
| 
 | ||||
|     if(benchmark_bps != BENCHMARK_ERROR) { | ||||
|         benchmark_time = stop_tick - start_tick; | ||||
|         benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; | ||||
|     } | ||||
| 
 | ||||
|     return benchmark_bps; | ||||
| } | ||||
| 
 | ||||
| // hash benchmark, store data to sd with known hash
 | ||||
| // then read, calculate hash and compare both hashes
 | ||||
| void SdTest::hash_benchmark() { | ||||
|     uint32_t mcu_data_hash = FNV_1A_INIT; | ||||
|     uint32_t sdcard_data_hash = FNV_1A_INIT; | ||||
|     uint8_t* read_buffer; | ||||
|     uint32_t bytes_readed; | ||||
| 
 | ||||
|     uint32_t bytes_written; | ||||
| 
 | ||||
|     const uint8_t str_buffer_size = 32; | ||||
|     char str_buffer[3][str_buffer_size] = {"", "", ""}; | ||||
| 
 | ||||
|     File file; | ||||
| 
 | ||||
|     const uint32_t b4096_size = 4096; | ||||
|     const uint32_t benchmark_count = 20; | ||||
| 
 | ||||
|     // prepare data for hash test
 | ||||
|     set_text({"prepare data", "for hash test"}); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // write data to test file and calculate hash
 | ||||
|     if(!fs_api->file.open(&file, "hash.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||
|         set_error({"cannot open file ", "in prepare hash"}); | ||||
|     } | ||||
| 
 | ||||
|     for(uint32_t i = 0; i < benchmark_count; i++) { | ||||
|         mcu_data_hash = fnv1a_buffer_hash(benchmark_data, b4096_size, mcu_data_hash); | ||||
|         bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); | ||||
| 
 | ||||
|         if(bytes_written != b4096_size || file.error_id != FSE_OK) { | ||||
|             set_error({"cannot write to file ", "in prepare hash"}); | ||||
|         } | ||||
| 
 | ||||
|         snprintf(str_buffer[0], str_buffer_size, "writing %lu of %lu x 4k", i, benchmark_count); | ||||
|         set_text({"prepare data", "for hash test", static_cast<const char*>(str_buffer[0])}); | ||||
|         delay(100); | ||||
|     } | ||||
| 
 | ||||
|     if(!fs_api->file.close(&file)) { | ||||
|         set_error({"cannot close file ", "in prepare hash"}); | ||||
|     } | ||||
| 
 | ||||
|     // show hash of data located in mcu memory
 | ||||
|     snprintf(str_buffer[0], str_buffer_size, "hash in mcu 0x%lx", mcu_data_hash); | ||||
|     set_text({str_buffer[0]}); | ||||
|     delay(100); | ||||
| 
 | ||||
|     // read data from sd card and calculate hash
 | ||||
|     read_buffer = static_cast<uint8_t*>(malloc(b4096_size)); | ||||
| 
 | ||||
|     if(read_buffer == NULL) { | ||||
|         set_error({"cannot allocate memory", "in hash test"}); | ||||
|     } | ||||
| 
 | ||||
|     if(!fs_api->file.open(&file, "hash.test", FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         set_error({"cannot open file ", "in hash test"}); | ||||
|     } | ||||
| 
 | ||||
|     for(uint32_t i = 0; i < benchmark_count; i++) { | ||||
|         bytes_readed = fs_api->file.read(&file, read_buffer, b4096_size); | ||||
|         sdcard_data_hash = fnv1a_buffer_hash(read_buffer, b4096_size, sdcard_data_hash); | ||||
| 
 | ||||
|         if(bytes_readed != b4096_size || file.error_id != FSE_OK) { | ||||
|             set_error({"cannot read from file ", "in hash test"}); | ||||
|         } | ||||
| 
 | ||||
|         snprintf(str_buffer[1], str_buffer_size, "reading %lu of %lu x 4k", i, benchmark_count); | ||||
|         set_text({str_buffer[0], str_buffer[1]}); | ||||
|         delay(100); | ||||
|     } | ||||
| 
 | ||||
|     if(!fs_api->file.close(&file)) { | ||||
|         set_error({"cannot close file ", "in hash test"}); | ||||
|     } | ||||
| 
 | ||||
|     free(read_buffer); | ||||
| 
 | ||||
|     snprintf(str_buffer[1], str_buffer_size, "hash in sdcard 0x%lx", sdcard_data_hash); | ||||
|     if(mcu_data_hash == sdcard_data_hash) { | ||||
|         snprintf(str_buffer[2], str_buffer_size, "hashes are equal, press OK"); | ||||
|         set_text( | ||||
|             {static_cast<const char*>(str_buffer[0]), | ||||
|              static_cast<const char*>(str_buffer[1]), | ||||
|              "", | ||||
|              "", | ||||
|              "", | ||||
|              static_cast<const char*>(str_buffer[2])}); | ||||
|     } else { | ||||
|         snprintf(str_buffer[2], str_buffer_size, "hash error, press BACK to exit"); | ||||
|         set_error( | ||||
|             {static_cast<const char*>(str_buffer[0]), | ||||
|              static_cast<const char*>(str_buffer[1]), | ||||
|              "", | ||||
|              "", | ||||
|              "", | ||||
|              static_cast<const char*>(str_buffer[2])}); | ||||
|     } | ||||
| 
 | ||||
|     blink_green(); | ||||
| 
 | ||||
|     wait_for_button(InputKeyOk); | ||||
| } | ||||
| 
 | ||||
| void SdTest::cli_read_benchmark(Cli* cli, string_t args, void* _ctx) { | ||||
|     SdTest* _this = static_cast<SdTest*>(_ctx); | ||||
| 
 | ||||
|     const uint32_t benchmark_data_size = 16384 * 8; | ||||
|     uint32_t bytes_written; | ||||
|     uint32_t benchmark_bps = 0; | ||||
|     File file; | ||||
| 
 | ||||
|     const uint32_t b1_size = 1; | ||||
|     const uint32_t b8_size = 8; | ||||
|     const uint32_t b32_size = 32; | ||||
|     const uint32_t b256_size = 256; | ||||
|     const uint32_t b4096_size = 4096; | ||||
| 
 | ||||
|     const uint8_t str_buffer_size = 64; | ||||
|     char str_buffer[str_buffer_size]; | ||||
| 
 | ||||
|     printf("preparing benchmark data\r\n"); | ||||
|     bool data_prepared = _this->prepare_benchmark_data(); | ||||
|     if(data_prepared) { | ||||
|         printf("benchmark data prepared\r\n"); | ||||
|     } else { | ||||
|         printf("error: cannot allocate buffer for benchmark data\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     // prepare data for read test
 | ||||
|     printf("prepare data for read speed test, procedure can be lengthy, please wait\r\n"); | ||||
| 
 | ||||
|     if(!_this->fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||
|         printf("error: cannot open file in prepare read\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { | ||||
|         bytes_written = _this->fs_api->file.write(&file, benchmark_data, b4096_size); | ||||
|         if(bytes_written != b4096_size || file.error_id != FSE_OK) { | ||||
|             printf("error: cannot write to file in prepare read\r\n"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!_this->fs_api->file.close(&file)) { | ||||
|         printf("error: cannot close file in prepare read\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     // test start
 | ||||
|     printf("read speed test, procedure can be lengthy, please wait\r\n"); | ||||
| 
 | ||||
|     // open file
 | ||||
|     if(!_this->fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         printf("error: cannot open file in read benchmark\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     // 1b test
 | ||||
|     benchmark_bps = | ||||
|         _this->read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 1-byte read test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 8b test
 | ||||
|     benchmark_bps = | ||||
|         _this->read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 8-byte read test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 32b test
 | ||||
|     benchmark_bps = | ||||
|         _this->read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 32-byte read test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 256b test
 | ||||
|     benchmark_bps = | ||||
|         _this->read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 256-byte read test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 4096b test
 | ||||
|     benchmark_bps = | ||||
|         _this->read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 4096-byte read test\r\n"); | ||||
|     } else { | ||||
|         snprintf( | ||||
|             str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // close file
 | ||||
|     if(!_this->fs_api->file.close(&file)) { | ||||
|         printf("error: cannot close file\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     _this->free_benchmark_data(); | ||||
| 
 | ||||
|     printf("test completed\r\n"); | ||||
| } | ||||
| 
 | ||||
| void SdTest::cli_write_benchmark(Cli* cli, string_t args, void* _ctx) { | ||||
|     SdTest* _this = static_cast<SdTest*>(_ctx); | ||||
| 
 | ||||
|     const uint32_t b1_size = 1; | ||||
|     const uint32_t b8_size = 8; | ||||
|     const uint32_t b32_size = 32; | ||||
|     const uint32_t b256_size = 256; | ||||
|     const uint32_t b4096_size = 4096; | ||||
| 
 | ||||
|     const uint32_t benchmark_data_size = 16384 * 4; | ||||
| 
 | ||||
|     uint32_t benchmark_bps = 0; | ||||
| 
 | ||||
|     const uint8_t str_buffer_size = 64; | ||||
|     char str_buffer[str_buffer_size]; | ||||
| 
 | ||||
|     printf("preparing benchmark data\r\n"); | ||||
|     bool data_prepared = _this->prepare_benchmark_data(); | ||||
|     if(data_prepared) { | ||||
|         printf("benchmark data prepared\r\n"); | ||||
|     } else { | ||||
|         printf("error: cannot allocate buffer for benchmark data\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     printf("write speed test, procedure can be lengthy, please wait\r\n"); | ||||
| 
 | ||||
|     // 1b test
 | ||||
|     benchmark_bps = _this->write_benchmark_internal(b1_size, benchmark_data_size / b1_size, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 1-byte write test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 8b test
 | ||||
|     benchmark_bps = _this->write_benchmark_internal(b8_size, benchmark_data_size / b8_size, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 8-byte write test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 32b test
 | ||||
|     benchmark_bps = | ||||
|         _this->write_benchmark_internal(b32_size, benchmark_data_size / b32_size, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 32-byte write test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 256b test
 | ||||
|     benchmark_bps = | ||||
|         _this->write_benchmark_internal(b256_size, benchmark_data_size / b256_size, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 256-byte write test\r\n"); | ||||
|     } else { | ||||
|         snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     // 4096b test
 | ||||
|     benchmark_bps = | ||||
|         _this->write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, true); | ||||
|     if(benchmark_bps == BENCHMARK_ERROR) { | ||||
|         printf("error: in 4096-byte write test\r\n"); | ||||
|     } else { | ||||
|         snprintf( | ||||
|             str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps); | ||||
|         printf(str_buffer); | ||||
|     } | ||||
| 
 | ||||
|     _this->free_benchmark_data(); | ||||
| 
 | ||||
|     printf("test completed\r\n"); | ||||
| } | ||||
| 
 | ||||
| // wait for button press
 | ||||
| void SdTest::wait_for_button(InputKey input_button) { | ||||
|     SdTestEvent event; | ||||
|     osMessageQueueReset(event_queue); | ||||
|     while(1) { | ||||
|         osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); | ||||
| 
 | ||||
|         if(result == osOK && event.type == SdTestEvent::EventTypeKey) { | ||||
|             if(event.value.input.type == InputTypeShort) { | ||||
|                 if(event.value.input.key == InputKeyBack) { | ||||
|                     exit(); | ||||
|                 } else { | ||||
|                     if(event.value.input.key == input_button) { | ||||
|                         blink_green(); | ||||
|                         break; | ||||
|                     } else { | ||||
|                         blink_red(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     osMessageQueueReset(event_queue); | ||||
| } | ||||
| 
 | ||||
| // ask user to proceed or cancel
 | ||||
| bool SdTest::ask(InputKey input_button_cancel, InputKey input_button_ok) { | ||||
|     bool return_result; | ||||
|     SdTestEvent event; | ||||
|     osMessageQueueReset(event_queue); | ||||
|     while(1) { | ||||
|         osStatus_t result = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); | ||||
| 
 | ||||
|         if(result == osOK && event.type == SdTestEvent::EventTypeKey) { | ||||
|             if(event.value.input.type == InputTypeShort) { | ||||
|                 if(event.value.input.key == InputKeyBack) { | ||||
|                     exit(); | ||||
|                 } else { | ||||
|                     if(event.value.input.key == input_button_ok) { | ||||
|                         blink_green(); | ||||
|                         return_result = true; | ||||
|                         break; | ||||
|                     } else if(event.value.input.key == input_button_cancel) { | ||||
|                         blink_green(); | ||||
|                         return_result = false; | ||||
|                         break; | ||||
|                     } else { | ||||
|                         blink_red(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     osMessageQueueReset(event_queue); | ||||
|     return return_result; | ||||
| } | ||||
| 
 | ||||
| // blink red led
 | ||||
| void SdTest::blink_red() { | ||||
|     notification_message(notification, &sequence_blink_red_100); | ||||
| } | ||||
| 
 | ||||
| // blink green led
 | ||||
| void SdTest::blink_green() { | ||||
|     notification_message(notification, &sequence_blink_green_100); | ||||
| } | ||||
| 
 | ||||
| // set text, but with infinite loop
 | ||||
| template <class T> void SdTest::set_error(std::initializer_list<T> list) { | ||||
|     set_text(list); | ||||
|     blink_red(); | ||||
|     wait_for_button(InputKeyBack); | ||||
|     exit(); | ||||
| } | ||||
| 
 | ||||
| // set text, sort of variadic function
 | ||||
| template <class T> void SdTest::set_text(std::initializer_list<T> list) { | ||||
|     uint8_t line_position = 0; | ||||
|     acquire_state(); | ||||
|     printf("------------------------\r\n"); | ||||
| 
 | ||||
|     // set line strings from args
 | ||||
|     for(auto element : list) { | ||||
|         state.line[line_position] = element; | ||||
|         printf("%s\n", element); | ||||
|         line_position++; | ||||
|         if(line_position == state.lines_count) break; | ||||
|     } | ||||
| 
 | ||||
|     // set empty lines
 | ||||
|     for(; line_position < state.lines_count; line_position++) { | ||||
|         state.line[line_position] = ""; | ||||
|         printf("\r\n"); | ||||
|     } | ||||
| 
 | ||||
|     printf("------------------------\r\n"); | ||||
|     release_state(); | ||||
|     update_gui(); | ||||
| } | ||||
| 
 | ||||
| // render app
 | ||||
| void SdTest::render(Canvas* canvas) { | ||||
|     canvas_set_color(canvas, ColorBlack); | ||||
|     canvas_set_font(canvas, FontSecondary); | ||||
|     for(uint8_t i = 0; i < state.lines_count; i++) { | ||||
|         canvas_draw_str(canvas, 0, (i + 1) * 10, state.line[i]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // app enter function
 | ||||
| extern "C" int32_t sd_card_test(void* p) { | ||||
|     SdTest* app = new SdTest(); | ||||
|     app->run(); | ||||
|     return 0; | ||||
| } | ||||
| @ -1,739 +0,0 @@ | ||||
| #include "fatfs.h" | ||||
| #include "filesystem-api.h" | ||||
| #include "sd-filesystem.h" | ||||
| 
 | ||||
| /******************* Global vars for api *******************/ | ||||
| 
 | ||||
| static SdFsInfo* fs_info; | ||||
| 
 | ||||
| /******************* Core Functions *******************/ | ||||
| 
 | ||||
| bool _fs_init(SdFsInfo* _fs_info) { | ||||
|     bool result = true; | ||||
|     _fs_info->mutex = osMutexNew(NULL); | ||||
|     if(_fs_info->mutex == NULL) result = false; | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) { | ||||
|         _fs_info->files[i].thread_id = NULL; | ||||
|     } | ||||
| 
 | ||||
|     _fs_info->path = "0:/"; | ||||
|     _fs_info->status = SD_NO_CARD; | ||||
| 
 | ||||
|     // store pointer for api fns
 | ||||
|     fs_info = _fs_info; | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool _fs_lock(SdFsInfo* fs_info) { | ||||
|     api_hal_power_insomnia_enter(); | ||||
|     return (osMutexAcquire(fs_info->mutex, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| bool _fs_unlock(SdFsInfo* fs_info) { | ||||
|     api_hal_power_insomnia_exit(); | ||||
|     return (osMutexRelease(fs_info->mutex) == osOK); | ||||
| } | ||||
| 
 | ||||
| SDError _get_filedata(SdFsInfo* fs_info, File* file, FileData** filedata, FiledataFilter filter) { | ||||
|     SDError error = SD_OK; | ||||
|     _fs_lock(fs_info); | ||||
| 
 | ||||
|     if(fs_info->status == SD_OK) { | ||||
|         if(file != NULL && file->file_id < SD_FS_MAX_FILES) { | ||||
|             if(fs_info->files[file->file_id].thread_id == osThreadGetId()) { | ||||
|                 if(filter == FDF_ANY) { | ||||
|                     // any type
 | ||||
|                     *filedata = &fs_info->files[file->file_id]; | ||||
|                 } else if(filter == FDF_FILE) { | ||||
|                     // file type
 | ||||
|                     if(!fs_info->files[file->file_id].is_dir) { | ||||
|                         *filedata = &fs_info->files[file->file_id]; | ||||
|                     } else { | ||||
|                         error = SD_NOT_A_FILE; | ||||
|                     } | ||||
|                 } else if(filter == FDF_DIR) { | ||||
|                     // dir type
 | ||||
|                     if(fs_info->files[file->file_id].is_dir) { | ||||
|                         *filedata = &fs_info->files[file->file_id]; | ||||
|                     } else { | ||||
|                         error = SD_NOT_A_DIR; | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 error = SD_OTHER_APP; | ||||
|             } | ||||
|         } else { | ||||
|             error = SD_INVALID_PARAMETER; | ||||
|         } | ||||
|     } else { | ||||
|         error = SD_NO_CARD; | ||||
|     } | ||||
| 
 | ||||
|     _fs_unlock(fs_info); | ||||
|     return error; | ||||
| } | ||||
| 
 | ||||
| SDError _get_file(SdFsInfo* fs_info, File* file, FileData** filedata) { | ||||
|     return _get_filedata(fs_info, file, filedata, FDF_FILE); | ||||
| } | ||||
| 
 | ||||
| SDError _get_dir(SdFsInfo* fs_info, File* file, FileData** filedata) { | ||||
|     return _get_filedata(fs_info, file, filedata, FDF_DIR); | ||||
| } | ||||
| 
 | ||||
| SDError _get_any(SdFsInfo* fs_info, File* file, FileData** filedata) { | ||||
|     return _get_filedata(fs_info, file, filedata, FDF_ANY); | ||||
| } | ||||
| 
 | ||||
| SDError _fs_status(SdFsInfo* fs_info) { | ||||
|     SDError result; | ||||
| 
 | ||||
|     _fs_lock(fs_info); | ||||
|     result = fs_info->status; | ||||
|     _fs_unlock(fs_info); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void _fs_on_client_app_exit(SdFsInfo* fs_info) { | ||||
|     _fs_lock(fs_info); | ||||
|     for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) { | ||||
|         if(fs_info->files[i].thread_id == osThreadGetId()) { | ||||
|             if(fs_info->files[i].is_dir) { | ||||
|                 // TODO close dir
 | ||||
|             } else { | ||||
|                 // TODO close file
 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     _fs_unlock(fs_info); | ||||
| } | ||||
| 
 | ||||
| FS_Error _fs_parse_error(SDError error) { | ||||
|     FS_Error result; | ||||
|     switch(error) { | ||||
|     case SD_OK: | ||||
|         result = FSE_OK; | ||||
|         break; | ||||
|     case SD_INT_ERR: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_NO_FILE: | ||||
|         result = FSE_NOT_EXIST; | ||||
|         break; | ||||
|     case SD_NO_PATH: | ||||
|         result = FSE_NOT_EXIST; | ||||
|         break; | ||||
|     case SD_INVALID_NAME: | ||||
|         result = FSE_INVALID_NAME; | ||||
|         break; | ||||
|     case SD_DENIED: | ||||
|         result = FSE_DENIED; | ||||
|         break; | ||||
|     case SD_EXIST: | ||||
|         result = FSE_EXIST; | ||||
|         break; | ||||
|     case SD_INVALID_OBJECT: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_WRITE_PROTECTED: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_INVALID_DRIVE: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_NOT_ENABLED: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_NO_FILESYSTEM: | ||||
|         result = FSE_NOT_READY; | ||||
|         break; | ||||
|     case SD_MKFS_ABORTED: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_TIMEOUT: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_LOCKED: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_NOT_ENOUGH_CORE: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_TOO_MANY_OPEN_FILES: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     case SD_INVALID_PARAMETER: | ||||
|         result = FSE_INVALID_PARAMETER; | ||||
|         break; | ||||
|     case SD_NO_CARD: | ||||
|         result = FSE_NOT_READY; | ||||
|         break; | ||||
|     case SD_NOT_A_FILE: | ||||
|         result = FSE_INVALID_PARAMETER; | ||||
|         break; | ||||
|     case SD_NOT_A_DIR: | ||||
|         result = FSE_INVALID_PARAMETER; | ||||
|         break; | ||||
|     case SD_OTHER_APP: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| /******************* File Functions *******************/ | ||||
| 
 | ||||
| // Open/Create a file
 | ||||
| bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode) { | ||||
|     SDFile* sd_file = NULL; | ||||
| 
 | ||||
|     _fs_lock(fs_info); | ||||
|     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { | ||||
|         FileData* filedata = &fs_info->files[index]; | ||||
| 
 | ||||
|         if(filedata->thread_id == NULL) { | ||||
|             file->file_id = index; | ||||
| 
 | ||||
|             memset(&(filedata->data), 0, sizeof(SDFileDirStorage)); | ||||
|             filedata->thread_id = osThreadGetId(); | ||||
|             filedata->is_dir = false; | ||||
|             sd_file = &(filedata->data.file); | ||||
| 
 | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     _fs_unlock(fs_info); | ||||
| 
 | ||||
|     if(sd_file == NULL) { | ||||
|         file->internal_error_id = SD_TOO_MANY_OPEN_FILES; | ||||
|     } else { | ||||
|         uint8_t _mode = 0; | ||||
| 
 | ||||
|         if(access_mode & FSAM_READ) _mode |= FA_READ; | ||||
|         if(access_mode & FSAM_WRITE) _mode |= FA_WRITE; | ||||
|         if(open_mode & FSOM_OPEN_EXISTING) _mode |= FA_OPEN_EXISTING; | ||||
|         if(open_mode & FSOM_OPEN_ALWAYS) _mode |= FA_OPEN_ALWAYS; | ||||
|         if(open_mode & FSOM_OPEN_APPEND) _mode |= FA_OPEN_APPEND; | ||||
|         if(open_mode & FSOM_CREATE_NEW) _mode |= FA_CREATE_NEW; | ||||
|         if(open_mode & FSOM_CREATE_ALWAYS) _mode |= FA_CREATE_ALWAYS; | ||||
| 
 | ||||
|         file->internal_error_id = f_open(sd_file, path, _mode); | ||||
|     } | ||||
| 
 | ||||
|     // TODO on exit
 | ||||
|     //furiac_onexit(_fs_on_client_app_exit, fs_info);
 | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| // Close an opened file
 | ||||
| bool fs_file_close(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         file->internal_error_id = f_close(&filedata->data.file); | ||||
| 
 | ||||
|         _fs_lock(fs_info); | ||||
|         filedata->thread_id = NULL; | ||||
|         _fs_unlock(fs_info); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| // Read data from the file
 | ||||
| uint16_t fs_file_read(File* file, void* buff, uint16_t const bytes_to_read) { | ||||
|     FileData* filedata = NULL; | ||||
|     uint16_t bytes_readed = 0; | ||||
| 
 | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         file->internal_error_id = f_read(&filedata->data.file, buff, bytes_to_read, &bytes_readed); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return bytes_readed; | ||||
| } | ||||
| 
 | ||||
| // Write data to the file
 | ||||
| uint16_t fs_file_write(File* file, const void* buff, uint16_t const bytes_to_write) { | ||||
|     FileData* filedata = NULL; | ||||
|     uint16_t bytes_written = 0; | ||||
| 
 | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         file->internal_error_id = | ||||
|             f_write(&filedata->data.file, buff, bytes_to_write, &bytes_written); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return bytes_written; | ||||
| } | ||||
| 
 | ||||
| // Move read/write pointer, expand size
 | ||||
| bool fs_file_seek(File* file, const uint32_t offset, const bool from_start) { | ||||
|     FileData* filedata = NULL; | ||||
| 
 | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         if(from_start) { | ||||
|             file->internal_error_id = f_lseek(&filedata->data.file, offset); | ||||
|         } else { | ||||
|             uint64_t position = f_tell(&filedata->data.file); | ||||
|             position += offset; | ||||
|             file->internal_error_id = f_lseek(&filedata->data.file, position); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| // Tell pointer position
 | ||||
| uint64_t fs_file_tell(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
|     uint64_t position = 0; | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         position = f_tell(&filedata->data.file); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return position; | ||||
| } | ||||
| 
 | ||||
| // Truncate file size to current pointer value
 | ||||
| bool fs_file_truncate(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
| 
 | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         file->internal_error_id = f_truncate(&filedata->data.file); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| // Flush cached data
 | ||||
| bool fs_file_sync(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
| 
 | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         file->internal_error_id = f_sync(&filedata->data.file); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| // Get size
 | ||||
| uint64_t fs_file_size(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
|     uint64_t size = 0; | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         size = f_size(&filedata->data.file); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return size; | ||||
| } | ||||
| 
 | ||||
| // Test EOF
 | ||||
| bool fs_file_eof(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
|     bool eof = true; | ||||
|     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         eof = f_eof(&filedata->data.file); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return eof; | ||||
| } | ||||
| 
 | ||||
| /******************* Dir Functions *******************/ | ||||
| 
 | ||||
| // Open directory
 | ||||
| bool fs_dir_open(File* file, const char* path) { | ||||
|     SDDir* sd_dir = NULL; | ||||
| 
 | ||||
|     _fs_lock(fs_info); | ||||
|     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { | ||||
|         FileData* filedata = &fs_info->files[index]; | ||||
| 
 | ||||
|         if(filedata->thread_id == NULL) { | ||||
|             file->file_id = index; | ||||
| 
 | ||||
|             memset(&(filedata->data), 0, sizeof(SDFileDirStorage)); | ||||
|             filedata->thread_id = osThreadGetId(); | ||||
|             filedata->is_dir = true; | ||||
|             sd_dir = &(filedata->data.dir); | ||||
| 
 | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     _fs_unlock(fs_info); | ||||
| 
 | ||||
|     if(sd_dir == NULL) { | ||||
|         file->internal_error_id = SD_TOO_MANY_OPEN_FILES; | ||||
|     } else { | ||||
|         file->internal_error_id = f_opendir(sd_dir, path); | ||||
|     } | ||||
| 
 | ||||
|     // TODO on exit
 | ||||
|     //furiac_onexit(_fs_on_client_app_exit, fs_info);
 | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| // Close directory
 | ||||
| bool fs_dir_close(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
|     file->internal_error_id = _get_dir(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         file->internal_error_id = f_closedir(&filedata->data.dir); | ||||
| 
 | ||||
|         _fs_lock(fs_info); | ||||
|         filedata->thread_id = NULL; | ||||
|         _fs_unlock(fs_info); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| // Read next file info and name from directory
 | ||||
| bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, const uint16_t name_length) { | ||||
|     FileData* filedata = NULL; | ||||
|     file->internal_error_id = _get_dir(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         SDFileInfo _fileinfo; | ||||
|         file->internal_error_id = f_readdir(&filedata->data.dir, &_fileinfo); | ||||
| 
 | ||||
|         if(fileinfo != NULL) { | ||||
|             fileinfo->date.value = _fileinfo.fdate; | ||||
|             fileinfo->time.value = _fileinfo.ftime; | ||||
|             fileinfo->size = _fileinfo.fsize; | ||||
|             fileinfo->flags = 0; | ||||
| 
 | ||||
|             if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY; | ||||
|             if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN; | ||||
|             if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM; | ||||
|             if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||
|             if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE; | ||||
|         } | ||||
| 
 | ||||
|         if(name != NULL && name_length > 0) { | ||||
|             strlcpy(name, _fileinfo.fname, name_length); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| bool fs_dir_rewind(File* file) { | ||||
|     FileData* filedata = NULL; | ||||
|     file->internal_error_id = _get_dir(fs_info, file, &filedata); | ||||
| 
 | ||||
|     if(file->internal_error_id == SD_OK) { | ||||
|         file->internal_error_id = f_readdir(&filedata->data.dir, NULL); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = _fs_parse_error(file->internal_error_id); | ||||
|     return (file->internal_error_id == SD_OK); | ||||
| } | ||||
| 
 | ||||
| /******************* Common FS Functions *******************/ | ||||
| 
 | ||||
| // Get info about file/dir
 | ||||
| FS_Error | ||||
|     fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length) { | ||||
|     SDFileInfo _fileinfo; | ||||
|     SDError fresult = _fs_status(fs_info); | ||||
| 
 | ||||
|     if(fresult == SD_OK) { | ||||
|         fresult = f_stat(path, &_fileinfo); | ||||
|         if((FRESULT)fresult == FR_OK) { | ||||
|             if(fileinfo != NULL) { | ||||
|                 fileinfo->date.value = _fileinfo.fdate; | ||||
|                 fileinfo->time.value = _fileinfo.ftime; | ||||
|                 fileinfo->size = _fileinfo.fsize; | ||||
|                 fileinfo->flags = 0; | ||||
| 
 | ||||
|                 if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY; | ||||
|                 if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN; | ||||
|                 if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM; | ||||
|                 if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||
|                 if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE; | ||||
|             } | ||||
| 
 | ||||
|             if(name != NULL && name_length > 0) { | ||||
|                 strlcpy(name, _fileinfo.fname, name_length); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return _fs_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| // Delete file/dir
 | ||||
| // File/dir must not have read-only attribute.
 | ||||
| // File/dir must be empty.
 | ||||
| // File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that.
 | ||||
| FS_Error fs_common_remove(const char* path) { | ||||
|     SDError fresult = _fs_status(fs_info); | ||||
| 
 | ||||
|     if(fresult == SD_OK) { | ||||
|         fresult = f_unlink(path); | ||||
|     } | ||||
| 
 | ||||
|     return _fs_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| // Rename file/dir
 | ||||
| // File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that.
 | ||||
| FS_Error fs_common_rename(const char* old_path, const char* new_path) { | ||||
|     SDError fresult = _fs_status(fs_info); | ||||
| 
 | ||||
|     if(fresult == SD_OK) { | ||||
|         fresult = f_rename(old_path, new_path); | ||||
|     } | ||||
| 
 | ||||
|     return _fs_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| // Set attributes of file/dir
 | ||||
| // For example:
 | ||||
| // set "read only" flag and remove "hidden" flag
 | ||||
| // fs_common_set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN);
 | ||||
| FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask) { | ||||
|     SDError fresult = _fs_status(fs_info); | ||||
| 
 | ||||
|     if(fresult == SD_OK) { | ||||
|         uint8_t _mask = 0; | ||||
|         uint8_t _attr = 0; | ||||
| 
 | ||||
|         if(mask & FSF_READ_ONLY) _mask |= AM_RDO; | ||||
|         if(mask & FSF_HIDDEN) _mask |= AM_HID; | ||||
|         if(mask & FSF_SYSTEM) _mask |= AM_SYS; | ||||
|         if(mask & FSF_DIRECTORY) _mask |= AM_DIR; | ||||
|         if(mask & FSF_ARCHIVE) _mask |= AM_ARC; | ||||
| 
 | ||||
|         if(attr & FSF_READ_ONLY) _attr |= AM_RDO; | ||||
|         if(attr & FSF_HIDDEN) _attr |= AM_HID; | ||||
|         if(attr & FSF_SYSTEM) _attr |= AM_SYS; | ||||
|         if(attr & FSF_DIRECTORY) _attr |= AM_DIR; | ||||
|         if(attr & FSF_ARCHIVE) _attr |= AM_ARC; | ||||
| 
 | ||||
|         fresult = f_chmod(path, attr, mask); | ||||
|     } | ||||
| 
 | ||||
|     return _fs_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| // Set time of file/dir
 | ||||
| FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time) { | ||||
|     SDError fresult = _fs_status(fs_info); | ||||
| 
 | ||||
|     if(fresult == SD_OK) { | ||||
|         SDFileInfo _fileinfo; | ||||
| 
 | ||||
|         _fileinfo.fdate = date.value; | ||||
|         _fileinfo.ftime = time.value; | ||||
| 
 | ||||
|         fresult = f_utime(path, &_fileinfo); | ||||
|     } | ||||
| 
 | ||||
|     return _fs_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| // Create new directory
 | ||||
| FS_Error fs_common_mkdir(const char* path) { | ||||
|     SDError fresult = _fs_status(fs_info); | ||||
| 
 | ||||
|     if(fresult == SD_OK) { | ||||
|         fresult = f_mkdir(path); | ||||
|     } | ||||
| 
 | ||||
|     return _fs_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| // Get common info about FS
 | ||||
| FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space) { | ||||
|     SDError fresult = _fs_status(fs_info); | ||||
| 
 | ||||
|     if(fresult == SD_OK) { | ||||
|         DWORD free_clusters; | ||||
|         FATFS* fs; | ||||
| 
 | ||||
|         fresult = f_getfree("0:/", &free_clusters, &fs); | ||||
|         if((FRESULT)fresult == FR_OK) { | ||||
|             uint32_t total_sectors = (fs->n_fatent - 2) * fs->csize; | ||||
|             uint32_t free_sectors = free_clusters * fs->csize; | ||||
| 
 | ||||
|             uint16_t sector_size = _MAX_SS; | ||||
| #if _MAX_SS != _MIN_SS | ||||
|             sector_size = fs->ssize; | ||||
| #endif | ||||
| 
 | ||||
|             if(total_space != NULL) { | ||||
|                 *total_space = (uint64_t)total_sectors * (uint64_t)sector_size; | ||||
|             } | ||||
| 
 | ||||
|             if(free_space != NULL) { | ||||
|                 *free_space = (uint64_t)free_sectors * (uint64_t)sector_size; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return _fs_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| /******************* Error Reporting Functions *******************/ | ||||
| 
 | ||||
| // Get common error description
 | ||||
| const char* fs_error_get_desc(FS_Error error_id) { | ||||
|     const char* result; | ||||
|     switch(error_id) { | ||||
|     case(FSE_OK): | ||||
|         result = "OK"; | ||||
|         break; | ||||
|     case(FSE_NOT_READY): | ||||
|         result = "filesystem not ready"; | ||||
|         break; | ||||
|     case(FSE_EXIST): | ||||
|         result = "file/dir already exist"; | ||||
|         break; | ||||
|     case(FSE_NOT_EXIST): | ||||
|         result = "file/dir not exist"; | ||||
|         break; | ||||
|     case(FSE_INVALID_PARAMETER): | ||||
|         result = "invalid parameter"; | ||||
|         break; | ||||
|     case(FSE_DENIED): | ||||
|         result = "access denied"; | ||||
|         break; | ||||
|     case(FSE_INVALID_NAME): | ||||
|         result = "invalid name/path"; | ||||
|         break; | ||||
|     case(FSE_INTERNAL): | ||||
|         result = "internal error"; | ||||
|         break; | ||||
|     case(FSE_NOT_IMPLEMENTED): | ||||
|         result = "function not implemented"; | ||||
|         break; | ||||
|     default: | ||||
|         result = "unknown error"; | ||||
|         break; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| // Get internal error description
 | ||||
| const char* fs_error_get_internal_desc(uint32_t internal_error_id) { | ||||
|     const char* result; | ||||
|     switch(internal_error_id) { | ||||
|     case(SD_OK): | ||||
|         result = "OK"; | ||||
|         break; | ||||
|     case(SD_DISK_ERR): | ||||
|         result = "disk error"; | ||||
|         break; | ||||
|     case(SD_INT_ERR): | ||||
|         result = "internal error"; | ||||
|         break; | ||||
|     case(SD_NO_FILE): | ||||
|         result = "no file"; | ||||
|         break; | ||||
|     case(SD_NO_PATH): | ||||
|         result = "no path"; | ||||
|         break; | ||||
|     case(SD_INVALID_NAME): | ||||
|         result = "invalid name"; | ||||
|         break; | ||||
|     case(SD_DENIED): | ||||
|         result = "access denied"; | ||||
|         break; | ||||
|     case(SD_EXIST): | ||||
|         result = "file/dir exist"; | ||||
|         break; | ||||
|     case(SD_INVALID_OBJECT): | ||||
|         result = "invalid object"; | ||||
|         break; | ||||
|     case(SD_WRITE_PROTECTED): | ||||
|         result = "write protected"; | ||||
|         break; | ||||
|     case(SD_INVALID_DRIVE): | ||||
|         result = "invalid drive"; | ||||
|         break; | ||||
|     case(SD_NOT_ENABLED): | ||||
|         result = "not enabled"; | ||||
|         break; | ||||
|     case(SD_NO_FILESYSTEM): | ||||
|         result = "no filesystem"; | ||||
|         break; | ||||
|     case(SD_MKFS_ABORTED): | ||||
|         result = "aborted"; | ||||
|         break; | ||||
|     case(SD_TIMEOUT): | ||||
|         result = "timeout"; | ||||
|         break; | ||||
|     case(SD_LOCKED): | ||||
|         result = "file locked"; | ||||
|         break; | ||||
|     case(SD_NOT_ENOUGH_CORE): | ||||
|         result = "not enough memory"; | ||||
|         break; | ||||
|     case(SD_TOO_MANY_OPEN_FILES): | ||||
|         result = "too many open files"; | ||||
|         break; | ||||
|     case(SD_INVALID_PARAMETER): | ||||
|         result = "invalid parameter"; | ||||
|         break; | ||||
|     case(SD_NO_CARD): | ||||
|         result = "no SD Card"; | ||||
|         break; | ||||
|     case(SD_NOT_A_FILE): | ||||
|         result = "not a file"; | ||||
|         break; | ||||
|     case(SD_NOT_A_DIR): | ||||
|         result = "not a directory"; | ||||
|         break; | ||||
|     case(SD_OTHER_APP): | ||||
|         result = "opened by other app"; | ||||
|         break; | ||||
|     case(SD_LOW_LEVEL_ERR): | ||||
|         result = "low level error"; | ||||
|         break; | ||||
|     default: | ||||
|         result = "unknown error"; | ||||
|         break; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| @ -1,956 +0,0 @@ | ||||
| #include "fatfs.h" | ||||
| #include "filesystem-api.h" | ||||
| #include "sd-filesystem.h" | ||||
| #include "menu/menu.h" | ||||
| #include "menu/menu_item.h" | ||||
| #include "cli/cli.h" | ||||
| #include "api-hal-sd.h" | ||||
| 
 | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <gui/modules/file_select.h> | ||||
| 
 | ||||
| typedef enum { | ||||
|     FST_FAT12 = FS_FAT12, | ||||
|     FST_FAT16 = FS_FAT16, | ||||
|     FST_FAT32 = FS_FAT32, | ||||
|     FST_EXFAT = FS_EXFAT, | ||||
| } SDFsType; | ||||
| 
 | ||||
| typedef struct { | ||||
|     SDFsType fs_type; | ||||
|     uint32_t kb_total; | ||||
|     uint32_t kb_free; | ||||
|     uint16_t cluster_size; | ||||
|     uint16_t sector_size; | ||||
|     char label[34]; | ||||
|     SDError error; | ||||
| } SDInfo; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SdAppEventTypeBack, | ||||
|     SdAppEventTypeOK, | ||||
|     SdAppEventTypeFormat, | ||||
|     SdAppEventTypeInfo, | ||||
|     SdAppEventTypeEject, | ||||
|     SdAppEventTypeFileSelect, | ||||
|     SdAppEventTypeCheckError, | ||||
|     SdAppEventTypeShowError, | ||||
| } SdAppEventType; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* path; | ||||
|     const char* extension; | ||||
|     char* result; | ||||
|     uint8_t result_size; | ||||
|     const char* selected_filename; | ||||
| } SdAppFileSelectData; | ||||
| 
 | ||||
| typedef struct { | ||||
|     bool result; | ||||
| } SdAppFileSelectResultEvent; | ||||
| 
 | ||||
| typedef struct { | ||||
|     SdAppEventType type; | ||||
|     union { | ||||
|         SdAppFileSelectData file_select_data; | ||||
|         const char* error_text; | ||||
|     } payload; | ||||
| } SdAppEvent; | ||||
| 
 | ||||
| static void sd_icon_draw_callback(Canvas* canvas, void* context); | ||||
| bool sd_api_file_select( | ||||
|     SdApp* sd_app, | ||||
|     const char* path, | ||||
|     const char* extension, | ||||
|     char* result, | ||||
|     uint8_t result_size, | ||||
|     const char* selected_filename); | ||||
| void sd_api_check_error(SdApp* sd_app); | ||||
| void sd_api_show_error(SdApp* sd_app, const char* error_text); | ||||
| 
 | ||||
| /******************* Allocators *******************/ | ||||
| 
 | ||||
| FS_Api* fs_api_alloc() { | ||||
|     FS_Api* fs_api = furi_alloc(sizeof(FS_Api)); | ||||
| 
 | ||||
|     // fill file api
 | ||||
|     fs_api->file.open = fs_file_open; | ||||
|     fs_api->file.close = fs_file_close; | ||||
|     fs_api->file.read = fs_file_read; | ||||
|     fs_api->file.write = fs_file_write; | ||||
|     fs_api->file.seek = fs_file_seek; | ||||
|     fs_api->file.tell = fs_file_tell; | ||||
|     fs_api->file.truncate = fs_file_truncate; | ||||
|     fs_api->file.size = fs_file_size; | ||||
|     fs_api->file.sync = fs_file_sync; | ||||
|     fs_api->file.eof = fs_file_eof; | ||||
| 
 | ||||
|     // fill dir api
 | ||||
|     fs_api->dir.open = fs_dir_open; | ||||
|     fs_api->dir.close = fs_dir_close; | ||||
|     fs_api->dir.read = fs_dir_read; | ||||
|     fs_api->dir.rewind = fs_dir_rewind; | ||||
| 
 | ||||
|     // fill common api
 | ||||
|     fs_api->common.info = fs_common_info; | ||||
|     fs_api->common.remove = fs_common_remove; | ||||
|     fs_api->common.rename = fs_common_rename; | ||||
|     fs_api->common.set_attr = fs_common_set_attr; | ||||
|     fs_api->common.mkdir = fs_common_mkdir; | ||||
|     fs_api->common.set_time = fs_common_set_time; | ||||
|     fs_api->common.get_fs_info = fs_get_fs_info; | ||||
| 
 | ||||
|     // fill errors api
 | ||||
|     fs_api->error.get_desc = fs_error_get_desc; | ||||
|     fs_api->error.get_internal_desc = fs_error_get_internal_desc; | ||||
| 
 | ||||
|     return fs_api; | ||||
| } | ||||
| 
 | ||||
| SdApp* sd_app_alloc() { | ||||
|     SdApp* sd_app = furi_alloc(sizeof(SdApp)); | ||||
| 
 | ||||
|     // init inner fs data
 | ||||
|     furi_check(_fs_init(&sd_app->info)); | ||||
| 
 | ||||
|     sd_app->event_queue = osMessageQueueNew(8, sizeof(SdAppEvent), NULL); | ||||
|     sd_app->result_receiver = osMessageQueueNew(1, sizeof(SdAppFileSelectResultEvent), NULL); | ||||
| 
 | ||||
|     // init icon view_port
 | ||||
|     sd_app->icon.view_port = view_port_alloc(); | ||||
|     sd_app->icon.mounted = &I_SDcardMounted_11x8; | ||||
|     sd_app->icon.fail = &I_SDcardFail_11x8; | ||||
|     view_port_set_width(sd_app->icon.view_port, icon_get_width(sd_app->icon.mounted)); | ||||
|     view_port_draw_callback_set(sd_app->icon.view_port, sd_icon_draw_callback, sd_app); | ||||
|     view_port_enabled_set(sd_app->icon.view_port, false); | ||||
| 
 | ||||
|     // init sd card api
 | ||||
|     sd_app->sd_card_api.context = sd_app; | ||||
|     sd_app->sd_card_api.file_select = sd_api_file_select; | ||||
|     sd_app->sd_card_api.check_error = sd_api_check_error; | ||||
|     sd_app->sd_card_api.show_error = sd_api_show_error; | ||||
| 
 | ||||
|     sd_app->sd_app_state = SdAppStateBackground; | ||||
|     string_init(sd_app->text_holder); | ||||
| 
 | ||||
|     return sd_app; | ||||
| } | ||||
| 
 | ||||
| /******************* Internal sd card related fns *******************/ | ||||
| 
 | ||||
| void get_sd_info(SdApp* sd_app, SDInfo* sd_info) { | ||||
|     uint32_t free_clusters, free_sectors, total_sectors; | ||||
|     FATFS* fs; | ||||
| 
 | ||||
|     // clean data
 | ||||
|     memset(sd_info, 0, sizeof(SDInfo)); | ||||
| 
 | ||||
|     // get fs info
 | ||||
|     _fs_lock(&sd_app->info); | ||||
|     sd_info->error = f_getlabel(sd_app->info.path, sd_info->label, NULL); | ||||
|     if(sd_info->error == SD_OK) { | ||||
|         sd_info->error = f_getfree(sd_app->info.path, &free_clusters, &fs); | ||||
|     } | ||||
|     _fs_unlock(&sd_app->info); | ||||
| 
 | ||||
|     if(sd_info->error == SD_OK) { | ||||
|         // calculate size
 | ||||
|         total_sectors = (fs->n_fatent - 2) * fs->csize; | ||||
|         free_sectors = free_clusters * fs->csize; | ||||
| 
 | ||||
|         uint16_t sector_size = _MAX_SS; | ||||
| #if _MAX_SS != _MIN_SS | ||||
|         sector_size = fs->ssize; | ||||
| #endif | ||||
| 
 | ||||
|         sd_info->fs_type = fs->fs_type; | ||||
| 
 | ||||
|         sd_info->kb_total = total_sectors / 1024 * sector_size; | ||||
|         sd_info->kb_free = free_sectors / 1024 * sector_size; | ||||
|         sd_info->cluster_size = fs->csize; | ||||
|         sd_info->sector_size = sector_size; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const char* get_fs_type_text(SDFsType fs_type) { | ||||
|     switch(fs_type) { | ||||
|     case(FST_FAT12): | ||||
|         return "FAT12"; | ||||
|         break; | ||||
|     case(FST_FAT16): | ||||
|         return "FAT16"; | ||||
|         break; | ||||
|     case(FST_FAT32): | ||||
|         return "FAT32"; | ||||
|         break; | ||||
|     case(FST_EXFAT): | ||||
|         return "EXFAT"; | ||||
|         break; | ||||
|     default: | ||||
|         return "UNKNOWN"; | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void app_sd_format_internal(SdApp* sd_app) { | ||||
|     uint8_t* work_area; | ||||
| 
 | ||||
|     _fs_lock(&sd_app->info); | ||||
|     work_area = malloc(_MAX_SS); | ||||
|     if(work_area == NULL) { | ||||
|         sd_app->info.status = SD_NOT_ENOUGH_CORE; | ||||
|     } else { | ||||
|         sd_app->info.status = f_mkfs(sd_app->info.path, FM_ANY, 0, work_area, _MAX_SS); | ||||
|         free(work_area); | ||||
| 
 | ||||
|         if(sd_app->info.status == SD_OK) { | ||||
|             // set label and mount card
 | ||||
|             f_setlabel("Flipper SD"); | ||||
|             sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _fs_unlock(&sd_app->info); | ||||
| } | ||||
| 
 | ||||
| const NotificationSequence sd_sequence_success = { | ||||
|     &message_green_255, | ||||
|     &message_delay_50, | ||||
|     &message_green_0, | ||||
|     &message_delay_50, | ||||
|     &message_green_255, | ||||
|     &message_delay_50, | ||||
|     &message_green_0, | ||||
|     &message_delay_50, | ||||
|     &message_green_255, | ||||
|     &message_delay_50, | ||||
|     &message_green_0, | ||||
|     &message_delay_50, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sd_sequence_error = { | ||||
|     &message_red_255, | ||||
|     &message_delay_50, | ||||
|     &message_red_0, | ||||
|     &message_delay_50, | ||||
|     &message_red_255, | ||||
|     &message_delay_50, | ||||
|     &message_red_0, | ||||
|     &message_delay_50, | ||||
|     &message_red_255, | ||||
|     &message_delay_50, | ||||
|     &message_red_0, | ||||
|     &message_delay_50, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sd_sequence_eject = { | ||||
|     &message_blue_255, | ||||
|     &message_delay_50, | ||||
|     &message_blue_0, | ||||
|     &message_delay_50, | ||||
|     &message_blue_255, | ||||
|     &message_delay_50, | ||||
|     &message_blue_0, | ||||
|     &message_delay_50, | ||||
|     &message_blue_255, | ||||
|     &message_delay_50, | ||||
|     &message_blue_0, | ||||
|     &message_delay_50, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sd_sequence_wait = { | ||||
|     &message_red_255, | ||||
|     &message_blue_255, | ||||
|     &message_do_not_reset, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| const NotificationSequence sd_sequence_wait_off = { | ||||
|     &message_red_0, | ||||
|     &message_blue_0, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| void app_sd_notify_wait(SdApp* sd_app) { | ||||
|     notification_message(sd_app->notifications, &sd_sequence_wait); | ||||
| } | ||||
| 
 | ||||
| void app_sd_notify_wait_off(SdApp* sd_app) { | ||||
|     notification_message(sd_app->notifications, &sd_sequence_wait_off); | ||||
| } | ||||
| 
 | ||||
| void app_sd_notify_success(SdApp* sd_app) { | ||||
|     notification_message(sd_app->notifications, &sd_sequence_success); | ||||
| } | ||||
| 
 | ||||
| void app_sd_notify_eject(SdApp* sd_app) { | ||||
|     notification_message(sd_app->notifications, &sd_sequence_eject); | ||||
| } | ||||
| 
 | ||||
| void app_sd_notify_error(SdApp* sd_app) { | ||||
|     notification_message(sd_app->notifications, &sd_sequence_error); | ||||
| } | ||||
| 
 | ||||
| bool app_sd_mount_card(SdApp* sd_app) { | ||||
|     bool result = false; | ||||
|     const uint8_t max_init_counts = 10; | ||||
|     uint8_t counter = max_init_counts; | ||||
|     uint8_t bsp_result; | ||||
| 
 | ||||
|     _fs_lock(&sd_app->info); | ||||
| 
 | ||||
|     while(result == false && counter > 0 && hal_sd_detect()) { | ||||
|         app_sd_notify_wait(sd_app); | ||||
| 
 | ||||
|         if((counter % 10) == 0) { | ||||
|             // power reset sd card
 | ||||
|             bsp_result = BSP_SD_Init(true); | ||||
|         } else { | ||||
|             bsp_result = BSP_SD_Init(false); | ||||
|         } | ||||
| 
 | ||||
|         if(bsp_result) { | ||||
|             // bsp error
 | ||||
|             sd_app->info.status = SD_LOW_LEVEL_ERR; | ||||
|         } else { | ||||
|             sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1); | ||||
| 
 | ||||
|             if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) { | ||||
|                 FATFS* fs; | ||||
|                 uint32_t free_clusters; | ||||
| 
 | ||||
|                 sd_app->info.status = f_getfree(sd_app->info.path, &free_clusters, &fs); | ||||
| 
 | ||||
|                 if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) { | ||||
|                     result = true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         app_sd_notify_wait_off(sd_app); | ||||
| 
 | ||||
|         if(!result) { | ||||
|             delay(1000); | ||||
|             FURI_LOG_E( | ||||
|                 "SD FILESYSTEM", | ||||
|                 "init(%d), error: %s\r\n", | ||||
|                 counter, | ||||
|                 fs_error_get_internal_desc(sd_app->info.status)); | ||||
| 
 | ||||
|             counter--; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _fs_unlock(&sd_app->info); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void app_sd_unmount_card(SdApp* sd_app) { | ||||
|     _fs_lock(&sd_app->info); | ||||
| 
 | ||||
|     // set status
 | ||||
|     sd_app->info.status = SD_NO_CARD; | ||||
|     view_port_enabled_set(sd_app->icon.view_port, false); | ||||
| 
 | ||||
|     // close files
 | ||||
|     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { | ||||
|         FileData* filedata = &sd_app->info.files[index]; | ||||
| 
 | ||||
|         if(filedata->thread_id != NULL) { | ||||
|             if(filedata->is_dir) { | ||||
|                 f_closedir(&filedata->data.dir); | ||||
|             } else { | ||||
|                 f_close(&filedata->data.file); | ||||
|             } | ||||
|             filedata->thread_id = NULL; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // unmount volume
 | ||||
|     f_mount(0, sd_app->info.path, 0); | ||||
| 
 | ||||
|     _fs_unlock(&sd_app->info); | ||||
| } | ||||
| 
 | ||||
| bool app_sd_make_path(const char* path) { | ||||
|     furi_assert(path); | ||||
| 
 | ||||
|     if(*path) { | ||||
|         char* file_path = strdup(path); | ||||
|         // Make parent directories
 | ||||
|         for(char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { | ||||
|             *p = '\0'; | ||||
|             SDError result = f_mkdir(file_path); | ||||
| 
 | ||||
|             if(result != SD_OK) { | ||||
|                 if(result != SD_EXIST) { | ||||
|                     *p = '/'; | ||||
|                     free(file_path); | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             *p = '/'; | ||||
|         } | ||||
|         // Make origin directory
 | ||||
|         SDError result = f_mkdir(file_path); | ||||
|         if(result != SD_OK) { | ||||
|             if(result != SD_EXIST) { | ||||
|                 free(file_path); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         free(file_path); | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| /******************* Draw callbacks *******************/ | ||||
| 
 | ||||
| static void sd_icon_draw_callback(Canvas* canvas, void* context) { | ||||
|     furi_assert(canvas); | ||||
|     furi_assert(context); | ||||
|     SdApp* sd_app = context; | ||||
| 
 | ||||
|     switch(sd_app->info.status) { | ||||
|     case SD_NO_CARD: | ||||
|         break; | ||||
|     case SD_OK: | ||||
|         canvas_draw_icon(canvas, 0, 0, sd_app->icon.mounted); | ||||
|         break; | ||||
|     default: | ||||
|         canvas_draw_icon(canvas, 0, 0, sd_app->icon.fail); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /******************* SD-api callbacks *******************/ | ||||
| 
 | ||||
| bool sd_api_file_select( | ||||
|     SdApp* sd_app, | ||||
|     const char* path, | ||||
|     const char* extension, | ||||
|     char* result, | ||||
|     uint8_t result_size, | ||||
|     const char* selected_filename) { | ||||
|     bool retval = false; | ||||
| 
 | ||||
|     SdAppEvent message = { | ||||
|         .type = SdAppEventTypeFileSelect, | ||||
|         .payload = { | ||||
|             .file_select_data = { | ||||
|                 .path = path, | ||||
|                 .extension = extension, | ||||
|                 .result = result, | ||||
|                 .result_size = result_size, | ||||
|                 .selected_filename = selected_filename, | ||||
|             }}}; | ||||
| 
 | ||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
| 
 | ||||
|     SdAppFileSelectResultEvent event; | ||||
|     while(1) { | ||||
|         osStatus_t event_status = | ||||
|             osMessageQueueGet(sd_app->result_receiver, &event, NULL, osWaitForever); | ||||
|         if(event_status == osOK) { | ||||
|             retval = event.result; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!retval) { | ||||
|         sd_api_check_error(sd_app); | ||||
|     } | ||||
| 
 | ||||
|     return retval; | ||||
| } | ||||
| 
 | ||||
| void sd_api_check_error(SdApp* sd_app) { | ||||
|     SdAppEvent message = {.type = SdAppEventTypeCheckError}; | ||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| void sd_api_show_error(SdApp* sd_app, const char* error_text) { | ||||
|     SdAppEvent message = {.type = SdAppEventTypeShowError, .payload.error_text = error_text}; | ||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| /******************* View callbacks *******************/ | ||||
| 
 | ||||
| void app_view_back_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     SdApp* sd_app = context; | ||||
|     SdAppEvent message = {.type = SdAppEventTypeBack}; | ||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| void app_view_dialog_callback(DialogExResult result, void* context) { | ||||
|     furi_assert(context); | ||||
|     SdApp* sd_app = context; | ||||
| 
 | ||||
|     if(result == DialogExResultLeft) { | ||||
|         SdAppEvent message = {.type = SdAppEventTypeBack}; | ||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } else if(result == DialogExResultRight) { | ||||
|         SdAppEvent message = {.type = SdAppEventTypeOK}; | ||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void app_view_file_select_callback(bool result, void* context) { | ||||
|     furi_assert(context); | ||||
|     SdApp* sd_app = context; | ||||
| 
 | ||||
|     if(result) { | ||||
|         SdAppEvent message = {.type = SdAppEventTypeOK}; | ||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } else { | ||||
|         SdAppEvent message = {.type = SdAppEventTypeBack}; | ||||
|         furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /******************* Menu callbacks *******************/ | ||||
| 
 | ||||
| void app_sd_info_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     SdApp* sd_app = context; | ||||
|     SdAppEvent message = {.type = SdAppEventTypeInfo}; | ||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| void app_sd_format_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     SdApp* sd_app = context; | ||||
|     SdAppEvent message = {.type = SdAppEventTypeFormat}; | ||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| void app_sd_eject_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     SdApp* sd_app = context; | ||||
|     SdAppEvent message = {.type = SdAppEventTypeEject}; | ||||
|     furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| /******************* Cli callbacks *******************/ | ||||
| 
 | ||||
| static void cli_sd_format(Cli* cli, string_t args, void* _ctx) { | ||||
|     SdApp* sd_app = (SdApp*)_ctx; | ||||
| 
 | ||||
|     printf("Formatting SD card, all data will be lost. Are you sure (y/n)?\r\n"); | ||||
|     char c = cli_getc(cli); | ||||
|     if(c == 'y' || c == 'Y') { | ||||
|         printf("Formatting, please wait...\r\n"); | ||||
|         // format card
 | ||||
|         app_sd_format_internal(sd_app); | ||||
| 
 | ||||
|         if(sd_app->info.status != SD_OK) { | ||||
|             printf("SD card format error: "); | ||||
|             printf(fs_error_get_internal_desc(sd_app->info.status)); | ||||
|             printf("\r\n"); | ||||
|         } else { | ||||
|             printf("SD card was successfully formatted.\r\n"); | ||||
|         } | ||||
|     } else { | ||||
|         printf("Cancelled.\r\n"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void cli_sd_info(Cli* cli, string_t args, void* _ctx) { | ||||
|     SdApp* sd_app = (SdApp*)_ctx; | ||||
|     SDInfo sd_info; | ||||
| 
 | ||||
|     get_sd_info(sd_app, &sd_info); | ||||
|     printf("SD Status: %s\r\n", fs_error_get_internal_desc(sd_app->info.status)); | ||||
| 
 | ||||
|     if(sd_info.error == SD_OK) { | ||||
|         const char* fs_type = get_fs_type_text(sd_info.fs_type); | ||||
|         printf("Label: %s\r\n", sd_info.label); | ||||
|         printf("Filesystem: %s\r\n", fs_type); | ||||
|         printf("Cluster: %d sectors\r\n", sd_info.cluster_size); | ||||
|         printf("Sector: %d bytes\r\n", sd_info.sector_size); | ||||
|         printf("%lu KB total\r\n", sd_info.kb_total); | ||||
|         printf("%lu KB free\r\n", sd_info.kb_free); | ||||
|     } else { | ||||
|         printf("SD Info error: %s\r\n", fs_error_get_internal_desc(sd_info.error)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /******************* Test *******************/ | ||||
| 
 | ||||
| bool try_to_alloc_view_holder(SdApp* sd_app, Gui* gui) { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     _fs_lock(&sd_app->info); | ||||
| 
 | ||||
|     if(sd_app->view_holder == NULL) { | ||||
|         sd_app->view_holder = view_holder_alloc(); | ||||
|         view_holder_attach_to_gui(sd_app->view_holder, gui); | ||||
|         view_holder_set_back_callback(sd_app->view_holder, app_view_back_callback, sd_app); | ||||
|         result = true; | ||||
|     } | ||||
| 
 | ||||
|     _fs_unlock(&sd_app->info); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DialogEx* alloc_and_attach_dialog(SdApp* sd_app) { | ||||
|     DialogEx* dialog = dialog_ex_alloc(); | ||||
|     dialog_ex_set_context(dialog, sd_app); | ||||
|     dialog_ex_set_result_callback(dialog, app_view_dialog_callback); | ||||
|     view_holder_set_view(sd_app->view_holder, dialog_ex_get_view(dialog)); | ||||
|     view_holder_set_free_callback(sd_app->view_holder, (FreeCallback)dialog_ex_free, dialog); | ||||
|     return dialog; | ||||
| } | ||||
| 
 | ||||
| FileSelect* alloc_and_attach_file_select(SdApp* sd_app) { | ||||
|     FileSelect* file_select = file_select_alloc(); | ||||
|     file_select_set_callback(file_select, app_view_file_select_callback, sd_app); | ||||
|     view_holder_set_view(sd_app->view_holder, file_select_get_view(file_select)); | ||||
|     view_holder_set_free_callback( | ||||
|         sd_app->view_holder, (FreeCallback)file_select_free, file_select); | ||||
|     return file_select; | ||||
| } | ||||
| 
 | ||||
| void free_view_holder(SdApp* sd_app) { | ||||
|     _fs_lock(&sd_app->info); | ||||
| 
 | ||||
|     if(sd_app->view_holder) { | ||||
|         view_holder_free(sd_app->view_holder); | ||||
|         sd_app->view_holder = NULL; | ||||
|     } | ||||
| 
 | ||||
|     _fs_unlock(&sd_app->info); | ||||
| } | ||||
| 
 | ||||
| void app_reset_state(SdApp* sd_app) { | ||||
|     _fs_lock(&sd_app->info); | ||||
|     if(sd_app->view_holder) { | ||||
|         view_holder_stop(sd_app->view_holder); | ||||
|     } | ||||
|     _fs_unlock(&sd_app->info); | ||||
| 
 | ||||
|     free_view_holder(sd_app); | ||||
|     string_set_str(sd_app->text_holder, ""); | ||||
|     sd_app->sd_app_state = SdAppStateBackground; | ||||
| } | ||||
| 
 | ||||
| /******************* Main app *******************/ | ||||
| 
 | ||||
| int32_t sd_filesystem(void* p) { | ||||
|     SdApp* sd_app = sd_app_alloc(); | ||||
|     FS_Api* fs_api = fs_api_alloc(); | ||||
| 
 | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     Cli* cli = furi_record_open("cli"); | ||||
|     sd_app->notifications = furi_record_open("notification"); | ||||
|     ValueMutex* menu_vm = furi_record_open("menu"); | ||||
| 
 | ||||
|     gui_add_view_port(gui, sd_app->icon.view_port, GuiLayerStatusBarLeft); | ||||
| 
 | ||||
|     cli_add_command(cli, "sd_format", CliCommandFlagDefault, cli_sd_format, sd_app); | ||||
|     cli_add_command(cli, "sd_info", CliCommandFlagDefault, cli_sd_info, sd_app); | ||||
| 
 | ||||
|     // add api record
 | ||||
|     furi_record_create("sdcard", fs_api); | ||||
| 
 | ||||
|     // init menu
 | ||||
|     // TODO menu icon
 | ||||
|     MenuItem* menu_item; | ||||
|     menu_item = menu_item_alloc_menu("SD Card", icon_animation_alloc(&I_SDcardMounted_11x8)); | ||||
| 
 | ||||
|     menu_item_subitem_add( | ||||
|         menu_item, menu_item_alloc_function("Info", NULL, app_sd_info_callback, sd_app)); | ||||
|     menu_item_subitem_add( | ||||
|         menu_item, menu_item_alloc_function("Format", NULL, app_sd_format_callback, sd_app)); | ||||
|     menu_item_subitem_add( | ||||
|         menu_item, menu_item_alloc_function("Eject", NULL, app_sd_eject_callback, sd_app)); | ||||
| 
 | ||||
|     // add item to menu
 | ||||
|     furi_check(menu_vm); | ||||
|     with_value_mutex( | ||||
|         menu_vm, (Menu * menu) { menu_item_add(menu, menu_item); }); | ||||
| 
 | ||||
|     FURI_LOG_I("SD FILESYSTEM", "start"); | ||||
| 
 | ||||
|     // add api record
 | ||||
|     furi_record_create("sdcard", fs_api); | ||||
|     furi_record_create("sdcard-ex", &sd_app->sd_card_api); | ||||
| 
 | ||||
|     // sd card cycle
 | ||||
|     bool sd_was_present = true; | ||||
| 
 | ||||
|     // init detect pins
 | ||||
|     hal_sd_detect_init(); | ||||
| 
 | ||||
|     while(true) { | ||||
|         if(sd_was_present) { | ||||
|             if(hal_sd_detect()) { | ||||
|                 FURI_LOG_I("SD FILESYSTEM", "Card detected"); | ||||
|                 app_sd_mount_card(sd_app); | ||||
| 
 | ||||
|                 if(sd_app->info.status != SD_OK) { | ||||
|                     FURI_LOG_E( | ||||
|                         "SD FILESYSTEM", | ||||
|                         "sd init error: %s", | ||||
|                         fs_error_get_internal_desc(sd_app->info.status)); | ||||
|                     app_sd_notify_error(sd_app); | ||||
|                 } else { | ||||
|                     FURI_LOG_I("SD FILESYSTEM", "sd init ok"); | ||||
|                     app_sd_notify_success(sd_app); | ||||
|                 } | ||||
| 
 | ||||
|                 view_port_enabled_set(sd_app->icon.view_port, true); | ||||
|                 sd_was_present = false; | ||||
| 
 | ||||
|                 if(!hal_sd_detect()) { | ||||
|                     FURI_LOG_I("SD FILESYSTEM", "card removed"); | ||||
| 
 | ||||
|                     view_port_enabled_set(sd_app->icon.view_port, false); | ||||
|                     app_sd_unmount_card(sd_app); | ||||
|                     sd_was_present = true; | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             if(!hal_sd_detect()) { | ||||
|                 FURI_LOG_I("SD FILESYSTEM", "card removed"); | ||||
| 
 | ||||
|                 view_port_enabled_set(sd_app->icon.view_port, false); | ||||
|                 app_sd_unmount_card(sd_app); | ||||
|                 sd_was_present = true; | ||||
|                 app_sd_notify_eject(sd_app); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         SdAppEvent event; | ||||
|         osStatus_t event_status = osMessageQueueGet(sd_app->event_queue, &event, NULL, 1000); | ||||
| 
 | ||||
|         const uint8_t y_1_line = 32; | ||||
|         const uint8_t y_2_line = 32; | ||||
|         const uint8_t y_4_line = 26; | ||||
| 
 | ||||
|         if(event_status == osOK) { | ||||
|             switch(event.type) { | ||||
|             case SdAppEventTypeOK: | ||||
|                 switch(sd_app->sd_app_state) { | ||||
|                 case SdAppStateFormat: { | ||||
|                     DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder); | ||||
|                     dialog_ex_set_left_button_text(dialog, NULL); | ||||
|                     dialog_ex_set_right_button_text(dialog, NULL); | ||||
|                     dialog_ex_set_header( | ||||
|                         dialog, "Formatting...", 64, y_1_line, AlignCenter, AlignCenter); | ||||
|                     dialog_ex_set_text(dialog, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|                     sd_app->sd_app_state = SdAppStateFormatInProgress; | ||||
|                     delay(100); | ||||
|                     app_sd_format_internal(sd_app); | ||||
|                     app_sd_notify_success(sd_app); | ||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); | ||||
|                     dialog_ex_set_header( | ||||
|                         dialog, "SD card formatted", 64, 10, AlignCenter, AlignCenter); | ||||
|                     dialog_ex_set_text( | ||||
|                         dialog, "Press back to return", 64, y_1_line, AlignCenter, AlignCenter); | ||||
|                     sd_app->sd_app_state = SdAppStateFormatCompleted; | ||||
|                 }; break; | ||||
|                 case SdAppStateEject: { | ||||
|                     DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder); | ||||
|                     dialog_ex_set_right_button_text(dialog, NULL); | ||||
|                     dialog_ex_set_header( | ||||
|                         dialog, "SD card ejected", 64, 10, AlignCenter, AlignCenter); | ||||
|                     dialog_ex_set_text( | ||||
|                         dialog, | ||||
|                         "Now the SD card\ncan be removed.", | ||||
|                         64, | ||||
|                         y_2_line, | ||||
|                         AlignCenter, | ||||
|                         AlignCenter); | ||||
|                     sd_app->sd_app_state = SdAppStateEjected; | ||||
|                     app_sd_unmount_card(sd_app); | ||||
|                     app_sd_notify_eject(sd_app); | ||||
|                 }; break; | ||||
|                 case SdAppStateFileSelect: { | ||||
|                     SdAppFileSelectResultEvent retval = {.result = true}; | ||||
|                     furi_check( | ||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == | ||||
|                         osOK); | ||||
|                     app_reset_state(sd_app); | ||||
|                 }; break; | ||||
|                 default: | ||||
|                     break; | ||||
|                 } | ||||
|                 break; | ||||
|             case SdAppEventTypeBack: | ||||
|                 switch(sd_app->sd_app_state) { | ||||
|                 case SdAppStateFormatInProgress: | ||||
|                     break; | ||||
|                 case SdAppStateFileSelect: { | ||||
|                     SdAppFileSelectResultEvent retval = {.result = false}; | ||||
|                     furi_check( | ||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == | ||||
|                         osOK); | ||||
|                     app_reset_state(sd_app); | ||||
|                 }; break; | ||||
| 
 | ||||
|                 default: | ||||
|                     app_reset_state(sd_app); | ||||
|                     break; | ||||
|                 } | ||||
|                 break; | ||||
|             case SdAppEventTypeFormat: | ||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { | ||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); | ||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); | ||||
|                     dialog_ex_set_right_button_text(dialog, "Format"); | ||||
|                     dialog_ex_set_header( | ||||
|                         dialog, "Format SD card?", 64, 10, AlignCenter, AlignCenter); | ||||
|                     dialog_ex_set_text( | ||||
|                         dialog, "All data will be lost.", 64, y_1_line, AlignCenter, AlignCenter); | ||||
|                     view_holder_start(sd_app->view_holder); | ||||
|                     sd_app->sd_app_state = SdAppStateFormat; | ||||
|                 } | ||||
|                 break; | ||||
|             case SdAppEventTypeInfo: | ||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { | ||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); | ||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); | ||||
| 
 | ||||
|                     SDInfo sd_info; | ||||
|                     get_sd_info(sd_app, &sd_info); | ||||
| 
 | ||||
|                     if(sd_info.error == SD_OK) { | ||||
|                         string_printf( | ||||
|                             sd_app->text_holder, | ||||
|                             "Label: %s\nType: %s\n%lu KB total\n%lu KB free", | ||||
|                             sd_info.label, | ||||
|                             get_fs_type_text(sd_info.fs_type), | ||||
|                             sd_info.kb_total, | ||||
|                             sd_info.kb_free); | ||||
|                         dialog_ex_set_text( | ||||
|                             dialog, | ||||
|                             string_get_cstr(sd_app->text_holder), | ||||
|                             4, | ||||
|                             y_4_line, | ||||
|                             AlignLeft, | ||||
|                             AlignCenter); | ||||
|                         view_holder_start(sd_app->view_holder); | ||||
|                     } else { | ||||
|                         string_printf( | ||||
|                             sd_app->text_holder, | ||||
|                             "SD status: %s\n SD info: %s", | ||||
|                             fs_error_get_internal_desc(_fs_status(&sd_app->info)), | ||||
|                             fs_error_get_internal_desc(sd_info.error)); | ||||
|                         dialog_ex_set_header(dialog, "Error", 64, 10, AlignCenter, AlignCenter); | ||||
|                         dialog_ex_set_text( | ||||
|                             dialog, | ||||
|                             string_get_cstr(sd_app->text_holder), | ||||
|                             64, | ||||
|                             y_2_line, | ||||
|                             AlignCenter, | ||||
|                             AlignCenter); | ||||
|                         view_holder_start(sd_app->view_holder); | ||||
|                     } | ||||
| 
 | ||||
|                     sd_app->sd_app_state = SdAppStateInfo; | ||||
|                 } | ||||
|                 break; | ||||
|             case SdAppEventTypeEject: | ||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { | ||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); | ||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); | ||||
|                     dialog_ex_set_right_button_text(dialog, "Eject"); | ||||
|                     dialog_ex_set_header( | ||||
|                         dialog, "Eject SD card?", 64, 10, AlignCenter, AlignCenter); | ||||
|                     dialog_ex_set_text( | ||||
|                         dialog, | ||||
|                         "SD card will be\nunavailable", | ||||
|                         64, | ||||
|                         y_2_line, | ||||
|                         AlignCenter, | ||||
|                         AlignCenter); | ||||
|                     view_holder_start(sd_app->view_holder); | ||||
|                     sd_app->sd_app_state = SdAppStateEject; | ||||
|                 } | ||||
|                 break; | ||||
|             case SdAppEventTypeFileSelect: | ||||
|                 if(!app_sd_make_path(event.payload.file_select_data.path)) { | ||||
|                     SdAppFileSelectResultEvent retval = {.result = false}; | ||||
|                     furi_check( | ||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == | ||||
|                         osOK); | ||||
|                     break; | ||||
|                 } | ||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { | ||||
|                     FileSelect* file_select = alloc_and_attach_file_select(sd_app); | ||||
|                     SdAppFileSelectData* file_select_data = &event.payload.file_select_data; | ||||
| 
 | ||||
|                     file_select_set_api(file_select, fs_api); | ||||
|                     file_select_set_filter( | ||||
|                         file_select, file_select_data->path, file_select_data->extension); | ||||
|                     file_select_set_result_buffer( | ||||
|                         file_select, file_select_data->result, file_select_data->result_size); | ||||
|                     if(!file_select_init(file_select)) { | ||||
|                         SdAppFileSelectResultEvent retval = {.result = false}; | ||||
|                         furi_check( | ||||
|                             osMessageQueuePut( | ||||
|                                 sd_app->result_receiver, &retval, 0, osWaitForever) == osOK); | ||||
|                         app_reset_state(sd_app); | ||||
|                     } else { | ||||
|                         sd_app->sd_app_state = SdAppStateFileSelect; | ||||
|                         if(file_select_data->selected_filename != NULL) { | ||||
|                             file_select_set_selected_file( | ||||
|                                 file_select, file_select_data->selected_filename); | ||||
|                         } | ||||
|                         view_holder_start(sd_app->view_holder); | ||||
|                     } | ||||
|                 } else { | ||||
|                     SdAppFileSelectResultEvent retval = {.result = false}; | ||||
|                     furi_check( | ||||
|                         osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) == | ||||
|                         osOK); | ||||
|                 } | ||||
|                 break; | ||||
|             case SdAppEventTypeCheckError: | ||||
|                 if(sd_app->info.status != SD_OK) { | ||||
|                     if(try_to_alloc_view_holder(sd_app, gui)) { | ||||
|                         DialogEx* dialog = alloc_and_attach_dialog(sd_app); | ||||
|                         dialog_ex_set_left_button_text(dialog, "Back"); | ||||
|                         if(sd_app->info.status == SD_NO_CARD) { | ||||
|                             dialog_ex_set_text( | ||||
|                                 dialog, | ||||
|                                 "SD card\nnot found", | ||||
|                                 88, | ||||
|                                 y_1_line, | ||||
|                                 AlignCenter, | ||||
|                                 AlignCenter); | ||||
|                             dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43); | ||||
|                         } else { | ||||
|                             dialog_ex_set_text( | ||||
|                                 dialog, "SD card\nerror", 88, y_1_line, AlignCenter, AlignCenter); | ||||
|                             dialog_ex_set_icon(dialog, 5, 10, &I_SDError_43x35); | ||||
|                         } | ||||
|                         sd_app->sd_app_state = SdAppStateCheckError; | ||||
|                         view_holder_start(sd_app->view_holder); | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|             case SdAppEventTypeShowError: | ||||
|                 if(try_to_alloc_view_holder(sd_app, gui)) { | ||||
|                     DialogEx* dialog = alloc_and_attach_dialog(sd_app); | ||||
|                     dialog_ex_set_left_button_text(dialog, "Back"); | ||||
|                     dialog_ex_set_text( | ||||
|                         dialog, event.payload.error_text, 88, y_1_line, AlignCenter, AlignCenter); | ||||
|                     dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43); | ||||
|                     sd_app->sd_app_state = SdAppStateShowError; | ||||
|                     view_holder_start(sd_app->view_holder); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| @ -1,145 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <api-hal.h> | ||||
| #include <gui/gui.h> | ||||
| #include <input/input.h> | ||||
| #include <m-string.h> | ||||
| #include "sd-card-api.h" | ||||
| #include "view_holder.h" | ||||
| #include <notification/notification-messages.h> | ||||
| 
 | ||||
| #define SD_FS_MAX_FILES _FS_LOCK | ||||
| #define SD_STATE_LINES_COUNT 6 | ||||
| 
 | ||||
| /* api data */ | ||||
| typedef FIL SDFile; | ||||
| typedef DIR SDDir; | ||||
| typedef FILINFO SDFileInfo; | ||||
| 
 | ||||
| /* storage for file/directory objects*/ | ||||
| typedef union { | ||||
|     SDFile file; | ||||
|     SDDir dir; | ||||
| } SDFileDirStorage; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SD_OK = FR_OK, | ||||
|     SD_DISK_ERR = FR_DISK_ERR, | ||||
|     SD_INT_ERR = FR_INT_ERR, | ||||
|     SD_NO_FILE = FR_NO_FILE, | ||||
|     SD_NO_PATH = FR_NO_PATH, | ||||
|     SD_INVALID_NAME = FR_INVALID_NAME, | ||||
|     SD_DENIED = FR_DENIED, | ||||
|     SD_EXIST = FR_EXIST, | ||||
|     SD_INVALID_OBJECT = FR_INVALID_OBJECT, | ||||
|     SD_WRITE_PROTECTED = FR_WRITE_PROTECTED, | ||||
|     SD_INVALID_DRIVE = FR_INVALID_DRIVE, | ||||
|     SD_NOT_ENABLED = FR_NOT_ENABLED, | ||||
|     SD_NO_FILESYSTEM = FR_NO_FILESYSTEM, | ||||
|     SD_MKFS_ABORTED = FR_MKFS_ABORTED, | ||||
|     SD_TIMEOUT = FR_TIMEOUT, | ||||
|     SD_LOCKED = FR_LOCKED, | ||||
|     SD_NOT_ENOUGH_CORE = FR_NOT_ENOUGH_CORE, | ||||
|     SD_TOO_MANY_OPEN_FILES = FR_TOO_MANY_OPEN_FILES, | ||||
|     SD_INVALID_PARAMETER = FR_INVALID_PARAMETER, | ||||
|     SD_NO_CARD, | ||||
|     SD_NOT_A_FILE, | ||||
|     SD_NOT_A_DIR, | ||||
|     SD_OTHER_APP, | ||||
|     SD_LOW_LEVEL_ERR, | ||||
| } SDError; | ||||
| 
 | ||||
| typedef enum { | ||||
|     FDF_DIR, | ||||
|     FDF_FILE, | ||||
|     FDF_ANY, | ||||
| } FiledataFilter; | ||||
| 
 | ||||
| typedef struct { | ||||
|     osThreadId_t thread_id; | ||||
|     bool is_dir; | ||||
|     SDFileDirStorage data; | ||||
| } FileData; | ||||
| 
 | ||||
| /* application data */ | ||||
| typedef struct { | ||||
|     ViewPort* view_port; | ||||
|     const Icon* mounted; | ||||
|     const Icon* fail; | ||||
| } SdFsIcon; | ||||
| 
 | ||||
| typedef struct { | ||||
|     osMutexId_t mutex; | ||||
|     FileData files[SD_FS_MAX_FILES]; | ||||
|     SDError status; | ||||
|     char* path; | ||||
|     FATFS fat_fs; | ||||
| } SdFsInfo; | ||||
| 
 | ||||
| typedef enum { | ||||
|     SdAppStateBackground, | ||||
|     SdAppStateFormat, | ||||
|     SdAppStateFormatInProgress, | ||||
|     SdAppStateFormatCompleted, | ||||
|     SdAppStateInfo, | ||||
|     SdAppStateEject, | ||||
|     SdAppStateEjected, | ||||
|     SdAppStateFileSelect, | ||||
|     SdAppStateCheckError, | ||||
|     SdAppStateShowError, | ||||
| } SdAppState; | ||||
| 
 | ||||
| struct SdApp { | ||||
|     SdFsInfo info; | ||||
|     SdFsIcon icon; | ||||
| 
 | ||||
|     SdCard_Api sd_card_api; | ||||
|     SdAppState sd_app_state; | ||||
| 
 | ||||
|     ViewHolder* view_holder; | ||||
|     osMessageQueueId_t result_receiver; | ||||
| 
 | ||||
|     osMessageQueueId_t event_queue; | ||||
|     string_t text_holder; | ||||
| 
 | ||||
|     NotificationApp* notifications; | ||||
| }; | ||||
| 
 | ||||
| /* core api fns */ | ||||
| bool _fs_init(SdFsInfo* _fs_info); | ||||
| bool _fs_lock(SdFsInfo* fs_info); | ||||
| bool _fs_unlock(SdFsInfo* fs_info); | ||||
| SDError _fs_status(SdFsInfo* fs_info); | ||||
| 
 | ||||
| /* sd api fns */ | ||||
| bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode); | ||||
| bool fs_file_close(File* file); | ||||
| uint16_t fs_file_read(File* file, void* buff, uint16_t bytes_to_read); | ||||
| uint16_t fs_file_write(File* file, const void* buff, uint16_t bytes_to_write); | ||||
| bool fs_file_seek(File* file, uint32_t offset, bool from_start); | ||||
| uint64_t fs_file_tell(File* file); | ||||
| bool fs_file_truncate(File* file); | ||||
| uint64_t fs_file_size(File* file); | ||||
| bool fs_file_sync(File* file); | ||||
| bool fs_file_eof(File* file); | ||||
| 
 | ||||
| /* dir api */ | ||||
| bool fs_dir_open(File* file, const char* path); | ||||
| bool fs_dir_close(File* file); | ||||
| bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||
| bool fs_dir_rewind(File* file); | ||||
| 
 | ||||
| /* common api */ | ||||
| FS_Error | ||||
|     fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length); | ||||
| FS_Error fs_common_remove(const char* path); | ||||
| FS_Error fs_common_rename(const char* old_path, const char* new_path); | ||||
| FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask); | ||||
| FS_Error fs_common_mkdir(const char* path); | ||||
| FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time); | ||||
| FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space); | ||||
| 
 | ||||
| /* errors api */ | ||||
| const char* fs_error_get_desc(FS_Error error_id); | ||||
| const char* fs_error_get_internal_desc(uint32_t internal_error_id); | ||||
| @ -0,0 +1,161 @@ | ||||
| #include "../storage-settings.h" | ||||
| 
 | ||||
| #define BENCH_DATA_SIZE 4096 | ||||
| #define BENCH_COUNT 6 | ||||
| #define BENCH_REPEATS 4 | ||||
| #define BENCH_FILE "/ext/rwfiletest.bin" | ||||
| 
 | ||||
| static void | ||||
|     storage_settings_scene_benchmark_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| static bool storage_settings_bench_write( | ||||
|     Storage* api, | ||||
|     uint16_t size, | ||||
|     const uint8_t* data, | ||||
|     uint32_t* speed) { | ||||
|     File* file = storage_file_alloc(api); | ||||
|     bool result = true; | ||||
|     if(storage_file_open(file, BENCH_FILE, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { | ||||
|         uint32_t ticks; | ||||
|         ticks = osKernelGetTickCount(); | ||||
| 
 | ||||
|         for(size_t repeat = 0; repeat < BENCH_REPEATS; repeat++) { | ||||
|             for(size_t i = 0; i < BENCH_DATA_SIZE / size; i++) { | ||||
|                 if(storage_file_write(file, (data + i * size), size) != size) { | ||||
|                     result = false; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         ticks = osKernelGetTickCount() - ticks; | ||||
|         *speed = BENCH_DATA_SIZE * osKernelGetTickFreq() * BENCH_REPEATS; | ||||
|         *speed /= ticks; | ||||
|         *speed /= 1024; | ||||
|     } | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
|     storage_settings_bench_read(Storage* api, uint16_t size, uint8_t* data, uint32_t* speed) { | ||||
|     File* file = storage_file_alloc(api); | ||||
|     bool result = true; | ||||
|     *speed = -1; | ||||
| 
 | ||||
|     if(storage_file_open(file, BENCH_FILE, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         uint32_t ticks; | ||||
|         ticks = osKernelGetTickCount(); | ||||
| 
 | ||||
|         for(size_t repeat = 0; repeat < BENCH_REPEATS; repeat++) { | ||||
|             for(size_t i = 0; i < BENCH_DATA_SIZE / size; i++) { | ||||
|                 if(storage_file_read(file, (data + i * size), size) != size) { | ||||
|                     result = false; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         ticks = osKernelGetTickCount() - ticks; | ||||
|         *speed = BENCH_DATA_SIZE * osKernelGetTickFreq() * BENCH_REPEATS; | ||||
|         *speed /= ticks; | ||||
|         *speed /= 1024; | ||||
|     } | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| static void storage_settings_benchmark(StorageSettings* app) { | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
|     uint8_t* bench_data; | ||||
|     dialog_ex_set_header(dialog_ex, "Preparing data...", 64, 32, AlignCenter, AlignCenter); | ||||
| 
 | ||||
|     bench_data = malloc(BENCH_DATA_SIZE); | ||||
|     for(size_t i = 0; i < BENCH_DATA_SIZE; i++) { | ||||
|         bench_data[i] = (uint8_t)i; | ||||
|     } | ||||
| 
 | ||||
|     uint16_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 1024, 4096}; | ||||
|     uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; | ||||
|     uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0}; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, "Benchmarking...", 64, 32, AlignCenter, AlignCenter); | ||||
|     for(size_t i = 0; i < BENCH_COUNT; i++) { | ||||
|         if(!storage_settings_bench_write(app->fs_api, bench_size[i], bench_data, &bench_w_speed[i])) | ||||
|             break; | ||||
| 
 | ||||
|         if(i > 0) string_cat_printf(app->text_string, "\n"); | ||||
|         string_cat_printf(app->text_string, "%ub : W %luK ", bench_size[i], bench_w_speed[i]); | ||||
|         dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); | ||||
| 
 | ||||
|         if(!storage_settings_bench_read(app->fs_api, bench_size[i], bench_data, &bench_r_speed[i])) | ||||
|             break; | ||||
| 
 | ||||
|         string_cat_printf(app->text_string, "R %luK", bench_r_speed[i]); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, string_get_cstr(app->text_string), 0, 32, AlignLeft, AlignCenter); | ||||
|     } | ||||
| 
 | ||||
|     free(bench_data); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_benchmark_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_benchmark_dialog_callback); | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
| 
 | ||||
|     if(storage_sd_status(app->fs_api) != FSE_OK) { | ||||
|         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, | ||||
|             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||
|             64, | ||||
|             32, | ||||
|             AlignCenter, | ||||
|             AlignCenter); | ||||
|         dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||
|     } else { | ||||
|         storage_settings_benchmark(app); | ||||
|         notification_message(app->notification, &sequence_blink_green_100); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = scene_manager_previous_scene(app->scene_manager); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_benchmark_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||
|     dialog_ex_set_context(dialog_ex, NULL); | ||||
| 
 | ||||
|     string_clean(app->text_string); | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| ADD_SCENE(storage_settings, start, Start) | ||||
| ADD_SCENE(storage_settings, unmount_confirm, UnmountConfirm) | ||||
| ADD_SCENE(storage_settings, unmounted, Unmounted) | ||||
| ADD_SCENE(storage_settings, format_confirm, FormatConfirm) | ||||
| ADD_SCENE(storage_settings, formatting, Formatting) | ||||
| ADD_SCENE(storage_settings, sd_info, SDInfo) | ||||
| ADD_SCENE(storage_settings, internal_info, InternalInfo) | ||||
| ADD_SCENE(storage_settings, benchmark, Benchmark) | ||||
| @ -0,0 +1,68 @@ | ||||
| #include "../storage-settings.h" | ||||
| 
 | ||||
| static void | ||||
|     storage_settings_scene_unmount_confirm_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_unmount_confirm_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     FS_Error sd_status = storage_sd_status(app->fs_api); | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||
| 
 | ||||
|     if(sd_status == FSE_NOT_READY) { | ||||
|         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, | ||||
|             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||
|             64, | ||||
|             32, | ||||
|             AlignCenter, | ||||
|             AlignCenter); | ||||
|     } else { | ||||
|         dialog_ex_set_right_button_text(dialog_ex, "Unmount"); | ||||
|         dialog_ex_set_header(dialog_ex, "Unmount SD card?", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, "SD card will be\nunavailable", 64, 32, AlignCenter, AlignCenter); | ||||
|     } | ||||
| 
 | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_set_result_callback( | ||||
|         dialog_ex, storage_settings_scene_unmount_confirm_dialog_callback); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = scene_manager_previous_scene(app->scene_manager); | ||||
|             break; | ||||
|         case DialogExResultRight: | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_unmount_confirm_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||
|     dialog_ex_set_context(dialog_ex, NULL); | ||||
| } | ||||
| @ -0,0 +1,65 @@ | ||||
| #include "../storage-settings.h" | ||||
| 
 | ||||
| static void | ||||
|     storage_settings_scene_unmounted_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_unmounted_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     FS_Error error = storage_sd_unmount(app->fs_api); | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||
| 
 | ||||
|     if(error == FSE_OK) { | ||||
|         dialog_ex_set_header(dialog_ex, "SD card unmounted", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, "Now the SD card\ncan be removed.", 64, 32, AlignCenter, AlignCenter); | ||||
|         notification_message(app->notification, &sequence_blink_green_100); | ||||
|     } else { | ||||
|         dialog_ex_set_header( | ||||
|             dialog_ex, "Cannot unmount SD Card", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); | ||||
|         notification_message(app->notification, &sequence_blink_red_100); | ||||
|     } | ||||
| 
 | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_unmounted_dialog_callback); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_unmounted_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = | ||||
|                 scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||
|             break; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeNavigation) { | ||||
|         consumed = scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_unmounted_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||
|     dialog_ex_set_context(dialog_ex, NULL); | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| #include "../storage-settings.h" | ||||
| 
 | ||||
| static void | ||||
|     storage_settings_scene_format_confirm_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_format_confirm_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     FS_Error sd_status = storage_sd_status(app->fs_api); | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||
| 
 | ||||
|     if(sd_status == FSE_NOT_READY) { | ||||
|         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, | ||||
|             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||
|             64, | ||||
|             32, | ||||
|             AlignCenter, | ||||
|             AlignCenter); | ||||
|     } else { | ||||
|         dialog_ex_set_right_button_text(dialog_ex, "Format"); | ||||
|         dialog_ex_set_header(dialog_ex, "Format SD card?", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text(dialog_ex, "All data will be lost", 64, 32, AlignCenter, AlignCenter); | ||||
|     } | ||||
| 
 | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_set_result_callback( | ||||
|         dialog_ex, storage_settings_scene_format_confirm_dialog_callback); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_format_confirm_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = scene_manager_previous_scene(app->scene_manager); | ||||
|             break; | ||||
|         case DialogExResultRight: | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsFormatting); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_format_confirm_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||
|     dialog_ex_set_context(dialog_ex, NULL); | ||||
| } | ||||
| @ -0,0 +1,85 @@ | ||||
| #include "../storage-settings.h" | ||||
| 
 | ||||
| static const NotificationMessage message_green_165 = { | ||||
|     .type = NotificationMessageTypeLedGreen, | ||||
|     .data.led.value = 165, | ||||
| }; | ||||
| 
 | ||||
| static const NotificationSequence sequence_set_formatting_leds = { | ||||
|     &message_red_255, | ||||
|     &message_green_165, | ||||
|     &message_blue_0, | ||||
|     &message_do_not_reset, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static const NotificationSequence sequence_reset_formatting_leds = { | ||||
|     &message_red_0, | ||||
|     &message_green_0, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static void | ||||
|     storage_settings_scene_formatting_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_formatting_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     FS_Error error; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, "Formatting...", 64, 32, AlignCenter, AlignCenter); | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
| 
 | ||||
|     notification_message_block(app->notification, &sequence_set_formatting_leds); | ||||
|     error = storage_sd_format(app->fs_api); | ||||
|     notification_message(app->notification, &sequence_reset_formatting_leds); | ||||
|     notification_message(app->notification, &sequence_blink_green_100); | ||||
| 
 | ||||
|     if(error != FSE_OK) { | ||||
|         dialog_ex_set_header(dialog_ex, "Cannot format SD Card", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); | ||||
|     } else { | ||||
|         dialog_ex_set_header(dialog_ex, "SD card formatted", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text(dialog_ex, "Press back to return", 64, 32, AlignCenter, AlignCenter); | ||||
|     } | ||||
| 
 | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_formatting_dialog_callback); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = | ||||
|                 scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||
|             break; | ||||
|         } | ||||
|     } else if(event.type == SceneManagerEventTypeNavigation) { | ||||
|         consumed = scene_manager_search_previous_scene(app->scene_manager, StorageSettingsStart); | ||||
|     } | ||||
| 
 | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_formatting_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||
|     dialog_ex_set_context(dialog_ex, NULL); | ||||
| } | ||||
| @ -0,0 +1,68 @@ | ||||
| #include "../storage-settings.h" | ||||
| #include <api-hal-version.h> | ||||
| 
 | ||||
| static void | ||||
|     storage_settings_scene_internal_info_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_internal_info_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     uint64_t total_space; | ||||
|     uint64_t free_space; | ||||
|     FS_Error error = storage_common_fs_info(app->fs_api, "/int", &total_space, &free_space); | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_internal_info_dialog_callback); | ||||
| 
 | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||
|     if(error != FSE_OK) { | ||||
|         dialog_ex_set_header( | ||||
|             dialog_ex, "Internal storage error", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, storage_error_get_desc(error), 64, 32, AlignCenter, AlignCenter); | ||||
|     } else { | ||||
|         string_printf( | ||||
|             app->text_string, | ||||
|             "Label: %s\nType: LittleFS\n%lu KB total\n%lu KB free", | ||||
|             api_hal_version_get_name_ptr(), | ||||
|             (uint32_t)(total_space / 1024), | ||||
|             (uint32_t)(free_space / 1024)); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_internal_info_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = scene_manager_previous_scene(app->scene_manager); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_internal_info_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||
|     dialog_ex_set_context(dialog_ex, NULL); | ||||
| 
 | ||||
|     string_clean(app->text_string); | ||||
| } | ||||
| @ -0,0 +1,74 @@ | ||||
| #include "../storage-settings.h" | ||||
| 
 | ||||
| static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result, void* context) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, result); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_sd_info_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     SDInfo sd_info; | ||||
|     FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info); | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_context(dialog_ex, app); | ||||
|     dialog_ex_set_result_callback(dialog_ex, storage_settings_scene_sd_info_dialog_callback); | ||||
| 
 | ||||
|     dialog_ex_set_left_button_text(dialog_ex, "Back"); | ||||
|     if(sd_status != FSE_OK) { | ||||
|         dialog_ex_set_header(dialog_ex, "SD card not mounted", 64, 10, AlignCenter, AlignCenter); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, | ||||
|             "If an SD card is inserted,\r\npull it out and reinsert it", | ||||
|             64, | ||||
|             32, | ||||
|             AlignCenter, | ||||
|             AlignCenter); | ||||
|     } else { | ||||
|         string_printf( | ||||
|             app->text_string, | ||||
|             "Label: %s\nType: %s\n%lu KB total\n%lu KB free", | ||||
|             sd_info.label, | ||||
|             sd_api_get_fs_type_text(sd_info.fs_type), | ||||
|             sd_info.kb_total, | ||||
|             sd_info.kb_free); | ||||
|         dialog_ex_set_text( | ||||
|             dialog_ex, string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop); | ||||
|     } | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_sd_info_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case DialogExResultLeft: | ||||
|             consumed = scene_manager_previous_scene(app->scene_manager); | ||||
|             break; | ||||
|         case DialogExResultRight: | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_sd_info_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     DialogEx* dialog_ex = app->dialog_ex; | ||||
| 
 | ||||
|     dialog_ex_set_header(dialog_ex, NULL, 0, 0, AlignCenter, AlignCenter); | ||||
|     dialog_ex_set_text(dialog_ex, NULL, 0, 0, AlignCenter, AlignTop); | ||||
|     dialog_ex_set_icon(dialog_ex, 0, 0, NULL); | ||||
|     dialog_ex_set_left_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_right_button_text(dialog_ex, NULL); | ||||
|     dialog_ex_set_result_callback(dialog_ex, NULL); | ||||
|     dialog_ex_set_context(dialog_ex, NULL); | ||||
| 
 | ||||
|     string_clean(app->text_string); | ||||
| } | ||||
| @ -0,0 +1,104 @@ | ||||
| #include "../storage-settings.h" | ||||
| 
 | ||||
| enum StorageSettingsStartSubmenuIndex { | ||||
|     StorageSettingsStartSubmenuIndexInternalInfo, | ||||
|     StorageSettingsStartSubmenuIndexSDInfo, | ||||
|     StorageSettingsStartSubmenuIndexUnmount, | ||||
|     StorageSettingsStartSubmenuIndexFormat, | ||||
|     StorageSettingsStartSubmenuIndexBenchy, | ||||
| }; | ||||
| 
 | ||||
| static void storage_settings_scene_start_submenu_callback(void* context, uint32_t index) { | ||||
|     StorageSettings* app = context; | ||||
| 
 | ||||
|     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_start_on_enter(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     Submenu* submenu = app->submenu; | ||||
| 
 | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "About internal storage", | ||||
|         StorageSettingsStartSubmenuIndexInternalInfo, | ||||
|         storage_settings_scene_start_submenu_callback, | ||||
|         app); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "About SD Card", | ||||
|         StorageSettingsStartSubmenuIndexSDInfo, | ||||
|         storage_settings_scene_start_submenu_callback, | ||||
|         app); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Unmount SD Card", | ||||
|         StorageSettingsStartSubmenuIndexUnmount, | ||||
|         storage_settings_scene_start_submenu_callback, | ||||
|         app); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Format SD Card", | ||||
|         StorageSettingsStartSubmenuIndexFormat, | ||||
|         storage_settings_scene_start_submenu_callback, | ||||
|         app); | ||||
|     submenu_add_item( | ||||
|         submenu, | ||||
|         "Benchmark SD Card", | ||||
|         StorageSettingsStartSubmenuIndexBenchy, | ||||
|         storage_settings_scene_start_submenu_callback, | ||||
|         app); | ||||
| 
 | ||||
|     submenu_set_selected_item( | ||||
|         submenu, scene_manager_get_scene_state(app->scene_manager, StorageSettingsStart)); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewSubmenu); | ||||
| } | ||||
| 
 | ||||
| bool storage_settings_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||
|     StorageSettings* app = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         switch(event.event) { | ||||
|         case StorageSettingsStartSubmenuIndexSDInfo: | ||||
|             scene_manager_set_scene_state( | ||||
|                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexSDInfo); | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsSDInfo); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case StorageSettingsStartSubmenuIndexInternalInfo: | ||||
|             scene_manager_set_scene_state( | ||||
|                 app->scene_manager, | ||||
|                 StorageSettingsStart, | ||||
|                 StorageSettingsStartSubmenuIndexInternalInfo); | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsInternalInfo); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case StorageSettingsStartSubmenuIndexUnmount: | ||||
|             scene_manager_set_scene_state( | ||||
|                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexUnmount); | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsUnmountConfirm); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case StorageSettingsStartSubmenuIndexFormat: | ||||
|             scene_manager_set_scene_state( | ||||
|                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexFormat); | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsFormatConfirm); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         case StorageSettingsStartSubmenuIndexBenchy: | ||||
|             scene_manager_set_scene_state( | ||||
|                 app->scene_manager, StorageSettingsStart, StorageSettingsStartSubmenuIndexBenchy); | ||||
|             scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark); | ||||
|             consumed = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void storage_settings_scene_start_on_exit(void* context) { | ||||
|     StorageSettings* app = context; | ||||
|     submenu_clean(app->submenu); | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| #include "storage-settings-scene.h" | ||||
| 
 | ||||
| // Generate scene on_enter handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||
| void (*const storage_settings_on_enter_handlers[])(void*) = { | ||||
| #include "storage-settings-scene-config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_event handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, | ||||
| bool (*const storage_settings_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||
| #include "storage-settings-scene-config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_exit handlers array
 | ||||
| #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, | ||||
| void (*const storage_settings_on_exit_handlers[])(void* context) = { | ||||
| #include "storage-settings-scene-config.h" | ||||
| }; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Initialize scene handlers configuration structure
 | ||||
| const SceneManagerHandlers storage_settings_scene_handlers = { | ||||
|     .on_enter_handlers = storage_settings_on_enter_handlers, | ||||
|     .on_event_handlers = storage_settings_on_event_handlers, | ||||
|     .on_exit_handlers = storage_settings_on_exit_handlers, | ||||
|     .scene_num = StorageSettingsSceneNum, | ||||
| }; | ||||
| @ -0,0 +1,29 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <gui/scene_manager.h> | ||||
| 
 | ||||
| // Generate scene id and total number
 | ||||
| #define ADD_SCENE(prefix, name, id) StorageSettings##id, | ||||
| typedef enum { | ||||
| #include "storage-settings-scene-config.h" | ||||
|     StorageSettingsSceneNum, | ||||
| } StorageSettingsScene; | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| extern const SceneManagerHandlers storage_settings_scene_handlers; | ||||
| 
 | ||||
| // Generate scene on_enter handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||
| #include "storage-settings-scene-config.h" | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_event handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) \ | ||||
|     bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); | ||||
| #include "storage-settings-scene-config.h" | ||||
| #undef ADD_SCENE | ||||
| 
 | ||||
| // Generate scene on_exit handlers declaration
 | ||||
| #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); | ||||
| #include "storage-settings-scene-config.h" | ||||
| #undef ADD_SCENE | ||||
							
								
								
									
										75
									
								
								applications/storage-settings/storage-settings.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								applications/storage-settings/storage-settings.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| #include "storage-settings.h" | ||||
| 
 | ||||
| static bool storage_settings_custom_event_callback(void* context, uint32_t event) { | ||||
|     furi_assert(context); | ||||
|     StorageSettings* app = context; | ||||
|     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||
| } | ||||
| 
 | ||||
| static bool storage_settings_navigation_event_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     StorageSettings* app = context; | ||||
|     return scene_manager_handle_navigation_event(app->scene_manager); | ||||
| } | ||||
| 
 | ||||
| static StorageSettings* storage_settings_alloc() { | ||||
|     StorageSettings* app = malloc(sizeof(StorageSettings)); | ||||
| 
 | ||||
|     app->gui = furi_record_open("gui"); | ||||
|     app->fs_api = furi_record_open("storage"); | ||||
|     app->notification = furi_record_open("notification"); | ||||
| 
 | ||||
|     app->view_dispatcher = view_dispatcher_alloc(); | ||||
|     app->scene_manager = scene_manager_alloc(&storage_settings_scene_handlers, app); | ||||
|     string_init(app->text_string); | ||||
| 
 | ||||
|     view_dispatcher_enable_queue(app->view_dispatcher); | ||||
|     view_dispatcher_set_event_callback_context(app->view_dispatcher, app); | ||||
| 
 | ||||
|     view_dispatcher_set_custom_event_callback( | ||||
|         app->view_dispatcher, storage_settings_custom_event_callback); | ||||
|     view_dispatcher_set_navigation_event_callback( | ||||
|         app->view_dispatcher, storage_settings_navigation_event_callback); | ||||
| 
 | ||||
|     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||
| 
 | ||||
|     app->submenu = submenu_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, StorageSettingsViewSubmenu, submenu_get_view(app->submenu)); | ||||
| 
 | ||||
|     app->dialog_ex = dialog_ex_alloc(); | ||||
|     view_dispatcher_add_view( | ||||
|         app->view_dispatcher, StorageSettingsViewDialogEx, dialog_ex_get_view(app->dialog_ex)); | ||||
| 
 | ||||
|     scene_manager_next_scene(app->scene_manager, StorageSettingsStart); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
| 
 | ||||
| static void storage_settings_free(StorageSettings* app) { | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewSubmenu); | ||||
|     submenu_free(app->submenu); | ||||
| 
 | ||||
|     view_dispatcher_remove_view(app->view_dispatcher, StorageSettingsViewDialogEx); | ||||
|     dialog_ex_free(app->dialog_ex); | ||||
| 
 | ||||
|     view_dispatcher_free(app->view_dispatcher); | ||||
|     scene_manager_free(app->scene_manager); | ||||
| 
 | ||||
|     furi_record_close("gui"); | ||||
|     furi_record_close("storage"); | ||||
|     furi_record_close("notification"); | ||||
| 
 | ||||
|     string_clear(app->text_string); | ||||
| 
 | ||||
|     free(app); | ||||
| } | ||||
| 
 | ||||
| int32_t storage_settings(void* p) { | ||||
|     StorageSettings* app = storage_settings_alloc(); | ||||
| 
 | ||||
|     view_dispatcher_run(app->view_dispatcher); | ||||
| 
 | ||||
|     storage_settings_free(app); | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										47
									
								
								applications/storage-settings/storage-settings.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								applications/storage-settings/storage-settings.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <gui/gui.h> | ||||
| #include <gui/view.h> | ||||
| #include <gui/view_dispatcher.h> | ||||
| #include <gui/scene_manager.h> | ||||
| #include <notification/notification-messages.h> | ||||
| 
 | ||||
| #include <gui/modules/submenu.h> | ||||
| #include <gui/modules/dialog_ex.h> | ||||
| #include <gui/modules/popup.h> | ||||
| 
 | ||||
| #include <storage/storage.h> | ||||
| #include <storage/storage-sd-api.h> | ||||
| 
 | ||||
| #include "scenes/storage-settings-scene.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct { | ||||
|     // records
 | ||||
|     Gui* gui; | ||||
|     NotificationApp* notification; | ||||
|     Storage* fs_api; | ||||
| 
 | ||||
|     // view managment
 | ||||
|     SceneManager* scene_manager; | ||||
|     ViewDispatcher* view_dispatcher; | ||||
| 
 | ||||
|     // view modules
 | ||||
|     Submenu* submenu; | ||||
|     DialogEx* dialog_ex; | ||||
| 
 | ||||
|     // text
 | ||||
|     string_t text_string; | ||||
| } StorageSettings; | ||||
| 
 | ||||
| typedef enum { | ||||
|     StorageSettingsViewSubmenu, | ||||
|     StorageSettingsViewDialogEx, | ||||
| } StorageSettingsView; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										59
									
								
								applications/storage/filesystem-api-defines.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/storage/filesystem-api-defines.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /** Access mode flags */ | ||||
| typedef enum { | ||||
|     FSAM_READ = (1 << 0), /**< Read access */ | ||||
|     FSAM_WRITE = (1 << 1), /**< Write access */ | ||||
| } FS_AccessMode; | ||||
| 
 | ||||
| /** Open mode flags */ | ||||
| typedef enum { | ||||
|     FSOM_OPEN_EXISTING = 1, /**< Open file, fail if file doesn't exist */ | ||||
|     FSOM_OPEN_ALWAYS = 2, /**< Open file. Create new file if not exist */ | ||||
|     FSOM_OPEN_APPEND = 4, /**< Open file. Create new file if not exist. Set R/W pointer to EOF */ | ||||
|     FSOM_CREATE_NEW = 8, /**< Creates a new file. Fails if the file is exist */ | ||||
|     FSOM_CREATE_ALWAYS = 16, /**< Creates a new file. If file exist, truncate to zero size */ | ||||
| } FS_OpenMode; | ||||
| 
 | ||||
| /** API errors enumeration */ | ||||
| typedef enum { | ||||
|     FSE_OK, /**< No error */ | ||||
|     FSE_NOT_READY, /**< FS not ready */ | ||||
|     FSE_EXIST, /**< File/Dir alrady exist */ | ||||
|     FSE_NOT_EXIST, /**< File/Dir does not exist */ | ||||
|     FSE_INVALID_PARAMETER, /**< Invalid API parameter */ | ||||
|     FSE_DENIED, /**< Access denied */ | ||||
|     FSE_INVALID_NAME, /**< Invalid name/path */ | ||||
|     FSE_INTERNAL, /**< Internal error */ | ||||
|     FSE_NOT_IMPLEMENTED, /**< Functon not implemented */ | ||||
|     FSE_ALREADY_OPEN, /**< File/Dir already opened */ | ||||
| } FS_Error; | ||||
| 
 | ||||
| /** FileInfo flags */ | ||||
| typedef enum { | ||||
|     FSF_DIRECTORY = (1 << 0), /**< Directory */ | ||||
| } FS_Flags; | ||||
| 
 | ||||
| /**  Structure that hold file index and returned api errors  */ | ||||
| typedef struct File File; | ||||
| 
 | ||||
| /**  Structure that hold file info */ | ||||
| typedef struct { | ||||
|     uint8_t flags; /**< flags from FS_Flags enum */ | ||||
|     uint64_t size; /**< file size */ | ||||
| } FileInfo; | ||||
| 
 | ||||
| /** Gets the error text from FS_Error
 | ||||
|  * @param error_id error id | ||||
|  * @return const char* error text | ||||
|  */ | ||||
| const char* filesystem_api_error_get_desc(FS_Error error_id); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										192
									
								
								applications/storage/filesystem-api-internal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								applications/storage/filesystem-api-internal.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,192 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "filesystem-api-defines.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /** Structure that hold file index and returned api errors */ | ||||
| struct File { | ||||
|     uint32_t file_id; /**< File ID for internal references */ | ||||
|     FS_Error error_id; /**< Standart API error from FS_Error enum */ | ||||
|     int32_t internal_error_id; /**< Internal API error value */ | ||||
|     void* storage; | ||||
| }; | ||||
| 
 | ||||
| /** File api structure
 | ||||
|  *  @var FS_File_Api::open | ||||
|  *      @brief Open file | ||||
|  *      @param file pointer to file object, filled by api | ||||
|  *      @param path path to file  | ||||
|  *      @param access_mode access mode from FS_AccessMode  | ||||
|  *      @param open_mode open mode from FS_OpenMode  | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::close  | ||||
|  *      @brief Close file | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::read | ||||
|  *      @brief Read bytes from file to buffer | ||||
|  *      @param file pointer to file object | ||||
|  *      @param buff pointer to buffer for reading | ||||
|  *      @param bytes_to_read how many bytes to read, must be smaller or equal to buffer size  | ||||
|  *      @return how many bytes actually has been readed | ||||
|  *  | ||||
|  *  @var FS_File_Api::write | ||||
|  *      @brief Write bytes from buffer to file | ||||
|  *      @param file pointer to file object | ||||
|  *      @param buff pointer to buffer for writing | ||||
|  *      @param bytes_to_read how many bytes to write, must be smaller or equal to buffer size  | ||||
|  *      @return how many bytes actually has been writed | ||||
|  *  | ||||
|  *  @var FS_File_Api::seek | ||||
|  *      @brief Move r/w pointer  | ||||
|  *      @param file pointer to file object | ||||
|  *      @param offset offset to move r/w pointer | ||||
|  *      @param from_start set offset from start, or from current position | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::tell | ||||
|  *      @brief Get r/w pointer position | ||||
|  *      @param file pointer to file object | ||||
|  *      @return current r/w pointer position | ||||
|  *  | ||||
|  *  @var FS_File_Api::truncate | ||||
|  *      @brief Truncate file size to current r/w pointer position | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::size | ||||
|  *      @brief Fet file size | ||||
|  *      @param file pointer to file object | ||||
|  *      @return file size | ||||
|  *  | ||||
|  *  @var FS_File_Api::sync | ||||
|  *      @brief Write file cache to storage | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::eof | ||||
|  *      @brief Checks that the r/w pointer is at the end of the file | ||||
|  *      @param file pointer to file object | ||||
|  *      @return end of file flag | ||||
|  */ | ||||
| typedef struct { | ||||
|     bool (*open)( | ||||
|         void* context, | ||||
|         File* file, | ||||
|         const char* path, | ||||
|         FS_AccessMode access_mode, | ||||
|         FS_OpenMode open_mode); | ||||
|     bool (*close)(void* context, File* file); | ||||
|     uint16_t (*read)(void* context, File* file, void* buff, uint16_t bytes_to_read); | ||||
|     uint16_t (*write)(void* context, File* file, const void* buff, uint16_t bytes_to_write); | ||||
|     bool (*seek)(void* context, File* file, uint32_t offset, bool from_start); | ||||
|     uint64_t (*tell)(void* context, File* file); | ||||
|     bool (*truncate)(void* context, File* file); | ||||
|     uint64_t (*size)(void* context, File* file); | ||||
|     bool (*sync)(void* context, File* file); | ||||
|     bool (*eof)(void* context, File* file); | ||||
| } FS_File_Api; | ||||
| 
 | ||||
| /** Dir api structure
 | ||||
|  *  @var FS_Dir_Api::open | ||||
|  *      @brief Open directory to get objects from | ||||
|  *      @param file pointer to file object, filled by api | ||||
|  *      @param path path to directory  | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_Dir_Api::close  | ||||
|  *      @brief Close directory | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_Dir_Api::read | ||||
|  *      @brief Read next object info in directory | ||||
|  *      @param file pointer to file object | ||||
|  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||
|  *      @param name pointer to name buffer, can be NULL | ||||
|  *      @param name_length name buffer length | ||||
|  *      @return success flag (if next object not exist also returns false and set error_id to FSE_NOT_EXIST) | ||||
|  *  | ||||
|  *  @var FS_Dir_Api::rewind | ||||
|  *      @brief Rewind to first object info in directory | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  */ | ||||
| typedef struct { | ||||
|     bool (*open)(void* context, File* file, const char* path); | ||||
|     bool (*close)(void* context, File* file); | ||||
|     bool (*read)(void* context, File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||
|     bool (*rewind)(void* context, File* file); | ||||
| } FS_Dir_Api; | ||||
| 
 | ||||
| /** Common api structure
 | ||||
|  *  @var FS_Common_Api::stat | ||||
|  *      @brief Open directory to get objects from | ||||
|  *      @param path path to file/directory | ||||
|  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||
|  *      @param name pointer to name buffer, can be NULL | ||||
|  *      @param name_length name buffer length | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::remove | ||||
|  *      @brief Remove file/directory from storage,  | ||||
|  *          directory must be empty, | ||||
|  *          file/directory must not be opened, | ||||
|  *          file/directory must not have FSF_READ_ONLY flag | ||||
|  *      @param path path to file/directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::rename | ||||
|  *      @brief Rename file/directory, | ||||
|  *          file/directory must not be opened | ||||
|  *      @param path path to file/directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::mkdir | ||||
|  *      @brief Create new directory | ||||
|  *      @param path path to new directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::fs_info | ||||
|  *      @brief Get total and free space storage values | ||||
|  *      @param fs_path path of fs | ||||
|  *      @param total_space pointer to total space value | ||||
|  *      @param free_space pointer to free space value | ||||
|  *      @return FS_Error error info | ||||
|  */ | ||||
| typedef struct { | ||||
|     FS_Error (*stat)(void* context, const char* path, FileInfo* fileinfo); | ||||
|     FS_Error (*remove)(void* context, const char* path); | ||||
|     FS_Error (*rename)(void* context, const char* old_path, const char* new_path); | ||||
|     FS_Error (*mkdir)(void* context, const char* path); | ||||
|     FS_Error ( | ||||
|         *fs_info)(void* context, const char* fs_path, uint64_t* total_space, uint64_t* free_space); | ||||
| } FS_Common_Api; | ||||
| 
 | ||||
| /** Errors api structure
 | ||||
|  *  @var FS_Error_Api::get_desc | ||||
|  *      @brief Get error description text | ||||
|  *      @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id) | ||||
|  *      @return pointer to description text | ||||
|  */ | ||||
| typedef struct { | ||||
|     const char* (*get_desc)(void* context, FS_Error error_id); | ||||
| } FS_Error_Api; | ||||
| 
 | ||||
| /** Full filesystem api structure */ | ||||
| typedef struct { | ||||
|     FS_File_Api file; | ||||
|     FS_Dir_Api dir; | ||||
|     FS_Common_Api common; | ||||
|     FS_Error_Api error; | ||||
|     void* context; | ||||
| } FS_Api; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										38
									
								
								applications/storage/filesystem-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								applications/storage/filesystem-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| #include "filesystem-api-defines.h" | ||||
| 
 | ||||
| const char* filesystem_api_error_get_desc(FS_Error error_id) { | ||||
|     const char* result = "unknown error"; | ||||
|     switch(error_id) { | ||||
|     case(FSE_OK): | ||||
|         result = "OK"; | ||||
|         break; | ||||
|     case(FSE_NOT_READY): | ||||
|         result = "filesystem not ready"; | ||||
|         break; | ||||
|     case(FSE_EXIST): | ||||
|         result = "file/dir already exist"; | ||||
|         break; | ||||
|     case(FSE_NOT_EXIST): | ||||
|         result = "file/dir not exist"; | ||||
|         break; | ||||
|     case(FSE_INVALID_PARAMETER): | ||||
|         result = "invalid parameter"; | ||||
|         break; | ||||
|     case(FSE_DENIED): | ||||
|         result = "access denied"; | ||||
|         break; | ||||
|     case(FSE_INVALID_NAME): | ||||
|         result = "invalid name/path"; | ||||
|         break; | ||||
|     case(FSE_INTERNAL): | ||||
|         result = "internal error"; | ||||
|         break; | ||||
|     case(FSE_NOT_IMPLEMENTED): | ||||
|         result = "function not implemented"; | ||||
|         break; | ||||
|     case(FSE_ALREADY_OPEN): | ||||
|         result = "file is already open"; | ||||
|         break; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
							
								
								
									
										350
									
								
								applications/storage/storage-cli.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								applications/storage/storage-cli.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,350 @@ | ||||
| #include <furi.h> | ||||
| #include <cli/cli.h> | ||||
| #include <args.h> | ||||
| #include <storage/storage.h> | ||||
| #include <storage/storage-sd-api.h> | ||||
| #include <api-hal-version.h> | ||||
| 
 | ||||
| #define MAX_NAME_LENGTH 255 | ||||
| 
 | ||||
| void storage_cli(Cli* cli, string_t args, void* context); | ||||
| 
 | ||||
| // app cli function
 | ||||
| void storage_cli_init() { | ||||
|     Cli* cli = furi_record_open("cli"); | ||||
|     cli_add_command(cli, "storage", CliCommandFlagDefault, storage_cli, NULL); | ||||
|     furi_record_close("cli"); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_print_usage() { | ||||
|     printf("Usage:\r\n"); | ||||
|     printf("storage <cmd> <path> <args>\r\n"); | ||||
|     printf("The path must start with /int or /ext\r\n"); | ||||
|     printf("Cmd list:\r\n"); | ||||
|     printf("\tinfo\t - get FS info\r\n"); | ||||
|     printf("\tformat\t - format filesystem\r\n"); | ||||
|     printf("\tlist\t - list files and dirs\r\n"); | ||||
|     printf("\tremove\t - delete the file or directory\r\n"); | ||||
|     printf("\tread\t - read data from file and print file size and content to cli\r\n"); | ||||
|     printf( | ||||
|         "\twrite\t - read data from cli and append it to file, <args> should contain how many bytes you want to write\r\n"); | ||||
|     printf("\tcopy\t - copy file to new file, <args> must contain new path\r\n"); | ||||
|     printf("\trename\t - move file to new file, <args> must contain new path\r\n"); | ||||
| }; | ||||
| 
 | ||||
| void storage_cli_print_error(FS_Error error) { | ||||
|     printf("Storage error: %s\r\n", storage_error_get_desc(error)); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_print_path_error(string_t path, FS_Error error) { | ||||
|     printf( | ||||
|         "Storage error for path \"%s\": %s\r\n", | ||||
|         string_get_cstr(path), | ||||
|         storage_error_get_desc(error)); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_print_file_error(string_t path, File* file) { | ||||
|     printf( | ||||
|         "Storage error for path \"%s\": %s\r\n", | ||||
|         string_get_cstr(path), | ||||
|         storage_file_get_error_desc(file)); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_info(Cli* cli, string_t path) { | ||||
|     Storage* api = furi_record_open("storage"); | ||||
| 
 | ||||
|     if(string_cmp_str(path, "/int") == 0) { | ||||
|         uint64_t total_space; | ||||
|         uint64_t free_space; | ||||
|         FS_Error error = storage_common_fs_info(api, "/int", &total_space, &free_space); | ||||
| 
 | ||||
|         if(error != FSE_OK) { | ||||
|             storage_cli_print_path_error(path, error); | ||||
|         } else { | ||||
|             printf( | ||||
|                 "Label: %s\r\nType: LittleFS\r\n%lu KB total\r\n%lu KB free\r\n", | ||||
|                 api_hal_version_get_name_ptr(), | ||||
|                 (uint32_t)(total_space / 1024), | ||||
|                 (uint32_t)(free_space / 1024)); | ||||
|         } | ||||
|     } else if(string_cmp_str(path, "/ext") == 0) { | ||||
|         SDInfo sd_info; | ||||
|         FS_Error error = storage_sd_info(api, &sd_info); | ||||
| 
 | ||||
|         if(error != FSE_OK) { | ||||
|             storage_cli_print_path_error(path, error); | ||||
|         } else { | ||||
|             printf( | ||||
|                 "Label: %s\r\nType: %s\r\n%lu KB total\r\n%lu KB free\r\n", | ||||
|                 sd_info.label, | ||||
|                 sd_api_get_fs_type_text(sd_info.fs_type), | ||||
|                 sd_info.kb_total, | ||||
|                 sd_info.kb_free); | ||||
|         } | ||||
|     } else { | ||||
|         storage_cli_print_usage(); | ||||
|     } | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
| }; | ||||
| 
 | ||||
| void storage_cli_format(Cli* cli, string_t path) { | ||||
|     if(string_cmp_str(path, "/int") == 0) { | ||||
|         storage_cli_print_path_error(path, FSE_NOT_IMPLEMENTED); | ||||
|     } else if(string_cmp_str(path, "/ext") == 0) { | ||||
|         printf("Formatting SD card, all data will be lost. Are you sure (y/n)?\r\n"); | ||||
|         char answer = cli_getc(cli); | ||||
|         if(answer == 'y' || answer == 'Y') { | ||||
|             Storage* api = furi_record_open("storage"); | ||||
|             printf("Formatting, please wait...\r\n"); | ||||
| 
 | ||||
|             FS_Error error = storage_sd_format(api); | ||||
| 
 | ||||
|             if(error != FSE_OK) { | ||||
|                 storage_cli_print_path_error(path, error); | ||||
|             } else { | ||||
|                 printf("SD card was successfully formatted.\r\n"); | ||||
|             } | ||||
|             furi_record_close("storage"); | ||||
|         } else { | ||||
|             printf("Cancelled.\r\n"); | ||||
|         } | ||||
|     } else { | ||||
|         storage_cli_print_usage(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| void storage_cli_list(Cli* cli, string_t path) { | ||||
|     if(string_cmp_str(path, "/") == 0) { | ||||
|         printf("\t[D] int\r\n"); | ||||
|         printf("\t[D] ext\r\n"); | ||||
|         printf("\t[D] any\r\n"); | ||||
|     } else { | ||||
|         Storage* api = furi_record_open("storage"); | ||||
|         File* file = storage_file_alloc(api); | ||||
| 
 | ||||
|         if(storage_dir_open(file, string_get_cstr(path))) { | ||||
|             FileInfo fileinfo; | ||||
|             char name[MAX_NAME_LENGTH]; | ||||
|             bool readed = false; | ||||
| 
 | ||||
|             while(storage_dir_read(file, &fileinfo, name, MAX_NAME_LENGTH)) { | ||||
|                 readed = true; | ||||
|                 if(fileinfo.flags & FSF_DIRECTORY) { | ||||
|                     printf("\t[D] %s\r\n", name); | ||||
|                 } else { | ||||
|                     printf("\t[F] %s %lub\r\n", name, (uint32_t)(fileinfo.size)); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(!readed) { | ||||
|                 printf("\tEmpty\r\n"); | ||||
|             } | ||||
|         } else { | ||||
|             storage_cli_print_file_error(path, file); | ||||
|         } | ||||
| 
 | ||||
|         storage_dir_close(file); | ||||
|         storage_file_free(file); | ||||
|         furi_record_close("storage"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void storage_cli_read(Cli* cli, string_t path) { | ||||
|     Storage* api = furi_record_open("storage"); | ||||
|     File* file = storage_file_alloc(api); | ||||
| 
 | ||||
|     if(storage_file_open(file, string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         const uint16_t read_size = 128; | ||||
|         uint16_t readed_size = 0; | ||||
|         uint8_t* data = furi_alloc(read_size); | ||||
| 
 | ||||
|         printf("Size: %lu\r\n", (uint32_t)storage_file_size(file)); | ||||
| 
 | ||||
|         do { | ||||
|             readed_size = storage_file_read(file, data, read_size); | ||||
|             for(uint16_t i = 0; i < readed_size; i++) { | ||||
|                 printf("%c", data[i]); | ||||
|             } | ||||
|         } while(readed_size > 0); | ||||
|         printf("\r\n"); | ||||
| 
 | ||||
|         free(data); | ||||
|     } else { | ||||
|         storage_cli_print_file_error(path, file); | ||||
|     } | ||||
| 
 | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_write(Cli* cli, string_t path, string_t args) { | ||||
|     Storage* api = furi_record_open("storage"); | ||||
|     File* file = storage_file_alloc(api); | ||||
| 
 | ||||
|     uint32_t size; | ||||
|     int parsed_count = sscanf(string_get_cstr(args), "%lu", &size); | ||||
| 
 | ||||
|     if(parsed_count == EOF || parsed_count != 1) { | ||||
|         storage_cli_print_usage(); | ||||
|     } else { | ||||
|         if(storage_file_open(file, string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { | ||||
|             const uint16_t write_size = 8; | ||||
|             uint32_t readed_index = 0; | ||||
|             uint8_t* data = furi_alloc(write_size); | ||||
| 
 | ||||
|             while(true) { | ||||
|                 data[readed_index % write_size] = cli_getc(cli); | ||||
|                 printf("%c", data[readed_index % write_size]); | ||||
|                 fflush(stdout); | ||||
|                 readed_index++; | ||||
| 
 | ||||
|                 if(((readed_index % write_size) == 0)) { | ||||
|                     uint16_t writed_size = storage_file_write(file, data, write_size); | ||||
| 
 | ||||
|                     if(writed_size != write_size) { | ||||
|                         storage_cli_print_file_error(path, file); | ||||
|                         break; | ||||
|                     } | ||||
|                 } else if(readed_index == size) { | ||||
|                     uint16_t writed_size = storage_file_write(file, data, size % write_size); | ||||
| 
 | ||||
|                     if(writed_size != (size % write_size)) { | ||||
|                         storage_cli_print_file_error(path, file); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if(readed_index == size) { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             printf("\r\n"); | ||||
| 
 | ||||
|             free(data); | ||||
|         } else { | ||||
|             storage_cli_print_file_error(path, file); | ||||
|         } | ||||
|         storage_file_close(file); | ||||
|     } | ||||
| 
 | ||||
|     storage_file_free(file); | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_copy(Cli* cli, string_t old_path, string_t args) { | ||||
|     Storage* api = furi_record_open("storage"); | ||||
|     string_t new_path; | ||||
|     string_init(new_path); | ||||
| 
 | ||||
|     if(!args_read_probably_quoted_string_and_trim(args, new_path)) { | ||||
|         storage_cli_print_usage(); | ||||
|     } else { | ||||
|         FS_Error error = | ||||
|             storage_common_copy(api, string_get_cstr(old_path), string_get_cstr(new_path)); | ||||
| 
 | ||||
|         if(error != FSE_OK) { | ||||
|             storage_cli_print_error(error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(new_path); | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_remove(Cli* cli, string_t path) { | ||||
|     Storage* api = furi_record_open("storage"); | ||||
|     FS_Error error = storage_common_remove(api, string_get_cstr(path)); | ||||
| 
 | ||||
|     if(error != FSE_OK) { | ||||
|         storage_cli_print_error(error); | ||||
|     } | ||||
| 
 | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| void storage_cli_rename(Cli* cli, string_t old_path, string_t args) { | ||||
|     Storage* api = furi_record_open("storage"); | ||||
|     string_t new_path; | ||||
|     string_init(new_path); | ||||
| 
 | ||||
|     if(!args_read_probably_quoted_string_and_trim(args, new_path)) { | ||||
|         storage_cli_print_usage(); | ||||
|     } else { | ||||
|         FS_Error error = | ||||
|             storage_common_rename(api, string_get_cstr(old_path), string_get_cstr(new_path)); | ||||
| 
 | ||||
|         if(error != FSE_OK) { | ||||
|             storage_cli_print_error(error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     string_clear(new_path); | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| void storage_cli(Cli* cli, string_t args, void* context) { | ||||
|     string_t cmd; | ||||
|     string_t path; | ||||
|     string_init(cmd); | ||||
|     string_init(path); | ||||
| 
 | ||||
|     do { | ||||
|         if(!args_read_string_and_trim(args, cmd)) { | ||||
|             storage_cli_print_usage(); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!args_read_probably_quoted_string_and_trim(args, path)) { | ||||
|             storage_cli_print_usage(); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "info") == 0) { | ||||
|             storage_cli_info(cli, path); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "format") == 0) { | ||||
|             storage_cli_format(cli, path); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "list") == 0) { | ||||
|             storage_cli_list(cli, path); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "read") == 0) { | ||||
|             storage_cli_read(cli, path); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "write") == 0) { | ||||
|             storage_cli_write(cli, path, args); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "copy") == 0) { | ||||
|             storage_cli_copy(cli, path, args); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "remove") == 0) { | ||||
|             storage_cli_remove(cli, path); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp_str(cmd, "rename") == 0) { | ||||
|             storage_cli_rename(cli, path, args); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         storage_cli_print_usage(); | ||||
|     } while(false); | ||||
| 
 | ||||
|     string_clear(path); | ||||
|     string_clear(cmd); | ||||
| } | ||||
							
								
								
									
										383
									
								
								applications/storage/storage-external-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								applications/storage/storage-external-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,383 @@ | ||||
| #include "storage.h" | ||||
| #include "storage-i.h" | ||||
| #include "storage-message.h" | ||||
| 
 | ||||
| #define S_API_PROLOGUE                                      \ | ||||
|     osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); \ | ||||
|     furi_check(semaphore != NULL); | ||||
| 
 | ||||
| #define S_FILE_API_PROLOGUE           \ | ||||
|     Storage* storage = file->storage; \ | ||||
|     furi_assert(storage); | ||||
| 
 | ||||
| #define S_API_EPILOGUE                                                                         \ | ||||
|     furi_check(osMessageQueuePut(storage->message_queue, &message, 0, osWaitForever) == osOK); \ | ||||
|     osSemaphoreAcquire(semaphore, osWaitForever);                                              \ | ||||
|     osSemaphoreDelete(semaphore); | ||||
| 
 | ||||
| #define S_API_MESSAGE(_command)      \ | ||||
|     SAReturn return_data;            \ | ||||
|     StorageMessage message = {       \ | ||||
|         .semaphore = semaphore,      \ | ||||
|         .command = _command,         \ | ||||
|         .data = &data,               \ | ||||
|         .return_data = &return_data, \ | ||||
|     }; | ||||
| 
 | ||||
| #define S_API_DATA_FILE   \ | ||||
|     SAData data = {       \ | ||||
|         .file = {         \ | ||||
|             .file = file, \ | ||||
|         }}; | ||||
| 
 | ||||
| #define S_API_DATA_PATH   \ | ||||
|     SAData data = {       \ | ||||
|         .path = {         \ | ||||
|             .path = path, \ | ||||
|         }}; | ||||
| 
 | ||||
| #define S_RETURN_BOOL (return_data.bool_value); | ||||
| #define S_RETURN_UINT16 (return_data.uint16_value); | ||||
| #define S_RETURN_UINT64 (return_data.uint64_value); | ||||
| #define S_RETURN_ERROR (return_data.error_value); | ||||
| #define S_RETURN_CSTRING (return_data.cstring_value); | ||||
| 
 | ||||
| #define FILE_OPENED 1 | ||||
| #define FILE_CLOSED 0 | ||||
| 
 | ||||
| /****************** FILE ******************/ | ||||
| 
 | ||||
| bool storage_file_open( | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     FS_AccessMode access_mode, | ||||
|     FS_OpenMode open_mode) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .fopen = { | ||||
|             .file = file, | ||||
|             .path = path, | ||||
|             .access_mode = access_mode, | ||||
|             .open_mode = open_mode, | ||||
|         }}; | ||||
| 
 | ||||
|     file->file_id = FILE_OPENED; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandFileOpen); | ||||
|     S_API_EPILOGUE; | ||||
| 
 | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| bool storage_file_close(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandFileClose); | ||||
|     S_API_EPILOGUE; | ||||
| 
 | ||||
|     file->file_id = FILE_CLOSED; | ||||
| 
 | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .fread = { | ||||
|             .file = file, | ||||
|             .buff = buff, | ||||
|             .bytes_to_read = bytes_to_read, | ||||
|         }}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandFileRead); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_UINT16; | ||||
| } | ||||
| 
 | ||||
| uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .fwrite = { | ||||
|             .file = file, | ||||
|             .buff = buff, | ||||
|             .bytes_to_write = bytes_to_write, | ||||
|         }}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandFileWrite); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_UINT16; | ||||
| } | ||||
| 
 | ||||
| bool storage_file_seek(File* file, uint32_t offset, bool from_start) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .fseek = { | ||||
|             .file = file, | ||||
|             .offset = offset, | ||||
|             .from_start = from_start, | ||||
|         }}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandFileSeek); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| uint64_t storage_file_tell(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandFileTell); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_UINT64; | ||||
| } | ||||
| 
 | ||||
| bool storage_file_truncate(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandFileTruncate); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| uint64_t storage_file_size(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandFileSize); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_UINT64; | ||||
| } | ||||
| 
 | ||||
| bool storage_file_sync(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandFileSync); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| bool storage_file_eof(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandFileEof); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| /****************** DIR ******************/ | ||||
| 
 | ||||
| bool storage_dir_open(File* file, const char* path) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .dopen = { | ||||
|             .file = file, | ||||
|             .path = path, | ||||
|         }}; | ||||
| 
 | ||||
|     file->file_id = FILE_OPENED; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandDirOpen); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| bool storage_dir_close(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandDirClose); | ||||
|     S_API_EPILOGUE; | ||||
| 
 | ||||
|     file->file_id = FILE_CLOSED; | ||||
| 
 | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .dread = { | ||||
|             .file = file, | ||||
|             .fileinfo = fileinfo, | ||||
|             .name = name, | ||||
|             .name_length = name_length, | ||||
|         }}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandDirRead); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| bool storage_dir_rewind(File* file) { | ||||
|     S_FILE_API_PROLOGUE; | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_FILE; | ||||
|     S_API_MESSAGE(StorageCommandDirRewind); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_BOOL; | ||||
| } | ||||
| 
 | ||||
| /****************** COMMON ******************/ | ||||
| 
 | ||||
| FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) { | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = {.cstat = {.path = path, .fileinfo = fileinfo}}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandCommonStat); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_common_remove(Storage* storage, const char* path) { | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_PATH; | ||||
|     S_API_MESSAGE(StorageCommandCommonRemove); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) { | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .cpaths = { | ||||
|             .old = old_path, | ||||
|             .new = new_path, | ||||
|         }}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandCommonRename); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) { | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .cpaths = { | ||||
|             .old = old_path, | ||||
|             .new = new_path, | ||||
|         }}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandCommonCopy); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_common_mkdir(Storage* storage, const char* path) { | ||||
|     S_API_PROLOGUE; | ||||
|     S_API_DATA_PATH; | ||||
|     S_API_MESSAGE(StorageCommandCommonMkDir); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_common_fs_info( | ||||
|     Storage* storage, | ||||
|     const char* fs_path, | ||||
|     uint64_t* total_space, | ||||
|     uint64_t* free_space) { | ||||
|     S_API_PROLOGUE; | ||||
| 
 | ||||
|     SAData data = { | ||||
|         .cfsinfo = { | ||||
|             .fs_path = fs_path, | ||||
|             .total_space = total_space, | ||||
|             .free_space = free_space, | ||||
|         }}; | ||||
| 
 | ||||
|     S_API_MESSAGE(StorageCommandCommonFSInfo); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| /****************** ERROR ******************/ | ||||
| 
 | ||||
| const char* storage_error_get_desc(FS_Error error_id) { | ||||
|     return filesystem_api_error_get_desc(error_id); | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_file_get_error(File* file) { | ||||
|     furi_check(file != NULL); | ||||
|     return file->error_id; | ||||
| } | ||||
| 
 | ||||
| const char* storage_file_get_error_desc(File* file) { | ||||
|     furi_check(file != NULL); | ||||
|     return filesystem_api_error_get_desc(file->error_id); | ||||
| } | ||||
| 
 | ||||
| /****************** Raw SD API ******************/ | ||||
| 
 | ||||
| FS_Error storage_sd_format(Storage* storage) { | ||||
|     S_API_PROLOGUE; | ||||
|     SAData data = {}; | ||||
|     S_API_MESSAGE(StorageCommandSDFormat); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_sd_unmount(Storage* storage) { | ||||
|     S_API_PROLOGUE; | ||||
|     SAData data = {}; | ||||
|     S_API_MESSAGE(StorageCommandSDUnmount); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_sd_info(Storage* storage, SDInfo* info) { | ||||
|     S_API_PROLOGUE; | ||||
|     SAData data = { | ||||
|         .sdinfo = { | ||||
|             .info = info, | ||||
|         }}; | ||||
|     S_API_MESSAGE(StorageCommandSDInfo); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| FS_Error storage_sd_status(Storage* storage) { | ||||
|     S_API_PROLOGUE; | ||||
|     SAData data = {}; | ||||
|     S_API_MESSAGE(StorageCommandSDStatus); | ||||
|     S_API_EPILOGUE; | ||||
|     return S_RETURN_ERROR; | ||||
| } | ||||
| 
 | ||||
| File* storage_file_alloc(Storage* storage) { | ||||
|     File* file = furi_alloc(sizeof(File)); | ||||
|     file->file_id = FILE_CLOSED; | ||||
|     file->storage = storage; | ||||
| 
 | ||||
|     return file; | ||||
| } | ||||
| 
 | ||||
| bool storage_file_is_open(File* file) { | ||||
|     return (file->file_id != FILE_CLOSED); | ||||
| } | ||||
| 
 | ||||
| void storage_file_free(File* file) { | ||||
|     if(storage_file_is_open(file)) { | ||||
|         storage_file_close(file); | ||||
|     } | ||||
| 
 | ||||
|     free(file); | ||||
| } | ||||
							
								
								
									
										212
									
								
								applications/storage/storage-glue.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								applications/storage/storage-glue.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,212 @@ | ||||
| #include "storage-glue.h" | ||||
| #include <api-hal.h> | ||||
| 
 | ||||
| /****************** storage file ******************/ | ||||
| 
 | ||||
| void storage_file_init(StorageFile* obj) { | ||||
|     obj->file = NULL; | ||||
|     obj->type = ST_ERROR; | ||||
|     obj->file_data = NULL; | ||||
|     string_init(obj->path); | ||||
| } | ||||
| 
 | ||||
| void storage_file_init_set(StorageFile* obj, const StorageFile* src) { | ||||
|     obj->file = src->file; | ||||
|     obj->type = src->type; | ||||
|     obj->file_data = src->file_data; | ||||
|     string_init_set(obj->path, src->path); | ||||
| } | ||||
| 
 | ||||
| void storage_file_set(StorageFile* obj, const StorageFile* src) { | ||||
|     obj->file = src->file; | ||||
|     obj->type = src->type; | ||||
|     obj->file_data = src->file_data; | ||||
|     string_set(obj->path, src->path); | ||||
| } | ||||
| 
 | ||||
| void storage_file_clear(StorageFile* obj) { | ||||
|     string_clear(obj->path); | ||||
| } | ||||
| 
 | ||||
| /****************** storage data ******************/ | ||||
| 
 | ||||
| void storage_data_init(StorageData* storage) { | ||||
|     storage->mutex = osMutexNew(NULL); | ||||
|     furi_check(storage->mutex != NULL); | ||||
|     storage->data = NULL; | ||||
|     storage->status = StorageStatusNotReady; | ||||
|     StorageFileList_init(storage->files); | ||||
| } | ||||
| 
 | ||||
| bool storage_data_lock(StorageData* storage) { | ||||
|     api_hal_power_insomnia_enter(); | ||||
|     return (osMutexAcquire(storage->mutex, osWaitForever) == osOK); | ||||
| } | ||||
| 
 | ||||
| bool storage_data_unlock(StorageData* storage) { | ||||
|     api_hal_power_insomnia_exit(); | ||||
|     return (osMutexRelease(storage->mutex) == osOK); | ||||
| } | ||||
| 
 | ||||
| StorageStatus storage_data_status(StorageData* storage) { | ||||
|     StorageStatus status; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
|     status = storage->status; | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     return status; | ||||
| } | ||||
| 
 | ||||
| const char* storage_data_status_text(StorageData* storage) { | ||||
|     const char* result = "unknown"; | ||||
|     switch(storage->status) { | ||||
|     case StorageStatusOK: | ||||
|         result = "ok"; | ||||
|         break; | ||||
|     case StorageStatusNotReady: | ||||
|         result = "not ready"; | ||||
|         break; | ||||
|     case StorageStatusNotMounted: | ||||
|         result = "not mounted"; | ||||
|         break; | ||||
|     case StorageStatusNoFS: | ||||
|         result = "no filesystem"; | ||||
|         break; | ||||
|     case StorageStatusNotAccessible: | ||||
|         result = "not accessible"; | ||||
|         break; | ||||
|     case StorageStatusErrorInternal: | ||||
|         result = "internal"; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| /****************** storage glue ******************/ | ||||
| 
 | ||||
| bool storage_has_file(const File* file, StorageData* storage_data) { | ||||
|     bool result = false; | ||||
| 
 | ||||
|     StorageFileList_it_t it; | ||||
|     for(StorageFileList_it(it, storage_data->files); !StorageFileList_end_p(it); | ||||
|         StorageFileList_next(it)) { | ||||
|         const StorageFile* storage_file = StorageFileList_cref(it); | ||||
| 
 | ||||
|         if(storage_file->file->file_id == file->file_id) { | ||||
|             result = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| StorageType storage_get_type_by_path(const char* path) { | ||||
|     StorageType type = ST_ERROR; | ||||
| 
 | ||||
|     const char* ext_path = "/ext"; | ||||
|     const char* int_path = "/int"; | ||||
|     const char* any_path = "/any"; | ||||
| 
 | ||||
|     if(strlen(path) >= strlen(ext_path) && memcmp(path, ext_path, strlen(ext_path)) == 0) { | ||||
|         type = ST_EXT; | ||||
|     } else if(strlen(path) >= strlen(int_path) && memcmp(path, int_path, strlen(int_path)) == 0) { | ||||
|         type = ST_INT; | ||||
|     } else if(strlen(path) >= strlen(any_path) && memcmp(path, any_path, strlen(any_path)) == 0) { | ||||
|         type = ST_ANY; | ||||
|     } | ||||
| 
 | ||||
|     return type; | ||||
| } | ||||
| 
 | ||||
| bool storage_path_already_open(const char* path, StorageFileList_t array) { | ||||
|     bool open = false; | ||||
| 
 | ||||
|     StorageFileList_it_t it; | ||||
| 
 | ||||
|     for(StorageFileList_it(it, array); !StorageFileList_end_p(it); StorageFileList_next(it)) { | ||||
|         const StorageFile* storage_file = StorageFileList_cref(it); | ||||
| 
 | ||||
|         if(string_cmp(storage_file->path, path) == 0) { | ||||
|             open = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return open; | ||||
| } | ||||
| 
 | ||||
| void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage) { | ||||
|     StorageFile* founded_file = NULL; | ||||
| 
 | ||||
|     StorageFileList_it_t it; | ||||
| 
 | ||||
|     for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); | ||||
|         StorageFileList_next(it)) { | ||||
|         StorageFile* storage_file = StorageFileList_ref(it); | ||||
| 
 | ||||
|         if(storage_file->file->file_id == file->file_id) { | ||||
|             founded_file = storage_file; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_check(founded_file != NULL); | ||||
| 
 | ||||
|     founded_file->file_data = file_data; | ||||
| } | ||||
| 
 | ||||
| void* storage_get_storage_file_data(const File* file, StorageData* storage) { | ||||
|     const StorageFile* founded_file = NULL; | ||||
| 
 | ||||
|     StorageFileList_it_t it; | ||||
| 
 | ||||
|     for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); | ||||
|         StorageFileList_next(it)) { | ||||
|         const StorageFile* storage_file = StorageFileList_cref(it); | ||||
| 
 | ||||
|         if(storage_file->file->file_id == file->file_id) { | ||||
|             founded_file = storage_file; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     furi_check(founded_file != NULL); | ||||
| 
 | ||||
|     return founded_file->file_data; | ||||
| } | ||||
| 
 | ||||
| void storage_push_storage_file( | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     StorageType type, | ||||
|     StorageData* storage) { | ||||
|     StorageFile* storage_file = StorageFileList_push_new(storage->files); | ||||
|     furi_check(storage_file != NULL); | ||||
| 
 | ||||
|     file->file_id = (uint32_t)storage_file; | ||||
|     storage_file->file = file; | ||||
|     storage_file->type = type; | ||||
|     string_set(storage_file->path, path); | ||||
| } | ||||
| 
 | ||||
| bool storage_pop_storage_file(File* file, StorageData* storage) { | ||||
|     StorageFileList_it_t it; | ||||
|     bool result = false; | ||||
| 
 | ||||
|     for(StorageFileList_it(it, storage->files); !StorageFileList_end_p(it); | ||||
|         StorageFileList_next(it)) { | ||||
|         if(StorageFileList_cref(it)->file->file_id == file->file_id) { | ||||
|             result = true; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(result) { | ||||
|         StorageFileList_remove(storage->files, it); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
							
								
								
									
										79
									
								
								applications/storage/storage-glue.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								applications/storage/storage-glue.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "filesystem-api-internal.h" | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef enum { ST_EXT = 0, ST_INT = 1, ST_ANY, ST_ERROR } StorageType; | ||||
| 
 | ||||
| typedef struct StorageData StorageData; | ||||
| 
 | ||||
| typedef struct { | ||||
|     void (*tick)(StorageData* storage); | ||||
| } StorageApi; | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
|     StorageType type; | ||||
|     void* file_data; | ||||
|     string_t path; | ||||
| } StorageFile; | ||||
| 
 | ||||
| typedef enum { | ||||
|     StorageStatusOK, /**< storage ok */ | ||||
|     StorageStatusNotReady, /**< storage not ready (not initialized or waiting for data storage to appear) */ | ||||
|     StorageStatusNotMounted, /**< datastore appeared, but we cannot mount it */ | ||||
|     StorageStatusNoFS, /**< datastore appeared and mounted, but does not have a file system */ | ||||
|     StorageStatusNotAccessible, /**< datastore appeared and mounted, but not available */ | ||||
|     StorageStatusErrorInternal, /**< any other internal error */ | ||||
| } StorageStatus; | ||||
| 
 | ||||
| void storage_file_init(StorageFile* obj); | ||||
| void storage_file_init_set(StorageFile* obj, const StorageFile* src); | ||||
| void storage_file_set(StorageFile* obj, const StorageFile* src); | ||||
| void storage_file_clear(StorageFile* obj); | ||||
| 
 | ||||
| void storage_data_init(StorageData* storage); | ||||
| bool storage_data_lock(StorageData* storage); | ||||
| bool storage_data_unlock(StorageData* storage); | ||||
| StorageStatus storage_data_status(StorageData* storage); | ||||
| const char* storage_data_status_text(StorageData* storage); | ||||
| 
 | ||||
| LIST_DEF( | ||||
|     StorageFileList, | ||||
|     StorageFile, | ||||
|     (INIT(API_2(storage_file_init)), | ||||
|      SET(API_6(storage_file_init_set)), | ||||
|      INIT_SET(API_6(storage_file_set)), | ||||
|      CLEAR(API_2(storage_file_clear)))) | ||||
| 
 | ||||
| struct StorageData { | ||||
|     FS_Api fs_api; | ||||
|     StorageApi api; | ||||
|     void* data; | ||||
|     osMutexId_t mutex; | ||||
|     StorageStatus status; | ||||
|     StorageFileList_t files; | ||||
| }; | ||||
| 
 | ||||
| bool storage_has_file(const File* file, StorageData* storage_data); | ||||
| StorageType storage_get_type_by_path(const char* path); | ||||
| bool storage_path_already_open(const char* path, StorageFileList_t files); | ||||
| 
 | ||||
| void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); | ||||
| void* storage_get_storage_file_data(const File* file, StorageData* storage); | ||||
| 
 | ||||
| void storage_push_storage_file( | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     StorageType type, | ||||
|     StorageData* storage); | ||||
| bool storage_pop_storage_file(File* file, StorageData* storage); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										27
									
								
								applications/storage/storage-i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								applications/storage/storage-i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <gui/gui.h> | ||||
| #include "storage-glue.h" | ||||
| #include "storage-sd-api.h" | ||||
| #include "filesystem-api-internal.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #define STORAGE_COUNT (ST_INT + 1) | ||||
| 
 | ||||
| typedef struct { | ||||
|     ViewPort* view_port; | ||||
|     bool enabled; | ||||
| } StorageSDGui; | ||||
| 
 | ||||
| struct Storage { | ||||
|     osMessageQueueId_t message_queue; | ||||
|     StorageData storage[STORAGE_COUNT]; | ||||
|     StorageSDGui sd_gui; | ||||
| }; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										142
									
								
								applications/storage/storage-message.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								applications/storage/storage-message.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,142 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
|     const char* path; | ||||
|     FS_AccessMode access_mode; | ||||
|     FS_OpenMode open_mode; | ||||
| } SADataFOpen; | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
|     void* buff; | ||||
|     uint16_t bytes_to_read; | ||||
| } SADataFRead; | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
|     const void* buff; | ||||
|     uint16_t bytes_to_write; | ||||
| } SADataFWrite; | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
|     uint32_t offset; | ||||
|     bool from_start; | ||||
| } SADataFSeek; | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
|     const char* path; | ||||
| } SADataDOpen; | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
|     FileInfo* fileinfo; | ||||
|     char* name; | ||||
|     uint16_t name_length; | ||||
| } SADataDRead; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* path; | ||||
|     FileInfo* fileinfo; | ||||
| } SADataCStat; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* old; | ||||
|     const char* new; | ||||
| } SADataCPaths; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* fs_path; | ||||
|     uint64_t* total_space; | ||||
|     uint64_t* free_space; | ||||
| } SADataCFSInfo; | ||||
| 
 | ||||
| typedef struct { | ||||
|     uint32_t id; | ||||
| } SADataError; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* path; | ||||
| } SADataPath; | ||||
| 
 | ||||
| typedef struct { | ||||
|     File* file; | ||||
| } SADataFile; | ||||
| 
 | ||||
| typedef struct { | ||||
|     SDInfo* info; | ||||
| } SAInfo; | ||||
| 
 | ||||
| typedef union { | ||||
|     SADataFOpen fopen; | ||||
|     SADataFRead fread; | ||||
|     SADataFWrite fwrite; | ||||
|     SADataFSeek fseek; | ||||
| 
 | ||||
|     SADataDOpen dopen; | ||||
|     SADataDRead dread; | ||||
| 
 | ||||
|     SADataCStat cstat; | ||||
|     SADataCPaths cpaths; | ||||
|     SADataCFSInfo cfsinfo; | ||||
| 
 | ||||
|     SADataError error; | ||||
| 
 | ||||
|     SADataFile file; | ||||
|     SADataPath path; | ||||
| 
 | ||||
|     SAInfo sdinfo; | ||||
| } SAData; | ||||
| 
 | ||||
| typedef union { | ||||
|     bool bool_value; | ||||
|     uint16_t uint16_value; | ||||
|     uint64_t uint64_value; | ||||
|     FS_Error error_value; | ||||
|     const char* cstring_value; | ||||
| } SAReturn; | ||||
| 
 | ||||
| typedef enum { | ||||
|     StorageCommandFileOpen, | ||||
|     StorageCommandFileClose, | ||||
|     StorageCommandFileRead, | ||||
|     StorageCommandFileWrite, | ||||
|     StorageCommandFileSeek, | ||||
|     StorageCommandFileTell, | ||||
|     StorageCommandFileTruncate, | ||||
|     StorageCommandFileSize, | ||||
|     StorageCommandFileSync, | ||||
|     StorageCommandFileEof, | ||||
|     StorageCommandDirOpen, | ||||
|     StorageCommandDirClose, | ||||
|     StorageCommandDirRead, | ||||
|     StorageCommandDirRewind, | ||||
|     StorageCommandCommonStat, | ||||
|     StorageCommandCommonRemove, | ||||
|     StorageCommandCommonRename, | ||||
|     StorageCommandCommonCopy, | ||||
|     StorageCommandCommonMkDir, | ||||
|     StorageCommandCommonFSInfo, | ||||
|     StorageCommandSDFormat, | ||||
|     StorageCommandSDUnmount, | ||||
|     StorageCommandSDInfo, | ||||
|     StorageCommandSDStatus, | ||||
| } StorageCommand; | ||||
| 
 | ||||
| typedef struct { | ||||
|     osSemaphoreId_t semaphore; | ||||
|     StorageCommand command; | ||||
|     SAData* data; | ||||
|     SAReturn* return_data; | ||||
| } StorageMessage; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										584
									
								
								applications/storage/storage-processing.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										584
									
								
								applications/storage/storage-processing.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,584 @@ | ||||
| #include "storage-processing.h" | ||||
| 
 | ||||
| #define FS_CALL(_storage, _fn)   \ | ||||
|     storage_data_lock(_storage); \ | ||||
|     ret = _storage->fs_api._fn;  \ | ||||
|     storage_data_unlock(_storage); | ||||
| 
 | ||||
| #define ST_CALL(_storage, _fn)   \ | ||||
|     storage_data_lock(_storage); \ | ||||
|     ret = _storage->api._fn;     \ | ||||
|     storage_data_unlock(_storage); | ||||
| 
 | ||||
| static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { | ||||
|     StorageData* storage; | ||||
| 
 | ||||
|     if(type == ST_ANY) { | ||||
|         type = ST_INT; | ||||
|         StorageData* ext_storage = &app->storage[ST_EXT]; | ||||
| 
 | ||||
|         if(storage_data_status(ext_storage) == StorageStatusOK) { | ||||
|             type = ST_EXT; | ||||
|         } | ||||
|     } | ||||
|     storage = &app->storage[type]; | ||||
| 
 | ||||
|     return storage; | ||||
| } | ||||
| 
 | ||||
| static bool storage_type_is_not_valid(StorageType type) { | ||||
|     return type >= ST_ERROR; | ||||
| } | ||||
| 
 | ||||
| static StorageData* get_storage_by_file(File* file, StorageData* storages) { | ||||
|     StorageData* storage_data = NULL; | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < STORAGE_COUNT; i++) { | ||||
|         if(storage_has_file(file, &storages[i])) { | ||||
|             storage_data = &storages[i]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return storage_data; | ||||
| } | ||||
| 
 | ||||
| const char* remove_vfs(const char* path) { | ||||
|     return path + MIN(4, strlen(path)); | ||||
| } | ||||
| 
 | ||||
| /******************* File Functions *******************/ | ||||
| 
 | ||||
| bool storage_process_file_open( | ||||
|     Storage* app, | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     FS_AccessMode access_mode, | ||||
|     FS_OpenMode open_mode) { | ||||
|     bool ret = false; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
|     StorageData* storage; | ||||
|     file->error_id = FSE_OK; | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type)) { | ||||
|         file->error_id = FSE_INVALID_NAME; | ||||
|     } else { | ||||
|         storage = storage_get_storage_by_type(app, type); | ||||
|         if(storage_path_already_open(path, storage->files)) { | ||||
|             file->error_id = FSE_ALREADY_OPEN; | ||||
|         } else { | ||||
|             storage_push_storage_file(file, path, type, storage); | ||||
|             FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool storage_process_file_close(Storage* app, File* file) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.close(storage, file)); | ||||
|         storage_pop_storage_file(file, storage); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static uint16_t | ||||
|     storage_process_file_read(Storage* app, File* file, void* buff, uint16_t const bytes_to_read) { | ||||
|     uint16_t ret = 0; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.read(storage, file, buff, bytes_to_read)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static uint16_t storage_process_file_write( | ||||
|     Storage* app, | ||||
|     File* file, | ||||
|     const void* buff, | ||||
|     uint16_t const bytes_to_write) { | ||||
|     uint16_t ret = 0; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.write(storage, file, buff, bytes_to_write)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static bool storage_process_file_seek( | ||||
|     Storage* app, | ||||
|     File* file, | ||||
|     const uint32_t offset, | ||||
|     const bool from_start) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.seek(storage, file, offset, from_start)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static uint64_t storage_process_file_tell(Storage* app, File* file) { | ||||
|     uint64_t ret = 0; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.tell(storage, file)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static bool storage_process_file_truncate(Storage* app, File* file) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.truncate(storage, file)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static bool storage_process_file_sync(Storage* app, File* file) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.sync(storage, file)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static uint64_t storage_process_file_size(Storage* app, File* file) { | ||||
|     uint64_t ret = 0; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.size(storage, file)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static bool storage_process_file_eof(Storage* app, File* file) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, file.eof(storage, file)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /******************* Dir Functions *******************/ | ||||
| 
 | ||||
| bool storage_process_dir_open(Storage* app, File* file, const char* path) { | ||||
|     bool ret = false; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
|     StorageData* storage; | ||||
|     file->error_id = FSE_OK; | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type)) { | ||||
|         file->error_id = FSE_INVALID_NAME; | ||||
|     } else { | ||||
|         storage = storage_get_storage_by_type(app, type); | ||||
|         if(storage_path_already_open(path, storage->files)) { | ||||
|             file->error_id = FSE_ALREADY_OPEN; | ||||
|         } else { | ||||
|             storage_push_storage_file(file, path, type, storage); | ||||
|             FS_CALL(storage, dir.open(storage, file, remove_vfs(path))); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool storage_process_dir_close(Storage* app, File* file) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, dir.close(storage, file)); | ||||
|         storage_pop_storage_file(file, storage); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool storage_process_dir_read( | ||||
|     Storage* app, | ||||
|     File* file, | ||||
|     FileInfo* fileinfo, | ||||
|     char* name, | ||||
|     const uint16_t name_length) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, dir.read(storage, file, fileinfo, name, name_length)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool storage_process_dir_rewind(Storage* app, File* file) { | ||||
|     bool ret = false; | ||||
|     StorageData* storage = get_storage_by_file(file, app->storage); | ||||
| 
 | ||||
|     if(storage == NULL) { | ||||
|         file->error_id = FSE_INVALID_PARAMETER; | ||||
|     } else { | ||||
|         FS_CALL(storage, dir.rewind(storage, file)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /******************* Common FS Functions *******************/ | ||||
| 
 | ||||
| static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) { | ||||
|     FS_Error ret = FSE_OK; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type)) { | ||||
|         ret = FSE_INVALID_NAME; | ||||
|     } else { | ||||
|         StorageData* storage = storage_get_storage_by_type(app, type); | ||||
|         FS_CALL(storage, common.stat(storage, remove_vfs(path), fileinfo)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_common_remove(Storage* app, const char* path) { | ||||
|     FS_Error ret = FSE_OK; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
| 
 | ||||
|     do { | ||||
|         if(storage_type_is_not_valid(type)) { | ||||
|             ret = FSE_INVALID_NAME; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         StorageData* storage = storage_get_storage_by_type(app, type); | ||||
|         if(storage_path_already_open(path, storage->files)) { | ||||
|             ret = FSE_ALREADY_OPEN; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         FS_CALL(storage, common.remove(storage, remove_vfs(path))); | ||||
|     } while(false); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_common_copy(Storage* app, const char* old, const char* new) { | ||||
|     FS_Error ret = FSE_INTERNAL; | ||||
|     File file_old; | ||||
|     File file_new; | ||||
| 
 | ||||
|     do { | ||||
|         if(!storage_process_file_open(app, &file_old, old, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|             ret = storage_file_get_error(&file_old); | ||||
|             storage_process_file_close(app, &file_old); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!storage_process_file_open(app, &file_new, new, FSAM_WRITE, FSOM_CREATE_NEW)) { | ||||
|             ret = storage_file_get_error(&file_new); | ||||
|             storage_process_file_close(app, &file_new); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         const uint16_t buffer_size = 64; | ||||
|         uint8_t* buffer = malloc(buffer_size); | ||||
|         uint16_t readed_size = 0; | ||||
|         uint16_t writed_size = 0; | ||||
| 
 | ||||
|         while(true) { | ||||
|             readed_size = storage_process_file_read(app, &file_old, buffer, buffer_size); | ||||
|             ret = storage_file_get_error(&file_old); | ||||
|             if(readed_size == 0) break; | ||||
| 
 | ||||
|             writed_size = storage_process_file_write(app, &file_new, buffer, readed_size); | ||||
|             ret = storage_file_get_error(&file_new); | ||||
|             if(writed_size < readed_size) break; | ||||
|         } | ||||
| 
 | ||||
|         free(buffer); | ||||
|         storage_process_file_close(app, &file_old); | ||||
|         storage_process_file_close(app, &file_new); | ||||
|     } while(false); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_common_rename(Storage* app, const char* old, const char* new) { | ||||
|     FS_Error ret = FSE_INTERNAL; | ||||
|     StorageType type_old = storage_get_type_by_path(old); | ||||
|     StorageType type_new = storage_get_type_by_path(new); | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type_old) || storage_type_is_not_valid(type_old)) { | ||||
|         ret = FSE_INVALID_NAME; | ||||
|     } else { | ||||
|         if(type_old != type_new) { | ||||
|             ret = storage_process_common_copy(app, old, new); | ||||
|             if(ret == FSE_OK) { | ||||
|                 ret = storage_process_common_remove(app, old); | ||||
|             } | ||||
|         } else { | ||||
|             StorageData* storage = storage_get_storage_by_type(app, type_old); | ||||
|             FS_CALL(storage, common.rename(storage, remove_vfs(old), remove_vfs(new))); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { | ||||
|     FS_Error ret = FSE_OK; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type)) { | ||||
|         ret = FSE_INVALID_NAME; | ||||
|     } else { | ||||
|         StorageData* storage = storage_get_storage_by_type(app, type); | ||||
|         FS_CALL(storage, common.mkdir(storage, remove_vfs(path))); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_common_fs_info( | ||||
|     Storage* app, | ||||
|     const char* fs_path, | ||||
|     uint64_t* total_space, | ||||
|     uint64_t* free_space) { | ||||
|     FS_Error ret = FSE_OK; | ||||
|     StorageType type = storage_get_type_by_path(fs_path); | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type)) { | ||||
|         ret = FSE_INVALID_NAME; | ||||
|     } else { | ||||
|         StorageData* storage = storage_get_storage_by_type(app, type); | ||||
|         FS_CALL(storage, common.fs_info(storage, remove_vfs(fs_path), total_space, free_space)); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /****************** Raw SD API ******************/ | ||||
| // TODO think about implementing a custom storage API to split that kind of api linkage
 | ||||
| #include "storages/storage-ext.h" | ||||
| 
 | ||||
| static FS_Error storage_process_sd_format(Storage* app) { | ||||
|     FS_Error ret = FSE_OK; | ||||
| 
 | ||||
|     if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) { | ||||
|         ret = FSE_NOT_READY; | ||||
|     } else { | ||||
|         ret = sd_format_card(&app->storage[ST_EXT]); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_sd_unmount(Storage* app) { | ||||
|     FS_Error ret = FSE_OK; | ||||
| 
 | ||||
|     if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) { | ||||
|         ret = FSE_NOT_READY; | ||||
|     } else { | ||||
|         sd_unmount_card(&app->storage[ST_EXT]); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_sd_info(Storage* app, SDInfo* info) { | ||||
|     FS_Error ret = FSE_OK; | ||||
| 
 | ||||
|     if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusNotReady) { | ||||
|         ret = FSE_NOT_READY; | ||||
|     } else { | ||||
|         ret = sd_card_info(&app->storage[ST_EXT], info); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_sd_status(Storage* app) { | ||||
|     FS_Error ret; | ||||
|     StorageStatus status = storage_data_status(&app->storage[ST_EXT]); | ||||
| 
 | ||||
|     switch(status) { | ||||
|     case StorageStatusOK: | ||||
|         ret = FSE_OK; | ||||
|         break; | ||||
|     case StorageStatusNotReady: | ||||
|         ret = FSE_NOT_READY; | ||||
|         break; | ||||
|     default: | ||||
|         ret = FSE_INTERNAL; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| /****************** API calls processing ******************/ | ||||
| 
 | ||||
| void storage_process_message(Storage* app, StorageMessage* message) { | ||||
|     switch(message->command) { | ||||
|     case StorageCommandFileOpen: | ||||
|         message->return_data->bool_value = storage_process_file_open( | ||||
|             app, | ||||
|             message->data->fopen.file, | ||||
|             message->data->fopen.path, | ||||
|             message->data->fopen.access_mode, | ||||
|             message->data->fopen.open_mode); | ||||
|         break; | ||||
|     case StorageCommandFileClose: | ||||
|         message->return_data->bool_value = | ||||
|             storage_process_file_close(app, message->data->fopen.file); | ||||
|         break; | ||||
|     case StorageCommandFileRead: | ||||
|         message->return_data->uint16_value = storage_process_file_read( | ||||
|             app, | ||||
|             message->data->fread.file, | ||||
|             message->data->fread.buff, | ||||
|             message->data->fread.bytes_to_read); | ||||
|         break; | ||||
|     case StorageCommandFileWrite: | ||||
|         message->return_data->uint16_value = storage_process_file_write( | ||||
|             app, | ||||
|             message->data->fwrite.file, | ||||
|             message->data->fwrite.buff, | ||||
|             message->data->fwrite.bytes_to_write); | ||||
|         break; | ||||
|     case StorageCommandFileSeek: | ||||
|         message->return_data->bool_value = storage_process_file_seek( | ||||
|             app, | ||||
|             message->data->fseek.file, | ||||
|             message->data->fseek.offset, | ||||
|             message->data->fseek.from_start); | ||||
|         break; | ||||
|     case StorageCommandFileTell: | ||||
|         message->return_data->uint64_value = | ||||
|             storage_process_file_tell(app, message->data->file.file); | ||||
|         break; | ||||
|     case StorageCommandFileTruncate: | ||||
|         message->return_data->bool_value = | ||||
|             storage_process_file_truncate(app, message->data->file.file); | ||||
|         break; | ||||
|     case StorageCommandFileSync: | ||||
|         message->return_data->bool_value = | ||||
|             storage_process_file_sync(app, message->data->file.file); | ||||
|         break; | ||||
|     case StorageCommandFileSize: | ||||
|         message->return_data->uint64_value = | ||||
|             storage_process_file_size(app, message->data->file.file); | ||||
|         break; | ||||
|     case StorageCommandFileEof: | ||||
|         message->return_data->bool_value = storage_process_file_eof(app, message->data->file.file); | ||||
|         break; | ||||
| 
 | ||||
|     case StorageCommandDirOpen: | ||||
|         message->return_data->bool_value = | ||||
|             storage_process_dir_open(app, message->data->dopen.file, message->data->dopen.path); | ||||
|         break; | ||||
|     case StorageCommandDirClose: | ||||
|         message->return_data->bool_value = | ||||
|             storage_process_dir_close(app, message->data->file.file); | ||||
|         break; | ||||
|     case StorageCommandDirRead: | ||||
|         message->return_data->bool_value = storage_process_dir_read( | ||||
|             app, | ||||
|             message->data->dread.file, | ||||
|             message->data->dread.fileinfo, | ||||
|             message->data->dread.name, | ||||
|             message->data->dread.name_length); | ||||
|         break; | ||||
|     case StorageCommandDirRewind: | ||||
|         message->return_data->bool_value = | ||||
|             storage_process_dir_rewind(app, message->data->file.file); | ||||
|         break; | ||||
|     case StorageCommandCommonStat: | ||||
|         message->return_data->error_value = storage_process_common_stat( | ||||
|             app, message->data->cstat.path, message->data->cstat.fileinfo); | ||||
|         break; | ||||
|     case StorageCommandCommonRemove: | ||||
|         message->return_data->error_value = | ||||
|             storage_process_common_remove(app, message->data->path.path); | ||||
|         break; | ||||
|     case StorageCommandCommonRename: | ||||
|         message->return_data->error_value = storage_process_common_rename( | ||||
|             app, message->data->cpaths.old, message->data->cpaths.new); | ||||
|         break; | ||||
|     case StorageCommandCommonCopy: | ||||
|         message->return_data->error_value = | ||||
|             storage_process_common_copy(app, message->data->cpaths.old, message->data->cpaths.new); | ||||
|         break; | ||||
|     case StorageCommandCommonMkDir: | ||||
|         message->return_data->error_value = | ||||
|             storage_process_common_mkdir(app, message->data->path.path); | ||||
|         break; | ||||
|     case StorageCommandCommonFSInfo: | ||||
|         message->return_data->error_value = storage_process_common_fs_info( | ||||
|             app, | ||||
|             message->data->cfsinfo.fs_path, | ||||
|             message->data->cfsinfo.total_space, | ||||
|             message->data->cfsinfo.free_space); | ||||
|         break; | ||||
|     case StorageCommandSDFormat: | ||||
|         message->return_data->error_value = storage_process_sd_format(app); | ||||
|         break; | ||||
|     case StorageCommandSDUnmount: | ||||
|         message->return_data->error_value = storage_process_sd_unmount(app); | ||||
|         break; | ||||
|     case StorageCommandSDInfo: | ||||
|         message->return_data->error_value = | ||||
|             storage_process_sd_info(app, message->data->sdinfo.info); | ||||
|         break; | ||||
|     case StorageCommandSDStatus: | ||||
|         message->return_data->error_value = storage_process_sd_status(app); | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     osSemaphoreRelease(message->semaphore); | ||||
| } | ||||
							
								
								
									
										16
									
								
								applications/storage/storage-processing.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								applications/storage/storage-processing.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "storage.h" | ||||
| #include "storage-i.h" | ||||
| #include "storage-message.h" | ||||
| #include "storage-glue.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void storage_process_message(Storage* app, StorageMessage* message); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										21
									
								
								applications/storage/storage-sd-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								applications/storage/storage-sd-api.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| #include "storage-sd-api.h" | ||||
| 
 | ||||
| const char* sd_api_get_fs_type_text(SDFsType fs_type) { | ||||
|     switch(fs_type) { | ||||
|     case(FST_FAT12): | ||||
|         return "FAT12"; | ||||
|         break; | ||||
|     case(FST_FAT16): | ||||
|         return "FAT16"; | ||||
|         break; | ||||
|     case(FST_FAT32): | ||||
|         return "FAT32"; | ||||
|         break; | ||||
|     case(FST_EXFAT): | ||||
|         return "EXFAT"; | ||||
|         break; | ||||
|     default: | ||||
|         return "UNKNOWN"; | ||||
|         break; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								applications/storage/storage-sd-api.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								applications/storage/storage-sd-api.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "filesystem-api-defines.h" | ||||
| #include <fatfs.h> | ||||
| #include "storage-glue.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #define SD_LABEL_LENGTH 34 | ||||
| 
 | ||||
| typedef enum { | ||||
|     FST_FAT12 = FS_FAT12, | ||||
|     FST_FAT16 = FS_FAT16, | ||||
|     FST_FAT32 = FS_FAT32, | ||||
|     FST_EXFAT = FS_EXFAT, | ||||
| } SDFsType; | ||||
| 
 | ||||
| typedef struct { | ||||
|     SDFsType fs_type; | ||||
|     uint32_t kb_total; | ||||
|     uint32_t kb_free; | ||||
|     uint16_t cluster_size; | ||||
|     uint16_t sector_size; | ||||
|     char label[SD_LABEL_LENGTH]; | ||||
|     FS_Error error; | ||||
| } SDInfo; | ||||
| 
 | ||||
| const char* sd_api_get_fs_type_text(SDFsType fs_type); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										341
									
								
								applications/storage/storage-test-app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								applications/storage/storage-test-app.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,341 @@ | ||||
| #include <furi.h> | ||||
| #include <api-hal.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #define TAG "storage-test" | ||||
| #define BYTES_COUNT 16 | ||||
| #define TEST_STRING "TestDataStringProvidedByDiceRoll" | ||||
| #define SEEK_OFFSET_FROM_START 10 | ||||
| #define SEEK_OFFSET_INCREASE 12 | ||||
| #define SEEK_OFFSET_SUM (SEEK_OFFSET_FROM_START + SEEK_OFFSET_INCREASE) | ||||
| 
 | ||||
| static void do_file_test(Storage* api, const char* path) { | ||||
|     File* file = storage_file_alloc(api); | ||||
|     bool result; | ||||
|     uint8_t bytes[BYTES_COUNT + 1]; | ||||
|     uint8_t bytes_count; | ||||
|     uint64_t position; | ||||
|     uint64_t size; | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "--------- FILE \"%s\" ---------", path); | ||||
| 
 | ||||
|     // open
 | ||||
|     result = storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "open"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     // write
 | ||||
|     bytes_count = storage_file_write(file, TEST_STRING, strlen(TEST_STRING)); | ||||
|     if(bytes_count == 0) { | ||||
|         FURI_LOG_E(TAG, "write, %s", storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         FURI_LOG_I(TAG, "write"); | ||||
|     } | ||||
| 
 | ||||
|     // sync
 | ||||
|     result = storage_file_sync(file); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "sync"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "sync, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     // eof #1
 | ||||
|     result = storage_file_eof(file); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "eof #1"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "eof #1, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     // seek from start and tell
 | ||||
|     result = storage_file_seek(file, SEEK_OFFSET_FROM_START, true); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "seek #1"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "seek #1, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
|     position = storage_file_tell(file); | ||||
|     if(position != SEEK_OFFSET_FROM_START) { | ||||
|         FURI_LOG_E(TAG, "tell #1, %s", storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         FURI_LOG_I(TAG, "tell #1"); | ||||
|     } | ||||
| 
 | ||||
|     // size
 | ||||
|     size = storage_file_size(file); | ||||
|     if(size != strlen(TEST_STRING)) { | ||||
|         FURI_LOG_E(TAG, "size #1, %s", storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         FURI_LOG_I(TAG, "size #1"); | ||||
|     } | ||||
| 
 | ||||
|     // seek and tell
 | ||||
|     result = storage_file_seek(file, SEEK_OFFSET_INCREASE, false); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "seek #2"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "seek #2, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
|     position = storage_file_tell(file); | ||||
|     if(position != SEEK_OFFSET_SUM) { | ||||
|         FURI_LOG_E(TAG, "tell #2, %s", storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         FURI_LOG_I(TAG, "tell #2"); | ||||
|     } | ||||
| 
 | ||||
|     // eof #2
 | ||||
|     result = storage_file_eof(file); | ||||
|     if(!result) { | ||||
|         FURI_LOG_I(TAG, "eof #2"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "eof #2, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     // truncate
 | ||||
|     result = storage_file_truncate(file); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "truncate"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "truncate, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
|     size = storage_file_size(file); | ||||
|     if(size != SEEK_OFFSET_SUM) { | ||||
|         FURI_LOG_E(TAG, "size #2, %s", storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         FURI_LOG_I(TAG, "size #2"); | ||||
|     } | ||||
| 
 | ||||
|     // close
 | ||||
|     result = storage_file_close(file); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "close"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "close, error"); | ||||
|     } | ||||
| 
 | ||||
|     // open
 | ||||
|     result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "open"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     // read
 | ||||
|     memset(bytes, 0, BYTES_COUNT + 1); | ||||
|     bytes_count = storage_file_read(file, bytes, BYTES_COUNT); | ||||
|     if(bytes_count == 0) { | ||||
|         FURI_LOG_E(TAG, "read, %s", storage_file_get_error_desc(file)); | ||||
|     } else { | ||||
|         if(memcmp(TEST_STRING, bytes, bytes_count) == 0) { | ||||
|             FURI_LOG_I(TAG, "read"); | ||||
|         } else { | ||||
|             FURI_LOG_E(TAG, "read, garbage"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // close
 | ||||
|     result = storage_file_close(file); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "close"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "close, error"); | ||||
|     } | ||||
| 
 | ||||
|     storage_file_free(file); | ||||
| } | ||||
| 
 | ||||
| static void do_dir_test(Storage* api, const char* path) { | ||||
|     File* file = storage_file_alloc(api); | ||||
|     bool result; | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "--------- DIR \"%s\" ---------", path); | ||||
| 
 | ||||
|     // open
 | ||||
|     result = storage_dir_open(file, path); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "open"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "open, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     // read
 | ||||
|     const uint8_t filename_size = 100; | ||||
|     char* filename = malloc(filename_size); | ||||
|     FileInfo fileinfo; | ||||
| 
 | ||||
|     do { | ||||
|         result = storage_dir_read(file, &fileinfo, filename, filename_size); | ||||
|         if(result) { | ||||
|             if(strlen(filename)) { | ||||
|                 FURI_LOG_I( | ||||
|                     TAG, | ||||
|                     "read #1, [%s]%s", | ||||
|                     ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), | ||||
|                     filename); | ||||
|             } | ||||
|         } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { | ||||
|             FURI_LOG_E(TAG, "read #1, %s", storage_file_get_error_desc(file)); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|     } while(result); | ||||
| 
 | ||||
|     // rewind
 | ||||
|     result = storage_dir_rewind(file); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "rewind"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "rewind, %s", storage_file_get_error_desc(file)); | ||||
|     } | ||||
| 
 | ||||
|     // read
 | ||||
|     do { | ||||
|         result = storage_dir_read(file, &fileinfo, filename, filename_size); | ||||
|         if(result) { | ||||
|             if(strlen(filename)) { | ||||
|                 FURI_LOG_I( | ||||
|                     TAG, | ||||
|                     "read #2, [%s]%s", | ||||
|                     ((fileinfo.flags & FSF_DIRECTORY) ? "D" : "F"), | ||||
|                     filename); | ||||
|             } | ||||
|         } else if(storage_file_get_error(file) != FSE_NOT_EXIST) { | ||||
|             FURI_LOG_E(TAG, "read #2, %s", storage_file_get_error_desc(file)); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|     } while((strlen(filename))); | ||||
| 
 | ||||
|     // close
 | ||||
|     result = storage_dir_close(file); | ||||
|     if(result) { | ||||
|         FURI_LOG_I(TAG, "close"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "close, error"); | ||||
|     } | ||||
| 
 | ||||
|     storage_file_free(file); | ||||
|     free(filename); | ||||
| } | ||||
| 
 | ||||
| static void do_test_start(Storage* api, const char* path) { | ||||
|     string_t str_path; | ||||
|     string_init_printf(str_path, "%s/test-folder", path); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "--------- START \"%s\" ---------", path); | ||||
| 
 | ||||
|     // mkdir
 | ||||
|     FS_Error result = storage_common_mkdir(api, string_get_cstr(str_path)); | ||||
| 
 | ||||
|     if(result == FSE_OK) { | ||||
|         FURI_LOG_I(TAG, "mkdir ok"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "mkdir, %s", storage_error_get_desc(result)); | ||||
|     } | ||||
| 
 | ||||
|     // stat
 | ||||
|     FileInfo fileinfo; | ||||
|     result = storage_common_stat(api, string_get_cstr(str_path), &fileinfo); | ||||
| 
 | ||||
|     if(result == FSE_OK) { | ||||
|         if(fileinfo.flags & FSF_DIRECTORY) { | ||||
|             FURI_LOG_I(TAG, "stat #1 ok"); | ||||
|         } else { | ||||
|             FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); | ||||
|         } | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "stat #1, %s", storage_error_get_desc(result)); | ||||
|     } | ||||
| 
 | ||||
|     string_clear(str_path); | ||||
| } | ||||
| 
 | ||||
| static void do_test_end(Storage* api, const char* path) { | ||||
|     uint64_t total_space; | ||||
|     uint64_t free_space; | ||||
|     string_t str_path_1; | ||||
|     string_t str_path_2; | ||||
|     string_init_printf(str_path_1, "%s/test-folder", path); | ||||
|     string_init_printf(str_path_2, "%s/test-folder2", path); | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "--------- END \"%s\" ---------", path); | ||||
| 
 | ||||
|     // fs stat
 | ||||
|     FS_Error result = storage_common_fs_info(api, path, &total_space, &free_space); | ||||
| 
 | ||||
|     if(result == FSE_OK) { | ||||
|         uint32_t total_kb = total_space / 1024; | ||||
|         uint32_t free_kb = free_space / 1024; | ||||
|         FURI_LOG_I(TAG, "fs_info: total %luk, free %luk", total_kb, free_kb); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "fs_info, %s", storage_error_get_desc(result)); | ||||
|     } | ||||
| 
 | ||||
|     // rename #1
 | ||||
|     result = storage_common_rename(api, string_get_cstr(str_path_1), string_get_cstr(str_path_2)); | ||||
|     if(result == FSE_OK) { | ||||
|         FURI_LOG_I(TAG, "rename #1 ok"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "rename #1, %s", storage_error_get_desc(result)); | ||||
|     } | ||||
| 
 | ||||
|     // remove #1
 | ||||
|     result = storage_common_remove(api, string_get_cstr(str_path_2)); | ||||
|     if(result == FSE_OK) { | ||||
|         FURI_LOG_I(TAG, "remove #1 ok"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "remove #1, %s", storage_error_get_desc(result)); | ||||
|     } | ||||
| 
 | ||||
|     // rename #2
 | ||||
|     string_printf(str_path_1, "%s/test.txt", path); | ||||
|     string_printf(str_path_2, "%s/test2.txt", path); | ||||
| 
 | ||||
|     result = storage_common_rename(api, string_get_cstr(str_path_1), string_get_cstr(str_path_2)); | ||||
|     if(result == FSE_OK) { | ||||
|         FURI_LOG_I(TAG, "rename #2 ok"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "rename #2, %s", storage_error_get_desc(result)); | ||||
|     } | ||||
| 
 | ||||
|     // remove #2
 | ||||
|     result = storage_common_remove(api, string_get_cstr(str_path_2)); | ||||
|     if(result == FSE_OK) { | ||||
|         FURI_LOG_I(TAG, "remove #2 ok"); | ||||
|     } else { | ||||
|         FURI_LOG_E(TAG, "remove #2, %s", storage_error_get_desc(result)); | ||||
|     } | ||||
| 
 | ||||
|     string_clear(str_path_1); | ||||
|     string_clear(str_path_2); | ||||
| } | ||||
| 
 | ||||
| int32_t storage_app_test(void* p) { | ||||
|     Storage* api = furi_record_open("storage"); | ||||
|     do_test_start(api, "/int"); | ||||
|     do_test_start(api, "/any"); | ||||
|     do_test_start(api, "/ext"); | ||||
| 
 | ||||
|     do_file_test(api, "/int/test.txt"); | ||||
|     do_file_test(api, "/any/test.txt"); | ||||
|     do_file_test(api, "/ext/test.txt"); | ||||
| 
 | ||||
|     do_dir_test(api, "/int"); | ||||
|     do_dir_test(api, "/any"); | ||||
|     do_dir_test(api, "/ext"); | ||||
| 
 | ||||
|     do_test_end(api, "/int"); | ||||
|     do_test_end(api, "/any"); | ||||
|     do_test_end(api, "/ext"); | ||||
| 
 | ||||
|     while(true) { | ||||
|         delay(1000); | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										96
									
								
								applications/storage/storage.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								applications/storage/storage.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | ||||
| #include "storage.h" | ||||
| #include "storage-i.h" | ||||
| #include "storage-message.h" | ||||
| #include "storage-processing.h" | ||||
| #include "storages/storage-int.h" | ||||
| #include "storages/storage-ext.h" | ||||
| 
 | ||||
| #define STORAGE_TICK 1000 | ||||
| 
 | ||||
| #define ICON_SD_MOUNTED &I_SDcardMounted_11x8 | ||||
| #define ICON_SD_ERROR &I_SDcardFail_11x8 | ||||
| 
 | ||||
| static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) { | ||||
|     furi_assert(canvas); | ||||
|     furi_assert(context); | ||||
|     Storage* app = context; | ||||
| 
 | ||||
|     // here we don't care about thread race when reading / writing status
 | ||||
|     switch(app->storage[ST_EXT].status) { | ||||
|     case StorageStatusNotReady: | ||||
|         break; | ||||
|     case StorageStatusOK: | ||||
|         canvas_draw_icon(canvas, 0, 0, ICON_SD_MOUNTED); | ||||
|         break; | ||||
|     default: | ||||
|         canvas_draw_icon(canvas, 0, 0, ICON_SD_ERROR); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| Storage* storage_app_alloc() { | ||||
|     Storage* app = malloc(sizeof(Storage)); | ||||
|     app->message_queue = osMessageQueueNew(8, sizeof(StorageMessage), NULL); | ||||
| 
 | ||||
|     for(uint8_t i = 0; i < STORAGE_COUNT; i++) { | ||||
|         storage_data_init(&app->storage[i]); | ||||
|     } | ||||
| 
 | ||||
|     storage_int_init(&app->storage[ST_INT]); | ||||
|     storage_ext_init(&app->storage[ST_EXT]); | ||||
| 
 | ||||
|     // sd icon gui
 | ||||
|     app->sd_gui.enabled = false; | ||||
|     app->sd_gui.view_port = view_port_alloc(); | ||||
|     view_port_set_width(app->sd_gui.view_port, icon_get_width(ICON_SD_MOUNTED)); | ||||
|     view_port_draw_callback_set(app->sd_gui.view_port, storage_app_sd_icon_draw_callback, app); | ||||
|     view_port_enabled_set(app->sd_gui.view_port, false); | ||||
| 
 | ||||
|     Gui* gui = furi_record_open("gui"); | ||||
|     gui_add_view_port(gui, app->sd_gui.view_port, GuiLayerStatusBarLeft); | ||||
|     furi_record_close("gui"); | ||||
| 
 | ||||
|     return app; | ||||
| } | ||||
| 
 | ||||
| void storage_tick(Storage* app) { | ||||
|     for(uint8_t i = 0; i < STORAGE_COUNT; i++) { | ||||
|         StorageApi api = app->storage[i].api; | ||||
|         if(api.tick != NULL) { | ||||
|             api.tick(&app->storage[i]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // storage not enabled but was enabled (sd card unmount)
 | ||||
|     if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) { | ||||
|         app->sd_gui.enabled = false; | ||||
|         view_port_enabled_set(app->sd_gui.view_port, false); | ||||
|     } | ||||
| 
 | ||||
|     // storage enabled (or in error state) but was not enabled (sd card mount)
 | ||||
|     if((app->storage[ST_EXT].status == StorageStatusOK || | ||||
|         app->storage[ST_EXT].status == StorageStatusNotMounted || | ||||
|         app->storage[ST_EXT].status == StorageStatusNoFS || | ||||
|         app->storage[ST_EXT].status == StorageStatusNotAccessible || | ||||
|         app->storage[ST_EXT].status == StorageStatusErrorInternal) && | ||||
|        app->sd_gui.enabled == false) { | ||||
|         app->sd_gui.enabled = true; | ||||
|         view_port_enabled_set(app->sd_gui.view_port, true); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| int32_t storage_app(void* p) { | ||||
|     Storage* app = storage_app_alloc(); | ||||
|     furi_record_create("storage", app); | ||||
| 
 | ||||
|     StorageMessage message; | ||||
|     while(1) { | ||||
|         if(osMessageQueueGet(app->message_queue, &message, NULL, STORAGE_TICK) == osOK) { | ||||
|             storage_process_message(app, &message); | ||||
|         } else { | ||||
|             storage_tick(app); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
							
								
								
									
										235
									
								
								applications/storage/storage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								applications/storage/storage.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,235 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "filesystem-api-defines.h" | ||||
| #include "storage-sd-api.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct Storage Storage; | ||||
| 
 | ||||
| /** Allocates and initializes a file descriptor
 | ||||
|  * @return File* | ||||
|  */ | ||||
| File* storage_file_alloc(Storage* storage); | ||||
| 
 | ||||
| /** Frees the file descriptor. Closes the file if it was open.
 | ||||
|  */ | ||||
| void storage_file_free(File* file); | ||||
| 
 | ||||
| /******************* File Functions *******************/ | ||||
| 
 | ||||
| /** Opens an existing file or create a new one.
 | ||||
|  * @param file pointer to file object. | ||||
|  * @param path path to file  | ||||
|  * @param access_mode access mode from FS_AccessMode  | ||||
|  * @param open_mode open mode from FS_OpenMode  | ||||
|  * @return success flag. You need to close the file even if the open operation failed. | ||||
|  */ | ||||
| bool storage_file_open( | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     FS_AccessMode access_mode, | ||||
|     FS_OpenMode open_mode); | ||||
| 
 | ||||
| /** Close the file.
 | ||||
|  * @param file pointer to a file object, the file object will be freed. | ||||
|  * @return success flag | ||||
|  */ | ||||
| bool storage_file_close(File* file); | ||||
| 
 | ||||
| /** Tells if the file is open
 | ||||
|  * @param file pointer to a file object | ||||
|  * @return bool true if file is open | ||||
|  */ | ||||
| bool storage_file_is_open(File* file); | ||||
| 
 | ||||
| /** Reads bytes from a file into a buffer
 | ||||
|  * @param file pointer to file object. | ||||
|  * @param buff pointer to a buffer, for reading | ||||
|  * @param bytes_to_read how many bytes to read. Must be less than or equal to the size of the buffer. | ||||
|  * @return uint16_t how many bytes were actually readed | ||||
|  */ | ||||
| uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read); | ||||
| 
 | ||||
| /** Writes bytes from a buffer to a file
 | ||||
|  * @param file pointer to file object. | ||||
|  * @param buff pointer to buffer, for writing | ||||
|  * @param bytes_to_write how many bytes to write. Must be less than or equal to the size of the buffer. | ||||
|  * @return uint16_t how many bytes were actually written | ||||
|  */ | ||||
| uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write); | ||||
| 
 | ||||
| /** Moves the r/w pointer 
 | ||||
|  * @param file pointer to file object. | ||||
|  * @param offset offset to move the r/w pointer | ||||
|  * @param from_start set an offset from the start or from the current position | ||||
|  * @return success flag | ||||
|  */ | ||||
| bool storage_file_seek(File* file, uint32_t offset, bool from_start); | ||||
| 
 | ||||
| /** Gets the position of the r/w pointer 
 | ||||
|  * @param file pointer to file object. | ||||
|  * @return uint64_t position of the r/w pointer  | ||||
|  */ | ||||
| uint64_t storage_file_tell(File* file); | ||||
| 
 | ||||
| /** Truncates the file size to the current position of the r/w pointer
 | ||||
|  * @param file pointer to file object. | ||||
|  * @return bool success flag | ||||
|  */ | ||||
| bool storage_file_truncate(File* file); | ||||
| 
 | ||||
| /** Gets the size of the file
 | ||||
|  * @param file pointer to file object. | ||||
|  * @return uint64_t size of the file | ||||
|  */ | ||||
| uint64_t storage_file_size(File* file); | ||||
| 
 | ||||
| /** Writes file cache to storage
 | ||||
|  * @param file pointer to file object. | ||||
|  * @return bool success flag | ||||
|  */ | ||||
| bool storage_file_sync(File* file); | ||||
| 
 | ||||
| /** Checks that the r/w pointer is at the end of the file
 | ||||
|  * @param file pointer to file object. | ||||
|  * @return bool success flag | ||||
|  */ | ||||
| bool storage_file_eof(File* file); | ||||
| 
 | ||||
| /******************* Dir Functions *******************/ | ||||
| 
 | ||||
| /** Opens a directory to get objects from it
 | ||||
|  * @param app pointer to the api | ||||
|  * @param file pointer to file object. | ||||
|  * @param path path to directory | ||||
|  * @return bool success flag. You need to close the directory even if the open operation failed. | ||||
|  */ | ||||
| bool storage_dir_open(File* file, const char* path); | ||||
| 
 | ||||
| /** Close the directory. Also free file handle structure and point it to the NULL.
 | ||||
|  * @param file pointer to a file object. | ||||
|  * @return bool success flag | ||||
|  */ | ||||
| bool storage_dir_close(File* file); | ||||
| 
 | ||||
| /** Reads the next object in the directory
 | ||||
|  * @param file pointer to file object. | ||||
|  * @param fileinfo pointer to the readed FileInfo, may be NULL | ||||
|  * @param name pointer to name buffer, may be NULL | ||||
|  * @param name_length name buffer length | ||||
|  * @return success flag (if the next object does not exist, it also returns false and sets the file error id to FSE_NOT_EXIST) | ||||
|  */ | ||||
| bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||
| 
 | ||||
| /** Rewinds the read pointer to first item in the directory
 | ||||
|  * @param file pointer to file object. | ||||
|  * @return bool success flag | ||||
|  */ | ||||
| bool storage_dir_rewind(File* file); | ||||
| 
 | ||||
| /******************* Common Functions *******************/ | ||||
| 
 | ||||
| /** Retrieves information about a file/directory
 | ||||
|  * @param app pointer to the api | ||||
|  * @param path path to file/directory | ||||
|  * @param fileinfo pointer to the readed FileInfo, may be NULL | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo); | ||||
| 
 | ||||
| /** Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open
 | ||||
|  * @param app pointer to the api | ||||
|  * @param path  | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_common_remove(Storage* storage, const char* path); | ||||
| 
 | ||||
| /** Renames file/directory, file/directory must not be open
 | ||||
|  * @param app pointer to the api | ||||
|  * @param old_path old path | ||||
|  * @param new_path new path | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path); | ||||
| 
 | ||||
| /** Copy file, file must not be open
 | ||||
|  * @param app pointer to the api | ||||
|  * @param old_path old path | ||||
|  * @param new_path new path | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path); | ||||
| 
 | ||||
| /** Creates a directory
 | ||||
|  * @param app pointer to the api | ||||
|  * @param path directory path | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_common_mkdir(Storage* storage, const char* path); | ||||
| 
 | ||||
| /** Gets general information about the storage
 | ||||
|  * @param app pointer to the api | ||||
|  * @param fs_path the path to the storage of interest | ||||
|  * @param total_space pointer to total space record, will be filled | ||||
|  * @param free_space pointer to free space record, will be filled | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_common_fs_info( | ||||
|     Storage* storage, | ||||
|     const char* fs_path, | ||||
|     uint64_t* total_space, | ||||
|     uint64_t* free_space); | ||||
| 
 | ||||
| /******************* Error Functions *******************/ | ||||
| 
 | ||||
| /** Retrieves the error text from the error id
 | ||||
|  * @param error_id error id | ||||
|  * @return const char* error text | ||||
|  */ | ||||
| const char* storage_error_get_desc(FS_Error error_id); | ||||
| 
 | ||||
| /** Retrieves the error id from the file object
 | ||||
|  * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR ID IF THE FILE HAS BEEN CLOSED | ||||
|  * @return FS_Error error id | ||||
|  */ | ||||
| FS_Error storage_file_get_error(File* file); | ||||
| 
 | ||||
| /** Retrieves the error text from the file object
 | ||||
|  * @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETREIVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED | ||||
|  * @return const char* error text | ||||
|  */ | ||||
| const char* storage_file_get_error_desc(File* file); | ||||
| 
 | ||||
| /******************* SD Card Functions *******************/ | ||||
| 
 | ||||
| /** Formats SD Card
 | ||||
|  * @param api pointer to the api | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_sd_format(Storage* api); | ||||
| 
 | ||||
| /** Will unmount the SD card
 | ||||
|  * @param api pointer to the api | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_sd_unmount(Storage* api); | ||||
| 
 | ||||
| /** Retrieves SD card information
 | ||||
|  * @param api pointer to the api | ||||
|  * @param info pointer to the info | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_sd_info(Storage* api, SDInfo* info); | ||||
| 
 | ||||
| /** Retrieves SD card status
 | ||||
|  * @param api pointer to the api | ||||
|  * @return FS_Error operation result | ||||
|  */ | ||||
| FS_Error storage_sd_status(Storage* api); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										82
									
								
								applications/storage/storages/sd-notify.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								applications/storage/storages/sd-notify.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| #include "sd-notify.h" | ||||
| 
 | ||||
| static const NotificationSequence sd_sequence_success = { | ||||
|     &message_green_255, | ||||
|     &message_delay_50, | ||||
|     &message_green_0, | ||||
|     &message_delay_50, | ||||
|     &message_green_255, | ||||
|     &message_delay_50, | ||||
|     &message_green_0, | ||||
|     &message_delay_50, | ||||
|     &message_green_255, | ||||
|     &message_delay_50, | ||||
|     &message_green_0, | ||||
|     &message_delay_50, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static const NotificationSequence sd_sequence_error = { | ||||
|     &message_red_255, | ||||
|     &message_delay_50, | ||||
|     &message_red_0, | ||||
|     &message_delay_50, | ||||
|     &message_red_255, | ||||
|     &message_delay_50, | ||||
|     &message_red_0, | ||||
|     &message_delay_50, | ||||
|     &message_red_255, | ||||
|     &message_delay_50, | ||||
|     &message_red_0, | ||||
|     &message_delay_50, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static const NotificationSequence sd_sequence_eject = { | ||||
|     &message_blue_255, | ||||
|     &message_delay_50, | ||||
|     &message_blue_0, | ||||
|     &message_delay_50, | ||||
|     &message_blue_255, | ||||
|     &message_delay_50, | ||||
|     &message_blue_0, | ||||
|     &message_delay_50, | ||||
|     &message_blue_255, | ||||
|     &message_delay_50, | ||||
|     &message_blue_0, | ||||
|     &message_delay_50, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static const NotificationSequence sd_sequence_wait = { | ||||
|     &message_red_255, | ||||
|     &message_blue_255, | ||||
|     &message_do_not_reset, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| static const NotificationSequence sd_sequence_wait_off = { | ||||
|     &message_red_0, | ||||
|     &message_blue_0, | ||||
|     NULL, | ||||
| }; | ||||
| 
 | ||||
| void sd_notify_wait(NotificationApp* notifications) { | ||||
|     notification_message(notifications, &sd_sequence_wait); | ||||
| } | ||||
| 
 | ||||
| void sd_notify_wait_off(NotificationApp* notifications) { | ||||
|     notification_message(notifications, &sd_sequence_wait_off); | ||||
| } | ||||
| 
 | ||||
| void sd_notify_success(NotificationApp* notifications) { | ||||
|     notification_message(notifications, &sd_sequence_success); | ||||
| } | ||||
| 
 | ||||
| void sd_notify_eject(NotificationApp* notifications) { | ||||
|     notification_message(notifications, &sd_sequence_eject); | ||||
| } | ||||
| 
 | ||||
| void sd_notify_error(NotificationApp* notifications) { | ||||
|     notification_message(notifications, &sd_sequence_error); | ||||
| } | ||||
							
								
								
									
										17
									
								
								applications/storage/storages/sd-notify.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								applications/storage/storages/sd-notify.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <notification/notification-messages.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void sd_notify_wait(NotificationApp* notifications); | ||||
| void sd_notify_wait_off(NotificationApp* notifications); | ||||
| void sd_notify_success(NotificationApp* notifications); | ||||
| void sd_notify_eject(NotificationApp* notifications); | ||||
| void sd_notify_error(NotificationApp* notifications); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										547
									
								
								applications/storage/storages/storage-ext.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										547
									
								
								applications/storage/storages/storage-ext.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,547 @@ | ||||
| #include "fatfs.h" | ||||
| #include "../filesystem-api-internal.h" | ||||
| #include "storage-ext.h" | ||||
| #include <api-hal.h> | ||||
| #include "sd-notify.h" | ||||
| #include <api-hal-sd.h> | ||||
| 
 | ||||
| typedef FIL SDFile; | ||||
| typedef DIR SDDir; | ||||
| typedef FILINFO SDFileInfo; | ||||
| typedef FRESULT SDError; | ||||
| 
 | ||||
| #define TAG "storage-ext" | ||||
| #define STORAGE_PATH "/ext" | ||||
| /********************* Definitions ********************/ | ||||
| 
 | ||||
| typedef struct { | ||||
|     FATFS* fs; | ||||
|     const char* path; | ||||
|     bool sd_was_present; | ||||
| } SDData; | ||||
| 
 | ||||
| static FS_Error storage_ext_parse_error(SDError error); | ||||
| 
 | ||||
| /******************* Core Functions *******************/ | ||||
| 
 | ||||
| static bool sd_mount_card(StorageData* storage, bool notify) { | ||||
|     bool result = false; | ||||
|     const uint8_t max_init_counts = 10; | ||||
|     uint8_t counter = max_init_counts; | ||||
|     uint8_t bsp_result; | ||||
|     SDData* sd_data = storage->data; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
| 
 | ||||
|     while(result == false && counter > 0 && hal_sd_detect()) { | ||||
|         if(notify) { | ||||
|             NotificationApp* notification = furi_record_open("notification"); | ||||
|             sd_notify_wait(notification); | ||||
|             furi_record_close("notification"); | ||||
|         } | ||||
| 
 | ||||
|         if((counter % 2) == 0) { | ||||
|             // power reset sd card
 | ||||
|             bsp_result = BSP_SD_Init(true); | ||||
|         } else { | ||||
|             bsp_result = BSP_SD_Init(false); | ||||
|         } | ||||
| 
 | ||||
|         if(bsp_result) { | ||||
|             // bsp error
 | ||||
|             storage->status = StorageStatusErrorInternal; | ||||
|         } else { | ||||
|             SDError status = f_mount(sd_data->fs, sd_data->path, 1); | ||||
| 
 | ||||
|             if(status == FR_OK || status == FR_NO_FILESYSTEM) { | ||||
|                 FATFS* fs; | ||||
|                 uint32_t free_clusters; | ||||
| 
 | ||||
|                 status = f_getfree(sd_data->path, &free_clusters, &fs); | ||||
| 
 | ||||
|                 if(status == FR_OK || status == FR_NO_FILESYSTEM) { | ||||
|                     result = true; | ||||
|                 } | ||||
| 
 | ||||
|                 if(status == FR_OK) { | ||||
|                     storage->status = StorageStatusOK; | ||||
|                 } else if(status == FR_NO_FILESYSTEM) { | ||||
|                     storage->status = StorageStatusNoFS; | ||||
|                 } else { | ||||
|                     storage->status = StorageStatusNotAccessible; | ||||
|                 } | ||||
|             } else { | ||||
|                 storage->status = StorageStatusNotMounted; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(notify) { | ||||
|             NotificationApp* notification = furi_record_open("notification"); | ||||
|             sd_notify_wait_off(notification); | ||||
|             furi_record_close("notification"); | ||||
|         } | ||||
| 
 | ||||
|         if(!result) { | ||||
|             delay(1000); | ||||
|             FURI_LOG_E( | ||||
|                 TAG, "init cycle %d, error: %s", counter, storage_data_status_text(storage)); | ||||
|             counter--; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| FS_Error sd_unmount_card(StorageData* storage) { | ||||
|     SDData* sd_data = storage->data; | ||||
|     SDError error; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
|     error = storage->status = StorageStatusNotReady; | ||||
| 
 | ||||
|     // TODO do i need to close the files?
 | ||||
| 
 | ||||
|     f_mount(0, sd_data->path, 0); | ||||
|     storage_data_unlock(storage); | ||||
|     return storage_ext_parse_error(error); | ||||
| } | ||||
| 
 | ||||
| FS_Error sd_format_card(StorageData* storage) { | ||||
|     uint8_t* work_area; | ||||
|     SDData* sd_data = storage->data; | ||||
|     SDError error; | ||||
| 
 | ||||
|     storage_data_lock(storage); | ||||
| 
 | ||||
|     work_area = malloc(_MAX_SS); | ||||
|     error = f_mkfs(sd_data->path, FM_ANY, 0, work_area, _MAX_SS); | ||||
|     free(work_area); | ||||
| 
 | ||||
|     do { | ||||
|         storage->status = StorageStatusNotAccessible; | ||||
|         if(error != FR_OK) break; | ||||
|         storage->status = StorageStatusNoFS; | ||||
|         error = f_setlabel("Flipper SD"); | ||||
|         if(error != FR_OK) break; | ||||
|         storage->status = StorageStatusNotMounted; | ||||
|         error = f_mount(sd_data->fs, sd_data->path, 1); | ||||
|         if(error != FR_OK) break; | ||||
|         storage->status = StorageStatusOK; | ||||
|     } while(false); | ||||
| 
 | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     return storage_ext_parse_error(error); | ||||
| } | ||||
| 
 | ||||
| FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { | ||||
|     uint32_t free_clusters, free_sectors, total_sectors; | ||||
|     FATFS* fs; | ||||
|     SDData* sd_data = storage->data; | ||||
|     SDError error; | ||||
| 
 | ||||
|     // clean data
 | ||||
|     memset(sd_info, 0, sizeof(SDInfo)); | ||||
| 
 | ||||
|     // get fs info
 | ||||
|     storage_data_lock(storage); | ||||
|     error = f_getlabel(sd_data->path, sd_info->label, NULL); | ||||
|     if(error == FR_OK) { | ||||
|         error = f_getfree(sd_data->path, &free_clusters, &fs); | ||||
|     } | ||||
|     storage_data_unlock(storage); | ||||
| 
 | ||||
|     if(error == FR_OK) { | ||||
|         // calculate size
 | ||||
|         total_sectors = (fs->n_fatent - 2) * fs->csize; | ||||
|         free_sectors = free_clusters * fs->csize; | ||||
| 
 | ||||
|         uint16_t sector_size = _MAX_SS; | ||||
| #if _MAX_SS != _MIN_SS | ||||
|         sector_size = fs->ssize; | ||||
| #endif | ||||
| 
 | ||||
|         sd_info->fs_type = fs->fs_type; | ||||
| 
 | ||||
|         sd_info->kb_total = total_sectors / 1024 * sector_size; | ||||
|         sd_info->kb_free = free_sectors / 1024 * sector_size; | ||||
|         sd_info->cluster_size = fs->csize; | ||||
|         sd_info->sector_size = sector_size; | ||||
|     } | ||||
| 
 | ||||
|     return storage_ext_parse_error(error); | ||||
| } | ||||
| 
 | ||||
| static void storage_ext_tick_internal(StorageData* storage, bool notify) { | ||||
|     SDData* sd_data = storage->data; | ||||
| 
 | ||||
|     if(sd_data->sd_was_present) { | ||||
|         if(hal_sd_detect()) { | ||||
|             FURI_LOG_I(TAG, "card detected"); | ||||
|             sd_mount_card(storage, notify); | ||||
| 
 | ||||
|             if(storage->status != StorageStatusOK) { | ||||
|                 FURI_LOG_E(TAG, "sd init error: %s", storage_data_status_text(storage)); | ||||
|                 if(notify) { | ||||
|                     NotificationApp* notification = furi_record_open("notification"); | ||||
|                     sd_notify_error(notification); | ||||
|                     furi_record_close("notification"); | ||||
|                 } | ||||
|             } else { | ||||
|                 FURI_LOG_I(TAG, "card mounted"); | ||||
|                 if(notify) { | ||||
|                     NotificationApp* notification = furi_record_open("notification"); | ||||
|                     sd_notify_success(notification); | ||||
|                     furi_record_close("notification"); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             sd_data->sd_was_present = false; | ||||
| 
 | ||||
|             if(!hal_sd_detect()) { | ||||
|                 FURI_LOG_I(TAG, "card removed while mounting"); | ||||
|                 sd_unmount_card(storage); | ||||
|                 sd_data->sd_was_present = true; | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         if(!hal_sd_detect()) { | ||||
|             FURI_LOG_I(TAG, "card removed"); | ||||
|             sd_data->sd_was_present = true; | ||||
| 
 | ||||
|             sd_unmount_card(storage); | ||||
|             if(notify) { | ||||
|                 NotificationApp* notification = furi_record_open("notification"); | ||||
|                 sd_notify_eject(notification); | ||||
|                 furi_record_close("notification"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void storage_ext_tick(StorageData* storage) { | ||||
|     storage_ext_tick_internal(storage, true); | ||||
| } | ||||
| 
 | ||||
| /****************** Common Functions ******************/ | ||||
| 
 | ||||
| static FS_Error storage_ext_parse_error(SDError error) { | ||||
|     FS_Error result; | ||||
|     switch(error) { | ||||
|     case FR_OK: | ||||
|         result = FSE_OK; | ||||
|         break; | ||||
|     case FR_NOT_READY: | ||||
|         result = FSE_NOT_READY; | ||||
|         break; | ||||
|     case FR_NO_FILE: | ||||
|     case FR_NO_PATH: | ||||
|     case FR_NO_FILESYSTEM: | ||||
|         result = FSE_NOT_EXIST; | ||||
|         break; | ||||
|     case FR_EXIST: | ||||
|         result = FSE_EXIST; | ||||
|         break; | ||||
|     case FR_INVALID_NAME: | ||||
|         result = FSE_INVALID_NAME; | ||||
|         break; | ||||
|     case FR_INVALID_OBJECT: | ||||
|     case FR_INVALID_PARAMETER: | ||||
|         result = FSE_INVALID_PARAMETER; | ||||
|         break; | ||||
|     case FR_DENIED: | ||||
|         result = FSE_DENIED; | ||||
|         break; | ||||
|     default: | ||||
|         result = FSE_INTERNAL; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| /******************* File Functions *******************/ | ||||
| 
 | ||||
| static bool storage_ext_file_open( | ||||
|     void* ctx, | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     FS_AccessMode access_mode, | ||||
|     FS_OpenMode open_mode) { | ||||
|     StorageData* storage = ctx; | ||||
|     uint8_t _mode = 0; | ||||
| 
 | ||||
|     if(access_mode & FSAM_READ) _mode |= FA_READ; | ||||
|     if(access_mode & FSAM_WRITE) _mode |= FA_WRITE; | ||||
|     if(open_mode & FSOM_OPEN_EXISTING) _mode |= FA_OPEN_EXISTING; | ||||
|     if(open_mode & FSOM_OPEN_ALWAYS) _mode |= FA_OPEN_ALWAYS; | ||||
|     if(open_mode & FSOM_OPEN_APPEND) _mode |= FA_OPEN_APPEND; | ||||
|     if(open_mode & FSOM_CREATE_NEW) _mode |= FA_CREATE_NEW; | ||||
|     if(open_mode & FSOM_CREATE_ALWAYS) _mode |= FA_CREATE_ALWAYS; | ||||
| 
 | ||||
|     SDFile* file_data = malloc(sizeof(SDFile)); | ||||
|     storage_set_storage_file_data(file, file_data, storage); | ||||
| 
 | ||||
|     file->internal_error_id = f_open(file_data, path, _mode); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_ext_file_close(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
|     file->internal_error_id = f_close(file_data); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     free(file_data); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static uint16_t | ||||
|     storage_ext_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
|     uint16_t bytes_readed = 0; | ||||
|     file->internal_error_id = f_read(file_data, buff, bytes_to_read, &bytes_readed); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return bytes_readed; | ||||
| } | ||||
| 
 | ||||
| static uint16_t | ||||
|     storage_ext_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
|     uint16_t bytes_written = 0; | ||||
|     file->internal_error_id = f_write(file_data, buff, bytes_to_write, &bytes_written); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return bytes_written; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
|     storage_ext_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(from_start) { | ||||
|         file->internal_error_id = f_lseek(file_data, offset); | ||||
|     } else { | ||||
|         uint64_t position = f_tell(file_data); | ||||
|         position += offset; | ||||
|         file->internal_error_id = f_lseek(file_data, position); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static uint64_t storage_ext_file_tell(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     uint64_t position = 0; | ||||
|     position = f_tell(file_data); | ||||
|     file->error_id = FSE_OK; | ||||
|     return position; | ||||
| } | ||||
| 
 | ||||
| static bool storage_ext_file_truncate(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     file->internal_error_id = f_truncate(file_data); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_ext_file_sync(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     file->internal_error_id = f_sync(file_data); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static uint64_t storage_ext_file_size(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     uint64_t size = 0; | ||||
|     size = f_size(file_data); | ||||
|     file->error_id = FSE_OK; | ||||
|     return size; | ||||
| } | ||||
| 
 | ||||
| static bool storage_ext_file_eof(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDFile* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     bool eof = f_eof(file_data); | ||||
|     file->internal_error_id = 0; | ||||
|     file->error_id = FSE_OK; | ||||
|     return eof; | ||||
| } | ||||
| 
 | ||||
| /******************* Dir Functions *******************/ | ||||
| 
 | ||||
| static bool storage_ext_dir_open(void* ctx, File* file, const char* path) { | ||||
|     StorageData* storage = ctx; | ||||
| 
 | ||||
|     SDDir* file_data = malloc(sizeof(SDDir)); | ||||
|     storage_set_storage_file_data(file, file_data, storage); | ||||
|     file->internal_error_id = f_opendir(file_data, path); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_ext_dir_close(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDDir* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     file->internal_error_id = f_closedir(file_data); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     free(file_data); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_ext_dir_read( | ||||
|     void* ctx, | ||||
|     File* file, | ||||
|     FileInfo* fileinfo, | ||||
|     char* name, | ||||
|     const uint16_t name_length) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDDir* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     SDFileInfo _fileinfo; | ||||
|     file->internal_error_id = f_readdir(file_data, &_fileinfo); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
| 
 | ||||
|     if(fileinfo != NULL) { | ||||
|         fileinfo->size = _fileinfo.fsize; | ||||
|         fileinfo->flags = 0; | ||||
| 
 | ||||
|         if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||
|     } | ||||
| 
 | ||||
|     if(name != NULL) { | ||||
|         snprintf(name, name_length, "%s", _fileinfo.fname); | ||||
|     } | ||||
| 
 | ||||
|     if(_fileinfo.fname[0] == 0) { | ||||
|         file->error_id = FSE_NOT_EXIST; | ||||
|     } | ||||
| 
 | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_ext_dir_rewind(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDDir* file_data = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     file->internal_error_id = f_readdir(file_data, NULL); | ||||
|     file->error_id = storage_ext_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| /******************* Common FS Functions *******************/ | ||||
| 
 | ||||
| static FS_Error storage_ext_common_stat(void* ctx, const char* path, FileInfo* fileinfo) { | ||||
|     SDFileInfo _fileinfo; | ||||
|     SDError result = f_stat(path, &_fileinfo); | ||||
| 
 | ||||
|     if(fileinfo != NULL) { | ||||
|         fileinfo->size = _fileinfo.fsize; | ||||
|         fileinfo->flags = 0; | ||||
| 
 | ||||
|         if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||
|     } | ||||
| 
 | ||||
|     return storage_ext_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_ext_common_remove(void* ctx, const char* path) { | ||||
|     SDError result = f_unlink(path); | ||||
|     return storage_ext_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_ext_common_rename(void* ctx, const char* old_path, const char* new_path) { | ||||
|     SDError result = f_rename(old_path, new_path); | ||||
|     return storage_ext_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_ext_common_mkdir(void* ctx, const char* path) { | ||||
|     SDError result = f_mkdir(path); | ||||
|     return storage_ext_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_ext_common_fs_info( | ||||
|     void* ctx, | ||||
|     const char* fs_path, | ||||
|     uint64_t* total_space, | ||||
|     uint64_t* free_space) { | ||||
|     StorageData* storage = ctx; | ||||
|     SDData* sd_data = storage->data; | ||||
| 
 | ||||
|     DWORD free_clusters; | ||||
|     FATFS* fs; | ||||
| 
 | ||||
|     SDError fresult = f_getfree(sd_data->path, &free_clusters, &fs); | ||||
|     if((FRESULT)fresult == FR_OK) { | ||||
|         uint32_t total_sectors = (fs->n_fatent - 2) * fs->csize; | ||||
|         uint32_t free_sectors = free_clusters * fs->csize; | ||||
| 
 | ||||
|         uint16_t sector_size = _MAX_SS; | ||||
| #if _MAX_SS != _MIN_SS | ||||
|         sector_size = fs->ssize; | ||||
| #endif | ||||
| 
 | ||||
|         if(total_space != NULL) { | ||||
|             *total_space = (uint64_t)total_sectors * (uint64_t)sector_size; | ||||
|         } | ||||
| 
 | ||||
|         if(free_space != NULL) { | ||||
|             *free_space = (uint64_t)free_sectors * (uint64_t)sector_size; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return storage_ext_parse_error(fresult); | ||||
| } | ||||
| 
 | ||||
| /******************* Init Storage *******************/ | ||||
| 
 | ||||
| void storage_ext_init(StorageData* storage) { | ||||
|     SDData* sd_data = malloc(sizeof(SDData)); | ||||
|     sd_data->fs = &USERFatFS; | ||||
|     sd_data->path = "0:/"; | ||||
|     sd_data->sd_was_present = true; | ||||
| 
 | ||||
|     storage->data = sd_data; | ||||
|     storage->api.tick = storage_ext_tick; | ||||
|     storage->fs_api.file.open = storage_ext_file_open; | ||||
|     storage->fs_api.file.close = storage_ext_file_close; | ||||
|     storage->fs_api.file.read = storage_ext_file_read; | ||||
|     storage->fs_api.file.write = storage_ext_file_write; | ||||
|     storage->fs_api.file.seek = storage_ext_file_seek; | ||||
|     storage->fs_api.file.tell = storage_ext_file_tell; | ||||
|     storage->fs_api.file.truncate = storage_ext_file_truncate; | ||||
|     storage->fs_api.file.size = storage_ext_file_size; | ||||
|     storage->fs_api.file.sync = storage_ext_file_sync; | ||||
|     storage->fs_api.file.eof = storage_ext_file_eof; | ||||
| 
 | ||||
|     storage->fs_api.dir.open = storage_ext_dir_open; | ||||
|     storage->fs_api.dir.close = storage_ext_dir_close; | ||||
|     storage->fs_api.dir.read = storage_ext_dir_read; | ||||
|     storage->fs_api.dir.rewind = storage_ext_dir_rewind; | ||||
| 
 | ||||
|     storage->fs_api.common.stat = storage_ext_common_stat; | ||||
|     storage->fs_api.common.mkdir = storage_ext_common_mkdir; | ||||
|     storage->fs_api.common.rename = storage_ext_common_rename; | ||||
|     storage->fs_api.common.remove = storage_ext_common_remove; | ||||
|     storage->fs_api.common.fs_info = storage_ext_common_fs_info; | ||||
| 
 | ||||
|     hal_sd_detect_init(); | ||||
| 
 | ||||
|     // do not notify on first launch, notifications app is waiting for our thread to read settings
 | ||||
|     storage_ext_tick_internal(storage, false); | ||||
| } | ||||
							
								
								
									
										16
									
								
								applications/storage/storages/storage-ext.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								applications/storage/storages/storage-ext.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "../storage-glue.h" | ||||
| #include "../storage-sd-api.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void storage_ext_init(StorageData* storage); | ||||
| FS_Error sd_unmount_card(StorageData* storage); | ||||
| FS_Error sd_format_card(StorageData* storage); | ||||
| FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info); | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										690
									
								
								applications/storage/storages/storage-int.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										690
									
								
								applications/storage/storages/storage-int.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,690 @@ | ||||
| #include "storage-int.h" | ||||
| #include <lfs.h> | ||||
| #include <api-hal.h> | ||||
| 
 | ||||
| #define TAG "storage-int" | ||||
| #define STORAGE_PATH "/int" | ||||
| 
 | ||||
| typedef struct { | ||||
|     const size_t start_address; | ||||
|     const size_t start_page; | ||||
|     struct lfs_config config; | ||||
|     lfs_t lfs; | ||||
| } LFSData; | ||||
| 
 | ||||
| typedef struct { | ||||
|     void* data; | ||||
|     bool open; | ||||
| } LFSHandle; | ||||
| 
 | ||||
| static LFSHandle* lfs_handle_alloc_file() { | ||||
|     LFSHandle* handle = furi_alloc(sizeof(LFSHandle)); | ||||
|     handle->data = furi_alloc(sizeof(lfs_file_t)); | ||||
|     return handle; | ||||
| } | ||||
| 
 | ||||
| static LFSHandle* lfs_handle_alloc_dir() { | ||||
|     LFSHandle* handle = furi_alloc(sizeof(LFSHandle)); | ||||
|     handle->data = furi_alloc(sizeof(lfs_dir_t)); | ||||
|     return handle; | ||||
| } | ||||
| 
 | ||||
| /* INTERNALS */ | ||||
| 
 | ||||
| static lfs_dir_t* lfs_handle_get_dir(LFSHandle* handle) { | ||||
|     return handle->data; | ||||
| } | ||||
| 
 | ||||
| static lfs_file_t* lfs_handle_get_file(LFSHandle* handle) { | ||||
|     return handle->data; | ||||
| } | ||||
| 
 | ||||
| static void lfs_handle_free(LFSHandle* handle) { | ||||
|     free(handle->data); | ||||
|     free(handle); | ||||
| } | ||||
| 
 | ||||
| static void lfs_handle_set_open(LFSHandle* handle) { | ||||
|     handle->open = true; | ||||
| } | ||||
| 
 | ||||
| static bool lfs_handle_is_open(LFSHandle* handle) { | ||||
|     return handle->open; | ||||
| } | ||||
| 
 | ||||
| static lfs_t* lfs_get_from_storage(StorageData* storage) { | ||||
|     return &((LFSData*)storage->data)->lfs; | ||||
| } | ||||
| 
 | ||||
| static LFSData* lfs_data_get_from_storage(StorageData* storage) { | ||||
|     return (LFSData*)storage->data; | ||||
| } | ||||
| 
 | ||||
| static int storage_int_device_read( | ||||
|     const struct lfs_config* c, | ||||
|     lfs_block_t block, | ||||
|     lfs_off_t off, | ||||
|     void* buffer, | ||||
|     lfs_size_t size) { | ||||
|     LFSData* lfs_data = c->context; | ||||
|     size_t address = lfs_data->start_address + block * c->block_size + off; | ||||
| 
 | ||||
|     FURI_LOG_D( | ||||
|         TAG, | ||||
|         "Device read: block %d, off %d, buffer: %p, size %d, translated address: %p", | ||||
|         block, | ||||
|         off, | ||||
|         buffer, | ||||
|         size, | ||||
|         address); | ||||
| 
 | ||||
|     memcpy(buffer, (void*)address, size); | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static int storage_int_device_prog( | ||||
|     const struct lfs_config* c, | ||||
|     lfs_block_t block, | ||||
|     lfs_off_t off, | ||||
|     const void* buffer, | ||||
|     lfs_size_t size) { | ||||
|     LFSData* lfs_data = c->context; | ||||
|     size_t address = lfs_data->start_address + block * c->block_size + off; | ||||
| 
 | ||||
|     FURI_LOG_D( | ||||
|         TAG, | ||||
|         "Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p", | ||||
|         block, | ||||
|         off, | ||||
|         buffer, | ||||
|         size, | ||||
|         address); | ||||
| 
 | ||||
|     int ret = 0; | ||||
|     while(size > 0) { | ||||
|         if(!api_hal_flash_write_dword(address, *(uint64_t*)buffer)) { | ||||
|             ret = -1; | ||||
|             break; | ||||
|         } | ||||
|         address += c->prog_size; | ||||
|         buffer += c->prog_size; | ||||
|         size -= c->prog_size; | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static int storage_int_device_erase(const struct lfs_config* c, lfs_block_t block) { | ||||
|     LFSData* lfs_data = c->context; | ||||
|     size_t page = lfs_data->start_page + block; | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "Device erase: page %d, translated page: %d", block, page); | ||||
| 
 | ||||
|     if(api_hal_flash_erase(page, 1)) { | ||||
|         return 0; | ||||
|     } else { | ||||
|         return -1; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static int storage_int_device_sync(const struct lfs_config* c) { | ||||
|     FURI_LOG_D(TAG, "Device sync: skipping, cause "); | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static LFSData* storage_int_lfs_data_alloc() { | ||||
|     LFSData* lfs_data = furi_alloc(sizeof(LFSData)); | ||||
| 
 | ||||
|     // Internal storage start address
 | ||||
|     *(size_t*)(&lfs_data->start_address) = api_hal_flash_get_free_page_start_address(); | ||||
|     *(size_t*)(&lfs_data->start_page) = | ||||
|         (lfs_data->start_address - api_hal_flash_get_base()) / api_hal_flash_get_page_size(); | ||||
| 
 | ||||
|     // LFS configuration
 | ||||
|     // Glue and context
 | ||||
|     lfs_data->config.context = lfs_data; | ||||
|     lfs_data->config.read = storage_int_device_read; | ||||
|     lfs_data->config.prog = storage_int_device_prog; | ||||
|     lfs_data->config.erase = storage_int_device_erase; | ||||
|     lfs_data->config.sync = storage_int_device_sync; | ||||
| 
 | ||||
|     // Block device description
 | ||||
|     lfs_data->config.read_size = api_hal_flash_get_read_block_size(); | ||||
|     lfs_data->config.prog_size = api_hal_flash_get_write_block_size(); | ||||
|     lfs_data->config.block_size = api_hal_flash_get_page_size(); | ||||
|     lfs_data->config.block_count = api_hal_flash_get_free_page_count(); | ||||
|     lfs_data->config.block_cycles = api_hal_flash_get_cycles_count(); | ||||
|     lfs_data->config.cache_size = 16; | ||||
|     lfs_data->config.lookahead_size = 16; | ||||
| 
 | ||||
|     return lfs_data; | ||||
| }; | ||||
| 
 | ||||
| static void storage_int_lfs_mount(LFSData* lfs_data, StorageData* storage) { | ||||
|     int err; | ||||
|     ApiHalBootFlag boot_flags = api_hal_boot_get_flags(); | ||||
|     lfs_t* lfs = &lfs_data->lfs; | ||||
| 
 | ||||
|     if(boot_flags & ApiHalBootFlagFactoryReset) { | ||||
|         // Factory reset
 | ||||
|         err = lfs_format(lfs, &lfs_data->config); | ||||
|         if(err == 0) { | ||||
|             FURI_LOG_I(TAG, "Factory reset: Format successful, trying to mount"); | ||||
|             api_hal_boot_set_flags(boot_flags & ~ApiHalBootFlagFactoryReset); | ||||
|             err = lfs_mount(lfs, &lfs_data->config); | ||||
|             if(err == 0) { | ||||
|                 FURI_LOG_I(TAG, "Factory reset: Mounted"); | ||||
|                 storage->status = StorageStatusOK; | ||||
|             } else { | ||||
|                 FURI_LOG_E(TAG, "Factory reset: Mount after format failed"); | ||||
|                 storage->status = StorageStatusNotMounted; | ||||
|             } | ||||
|         } else { | ||||
|             FURI_LOG_E(TAG, "Factory reset: Format failed"); | ||||
|             storage->status = StorageStatusNoFS; | ||||
|         } | ||||
|     } else { | ||||
|         // Normal
 | ||||
|         err = lfs_mount(lfs, &lfs_data->config); | ||||
|         if(err == 0) { | ||||
|             FURI_LOG_I(TAG, "Mounted"); | ||||
|             storage->status = StorageStatusOK; | ||||
|         } else { | ||||
|             FURI_LOG_E(TAG, "Mount failed, formatting"); | ||||
|             err = lfs_format(lfs, &lfs_data->config); | ||||
|             if(err == 0) { | ||||
|                 FURI_LOG_I(TAG, "Format successful, trying to mount"); | ||||
|                 err = lfs_mount(lfs, &lfs_data->config); | ||||
|                 if(err == 0) { | ||||
|                     FURI_LOG_I(TAG, "Mounted"); | ||||
|                     storage->status = StorageStatusOK; | ||||
|                 } else { | ||||
|                     FURI_LOG_E(TAG, "Mount after format failed"); | ||||
|                     storage->status = StorageStatusNotMounted; | ||||
|                 } | ||||
|             } else { | ||||
|                 FURI_LOG_E(TAG, "Format failed"); | ||||
|                 storage->status = StorageStatusNoFS; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /****************** Common Functions ******************/ | ||||
| 
 | ||||
| static FS_Error storage_int_parse_error(int error) { | ||||
|     FS_Error result = FSE_INTERNAL; | ||||
| 
 | ||||
|     if(error >= LFS_ERR_OK) { | ||||
|         result = FSE_OK; | ||||
|     } else { | ||||
|         switch(error) { | ||||
|         case LFS_ERR_IO: | ||||
|             result = FSE_INTERNAL; | ||||
|             break; | ||||
|         case LFS_ERR_CORRUPT: | ||||
|             result = FSE_INTERNAL; | ||||
|             break; | ||||
|         case LFS_ERR_NOENT: | ||||
|             result = FSE_NOT_EXIST; | ||||
|             break; | ||||
|         case LFS_ERR_EXIST: | ||||
|             result = FSE_EXIST; | ||||
|             break; | ||||
|         case LFS_ERR_NOTDIR: | ||||
|             result = FSE_INVALID_NAME; | ||||
|             break; | ||||
|         case LFS_ERR_ISDIR: | ||||
|             result = FSE_INVALID_NAME; | ||||
|             break; | ||||
|         case LFS_ERR_NOTEMPTY: | ||||
|             result = FSE_DENIED; | ||||
|             break; | ||||
|         case LFS_ERR_BADF: | ||||
|             result = FSE_INVALID_NAME; | ||||
|             break; | ||||
|         case LFS_ERR_FBIG: | ||||
|             result = FSE_INTERNAL; | ||||
|             break; | ||||
|         case LFS_ERR_INVAL: | ||||
|             result = FSE_INVALID_PARAMETER; | ||||
|             break; | ||||
|         case LFS_ERR_NOSPC: | ||||
|             result = FSE_INTERNAL; | ||||
|             break; | ||||
|         case LFS_ERR_NOMEM: | ||||
|             result = FSE_INTERNAL; | ||||
|             break; | ||||
|         case LFS_ERR_NOATTR: | ||||
|             result = FSE_INVALID_PARAMETER; | ||||
|             break; | ||||
|         case LFS_ERR_NAMETOOLONG: | ||||
|             result = FSE_INVALID_NAME; | ||||
|             break; | ||||
|         default: | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| /******************* File Functions *******************/ | ||||
| 
 | ||||
| static bool storage_int_file_open( | ||||
|     void* ctx, | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     FS_AccessMode access_mode, | ||||
|     FS_OpenMode open_mode) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
| 
 | ||||
|     int flags = 0; | ||||
| 
 | ||||
|     if(access_mode & FSAM_READ) flags |= LFS_O_RDONLY; | ||||
|     if(access_mode & FSAM_WRITE) flags |= LFS_O_WRONLY; | ||||
| 
 | ||||
|     if(open_mode & FSOM_OPEN_EXISTING) flags = flags; | ||||
|     if(open_mode & FSOM_OPEN_ALWAYS) flags |= LFS_O_CREAT; | ||||
|     if(open_mode & FSOM_OPEN_APPEND) flags |= LFS_O_CREAT | LFS_O_APPEND; | ||||
|     if(open_mode & FSOM_CREATE_NEW) flags |= LFS_O_CREAT | LFS_O_EXCL; | ||||
|     if(open_mode & FSOM_CREATE_ALWAYS) flags |= LFS_O_CREAT | LFS_O_TRUNC; | ||||
| 
 | ||||
|     LFSHandle* handle = lfs_handle_alloc_file(); | ||||
|     storage_set_storage_file_data(file, handle, storage); | ||||
|     file->internal_error_id = lfs_file_open(lfs, lfs_handle_get_file(handle), path, flags); | ||||
| 
 | ||||
|     if(file->internal_error_id >= LFS_ERR_OK) { | ||||
|         lfs_handle_set_open(handle); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_int_file_close(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = lfs_file_close(lfs, lfs_handle_get_file(handle)); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     lfs_handle_free(handle); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static uint16_t | ||||
|     storage_int_file_read(void* ctx, File* file, void* buff, uint16_t const bytes_to_read) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     uint16_t bytes_readed = 0; | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = | ||||
|             lfs_file_read(lfs, lfs_handle_get_file(handle), buff, bytes_to_read); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
| 
 | ||||
|     if(file->error_id == FSE_OK) { | ||||
|         bytes_readed = file->internal_error_id; | ||||
|         file->internal_error_id = 0; | ||||
|     } | ||||
|     return bytes_readed; | ||||
| } | ||||
| 
 | ||||
| static uint16_t | ||||
|     storage_int_file_write(void* ctx, File* file, const void* buff, uint16_t const bytes_to_write) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     uint16_t bytes_written = 0; | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = | ||||
|             lfs_file_write(lfs, lfs_handle_get_file(handle), buff, bytes_to_write); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
| 
 | ||||
|     if(file->error_id == FSE_OK) { | ||||
|         bytes_written = file->internal_error_id; | ||||
|         file->internal_error_id = 0; | ||||
|     } | ||||
|     return bytes_written; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
|     storage_int_file_seek(void* ctx, File* file, const uint32_t offset, const bool from_start) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         if(from_start) { | ||||
|             file->internal_error_id = | ||||
|                 lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_SET); | ||||
|         } else { | ||||
|             file->internal_error_id = | ||||
|                 lfs_file_seek(lfs, lfs_handle_get_file(handle), offset, LFS_SEEK_CUR); | ||||
|         } | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static uint64_t storage_int_file_tell(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle)); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
| 
 | ||||
|     int32_t position = 0; | ||||
|     if(file->error_id == FSE_OK) { | ||||
|         position = file->internal_error_id; | ||||
|         file->internal_error_id = 0; | ||||
|     } | ||||
| 
 | ||||
|     return position; | ||||
| } | ||||
| 
 | ||||
| static bool storage_int_file_truncate(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = lfs_file_tell(lfs, lfs_handle_get_file(handle)); | ||||
|         file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
| 
 | ||||
|         if(file->error_id == FSE_OK) { | ||||
|             uint32_t position = file->internal_error_id; | ||||
|             file->internal_error_id = | ||||
|                 lfs_file_truncate(lfs, lfs_handle_get_file(handle), position); | ||||
|             file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|         } | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|         file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     } | ||||
| 
 | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_int_file_sync(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = lfs_file_sync(lfs, lfs_handle_get_file(handle)); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static uint64_t storage_int_file_size(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = lfs_file_size(lfs, lfs_handle_get_file(handle)); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
| 
 | ||||
|     uint32_t size = 0; | ||||
|     if(file->error_id == FSE_OK) { | ||||
|         size = file->internal_error_id; | ||||
|         file->internal_error_id = 0; | ||||
|     } | ||||
| 
 | ||||
|     return size; | ||||
| } | ||||
| 
 | ||||
| static bool storage_int_file_eof(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     bool eof = true; | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         int32_t position = lfs_file_tell(lfs, lfs_handle_get_file(handle)); | ||||
|         int32_t size = lfs_file_size(lfs, lfs_handle_get_file(handle)); | ||||
| 
 | ||||
|         if(position < 0) { | ||||
|             file->internal_error_id = position; | ||||
|         } else if(size < 0) { | ||||
|             file->internal_error_id = size; | ||||
|         } else { | ||||
|             file->internal_error_id = LFS_ERR_OK; | ||||
|             eof = (position >= size); | ||||
|         } | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     return eof; | ||||
| } | ||||
| 
 | ||||
| /******************* Dir Functions *******************/ | ||||
| 
 | ||||
| static bool storage_int_dir_open(void* ctx, File* file, const char* path) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
| 
 | ||||
|     LFSHandle* handle = lfs_handle_alloc_dir(); | ||||
|     storage_set_storage_file_data(file, handle, storage); | ||||
| 
 | ||||
|     file->internal_error_id = lfs_dir_open(lfs, lfs_handle_get_dir(handle), path); | ||||
|     if(file->internal_error_id >= LFS_ERR_OK) { | ||||
|         lfs_handle_set_open(handle); | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_int_dir_close(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = lfs_dir_close(lfs, lfs_handle_get_dir(handle)); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     lfs_handle_free(handle); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_int_dir_read( | ||||
|     void* ctx, | ||||
|     File* file, | ||||
|     FileInfo* fileinfo, | ||||
|     char* name, | ||||
|     const uint16_t name_length) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         struct lfs_info _fileinfo; | ||||
| 
 | ||||
|         // LFS returns virtual directories "." and "..", so we read until we get something meaningful or an empty string
 | ||||
|         do { | ||||
|             file->internal_error_id = lfs_dir_read(lfs, lfs_handle_get_dir(handle), &_fileinfo); | ||||
|             file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|         } while(strcmp(_fileinfo.name, ".") == 0 || strcmp(_fileinfo.name, "..") == 0); | ||||
| 
 | ||||
|         if(fileinfo != NULL) { | ||||
|             fileinfo->size = _fileinfo.size; | ||||
|             fileinfo->flags = 0; | ||||
|             if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||
|         } | ||||
| 
 | ||||
|         if(name != NULL) { | ||||
|             snprintf(name, name_length, "%s", _fileinfo.name); | ||||
|         } | ||||
| 
 | ||||
|         // set FSE_NOT_EXIST error on end of directory
 | ||||
|         if(file->internal_error_id == 0) { | ||||
|             file->error_id = FSE_NOT_EXIST; | ||||
|         } | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|         file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     } | ||||
| 
 | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| static bool storage_int_dir_rewind(void* ctx, File* file) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSHandle* handle = storage_get_storage_file_data(file, storage); | ||||
| 
 | ||||
|     if(lfs_handle_is_open(handle)) { | ||||
|         file->internal_error_id = lfs_dir_rewind(lfs, lfs_handle_get_dir(handle)); | ||||
|     } else { | ||||
|         file->internal_error_id = LFS_ERR_BADF; | ||||
|     } | ||||
| 
 | ||||
|     file->error_id = storage_int_parse_error(file->internal_error_id); | ||||
|     return (file->error_id == FSE_OK); | ||||
| } | ||||
| 
 | ||||
| /******************* Common FS Functions *******************/ | ||||
| 
 | ||||
| static FS_Error storage_int_common_stat(void* ctx, const char* path, FileInfo* fileinfo) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     struct lfs_info _fileinfo; | ||||
|     int result = lfs_stat(lfs, path, &_fileinfo); | ||||
| 
 | ||||
|     if(fileinfo != NULL) { | ||||
|         fileinfo->size = _fileinfo.size; | ||||
|         fileinfo->flags = 0; | ||||
|         if(_fileinfo.type & LFS_TYPE_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||
|     } | ||||
| 
 | ||||
|     return storage_int_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_int_common_remove(void* ctx, const char* path) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     int result = lfs_remove(lfs, path); | ||||
|     return storage_int_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_int_common_rename(void* ctx, const char* old_path, const char* new_path) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     int result = lfs_rename(lfs, old_path, new_path); | ||||
|     return storage_int_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_int_common_mkdir(void* ctx, const char* path) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     int result = lfs_mkdir(lfs, path); | ||||
|     return storage_int_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_int_common_fs_info( | ||||
|     void* ctx, | ||||
|     const char* fs_path, | ||||
|     uint64_t* total_space, | ||||
|     uint64_t* free_space) { | ||||
|     StorageData* storage = ctx; | ||||
| 
 | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     LFSData* lfs_data = lfs_data_get_from_storage(storage); | ||||
| 
 | ||||
|     *total_space = lfs_data->config.block_size * lfs_data->config.block_count; | ||||
| 
 | ||||
|     lfs_ssize_t result = lfs_fs_size(lfs); | ||||
|     if(result >= 0) { | ||||
|         *free_space = *total_space - (result * lfs_data->config.block_size); | ||||
|     } | ||||
| 
 | ||||
|     return storage_int_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| /******************* Init Storage *******************/ | ||||
| 
 | ||||
| void storage_int_init(StorageData* storage) { | ||||
|     FURI_LOG_I(TAG, "Starting"); | ||||
|     LFSData* lfs_data = storage_int_lfs_data_alloc(); | ||||
|     FURI_LOG_I( | ||||
|         TAG, | ||||
|         "Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d", | ||||
|         lfs_data->start_address, | ||||
|         lfs_data->config.read_size, | ||||
|         lfs_data->config.prog_size, | ||||
|         lfs_data->config.block_size, | ||||
|         lfs_data->config.block_count, | ||||
|         lfs_data->config.block_cycles); | ||||
| 
 | ||||
|     storage_int_lfs_mount(lfs_data, storage); | ||||
| 
 | ||||
|     storage->data = lfs_data; | ||||
|     storage->api.tick = NULL; | ||||
|     storage->fs_api.file.open = storage_int_file_open; | ||||
|     storage->fs_api.file.close = storage_int_file_close; | ||||
|     storage->fs_api.file.read = storage_int_file_read; | ||||
|     storage->fs_api.file.write = storage_int_file_write; | ||||
|     storage->fs_api.file.seek = storage_int_file_seek; | ||||
|     storage->fs_api.file.tell = storage_int_file_tell; | ||||
|     storage->fs_api.file.truncate = storage_int_file_truncate; | ||||
|     storage->fs_api.file.size = storage_int_file_size; | ||||
|     storage->fs_api.file.sync = storage_int_file_sync; | ||||
|     storage->fs_api.file.eof = storage_int_file_eof; | ||||
| 
 | ||||
|     storage->fs_api.dir.open = storage_int_dir_open; | ||||
|     storage->fs_api.dir.close = storage_int_dir_close; | ||||
|     storage->fs_api.dir.read = storage_int_dir_read; | ||||
|     storage->fs_api.dir.rewind = storage_int_dir_rewind; | ||||
| 
 | ||||
|     storage->fs_api.common.stat = storage_int_common_stat; | ||||
|     storage->fs_api.common.mkdir = storage_int_common_mkdir; | ||||
|     storage->fs_api.common.rename = storage_int_common_rename; | ||||
|     storage->fs_api.common.remove = storage_int_common_remove; | ||||
|     storage->fs_api.common.fs_info = storage_int_common_fs_info; | ||||
| } | ||||
							
								
								
									
										13
									
								
								applications/storage/storages/storage-int.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/storage/storages/storage-int.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "../storage-glue.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void storage_int_init(StorageData* storage); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -223,8 +223,8 @@ void subghz_cli_command_rx(Cli* cli, string_t args, void* context) { | ||||
|     furi_check(instance->stream); | ||||
| 
 | ||||
|     SubGhzProtocol* protocol = subghz_protocol_alloc(); | ||||
|     subghz_protocol_load_keeloq_file(protocol, "/assets/subghz/keeloq_mfcodes"); | ||||
|     subghz_protocol_load_nice_flor_s_file(protocol, "/assets/subghz/nice_floor_s_rx"); | ||||
|     subghz_protocol_load_keeloq_file(protocol, "/ext/assets/subghz/keeloq_mfcodes"); | ||||
|     subghz_protocol_load_nice_flor_s_file(protocol, "/ext/assets/subghz/nice_floor_s_rx"); | ||||
|     subghz_protocol_enable_dump_text(protocol, subghz_cli_command_rx_text_callback, instance); | ||||
| 
 | ||||
|     // Configure radio
 | ||||
|  | ||||
| @ -207,9 +207,10 @@ SubghzCapture* subghz_capture_alloc() { | ||||
|         subghz_capture->worker, (SubGhzWorkerPairCallback)subghz_protocol_parse); | ||||
|     subghz_worker_set_context(subghz_capture->worker, subghz_capture->protocol); | ||||
| 
 | ||||
|     subghz_protocol_load_keeloq_file(subghz_capture->protocol, "/assets/subghz/keeloq_mfcodes"); | ||||
|     subghz_protocol_load_keeloq_file( | ||||
|         subghz_capture->protocol, "/ext/assets/subghz/keeloq_mfcodes"); | ||||
|     subghz_protocol_load_nice_flor_s_file( | ||||
|         subghz_capture->protocol, "/assets/subghz/nice_floor_s_rx"); | ||||
|         subghz_capture->protocol, "/ext/assets/subghz/nice_floor_s_rx"); | ||||
|     subghz_protocol_enable_dump_text( | ||||
|         subghz_capture->protocol, subghz_capture_text_callback, subghz_capture); | ||||
| 
 | ||||
|  | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -10,67 +10,67 @@ extern const Icon A_MDWRB_32x32; | ||||
| extern const Icon A_MDWR_32x32; | ||||
| extern const Icon A_WatchingTV_128x64; | ||||
| extern const Icon A_Wink_128x64; | ||||
| extern const Icon I_125_10px; | ||||
| extern const Icon I_ble_10px; | ||||
| extern const Icon I_dir_10px; | ||||
| extern const Icon I_ibutt_10px; | ||||
| extern const Icon I_ir_10px; | ||||
| extern const Icon I_Nfc_10px; | ||||
| extern const Icon I_sub1_10px; | ||||
| extern const Icon I_ir_10px; | ||||
| extern const Icon I_ibutt_10px; | ||||
| extern const Icon I_unknown_10px; | ||||
| extern const Icon I_ble_10px; | ||||
| extern const Icon I_125_10px; | ||||
| extern const Icon I_ButtonRightSmall_3x5; | ||||
| extern const Icon I_ButtonLeft_4x7; | ||||
| extern const Icon I_ButtonLeftSmall_3x5; | ||||
| extern const Icon I_ButtonRight_4x7; | ||||
| extern const Icon I_ButtonCenter_7x7; | ||||
| extern const Icon I_FX_SittingB_40x27; | ||||
| extern const Icon I_ButtonLeftSmall_3x5; | ||||
| extern const Icon I_ButtonLeft_4x7; | ||||
| extern const Icon I_ButtonRightSmall_3x5; | ||||
| extern const Icon I_ButtonRight_4x7; | ||||
| extern const Icon I_BigBurger_24x24; | ||||
| extern const Icon I_BigGames_24x24; | ||||
| extern const Icon I_BigProfile_24x24; | ||||
| extern const Icon I_DolphinOkay_41x43; | ||||
| extern const Icon I_DolphinFirstStart5_45x53; | ||||
| extern const Icon I_DolphinFirstStart4_67x53; | ||||
| extern const Icon I_DolphinFirstStart2_59x51; | ||||
| extern const Icon I_DolphinFirstStart0_70x53; | ||||
| extern const Icon I_DolphinFirstStart6_58x54; | ||||
| extern const Icon I_DolphinFirstStart1_59x53; | ||||
| extern const Icon I_DolphinFirstStart8_56x51; | ||||
| extern const Icon I_DolphinFirstStart7_61x51; | ||||
| extern const Icon I_Flipper_young_80x60; | ||||
| extern const Icon I_BigBurger_24x24; | ||||
| extern const Icon I_FX_Bang_32x6; | ||||
| extern const Icon I_DolphinFirstStart2_59x51; | ||||
| extern const Icon I_DolphinFirstStart3_57x48; | ||||
| extern const Icon I_PassportBottom_128x17; | ||||
| extern const Icon I_DolphinFirstStart4_67x53; | ||||
| extern const Icon I_DolphinFirstStart5_45x53; | ||||
| extern const Icon I_DolphinFirstStart6_58x54; | ||||
| extern const Icon I_DolphinFirstStart7_61x51; | ||||
| extern const Icon I_DolphinFirstStart8_56x51; | ||||
| extern const Icon I_DolphinOkay_41x43; | ||||
| extern const Icon I_Flipper_young_80x60; | ||||
| extern const Icon I_FX_Bang_32x6; | ||||
| extern const Icon I_FX_SittingB_40x27; | ||||
| extern const Icon I_DoorLeft_70x55; | ||||
| extern const Icon I_DoorLeft_8x56; | ||||
| extern const Icon I_DoorLocked_10x56; | ||||
| extern const Icon I_DoorRight_8x56; | ||||
| extern const Icon I_DoorLeft_70x55; | ||||
| extern const Icon I_PassportLeft_6x47; | ||||
| extern const Icon I_DoorRight_70x55; | ||||
| extern const Icon I_DoorRight_8x56; | ||||
| extern const Icon I_LockPopup_100x49; | ||||
| extern const Icon I_Mute_25x27; | ||||
| extern const Icon I_IrdaArrowUp_4x8; | ||||
| extern const Icon I_Up_hvr_25x27; | ||||
| extern const Icon I_Mute_hvr_25x27; | ||||
| extern const Icon I_Vol_down_25x27; | ||||
| extern const Icon I_PassportBottom_128x17; | ||||
| extern const Icon I_PassportLeft_6x47; | ||||
| extern const Icon I_Back_15x10; | ||||
| extern const Icon I_Down_25x27; | ||||
| extern const Icon I_Power_hvr_25x27; | ||||
| extern const Icon I_IrdaLearnShort_128x31; | ||||
| extern const Icon I_IrdaArrowDown_4x8; | ||||
| extern const Icon I_Vol_down_hvr_25x27; | ||||
| extern const Icon I_IrdaLearn_128x64; | ||||
| extern const Icon I_Down_hvr_25x27; | ||||
| extern const Icon I_Fill_marker_7x7; | ||||
| extern const Icon I_Power_25x27; | ||||
| extern const Icon I_Vol_up_25x27; | ||||
| extern const Icon I_Up_25x27; | ||||
| extern const Icon I_Back_15x10; | ||||
| extern const Icon I_IrdaSend_128x64; | ||||
| extern const Icon I_IrdaArrowDown_4x8; | ||||
| extern const Icon I_IrdaArrowUp_4x8; | ||||
| extern const Icon I_IrdaLearnShort_128x31; | ||||
| extern const Icon I_IrdaLearn_128x64; | ||||
| extern const Icon I_IrdaSendShort_128x34; | ||||
| extern const Icon I_IrdaSend_128x64; | ||||
| extern const Icon I_Mute_25x27; | ||||
| extern const Icon I_Mute_hvr_25x27; | ||||
| extern const Icon I_Power_25x27; | ||||
| extern const Icon I_Power_hvr_25x27; | ||||
| extern const Icon I_Up_25x27; | ||||
| extern const Icon I_Up_hvr_25x27; | ||||
| extern const Icon I_Vol_down_25x27; | ||||
| extern const Icon I_Vol_down_hvr_25x27; | ||||
| extern const Icon I_Vol_up_25x27; | ||||
| extern const Icon I_Vol_up_hvr_25x27; | ||||
| extern const Icon I_KeySave_24x11; | ||||
| extern const Icon I_KeyBackspaceSelected_16x9; | ||||
| extern const Icon I_KeySaveSelected_24x11; | ||||
| extern const Icon I_KeyBackspace_16x9; | ||||
| extern const Icon I_KeySaveSelected_24x11; | ||||
| extern const Icon I_KeySave_24x11; | ||||
| extern const Icon A_125khz_14; | ||||
| extern const Icon A_Bluetooth_14; | ||||
| extern const Icon A_FileManager_14; | ||||
| @ -86,61 +86,61 @@ extern const Icon A_Sub1ghz_14; | ||||
| extern const Icon A_Tamagotchi_14; | ||||
| extern const Icon A_U2F_14; | ||||
| extern const Icon A_iButton_14; | ||||
| extern const Icon I_Medium_chip_22x21; | ||||
| extern const Icon I_EMV_Chip_14x11; | ||||
| extern const Icon I_passport_happy1_43x45; | ||||
| extern const Icon I_passport_bad3_43x45; | ||||
| extern const Icon I_passport_okay2_43x45; | ||||
| extern const Icon I_passport_bad2_43x45; | ||||
| extern const Icon I_passport_okay3_43x45; | ||||
| extern const Icon I_Medium_chip_22x21; | ||||
| extern const Icon I_passport_bad1_43x45; | ||||
| extern const Icon I_passport_happy3_43x45; | ||||
| extern const Icon I_passport_bad2_43x45; | ||||
| extern const Icon I_passport_bad3_43x45; | ||||
| extern const Icon I_passport_happy1_43x45; | ||||
| extern const Icon I_passport_happy2_43x45; | ||||
| extern const Icon I_passport_happy3_43x45; | ||||
| extern const Icon I_passport_okay1_43x45; | ||||
| extern const Icon I_Health_16x16; | ||||
| extern const Icon I_FaceCharging_29x14; | ||||
| extern const Icon I_passport_okay2_43x45; | ||||
| extern const Icon I_passport_okay3_43x45; | ||||
| extern const Icon I_BatteryBody_52x28; | ||||
| extern const Icon I_Voltage_16x16; | ||||
| extern const Icon I_Temperature_16x16; | ||||
| extern const Icon I_Battery_16x16; | ||||
| extern const Icon I_FaceCharging_29x14; | ||||
| extern const Icon I_FaceConfused_29x14; | ||||
| extern const Icon I_FaceNopower_29x14; | ||||
| extern const Icon I_FaceNormal_29x14; | ||||
| extern const Icon I_Battery_16x16; | ||||
| extern const Icon I_FaceConfused_29x14; | ||||
| extern const Icon I_RFIDDolphinSuccess_108x57; | ||||
| extern const Icon I_Health_16x16; | ||||
| extern const Icon I_Temperature_16x16; | ||||
| extern const Icon I_Voltage_16x16; | ||||
| extern const Icon I_RFIDBigChip_37x36; | ||||
| extern const Icon I_RFIDDolphinSend_97x61; | ||||
| extern const Icon I_RFIDDolphinReceive_97x61; | ||||
| extern const Icon I_SDQuestion_35x43; | ||||
| extern const Icon I_RFIDDolphinSend_97x61; | ||||
| extern const Icon I_RFIDDolphinSuccess_108x57; | ||||
| extern const Icon I_SDError_43x35; | ||||
| extern const Icon I_WalkR2_32x32; | ||||
| extern const Icon I_WalkL2_32x32; | ||||
| extern const Icon I_WalkRB1_32x32; | ||||
| extern const Icon I_SDQuestion_35x43; | ||||
| extern const Icon I_Home_painting_17x20; | ||||
| extern const Icon I_WalkLB2_32x32; | ||||
| extern const Icon I_Sofa_40x13; | ||||
| extern const Icon I_WalkLB1_32x32; | ||||
| extern const Icon I_PC_22x29; | ||||
| extern const Icon I_WalkL1_32x32; | ||||
| extern const Icon I_Sofa_40x13; | ||||
| extern const Icon I_TV_20x20; | ||||
| extern const Icon I_WalkR1_32x32; | ||||
| extern const Icon I_WalkRB2_32x32; | ||||
| extern const Icon I_TV_20x24; | ||||
| extern const Icon I_BadUsb_9x8; | ||||
| extern const Icon I_PlaceholderR_30x13; | ||||
| extern const Icon I_Background_128x8; | ||||
| extern const Icon I_Lock_8x8; | ||||
| extern const Icon I_Battery_26x8; | ||||
| extern const Icon I_PlaceholderL_11x13; | ||||
| extern const Icon I_Battery_19x8; | ||||
| extern const Icon I_SDcardMounted_11x8; | ||||
| extern const Icon I_SDcardFail_11x8; | ||||
| extern const Icon I_USBConnected_15x8; | ||||
| extern const Icon I_Bluetooth_5x8; | ||||
| extern const Icon I_WalkL1_32x32; | ||||
| extern const Icon I_WalkL2_32x32; | ||||
| extern const Icon I_WalkLB1_32x32; | ||||
| extern const Icon I_WalkLB2_32x32; | ||||
| extern const Icon I_WalkR1_32x32; | ||||
| extern const Icon I_WalkR2_32x32; | ||||
| extern const Icon I_WalkRB1_32x32; | ||||
| extern const Icon I_WalkRB2_32x32; | ||||
| extern const Icon I_Background_128x11; | ||||
| extern const Icon I_DolphinMafia_115x62; | ||||
| extern const Icon I_Background_128x8; | ||||
| extern const Icon I_BadUsb_9x8; | ||||
| extern const Icon I_Battery_19x8; | ||||
| extern const Icon I_Battery_26x8; | ||||
| extern const Icon I_Bluetooth_5x8; | ||||
| extern const Icon I_Lock_8x8; | ||||
| extern const Icon I_PlaceholderL_11x13; | ||||
| extern const Icon I_PlaceholderR_30x13; | ||||
| extern const Icon I_SDcardFail_11x8; | ||||
| extern const Icon I_SDcardMounted_11x8; | ||||
| extern const Icon I_USBConnected_15x8; | ||||
| extern const Icon I_DolphinExcited_64x63; | ||||
| extern const Icon I_DolphinMafia_115x62; | ||||
| extern const Icon I_DolphinNice_96x59; | ||||
| extern const Icon I_DolphinWait_61x59; | ||||
| extern const Icon I_iButtonDolphinSuccess_109x60; | ||||
| extern const Icon I_iButtonDolphinVerySuccess_108x52; | ||||
| extern const Icon I_iButtonKey_49x44; | ||||
| extern const Icon I_DolphinNice_96x59; | ||||
| extern const Icon I_DolphinWait_61x59; | ||||
|  | ||||
| @ -223,6 +223,20 @@ size_t memmgr_heap_get_max_free_block() { | ||||
|     osKernelUnlock(); | ||||
|     return max_free_size; | ||||
| } | ||||
| 
 | ||||
| void memmgr_heap_printf_free_blocks() { | ||||
|     BlockLink_t* pxBlock; | ||||
|     //TODO enable when we can do printf with a locked scheduler
 | ||||
|     //osKernelLock();
 | ||||
| 
 | ||||
|     pxBlock = xStart.pxNextFreeBlock; | ||||
|     while(pxBlock->pxNextFreeBlock != NULL) { | ||||
|         printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize); | ||||
|         pxBlock = pxBlock->pxNextFreeBlock; | ||||
|     } | ||||
| 
 | ||||
|     //osKernelUnlock();
 | ||||
| } | ||||
| /*-----------------------------------------------------------*/ | ||||
| 
 | ||||
| void* pvPortMalloc(size_t xWantedSize) { | ||||
|  | ||||
| @ -28,6 +28,11 @@ size_t memmgr_heap_get_thread_memory(osThreadId_t thread_id); | ||||
|  */ | ||||
| size_t memmgr_heap_get_max_free_block(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Print the address and size of all free blocks to stdout | ||||
|  */ | ||||
| void memmgr_heap_printf_free_blocks(); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
|  | ||||
| @ -68,7 +68,7 @@ | ||||
| #define	_USE_EXPAND		0 | ||||
| /* This option switches f_expand function. (0:Disable or 1:Enable) */ | ||||
| 
 | ||||
| #define _USE_CHMOD		1 | ||||
| #define _USE_CHMOD		0 | ||||
| /* This option switches attribute manipulation functions, f_chmod() and f_utime().
 | ||||
| /  (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */ | ||||
| 
 | ||||
| @ -133,7 +133,7 @@ | ||||
| /  To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1. | ||||
| /  This option also affects behavior of string I/O functions. */ | ||||
| 
 | ||||
| #define _STRF_ENCODE    3 | ||||
| #define _STRF_ENCODE    0 | ||||
| /* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
 | ||||
| /  be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf(). | ||||
| / | ||||
| @ -205,7 +205,7 @@ | ||||
| / System Configurations | ||||
| /----------------------------------------------------------------------------*/ | ||||
| 
 | ||||
| #define _FS_TINY    1      /* 0:Normal or 1:Tiny */ | ||||
| #define _FS_TINY    0      /* 0:Normal or 1:Tiny */ | ||||
| /* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
 | ||||
| /  At the tiny configuration, size of file object (FIL) is reduced _MAX_SS bytes. | ||||
| /  Instead of private sector buffer eliminated from the file object, common sector | ||||
| @ -216,10 +216,10 @@ | ||||
| /  When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1) | ||||
| /  Note that enabling exFAT discards C89 compatibility. */ | ||||
| 
 | ||||
| #define _FS_NORTC	0 | ||||
| #define _NORTC_MON	6 | ||||
| #define _NORTC_MDAY	4 | ||||
| #define _NORTC_YEAR	2015 | ||||
| #define _FS_NORTC	1 | ||||
| #define _NORTC_MON	7 | ||||
| #define _NORTC_MDAY	20 | ||||
| #define _NORTC_YEAR	2021 | ||||
| /* The option _FS_NORTC switches timestamp functiton. If the system does not have
 | ||||
| /  any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable | ||||
| /  the timestamp function. All objects modified by FatFs will have a fixed timestamp | ||||
| @ -229,7 +229,7 @@ | ||||
| /  _NORTC_MDAY and _NORTC_YEAR have no effect.  | ||||
| /  These options have no effect at read-only configuration (_FS_READONLY = 1). */ | ||||
| 
 | ||||
| #define _FS_LOCK    2     /* 0:Disable or >=1:Enable */ | ||||
| #define _FS_LOCK    0     /* 0:Disable or >=1:Enable */ | ||||
| /* The option _FS_LOCK switches file lock function to control duplicated file open
 | ||||
| /  and illegal operation to open objects. This option must be 0 when _FS_READONLY | ||||
| /  is 1. | ||||
| @ -240,7 +240,7 @@ | ||||
| /      can be opened simultaneously under file lock control. Note that the file | ||||
| /      lock control is independent of re-entrancy. */ | ||||
| 
 | ||||
| #define _FS_REENTRANT    1  /* 0:Disable or 1:Enable */ | ||||
| #define _FS_REENTRANT    0  /* 0:Disable or 1:Enable */ | ||||
| #define _FS_TIMEOUT      1000 /* Timeout period in unit of time ticks */ | ||||
| #define _SYNC_t          osMutexId_t | ||||
| /* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
 | ||||
|  | ||||
| @ -1,16 +1,13 @@ | ||||
| #include "file-worker.h" | ||||
| #include "m-string.h" | ||||
| #include <hex.h> | ||||
| #include <sd-card-api.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| #include <furi.h> | ||||
| 
 | ||||
| struct FileWorker { | ||||
|     FS_Api* fs_api; | ||||
|     SdCard_Api* sd_ex_api; | ||||
|     Storage* api; | ||||
|     bool silent; | ||||
|     File file; | ||||
|     char file_buf[48]; | ||||
|     size_t file_buf_cnt; | ||||
|     File* file; | ||||
| }; | ||||
| 
 | ||||
| bool file_worker_check_common_errors(FileWorker* file_worker); | ||||
| @ -26,16 +23,15 @@ bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool | ||||
| FileWorker* file_worker_alloc(bool _silent) { | ||||
|     FileWorker* file_worker = malloc(sizeof(FileWorker)); | ||||
|     file_worker->silent = _silent; | ||||
|     file_worker->fs_api = furi_record_open("sdcard"); | ||||
|     file_worker->sd_ex_api = furi_record_open("sdcard-ex"); | ||||
|     file_worker->file_buf_cnt = 0; | ||||
|     file_worker->api = furi_record_open("storage"); | ||||
|     file_worker->file = storage_file_alloc(file_worker->api); | ||||
| 
 | ||||
|     return file_worker; | ||||
| } | ||||
| 
 | ||||
| void file_worker_free(FileWorker* file_worker) { | ||||
|     furi_record_close("sdcard"); | ||||
|     furi_record_close("sdcard-ex"); | ||||
|     storage_file_free(file_worker->file); | ||||
|     furi_record_close("storage"); | ||||
|     free(file_worker); | ||||
| } | ||||
| 
 | ||||
| @ -44,8 +40,7 @@ bool file_worker_open( | ||||
|     const char* filename, | ||||
|     FS_AccessMode access_mode, | ||||
|     FS_OpenMode open_mode) { | ||||
|     bool result = | ||||
|         file_worker->fs_api->file.open(&file_worker->file, filename, access_mode, open_mode); | ||||
|     bool result = storage_file_open(file_worker->file, filename, access_mode, open_mode); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot open\nfile"); | ||||
| @ -56,13 +51,15 @@ bool file_worker_open( | ||||
| } | ||||
| 
 | ||||
| bool file_worker_close(FileWorker* file_worker) { | ||||
|     file_worker->fs_api->file.close(&file_worker->file); | ||||
|     if(storage_file_is_open(file_worker->file)) { | ||||
|         storage_file_close(file_worker->file); | ||||
|     } | ||||
| 
 | ||||
|     return file_worker_check_common_errors(file_worker); | ||||
| } | ||||
| 
 | ||||
| bool file_worker_mkdir(FileWorker* file_worker, const char* dirname) { | ||||
|     FS_Error fs_result = file_worker->fs_api->common.mkdir(dirname); | ||||
|     FS_Error fs_result = storage_common_mkdir(file_worker->api, dirname); | ||||
| 
 | ||||
|     if(fs_result != FSE_OK && fs_result != FSE_EXIST) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot create\nfolder"); | ||||
| @ -73,7 +70,7 @@ bool file_worker_mkdir(FileWorker* file_worker, const char* dirname) { | ||||
| } | ||||
| 
 | ||||
| bool file_worker_remove(FileWorker* file_worker, const char* filename) { | ||||
|     FS_Error fs_result = file_worker->fs_api->common.remove(filename); | ||||
|     FS_Error fs_result = storage_common_remove(file_worker->api, filename); | ||||
|     if(fs_result != FSE_OK && fs_result != FSE_NOT_EXIST) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot remove\nold file"); | ||||
|         return false; | ||||
| @ -96,9 +93,8 @@ bool file_worker_read_until(FileWorker* file_worker, string_t str_result, char s | ||||
|     uint8_t buffer[buffer_size]; | ||||
| 
 | ||||
|     do { | ||||
|         uint16_t read_count = | ||||
|             file_worker->fs_api->file.read(&file_worker->file, buffer, buffer_size); | ||||
|         if(file_worker->file.error_id != FSE_OK) { | ||||
|         uint16_t read_count = storage_file_read(file_worker->file, buffer, buffer_size); | ||||
|         if(storage_file_get_error(file_worker->file) != FSE_OK) { | ||||
|             file_worker_show_error_internal(file_worker, "Cannot read\nfile"); | ||||
|             return false; | ||||
|         } | ||||
| @ -210,7 +206,16 @@ bool file_worker_write_hex(FileWorker* file_worker, const uint8_t* buffer, uint1 | ||||
| } | ||||
| 
 | ||||
| void file_worker_show_error(FileWorker* file_worker, const char* error_text) { | ||||
|     file_worker->sd_ex_api->show_error(file_worker->sd_ex_api->context, error_text); | ||||
|     DialogsApp* dialogs = furi_record_open("dialogs"); | ||||
| 
 | ||||
|     DialogMessage* message = dialog_message_alloc(); | ||||
|     dialog_message_set_text(message, error_text, 88, 32, AlignCenter, AlignCenter); | ||||
|     dialog_message_set_icon(message, &I_SDQuestion_35x43, 5, 6); | ||||
|     dialog_message_set_buttons(message, "Back", NULL, NULL); | ||||
|     dialog_message_show(dialogs, message); | ||||
|     dialog_message_free(message); | ||||
| 
 | ||||
|     furi_record_close("dialogs"); | ||||
| } | ||||
| 
 | ||||
| bool file_worker_file_select( | ||||
| @ -220,16 +225,17 @@ bool file_worker_file_select( | ||||
|     char* result, | ||||
|     uint8_t result_size, | ||||
|     const char* selected_filename) { | ||||
|     return file_worker->sd_ex_api->file_select( | ||||
|         file_worker->sd_ex_api->context, path, extension, result, result_size, selected_filename); | ||||
|     DialogsApp* dialogs = furi_record_open("dialogs"); | ||||
|     bool ret = | ||||
|         dialog_file_select_show(dialogs, path, extension, result, result_size, selected_filename); | ||||
|     furi_record_close("dialogs"); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| bool file_worker_check_common_errors(FileWorker* file_worker) { | ||||
|     //TODO remove
 | ||||
|     /* TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info(). */ | ||||
|     FS_Error fs_err = file_worker->fs_api->common.get_fs_info(NULL, NULL); | ||||
|     if(fs_err != FSE_OK) | ||||
|         file_worker->sd_ex_api->show_error(file_worker->sd_ex_api->context, "SD card not found"); | ||||
|     return fs_err == FSE_OK; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text) { | ||||
| @ -239,10 +245,9 @@ void file_worker_show_error_internal(FileWorker* file_worker, const char* error_ | ||||
| } | ||||
| 
 | ||||
| bool file_worker_read_internal(FileWorker* file_worker, void* buffer, uint16_t bytes_to_read) { | ||||
|     uint16_t read_count = | ||||
|         file_worker->fs_api->file.read(&file_worker->file, buffer, bytes_to_read); | ||||
|     uint16_t read_count = storage_file_read(file_worker->file, buffer, bytes_to_read); | ||||
| 
 | ||||
|     if(file_worker->file.error_id != FSE_OK || read_count != bytes_to_read) { | ||||
|     if(storage_file_get_error(file_worker->file) != FSE_OK || read_count != bytes_to_read) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot read\nfile"); | ||||
|         return false; | ||||
|     } | ||||
| @ -254,10 +259,9 @@ bool file_worker_write_internal( | ||||
|     FileWorker* file_worker, | ||||
|     const void* buffer, | ||||
|     uint16_t bytes_to_write) { | ||||
|     uint16_t write_count = | ||||
|         file_worker->fs_api->file.write(&file_worker->file, buffer, bytes_to_write); | ||||
|     uint16_t write_count = storage_file_write(file_worker->file, buffer, bytes_to_write); | ||||
| 
 | ||||
|     if(file_worker->file.error_id != FSE_OK || write_count != bytes_to_write) { | ||||
|     if(storage_file_get_error(file_worker->file) != FSE_OK || write_count != bytes_to_write) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot write\nto file"); | ||||
|         return false; | ||||
|     } | ||||
| @ -266,9 +270,9 @@ bool file_worker_write_internal( | ||||
| } | ||||
| 
 | ||||
| bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position) { | ||||
|     *position = file_worker->fs_api->file.tell(&file_worker->file); | ||||
|     *position = storage_file_tell(file_worker->file); | ||||
| 
 | ||||
|     if(file_worker->file.error_id != FSE_OK) { | ||||
|     if(storage_file_get_error(file_worker->file) != FSE_OK) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot tell\nfile offset"); | ||||
|         return false; | ||||
|     } | ||||
| @ -277,8 +281,8 @@ bool file_worker_tell_internal(FileWorker* file_worker, uint64_t* position) { | ||||
| } | ||||
| 
 | ||||
| bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool from_start) { | ||||
|     file_worker->fs_api->file.seek(&file_worker->file, position, from_start); | ||||
|     if(file_worker->file.error_id != FSE_OK) { | ||||
|     storage_file_seek(file_worker->file, position, from_start); | ||||
|     if(storage_file_get_error(file_worker->file) != FSE_OK) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot seek\nfile"); | ||||
|         return false; | ||||
|     } | ||||
| @ -286,9 +290,17 @@ bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t file_buf_size, char separator) { | ||||
| bool file_worker_read_until_buffered( | ||||
|     FileWorker* file_worker, | ||||
|     string_t str_result, | ||||
|     char* file_buf, | ||||
|     size_t* file_buf_cnt, | ||||
|     size_t file_buf_size, | ||||
|     char separator) { | ||||
|     furi_assert(string_capacity(str_result) > 0); | ||||
|     furi_assert(file_buf_size <= 512);  /* fs_api->file.read now supports up to 512 bytes reading at a time */ | ||||
| 
 | ||||
|     // fs_api->file.read now supports up to 512 bytes reading at a time
 | ||||
|     furi_assert(file_buf_size <= 512); | ||||
| 
 | ||||
|     string_clean(str_result); | ||||
|     size_t newline_index = 0; | ||||
| @ -311,11 +323,11 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul | ||||
|                 furi_assert(0); | ||||
|             } | ||||
| 
 | ||||
|             if (max_length && (string_size(str_result) + end_index > max_length)) | ||||
|             if(max_length && (string_size(str_result) + end_index > max_length)) | ||||
|                 max_length_exceeded = true; | ||||
| 
 | ||||
|             if (!max_length_exceeded) { | ||||
|                 for (size_t i = 0; i < end_index; ++i) { | ||||
|             if(!max_length_exceeded) { | ||||
|                 for(size_t i = 0; i < end_index; ++i) { | ||||
|                     string_push_back(str_result, file_buf[i]); | ||||
|                 } | ||||
|             } | ||||
| @ -325,9 +337,9 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul | ||||
|             if(found_eol) break; | ||||
|         } | ||||
| 
 | ||||
|         *file_buf_cnt += | ||||
|             file_worker->fs_api->file.read(&file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); | ||||
|         if(file_worker->file.error_id != FSE_OK) { | ||||
|         *file_buf_cnt += storage_file_read( | ||||
|             file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); | ||||
|         if(storage_file_get_error(file_worker->file) != FSE_OK) { | ||||
|             file_worker_show_error_internal(file_worker, "Cannot read\nfile"); | ||||
|             string_clear(str_result); | ||||
|             *file_buf_cnt = 0; | ||||
| @ -338,14 +350,13 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (max_length_exceeded) | ||||
|         string_clear(str_result); | ||||
|     if(max_length_exceeded) string_clear(str_result); | ||||
| 
 | ||||
|     return string_size(str_result) || *file_buf_cnt; | ||||
| } | ||||
| 
 | ||||
| bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path) { | ||||
|     FS_Error fs_result = file_worker->fs_api->common.rename(old_path, new_path); | ||||
|     FS_Error fs_result = storage_common_rename(file_worker->api, old_path, new_path); | ||||
| 
 | ||||
|     if(fs_result != FSE_OK && fs_result != FSE_EXIST) { | ||||
|         file_worker_show_error_internal(file_worker, "Cannot rename\n file/directory"); | ||||
| @ -359,16 +370,12 @@ bool file_worker_check_errors(FileWorker* file_worker) { | ||||
|     return file_worker_check_common_errors(file_worker); | ||||
| } | ||||
| 
 | ||||
| bool file_worker_is_file_exist( | ||||
|     FileWorker* file_worker, | ||||
|     const char* filename, | ||||
|     bool* exist) { | ||||
| bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist) { | ||||
|     File* file = storage_file_alloc(file_worker->api); | ||||
| 
 | ||||
|     File file; | ||||
|     *exist = file_worker->fs_api->file.open(&file, filename, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     if (*exist) | ||||
|         file_worker->fs_api->file.close(&file); | ||||
|     *exist = storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     storage_file_close(file); | ||||
|     storage_file_free(file); | ||||
| 
 | ||||
|     return file_worker_check_common_errors(file_worker); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| #pragma once | ||||
| #include <m-string.h> | ||||
| #include <filesystem-api.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| @ -150,20 +150,20 @@ void file_worker_show_error(FileWorker* file_worker, const char* error_text); | ||||
|  * @brief Show system file select widget | ||||
|  *  | ||||
|  * @param file_worker FileWorker instance  | ||||
|  * @param path  | ||||
|  * @param extension  | ||||
|  * @param result  | ||||
|  * @param result_size  | ||||
|  * @param selected_filename  | ||||
|  * @return true if file was selected | ||||
|  * @param path path to directory | ||||
|  * @param extension file extension to be offered for selection | ||||
|  * @param selected_filename buffer where the selected filename will be saved | ||||
|  * @param selected_filename_size and the size of this buffer | ||||
|  * @param preselected_filename filename to be preselected | ||||
|  * @return bool whether a file was selected | ||||
|  */ | ||||
| bool file_worker_file_select( | ||||
|     FileWorker* file_worker, | ||||
|     const char* path, | ||||
|     const char* extension, | ||||
|     char* result, | ||||
|     uint8_t result_size, | ||||
|     const char* selected_filename); | ||||
|     char* selected_filename, | ||||
|     uint8_t selected_filename_size, | ||||
|     const char* preselected_filename); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Reads data from a file until separator or EOF is found. | ||||
| @ -177,7 +177,13 @@ bool file_worker_file_select( | ||||
|  * @param separator | ||||
|  * @return true on success | ||||
|  */ | ||||
| bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t max_length, char separator); | ||||
| bool file_worker_read_until_buffered( | ||||
|     FileWorker* file_worker, | ||||
|     string_t str_result, | ||||
|     char* file_buf, | ||||
|     size_t* file_buf_cnt, | ||||
|     size_t max_length, | ||||
|     char separator); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Check whether file exist or not | ||||
| @ -187,10 +193,7 @@ bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_resul | ||||
|  * @param exist - flag to show file exist | ||||
|  * @return true on success | ||||
|  */ | ||||
| bool file_worker_is_file_exist( | ||||
|     FileWorker* file_worker, | ||||
|     const char* filename, | ||||
|     bool* exist); | ||||
| bool file_worker_is_file_exist(FileWorker* file_worker, const char* filename, bool* exist); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Rename file or directory | ||||
| @ -200,9 +203,7 @@ bool file_worker_is_file_exist( | ||||
|  * @param new_filename | ||||
|  * @return true on success | ||||
|  */ | ||||
| bool file_worker_rename(FileWorker* file_worker, | ||||
|     const char* old_path, | ||||
|     const char* new_path); | ||||
| bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Check errors | ||||
|  | ||||
| @ -28,6 +28,23 @@ bool args_read_string_and_trim(string_t args, string_t word) { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool args_read_probably_quoted_string_and_trim(string_t args, string_t word) { | ||||
|     if(string_size(args) > 1 && string_get_char(args, 0) == '\"') { | ||||
|         size_t second_quote_pos = string_search_char(args, '\"', 1); | ||||
| 
 | ||||
|         if(second_quote_pos == 0) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         string_set_n(word, args, 1, second_quote_pos - 1); | ||||
|         string_right(args, second_quote_pos + 1); | ||||
|         string_strim(args); | ||||
|         return true; | ||||
|     } else { | ||||
|         return args_read_string_and_trim(args, word); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool args_char_to_hex(char hi_nibble, char low_nibble, uint8_t* byte) { | ||||
|     uint8_t hi_nibble_value = 0; | ||||
|     uint8_t low_nibble_value = 0; | ||||
|  | ||||
| @ -8,15 +8,25 @@ extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Extract first word from arguments string and trim arguments string | ||||
|  * @brief Extract first argument from arguments string and trim arguments string | ||||
|  *  | ||||
|  * @param args arguments string | ||||
|  * @param word first word, output | ||||
|  * @param word first argument, output | ||||
|  * @return true - success | ||||
|  * @return false - arguments string does not contain anything | ||||
|  */ | ||||
| bool args_read_string_and_trim(string_t args, string_t word); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Extract the first quoted argument from the argument string and trim the argument string. If the argument is not quoted, calls args_read_string_and_trim. | ||||
|  *  | ||||
|  * @param args arguments string | ||||
|  * @param word first argument, output, without quotes | ||||
|  * @return true - success | ||||
|  * @return false - arguments string does not contain anything | ||||
|  */ | ||||
| bool args_read_probably_quoted_string_and_trim(string_t args, string_t word); | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Convert hex ASCII values to byte array | ||||
|  *  | ||||
|  | ||||
| @ -1,329 +0,0 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /**
 | ||||
|  *  @brief Access mode flags | ||||
|  */ | ||||
| typedef enum { | ||||
|     FSAM_READ = (1 << 0), /**< Read access */ | ||||
|     FSAM_WRITE = (1 << 1), /**< Write access */ | ||||
| } FS_AccessMode; | ||||
| 
 | ||||
| /**
 | ||||
|  *  @brief Open mode flags | ||||
|  */ | ||||
| typedef enum { | ||||
|     FSOM_OPEN_EXISTING = 1, /**< Open file, fail if file doesn't exist */ | ||||
|     FSOM_OPEN_ALWAYS = 2, /**< Open file. Create new file if not exist */ | ||||
|     FSOM_OPEN_APPEND = 4, /**< Open file. Create new file if not exist. Set R/W pointer to EOF */ | ||||
|     FSOM_CREATE_NEW = 8, /**< Creates a new file. Fails if the file is exist */ | ||||
|     FSOM_CREATE_ALWAYS = 16, /**< Creates a new file. If file exist, truncate to zero size */ | ||||
| } FS_OpenMode; | ||||
| 
 | ||||
| /**
 | ||||
|  *  @brief API errors enumeration | ||||
|  */ | ||||
| typedef enum { | ||||
|     FSE_OK, /**< No error */ | ||||
|     FSE_NOT_READY, /**< FS not ready */ | ||||
|     FSE_EXIST, /**< File/Dir alrady exist */ | ||||
|     FSE_NOT_EXIST, /**< File/Dir does not exist */ | ||||
|     FSE_INVALID_PARAMETER, /**< Invalid API parameter */ | ||||
|     FSE_DENIED, /**< Access denied */ | ||||
|     FSE_INVALID_NAME, /**< Invalid name/path */ | ||||
|     FSE_INTERNAL, /**< Internal error */ | ||||
|     FSE_NOT_IMPLEMENTED, /**< Functon not implemented */ | ||||
| } FS_Error; | ||||
| 
 | ||||
| /**
 | ||||
|  *  @brief FileInfo flags | ||||
|  */ | ||||
| typedef enum { | ||||
|     FSF_READ_ONLY = (1 << 0), /**< Readonly */ | ||||
|     FSF_HIDDEN = (1 << 1), /**< Hidden */ | ||||
|     FSF_SYSTEM = (1 << 2), /**< System */ | ||||
|     FSF_DIRECTORY = (1 << 3), /**< Directory */ | ||||
|     FSF_ARCHIVE = (1 << 4), /**< Archive */ | ||||
| } FS_Flags; | ||||
| 
 | ||||
| /** 
 | ||||
|  *  @brief Structure that hold file index and returned api errors  | ||||
|  */ | ||||
| typedef struct { | ||||
|     uint32_t file_id; /**< File ID for internal references */ | ||||
|     FS_Error error_id; /**< Standart API error from FS_Error enum */ | ||||
|     uint32_t internal_error_id; /**< Internal API error value */ | ||||
| } File; | ||||
| 
 | ||||
| // TODO: solve year 2107 problem
 | ||||
| /** 
 | ||||
|  *  @brief Structure that hold packed date values  | ||||
|  */ | ||||
| typedef struct __attribute__((packed)) { | ||||
|     uint16_t month_day : 5; /**< month day */ | ||||
|     uint16_t month : 4; /**< month index */ | ||||
|     uint16_t year : 7; /**< year, year + 1980 to get actual value */ | ||||
| } FileDate; | ||||
| 
 | ||||
| /** 
 | ||||
|  *  @brief Structure that hold packed time values  | ||||
|  */ | ||||
| typedef struct __attribute__((packed)) { | ||||
|     uint16_t second : 5; /**< second, second * 2 to get actual value  */ | ||||
|     uint16_t minute : 6; /**< minute */ | ||||
|     uint16_t hour : 5; /**< hour */ | ||||
| } FileTime; | ||||
| 
 | ||||
| /** 
 | ||||
|  *  @brief Union of simple date and real value  | ||||
|  */ | ||||
| typedef union { | ||||
|     FileDate simple; /**< simple access to date */ | ||||
|     uint16_t value; /**< real date value */ | ||||
| } FileDateUnion; | ||||
| 
 | ||||
| /** 
 | ||||
|  *  @brief Union of simple time and real value  | ||||
|  */ | ||||
| typedef union { | ||||
|     FileTime simple; /**< simple access to time */ | ||||
|     uint16_t value; /**< real time value */ | ||||
| } FileTimeUnion; | ||||
| 
 | ||||
| /** 
 | ||||
|  *  @brief Structure that hold file info | ||||
|  */ | ||||
| typedef struct { | ||||
|     uint8_t flags; /**< flags from FS_Flags enum */ | ||||
|     uint64_t size; /**< file size */ | ||||
|     FileDateUnion date; /**< file date */ | ||||
|     FileTimeUnion time; /**< file time */ | ||||
| } FileInfo; | ||||
| 
 | ||||
| /** @struct FS_File_Api
 | ||||
|  *  @brief File api structure | ||||
|  *  | ||||
|  *  @var FS_File_Api::open | ||||
|  *      @brief Open file | ||||
|  *      @param file pointer to file object, filled by api | ||||
|  *      @param path path to file  | ||||
|  *      @param access_mode access mode from FS_AccessMode  | ||||
|  *      @param open_mode open mode from FS_OpenMode  | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::close  | ||||
|  *      @brief Close file | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::read | ||||
|  *      @brief Read bytes from file to buffer | ||||
|  *      @param file pointer to file object | ||||
|  *      @param buff pointer to buffer for reading | ||||
|  *      @param bytes_to_read how many bytes to read, must be smaller or equal to buffer size  | ||||
|  *      @return how many bytes actually has been readed | ||||
|  *  | ||||
|  *  @var FS_File_Api::write | ||||
|  *      @brief Write bytes from buffer to file | ||||
|  *      @param file pointer to file object | ||||
|  *      @param buff pointer to buffer for writing | ||||
|  *      @param bytes_to_read how many bytes to write, must be smaller or equal to buffer size  | ||||
|  *      @return how many bytes actually has been writed | ||||
|  *  | ||||
|  *  @var FS_File_Api::seek | ||||
|  *      @brief Move r/w pointer  | ||||
|  *      @param file pointer to file object | ||||
|  *      @param offset offset to move r/w pointer | ||||
|  *      @param from_start set offset from start, or from current position | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::tell | ||||
|  *      @brief Get r/w pointer position | ||||
|  *      @param file pointer to file object | ||||
|  *      @return current r/w pointer position | ||||
|  *  | ||||
|  *  @var FS_File_Api::truncate | ||||
|  *      @brief Truncate file size to current r/w pointer position | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::size | ||||
|  *      @brief Fet file size | ||||
|  *      @param file pointer to file object | ||||
|  *      @return file size | ||||
|  *  | ||||
|  *  @var FS_File_Api::sync | ||||
|  *      @brief Write file cache to storage | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_File_Api::eof | ||||
|  *      @brief Checks that the r/w pointer is at the end of the file | ||||
|  *      @param file pointer to file object | ||||
|  *      @return end of file flag | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief File api structure | ||||
|  */ | ||||
| typedef struct { | ||||
|     bool (*open)(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode); | ||||
|     bool (*close)(File* file); | ||||
|     uint16_t (*read)(File* file, void* buff, uint16_t bytes_to_read); | ||||
|     uint16_t (*write)(File* file, const void* buff, uint16_t bytes_to_write); | ||||
|     bool (*seek)(File* file, uint32_t offset, bool from_start); | ||||
|     uint64_t (*tell)(File* file); | ||||
|     bool (*truncate)(File* file); | ||||
|     uint64_t (*size)(File* file); | ||||
|     bool (*sync)(File* file); | ||||
|     bool (*eof)(File* file); | ||||
| } FS_File_Api; | ||||
| 
 | ||||
| /** @struct FS_Dir_Api
 | ||||
|  *  @brief Dir api structure | ||||
|  *  | ||||
|  *  @var FS_Dir_Api::open | ||||
|  *      @brief Open directory to get objects from | ||||
|  *      @param file pointer to file object, filled by api | ||||
|  *      @param path path to directory  | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_Dir_Api::close  | ||||
|  *      @brief Close directory | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  *  | ||||
|  *  @var FS_Dir_Api::read | ||||
|  *      @brief Read next object info in directory | ||||
|  *      @param file pointer to file object | ||||
|  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||
|  *      @param name pointer to name buffer, can be NULL | ||||
|  *      @param name_length name buffer length | ||||
|  *      @return success flag (if next object not exist also returns false and set error_id to FSE_NOT_EXIST) | ||||
|  *  | ||||
|  *  @var FS_Dir_Api::rewind | ||||
|  *      @brief Rewind to first object info in directory | ||||
|  *      @param file pointer to file object | ||||
|  *      @return success flag | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Dir api structure | ||||
|  */ | ||||
| typedef struct { | ||||
|     bool (*open)(File* file, const char* path); | ||||
|     bool (*close)(File* file); | ||||
|     bool (*read)(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||
|     bool (*rewind)(File* file); | ||||
| } FS_Dir_Api; | ||||
| 
 | ||||
| /** @struct FS_Common_Api
 | ||||
|  *  @brief Common api structure | ||||
|  *  | ||||
|  *  @var FS_Common_Api::info | ||||
|  *      @brief Open directory to get objects from | ||||
|  *      @param path path to file/directory | ||||
|  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||
|  *      @param name pointer to name buffer, can be NULL | ||||
|  *      @param name_length name buffer length | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::remove | ||||
|  *      @brief Remove file/directory from storage,  | ||||
|  *          directory must be empty, | ||||
|  *          file/directory must not be opened, | ||||
|  *          file/directory must not have FSF_READ_ONLY flag | ||||
|  *      @param path path to file/directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::rename | ||||
|  *      @brief Rename file/directory, | ||||
|  *          file/directory must not be opened | ||||
|  *      @param path path to file/directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::set_attr | ||||
|  *      @brief Set attributes of file/directory,  | ||||
|  *          for example: | ||||
|  *          @code | ||||
|  *          set "read only" flag and remove "hidden" flag | ||||
|  *          set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN); | ||||
|  *          @endcode | ||||
|  *      @param path path to file/directory | ||||
|  *      @param attr attribute values consist of FS_Flags | ||||
|  *      @param mask attribute mask consist of FS_Flags | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::mkdir | ||||
|  *      @brief Create new directory | ||||
|  *      @param path path to new directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::set_time | ||||
|  *      @brief Set file/directory modification time | ||||
|  *      @param path path to file/directory | ||||
|  *      @param date modification date  | ||||
|  *      @param time modification time | ||||
|  *      @see FileDateUnion | ||||
|  *      @see FileTimeUnion | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::get_fs_info | ||||
|  *      @brief Get total and free space storage values | ||||
|  *      @param total_space pointer to total space value | ||||
|  *      @param free_space pointer to free space value | ||||
|  *      @return FS_Error error info | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Common api structure | ||||
|  */ | ||||
| typedef struct { | ||||
|     FS_Error (*info)(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length); | ||||
|     FS_Error (*remove)(const char* path); | ||||
|     FS_Error (*rename)(const char* old_path, const char* new_path); | ||||
|     FS_Error (*set_attr)(const char* path, uint8_t attr, uint8_t mask); | ||||
|     FS_Error (*mkdir)(const char* path); | ||||
|     FS_Error (*set_time)(const char* path, FileDateUnion date, FileTimeUnion time); | ||||
|     FS_Error (*get_fs_info)(uint64_t* total_space, uint64_t* free_space); | ||||
| } FS_Common_Api; | ||||
| 
 | ||||
| /** @struct FS_Error_Api
 | ||||
|  *  @brief Errors api structure | ||||
|  *  | ||||
|  *  @var FS_Error_Api::get_desc | ||||
|  *      @brief Get error description text | ||||
|  *      @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id) | ||||
|  *      @return pointer to description text | ||||
|  *  | ||||
|  *  @var FS_Error_Api::get_internal_desc | ||||
|  *      @brief Get internal error description text | ||||
|  *      @param internal_error_id error id (for fire/dir functions result can be obtained from File.internal_error_id) | ||||
|  *      @return pointer to description text | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Errors api structure | ||||
|  */ | ||||
| typedef struct { | ||||
|     const char* (*get_desc)(FS_Error error_id); | ||||
|     const char* (*get_internal_desc)(uint32_t internal_error_id); | ||||
| } FS_Error_Api; | ||||
| 
 | ||||
| /**
 | ||||
|  * @brief Full filesystem api structure | ||||
|  */ | ||||
| typedef struct { | ||||
|     FS_File_Api file; | ||||
|     FS_Dir_Api dir; | ||||
|     FS_Common_Api common; | ||||
|     FS_Error_Api error; | ||||
| } FS_Api; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -1,25 +0,0 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct SdApp SdApp; | ||||
| 
 | ||||
| typedef struct { | ||||
|     SdApp* context; | ||||
|     bool (*file_select)( | ||||
|         SdApp* context, | ||||
|         const char* path, | ||||
|         const char* extension, | ||||
|         char* result, | ||||
|         uint8_t result_size, | ||||
|         const char* selected_filename); | ||||
|     void (*check_error)(SdApp* context); | ||||
|     void (*show_error)(SdApp* context, const char* error_text); | ||||
| } SdCard_Api; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -1,48 +0,0 @@ | ||||
| #include <file_reader.h> | ||||
| 
 | ||||
| std::string FileReader::getline(File* file) { | ||||
|     std::string str; | ||||
|     size_t newline_index = 0; | ||||
|     bool found_eol = false; | ||||
|     bool max_length_exceeded = false; | ||||
| 
 | ||||
|     while(1) { | ||||
|         if(file_buf_cnt > 0) { | ||||
|             size_t end_index = 0; | ||||
|             char* endline_ptr = (char*)memchr(file_buf, '\n', file_buf_cnt); | ||||
|             newline_index = endline_ptr - file_buf; | ||||
| 
 | ||||
|             if(endline_ptr == 0) { | ||||
|                 end_index = file_buf_cnt; | ||||
|             } else if(newline_index < file_buf_cnt) { | ||||
|                 end_index = newline_index + 1; | ||||
|                 found_eol = true; | ||||
|             } else { | ||||
|                 furi_assert(0); | ||||
|             } | ||||
| 
 | ||||
|             if (max_line_length && (str.size() + end_index > max_line_length)) | ||||
|                 max_length_exceeded = true; | ||||
| 
 | ||||
|             if (!max_length_exceeded) | ||||
|                 str.append(file_buf, end_index); | ||||
| 
 | ||||
|             memmove(file_buf, &file_buf[end_index], file_buf_cnt - end_index); | ||||
|             file_buf_cnt = file_buf_cnt - end_index; | ||||
|             if(found_eol) break; | ||||
|         } | ||||
| 
 | ||||
|         file_buf_cnt += | ||||
|             fs_api->file.read(file, &file_buf[file_buf_cnt], sizeof(file_buf) - file_buf_cnt); | ||||
|         if(file_buf_cnt == 0) { | ||||
|             break; // end of reading
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (max_length_exceeded) | ||||
|         str.clear(); | ||||
| 
 | ||||
|     return str; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -1,44 +0,0 @@ | ||||
| #pragma once | ||||
| #include <string> | ||||
| #include <memory> | ||||
| #include "sd-card-api.h" | ||||
| #include "filesystem-api.h" | ||||
| 
 | ||||
| class FileReader { | ||||
| private: | ||||
|     char file_buf[48]; | ||||
|     size_t file_buf_cnt = 0; | ||||
|     size_t max_line_length = 0; | ||||
|     SdCard_Api* sd_ex_api; | ||||
|     FS_Api* fs_api; | ||||
| 
 | ||||
| public: | ||||
|     FileReader() { | ||||
|         sd_ex_api = static_cast<SdCard_Api*>(furi_record_open("sdcard-ex")); | ||||
|         fs_api = static_cast<FS_Api*>(furi_record_open("sdcard")); | ||||
|         reset(); | ||||
|     } | ||||
|     ~FileReader() { | ||||
|         furi_record_close("sdcard"); | ||||
|         furi_record_close("sdcard-ex"); | ||||
|     } | ||||
| 
 | ||||
|     std::string getline(File* file); | ||||
| 
 | ||||
|     void reset(void) { | ||||
|         file_buf_cnt = 0; | ||||
|     } | ||||
| 
 | ||||
|     SdCard_Api& get_sd_api() { | ||||
|         return *sd_ex_api; | ||||
|     } | ||||
| 
 | ||||
|     FS_Api& get_fs_api() { | ||||
|         return *fs_api; | ||||
|     } | ||||
| 
 | ||||
|     void set_max_line_length(size_t value) { | ||||
|         max_line_length = value; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| @ -1,7 +1,7 @@ | ||||
| #include "subghz_keystore.h" | ||||
| 
 | ||||
| #include <furi.h> | ||||
| #include <filesystem-api.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #define FILE_BUFFER_SIZE 64 | ||||
| 
 | ||||
| @ -52,17 +52,15 @@ static void subghz_keystore_process_line(SubGhzKeystore* instance, string_t line | ||||
| } | ||||
| 
 | ||||
| void subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { | ||||
|     File manufacture_keys_file; | ||||
|     FS_Api* fs_api = furi_record_open("sdcard"); | ||||
|     fs_api->file.open(&manufacture_keys_file, file_name, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     File* manufacture_keys_file = storage_file_alloc(furi_record_open("storage")); | ||||
|     string_t line; | ||||
|     string_init(line); | ||||
|     if(manufacture_keys_file.error_id == FSE_OK) { | ||||
|     if(storage_file_open(manufacture_keys_file, file_name, FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         printf("Loading manufacture keys file %s\r\n", file_name); | ||||
|         char buffer[FILE_BUFFER_SIZE]; | ||||
|         uint16_t ret; | ||||
|         do { | ||||
|             ret = fs_api->file.read(&manufacture_keys_file, buffer, FILE_BUFFER_SIZE); | ||||
|             ret = storage_file_read(manufacture_keys_file, buffer, FILE_BUFFER_SIZE); | ||||
|             for (uint16_t i=0; i < ret; i++) { | ||||
|                 if (buffer[i] == '\n' && string_size(line) > 0) { | ||||
|                     subghz_keystore_process_line(instance, line); | ||||
| @ -76,8 +74,9 @@ void subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { | ||||
|         printf("Manufacture keys file is not found: %s\r\n", file_name); | ||||
|     } | ||||
|     string_clear(line); | ||||
|     fs_api->file.close(&manufacture_keys_file); | ||||
|     furi_record_close("sdcard"); | ||||
|     storage_file_close(manufacture_keys_file); | ||||
|     storage_file_free(manufacture_keys_file); | ||||
|     furi_record_close("storage"); | ||||
| } | ||||
| 
 | ||||
| SubGhzKeyArray_t* subghz_keystore_get_data(SubGhzKeystore* instance) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 SG
						SG