瀏覽代碼

[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>
SG 4 年之前
父節點
當前提交
ad421a81bc
共有 95 個文件被更改,包括 6160 次插入4058 次删除
  1. 28 31
      applications/applications.c
  2. 12 34
      applications/applications.mk
  3. 48 77
      applications/archive/archive.c
  4. 13 9
      applications/archive/archive_i.h
  5. 1 1
      applications/archive/archive_views.h
  6. 5 0
      applications/cli/cli_commands.c
  7. 8 0
      applications/dialogs/dialogs-api-lock.h
  8. 62 0
      applications/dialogs/dialogs-api.c
  9. 15 0
      applications/dialogs/dialogs-i.h
  10. 45 0
      applications/dialogs/dialogs-message.h
  11. 59 0
      applications/dialogs/dialogs-module-file-select.c
  12. 12 0
      applications/dialogs/dialogs-module-file-select.h
  13. 152 0
      applications/dialogs/dialogs-module-message.c
  14. 12 0
      applications/dialogs/dialogs-module-message.h
  15. 39 0
      applications/dialogs/dialogs.c
  16. 128 0
      applications/dialogs/dialogs.h
  17. 0 0
      applications/dialogs/view-holder.h
  18. 1 1
      applications/dialogs/view_holder.c
  19. 79 45
      applications/dolphin/dolphin_state.c
  20. 77 105
      applications/gui/modules/file_select.c
  21. 0 2
      applications/gui/modules/file_select.h
  22. 11 6
      applications/ibutton/ibutton-app.cpp
  23. 1 3
      applications/ibutton/ibutton-app.h
  24. 0 1
      applications/ibutton/scene/ibutton-scene-save-name.cpp
  25. 0 1
      applications/ibutton/scene/ibutton-scene-start.cpp
  26. 0 62
      applications/internal-storage/internal-storage-i.h
  27. 0 252
      applications/internal-storage/internal-storage.c
  28. 0 40
      applications/internal-storage/internal-storage.h
  29. 0 1
      applications/irda/irda-app-brute-force.hpp
  30. 1 1
      applications/irda/irda-app-file-parser.cpp
  31. 1 1
      applications/irda/irda-app-file-parser.hpp
  32. 1 1
      applications/irda/irda-app-remote-manager.cpp
  33. 1 2
      applications/irda/irda-app-remote-manager.hpp
  34. 4 2
      applications/irda/scene/irda-app-scene.hpp
  35. 3 3
      applications/lfrfid/lfrfid-app.cpp
  36. 0 4
      applications/lfrfid/lfrfid-app.h
  37. 9 4
      applications/loader/loader.c
  38. 1 1
      applications/nfc/nfc_device.c
  39. 54 22
      applications/notification/notification-app.c
  40. 1 1
      applications/notification/notification-app.h
  41. 0 892
      applications/sd-card-test/sd-card-test.cpp
  42. 0 739
      applications/sd-filesystem/sd-filesystem-api.c
  43. 0 956
      applications/sd-filesystem/sd-filesystem.c
  44. 0 145
      applications/sd-filesystem/sd-filesystem.h
  45. 161 0
      applications/storage-settings/scenes/storage-settings-benchmark.c
  46. 8 0
      applications/storage-settings/scenes/storage-settings-scene-config.h
  47. 68 0
      applications/storage-settings/scenes/storage-settings-scene-eject-confirm.c
  48. 65 0
      applications/storage-settings/scenes/storage-settings-scene-ejected.c
  49. 67 0
      applications/storage-settings/scenes/storage-settings-scene-format-confirm.c
  50. 85 0
      applications/storage-settings/scenes/storage-settings-scene-formatting.c
  51. 68 0
      applications/storage-settings/scenes/storage-settings-scene-internal-info.c
  52. 74 0
      applications/storage-settings/scenes/storage-settings-scene-sd-info.c
  53. 104 0
      applications/storage-settings/scenes/storage-settings-scene-start.c
  54. 30 0
      applications/storage-settings/scenes/storage-settings-scene.c
  55. 29 0
      applications/storage-settings/scenes/storage-settings-scene.h
  56. 75 0
      applications/storage-settings/storage-settings.c
  57. 47 0
      applications/storage-settings/storage-settings.h
  58. 59 0
      applications/storage/filesystem-api-defines.h
  59. 192 0
      applications/storage/filesystem-api-internal.h
  60. 38 0
      applications/storage/filesystem-api.c
  61. 350 0
      applications/storage/storage-cli.c
  62. 383 0
      applications/storage/storage-external-api.c
  63. 212 0
      applications/storage/storage-glue.c
  64. 79 0
      applications/storage/storage-glue.h
  65. 27 0
      applications/storage/storage-i.h
  66. 142 0
      applications/storage/storage-message.h
  67. 584 0
      applications/storage/storage-processing.c
  68. 16 0
      applications/storage/storage-processing.h
  69. 21 0
      applications/storage/storage-sd-api.c
  70. 34 0
      applications/storage/storage-sd-api.h
  71. 341 0
      applications/storage/storage-test-app.c
  72. 96 0
      applications/storage/storage.c
  73. 235 0
      applications/storage/storage.h
  74. 82 0
      applications/storage/storages/sd-notify.c
  75. 17 0
      applications/storage/storages/sd-notify.h
  76. 547 0
      applications/storage/storages/storage-ext.c
  77. 16 0
      applications/storage/storages/storage-ext.h
  78. 690 0
      applications/storage/storages/storage-int.c
  79. 13 0
      applications/storage/storages/storage-int.h
  80. 2 2
      applications/subghz/subghz_cli.c
  81. 3 2
      applications/subghz/views/subghz_capture.c
  82. 0 0
      assets/compiled/assets_icons.c
  83. 70 70
      assets/compiled/assets_icons.h
  84. 14 0
      core/furi/memmgr_heap.c
  85. 5 0
      core/furi/memmgr_heap.h
  86. 9 9
      firmware/targets/f6/Src/fatfs/ffconf.h
  87. 63 56
      lib/app-scened-template/file-worker.c
  88. 19 18
      lib/app-scened-template/file-worker.h
  89. 17 0
      lib/args/args.c
  90. 12 2
      lib/args/args.h
  91. 0 329
      lib/common-api/filesystem-api.h
  92. 0 25
      lib/common-api/sd-card-api.h
  93. 0 48
      lib/file_reader/file_reader.cpp
  94. 0 44
      lib/file_reader/file_reader.h
  95. 7 8
      lib/subghz/subghz_keystore.c

+ 28 - 31
applications/applications.c

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

+ 12 - 34
applications/applications.mk

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

+ 48 - 77
applications/archive/archive.c

@@ -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);
-
-                    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;
+        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;
+                    });
+
+                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));
-
-    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;
-    }
+    string_init_printf(
+        buffer_dst, "%s/%s", get_favorites_path(), string_get_cstr(archive->browser.name));
 
-    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();
 

+ 13 - 9
applications/archive/archive_i.h

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

+ 1 - 1
applications/archive/archive_views.h

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

+ 5 - 0
applications/cli/cli_commands.c

@@ -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 - 0
applications/dialogs/dialogs-api-lock.h

@@ -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 - 0
applications/dialogs/dialogs-api.c

@@ -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 - 0
applications/dialogs/dialogs-i.h

@@ -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 - 0
applications/dialogs/dialogs-message.h

@@ -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 - 0
applications/dialogs/dialogs-module-file-select.c

@@ -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 - 0
applications/dialogs/dialogs-module-file-select.h

@@ -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 - 0
applications/dialogs/dialogs-module-message.c

@@ -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 - 0
applications/dialogs/dialogs-module-message.h

@@ -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 - 0
applications/dialogs/dialogs.c

@@ -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 - 0
applications/dialogs/dialogs.h

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

+ 0 - 0
applications/sd-filesystem/view_holder.h → applications/dialogs/view-holder.h


+ 1 - 1
applications/sd-filesystem/view_holder.c → applications/dialogs/view_holder.c

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

+ 79 - 45
applications/dolphin/dolphin_state.c

@@ -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(
+    if(!load_result) {
+        FURI_LOG_E(
             "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;
+            "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", "Checksum(%d != %d) mismatch", store.header.checksum, checksum);
+                "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;
         }
-    } 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);
     }
-    return false;
+
+    storage_file_close(file);
+    storage_file_free(file);
+    return load_result;
 }
 
 void dolphin_state_clear(DolphinState* dolphin_state) {

+ 77 - 105
applications/gui/modules/file_select.c

@@ -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(strcmp(file_select->extension, "*") != 0) {
-                                    string_replace_all_str(
-                                        model->filename[string_counter],
-                                        file_select->extension,
-                                        "");
-                                }
-
-                                return true;
-                            });
-                        string_counter++;
-
-                        if(string_counter >= FILENAME_COUNT) {
-                            break;
-                        }
+        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, "");
+                            }
+
+                            return true;
+                        });
+                    string_counter++;
+
+                    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;
-    const uint8_t name_length = 100;
-    char* name = calloc(name_length, sizeof(char));
-
-    if(name == NULL) {
-        return;
-    }
+    File* directory = storage_file_alloc(file_select->fs_api);
 
+    const uint8_t name_length = 100;
+    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);
 }
 

+ 0 - 2
applications/gui/modules/file_select.h

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

+ 11 - 6
applications/ibutton/ibutton-app.cpp

@@ -5,7 +5,7 @@
 #include <file-worker-cpp.h>
 #include <path.h>
 
-const char* iButtonApp::app_folder = "ibutton";
+const char* iButtonApp::app_folder = "/any/ibutton";
 const char* iButtonApp::app_extension = ".ibtn";
 
 void iButtonApp::run(void* args) {
@@ -13,6 +13,8 @@ void iButtonApp::run(void* args) {
     bool consumed;
     bool exit = false;
 
+    make_app_folder();
+
     if(args && load_key((const char*)args)) {
         current_scene = Scene::SceneEmulate;
     }
@@ -218,15 +220,13 @@ void iButtonApp::generate_random_name(char* name, uint8_t max_name_size) {
 
 // file managment
 bool iButtonApp::save_key(const char* key_name) {
+    // Create ibutton directory if necessary
+    make_app_folder();
+
     FileWorkerCpp file_worker;
     string_t key_file_name;
     bool result = false;
 
-    // Create ibutton directory if necessary
-    if(!file_worker.mkdir(app_folder)) {
-        return false;
-    };
-
     // First remove key if it was saved
     string_init_printf(key_file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension);
     if(!file_worker.remove(string_get_cstr(key_file_name))) {
@@ -370,4 +370,9 @@ bool iButtonApp::delete_key() {
     string_clear(file_name);
 
     return result;
+}
+
+void iButtonApp::make_app_folder() {
+    FileWorkerCpp file_worker;
+    file_worker.mkdir(app_folder);
 }

+ 1 - 3
applications/ibutton/ibutton-app.h

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

+ 0 - 1
applications/ibutton/scene/ibutton-scene-save-name.cpp

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

+ 0 - 1
applications/ibutton/scene/ibutton-scene-start.cpp

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

+ 0 - 62
applications/internal-storage/internal-storage-i.h

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

+ 0 - 252
applications/internal-storage/internal-storage.c

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

+ 0 - 40
applications/internal-storage/internal-storage.h

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

+ 0 - 1
applications/irda/irda-app-brute-force.hpp

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

+ 1 - 1
applications/irda/irda-app-file-parser.cpp

@@ -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 - 1
applications/irda/irda-app-file-parser.hpp

@@ -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 - 1
applications/irda/irda-app-remote-manager.cpp

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

+ 1 - 2
applications/irda/irda-app-remote-manager.hpp

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

+ 4 - 2
applications/irda/scene/irda-app-scene.hpp

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

+ 3 - 3
applications/lfrfid/lfrfid-app.cpp

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

+ 0 - 4
applications/lfrfid/lfrfid-app.h

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

+ 9 - 4
applications/loader/loader.c

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

+ 1 - 1
applications/nfc/nfc_device.c

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

+ 54 - 22
applications/notification/notification-app.c

@@ -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);
+    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;
+        }
+    }
+
+    if(fs_result) {
+        FURI_LOG_I("notification", "save success");
     } else {
-        FURI_LOG_I("notification", "Saved");
+        FURI_LOG_E("notification", "save 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 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();

+ 1 - 1
applications/notification/notification-app.h

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

+ 0 - 892
applications/sd-card-test/sd-card-test.cpp

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

+ 0 - 739
applications/sd-filesystem/sd-filesystem-api.c

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

+ 0 - 956
applications/sd-filesystem/sd-filesystem.c

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

+ 0 - 145
applications/sd-filesystem/sd-filesystem.h

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

+ 161 - 0
applications/storage-settings/scenes/storage-settings-benchmark.c

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

+ 8 - 0
applications/storage-settings/scenes/storage-settings-scene-config.h

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

+ 68 - 0
applications/storage-settings/scenes/storage-settings-scene-eject-confirm.c

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

+ 65 - 0
applications/storage-settings/scenes/storage-settings-scene-ejected.c

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

+ 67 - 0
applications/storage-settings/scenes/storage-settings-scene-format-confirm.c

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

+ 85 - 0
applications/storage-settings/scenes/storage-settings-scene-formatting.c

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

+ 68 - 0
applications/storage-settings/scenes/storage-settings-scene-internal-info.c

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

+ 74 - 0
applications/storage-settings/scenes/storage-settings-scene-sd-info.c

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

+ 104 - 0
applications/storage-settings/scenes/storage-settings-scene-start.c

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

+ 30 - 0
applications/storage-settings/scenes/storage-settings-scene.c

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

+ 29 - 0
applications/storage-settings/scenes/storage-settings-scene.h

@@ -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 - 0
applications/storage-settings/storage-settings.c

@@ -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 - 0
applications/storage-settings/storage-settings.h

@@ -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 - 0
applications/storage/filesystem-api-defines.h

@@ -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 - 0
applications/storage/filesystem-api-internal.h

@@ -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 - 0
applications/storage/filesystem-api.c

@@ -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 - 0
applications/storage/storage-cli.c

@@ -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 - 0
applications/storage/storage-external-api.c

@@ -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 - 0
applications/storage/storage-glue.c

@@ -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 - 0
applications/storage/storage-glue.h

@@ -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 - 0
applications/storage/storage-i.h

@@ -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 - 0
applications/storage/storage-message.h

@@ -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 - 0
applications/storage/storage-processing.c

@@ -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 - 0
applications/storage/storage-processing.h

@@ -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 - 0
applications/storage/storage-sd-api.c

@@ -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 - 0
applications/storage/storage-sd-api.h

@@ -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 - 0
applications/storage/storage-test-app.c

@@ -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 - 0
applications/storage/storage.c

@@ -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 - 0
applications/storage/storage.h

@@ -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 - 0
applications/storage/storages/sd-notify.c

@@ -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 - 0
applications/storage/storages/sd-notify.h

@@ -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 - 0
applications/storage/storages/storage-ext.c

@@ -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 - 0
applications/storage/storages/storage-ext.h

@@ -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 - 0
applications/storage/storages/storage-int.c

@@ -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 - 0
applications/storage/storages/storage-int.h

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

+ 2 - 2
applications/subghz/subghz_cli.c

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

+ 3 - 2
applications/subghz/views/subghz_capture.c

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

文件差異過大導致無法顯示
+ 0 - 0
assets/compiled/assets_icons.c


+ 70 - 70
assets/compiled/assets_icons.h

@@ -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_ButtonCenter_7x7;
 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_ButtonCenter_7x7;
-extern const Icon I_FX_SittingB_40x27;
+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_DolphinFirstStart2_59x51;
+extern const Icon I_DolphinFirstStart3_57x48;
+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_BigBurger_24x24;
 extern const Icon I_FX_Bang_32x6;
-extern const Icon I_DolphinFirstStart3_57x48;
-extern const Icon I_PassportBottom_128x17;
+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_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_Vol_up_25x27;
+extern const Icon I_Power_hvr_25x27;
 extern const Icon I_Up_25x27;
-extern const Icon I_Back_15x10;
-extern const Icon I_IrdaSend_128x64;
-extern const Icon I_IrdaSendShort_128x34;
+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_FaceNopower_29x14;
-extern const Icon I_FaceNormal_29x14;
 extern const Icon I_Battery_16x16;
+extern const Icon I_FaceCharging_29x14;
 extern const Icon I_FaceConfused_29x14;
-extern const Icon I_RFIDDolphinSuccess_108x57;
+extern const Icon I_FaceNopower_29x14;
+extern const Icon I_FaceNormal_29x14;
+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_TV_20x24;
+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_TV_20x24;
-extern const Icon I_BadUsb_9x8;
-extern const Icon I_PlaceholderR_30x13;
+extern const Icon I_Background_128x11;
 extern const Icon I_Background_128x8;
-extern const Icon I_Lock_8x8;
+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_Battery_19x8;
-extern const Icon I_SDcardMounted_11x8;
+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_Bluetooth_5x8;
-extern const Icon I_Background_128x11;
-extern const Icon I_DolphinMafia_115x62;
 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;

+ 14 - 0
core/furi/memmgr_heap.c

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

+ 5 - 0
core/furi/memmgr_heap.h

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

+ 9 - 9
firmware/targets/f6/Src/fatfs/ffconf.h

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

+ 63 - 56
lib/app-scened-template/file-worker.c

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

+ 19 - 18
lib/app-scened-template/file-worker.h

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

+ 17 - 0
lib/args/args.c

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

+ 12 - 2
lib/args/args.h

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

+ 0 - 329
lib/common-api/filesystem-api.h

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

+ 0 - 25
lib/common-api/sd-card-api.h

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

+ 0 - 48
lib/file_reader/file_reader.cpp

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

+ 0 - 44
lib/file_reader/file_reader.h

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

+ 7 - 8
lib/subghz/subghz_keystore.c

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

部分文件因文件數量過多而無法顯示