فهرست منبع

[FL-2415] Storage: blocking file open (#1078)

* Storage: correct replacement for "/any" path in path holder
* Unit tests: storage, blocking file open test
* File stream: error getter
* Storage: common copy and common remove now executes in external thread
* Filesystems: got rid of unused functions
* Storage: untangle dependencies, ram-frendly filesystem api
* iButton: context assertions
* Storage: pubsub messages
* Storage: wait for the file to close if it was open
* Storage: fix folder copying
* Storage: unit test
* Storage: pubsub documentation
* Fix merge error
* Fix memleak in storage test
* Storage: remove unused define

Co-authored-by: あく <alleteam@gmail.com>
SG 3 سال پیش
والد
کامیت
855f2584ab
37فایلهای تغییر یافته به همراه443 افزوده شده و 281 حذف شده
  1. 1 0
      applications/archive/helpers/archive_files.h
  2. 15 4
      applications/desktop/animations/animation_manager.c
  3. 2 0
      applications/ibutton/ibutton_cli.c
  4. 1 0
      applications/ibutton/scene/ibutton_scene_add_type.cpp
  5. 1 0
      applications/ibutton/scene/ibutton_scene_add_value.cpp
  6. 1 0
      applications/ibutton/scene/ibutton_scene_delete_confirm.cpp
  7. 1 0
      applications/ibutton/scene/ibutton_scene_delete_success.cpp
  8. 1 0
      applications/ibutton/scene/ibutton_scene_emulate.cpp
  9. 1 0
      applications/ibutton/scene/ibutton_scene_info.cpp
  10. 1 0
      applications/ibutton/scene/ibutton_scene_read.cpp
  11. 1 0
      applications/ibutton/scene/ibutton_scene_read_crc_error.cpp
  12. 1 0
      applications/ibutton/scene/ibutton_scene_read_not_key_error.cpp
  13. 1 0
      applications/ibutton/scene/ibutton_scene_read_success.cpp
  14. 1 0
      applications/ibutton/scene/ibutton_scene_readed_key_menu.cpp
  15. 1 0
      applications/ibutton/scene/ibutton_scene_save_name.cpp
  16. 1 0
      applications/ibutton/scene/ibutton_scene_save_success.cpp
  17. 1 0
      applications/ibutton/scene/ibutton_scene_saved_key_menu.cpp
  18. 1 0
      applications/ibutton/scene/ibutton_scene_start.cpp
  19. 1 0
      applications/ibutton/scene/ibutton_scene_write.cpp
  20. 1 0
      applications/ibutton/scene/ibutton_scene_write_success.cpp
  21. 1 1
      applications/storage/filesystem_api_defines.h
  22. 26 37
      applications/storage/filesystem_api_internal.h
  23. 16 5
      applications/storage/storage.c
  24. 19 1
      applications/storage/storage.h
  25. 72 20
      applications/storage/storage_external_api.c
  26. 2 24
      applications/storage/storage_glue.c
  27. 3 9
      applications/storage/storage_glue.h
  28. 0 1
      applications/storage/storage_i.h
  29. 0 8
      applications/storage/storage_message.h
  30. 88 108
      applications/storage/storage_processing.c
  31. 5 6
      applications/storage/storage_sd_api.h
  32. 48 27
      applications/storage/storages/storage_ext.c
  33. 30 28
      applications/storage/storages/storage_int.c
  34. 80 0
      applications/tests/storage/storage_test.c
  35. 4 2
      applications/tests/test_index.c
  36. 7 0
      lib/toolbox/stream/file_stream.c
  37. 7 0
      lib/toolbox/stream/file_stream.h

+ 1 - 0
applications/archive/helpers/archive_files.h

@@ -1,5 +1,6 @@
 #pragma once
 #pragma once
 #include "file_worker.h"
 #include "file_worker.h"
+#include <m-array.h>
 
 
 #define MAX_FILES 100 //temp
 #define MAX_FILES 100 //temp
 
 

+ 15 - 4
applications/desktop/animations/animation_manager.c

@@ -94,10 +94,21 @@ void animation_manager_set_interact_callback(
 }
 }
 
 
 static void animation_manager_check_blocking_callback(const void* message, void* context) {
 static void animation_manager_check_blocking_callback(const void* message, void* context) {
-    furi_assert(context);
-    AnimationManager* animation_manager = context;
-    if(animation_manager->check_blocking_callback) {
-        animation_manager->check_blocking_callback(animation_manager->context);
+    const StorageEvent* storage_event = message;
+
+    switch(storage_event->type) {
+    case StorageEventTypeCardMount:
+    case StorageEventTypeCardUnmount:
+    case StorageEventTypeCardMountError:
+        furi_assert(context);
+        AnimationManager* animation_manager = context;
+        if(animation_manager->check_blocking_callback) {
+            animation_manager->check_blocking_callback(animation_manager->context);
+        }
+        break;
+
+    default:
+        break;
     }
     }
 }
 }
 
 

+ 2 - 0
applications/ibutton/ibutton_cli.c

@@ -63,6 +63,7 @@ void ibutton_cli_print_key_data(iButtonKey* key) {
 #define EVENT_FLAG_IBUTTON_COMPLETE (1 << 0)
 #define EVENT_FLAG_IBUTTON_COMPLETE (1 << 0)
 
 
 static void ibutton_cli_worker_read_cb(void* context) {
 static void ibutton_cli_worker_read_cb(void* context) {
+    furi_assert(context);
     osEventFlagsId_t event = context;
     osEventFlagsId_t event = context;
     osEventFlagsSet(event, EVENT_FLAG_IBUTTON_COMPLETE);
     osEventFlagsSet(event, EVENT_FLAG_IBUTTON_COMPLETE);
 }
 }
@@ -112,6 +113,7 @@ typedef struct {
 } iButtonWriteContext;
 } iButtonWriteContext;
 
 
 static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult result) {
 static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult result) {
+    furi_assert(context);
     iButtonWriteContext* write_context = (iButtonWriteContext*)context;
     iButtonWriteContext* write_context = (iButtonWriteContext*)context;
     write_context->result = result;
     write_context->result = result;
     osEventFlagsSet(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE);
     osEventFlagsSet(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE);

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

@@ -9,6 +9,7 @@ typedef enum {
 } SubmenuIndex;
 } SubmenuIndex;
 
 
 static void submenu_callback(void* context, uint32_t index) {
 static void submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -3,6 +3,7 @@
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
 static void byte_input_callback(void* context) {
 static void byte_input_callback(void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -2,6 +2,7 @@
 #include "../ibutton_app.h"
 #include "../ibutton_app.h"
 
 
 static void widget_callback(GuiButtonType result, InputType type, void* context) {
 static void widget_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -2,6 +2,7 @@
 #include "../ibutton_app.h"
 #include "../ibutton_app.h"
 
 
 static void popup_callback(void* context) {
 static void popup_callback(void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
     event.type = iButtonEvent::Type::EventTypeBack;
     event.type = iButtonEvent::Type::EventTypeBack;

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

@@ -3,6 +3,7 @@
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
 static void emulate_callback(void* context, bool emulated) {
 static void emulate_callback(void* context, bool emulated) {
+    furi_assert(context);
     if(emulated) {
     if(emulated) {
         iButtonApp* app = static_cast<iButtonApp*>(context);
         iButtonApp* app = static_cast<iButtonApp*>(context);
         iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerEmulated};
         iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerEmulated};

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

@@ -2,6 +2,7 @@
 #include "../ibutton_app.h"
 #include "../ibutton_app.h"
 
 
 static void widget_callback(GuiButtonType result, InputType type, void* context) {
 static void widget_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -3,6 +3,7 @@
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
 static void read_callback(void* context) {
 static void read_callback(void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerRead};
     iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerRead};
     app->get_view_manager()->send_event(&event);
     app->get_view_manager()->send_event(&event);

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

@@ -3,6 +3,7 @@
 #include <one_wire/maxim_crc.h>
 #include <one_wire/maxim_crc.h>
 
 
 static void dialog_ex_callback(DialogExResult result, void* context) {
 static void dialog_ex_callback(DialogExResult result, void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -3,6 +3,7 @@
 #include <one_wire/maxim_crc.h>
 #include <one_wire/maxim_crc.h>
 
 
 static void dialog_ex_callback(DialogExResult result, void* context) {
 static void dialog_ex_callback(DialogExResult result, void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -3,6 +3,7 @@
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
 static void dialog_ex_callback(DialogExResult result, void* context) {
 static void dialog_ex_callback(DialogExResult result, void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -9,6 +9,7 @@ typedef enum {
 } SubmenuIndex;
 } SubmenuIndex;
 
 
 static void submenu_callback(void* context, uint32_t index) {
 static void submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -3,6 +3,7 @@
 #include <lib/toolbox/random_name.h>
 #include <lib/toolbox/random_name.h>
 
 
 static void text_input_callback(void* context) {
 static void text_input_callback(void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -3,6 +3,7 @@
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
 
 
 static void popup_callback(void* context) {
 static void popup_callback(void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
     event.type = iButtonEvent::Type::EventTypeBack;
     event.type = iButtonEvent::Type::EventTypeBack;

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

@@ -11,6 +11,7 @@ typedef enum {
 } SubmenuIndex;
 } SubmenuIndex;
 
 
 static void submenu_callback(void* context, uint32_t index) {
 static void submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -8,6 +8,7 @@ typedef enum {
 } SubmenuIndex;
 } SubmenuIndex;
 
 
 static void submenu_callback(void* context, uint32_t index) {
 static void submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
 
 

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

@@ -2,6 +2,7 @@
 #include "../ibutton_app.h"
 #include "../ibutton_app.h"
 
 
 static void ibutton_worker_write_cb(void* context, iButtonWorkerWriteResult result) {
 static void ibutton_worker_write_cb(void* context, iButtonWorkerWriteResult result) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
     event.type = iButtonEvent::Type::EventTypeWorkerWrite;
     event.type = iButtonEvent::Type::EventTypeWorkerWrite;

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

@@ -2,6 +2,7 @@
 #include "../ibutton_app.h"
 #include "../ibutton_app.h"
 
 
 static void popup_callback(void* context) {
 static void popup_callback(void* context) {
+    furi_assert(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonApp* app = static_cast<iButtonApp*>(context);
     iButtonEvent event;
     iButtonEvent event;
     event.type = iButtonEvent::Type::EventTypeBack;
     event.type = iButtonEvent::Type::EventTypeBack;

+ 1 - 1
applications/storage/filesystem_api_defines.h

@@ -1,5 +1,5 @@
 #pragma once
 #pragma once
-#include <furi.h>
+#include <stdint.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {

+ 26 - 37
applications/storage/filesystem_api_internal.h

@@ -75,21 +75,21 @@ struct File {
  *      @return end of file flag
  *      @return end of file flag
  */
  */
 typedef struct {
 typedef struct {
-    bool (*open)(
+    bool (*const open)(
         void* context,
         void* context,
         File* file,
         File* file,
         const char* path,
         const char* path,
         FS_AccessMode access_mode,
         FS_AccessMode access_mode,
         FS_OpenMode open_mode);
         FS_OpenMode open_mode);
-    bool (*close)(void* context, File* file);
+    bool (*const close)(void* context, File* file);
     uint16_t (*read)(void* context, File* file, void* buff, uint16_t bytes_to_read);
     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);
     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);
+    bool (*const seek)(void* context, File* file, uint32_t offset, bool from_start);
     uint64_t (*tell)(void* context, File* file);
     uint64_t (*tell)(void* context, File* file);
-    bool (*truncate)(void* context, File* file);
+    bool (*const truncate)(void* context, File* file);
     uint64_t (*size)(void* context, File* file);
     uint64_t (*size)(void* context, File* file);
-    bool (*sync)(void* context, File* file);
-    bool (*eof)(void* context, File* file);
+    bool (*const sync)(void* context, File* file);
+    bool (*const eof)(void* context, File* file);
 } FS_File_Api;
 } FS_File_Api;
 
 
 /** Dir api structure
 /** Dir api structure
@@ -118,10 +118,15 @@ typedef struct {
  *      @return success flag
  *      @return success flag
  */
  */
 typedef struct {
 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);
+    bool (*const open)(void* context, File* file, const char* path);
+    bool (*const close)(void* context, File* file);
+    bool (*const read)(
+        void* context,
+        File* file,
+        FileInfo* fileinfo,
+        char* name,
+        uint16_t name_length);
+    bool (*const rewind)(void* context, File* file);
 } FS_Dir_Api;
 } FS_Dir_Api;
 
 
 /** Common api structure
 /** Common api structure
@@ -141,12 +146,6 @@ typedef struct {
  *      @param path path to file/directory
  *      @param path path to file/directory
  *      @return FS_Error error info
  *      @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
  *  @var FS_Common_Api::mkdir
  *      @brief Create new directory
  *      @brief Create new directory
  *      @param path path to new directory
  *      @param path path to new directory
@@ -160,31 +159,21 @@ typedef struct {
  *      @return FS_Error error info
  *      @return FS_Error error info
  */
  */
 typedef struct {
 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_Error (*const stat)(void* context, const char* path, FileInfo* fileinfo);
+    FS_Error (*const remove)(void* context, const char* path);
+    FS_Error (*const mkdir)(void* context, const char* path);
+    FS_Error (*const fs_info)(
+        void* context,
+        const char* fs_path,
+        uint64_t* total_space,
+        uint64_t* free_space);
 } FS_Common_Api;
 } 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 */
 /** Full filesystem api structure */
 typedef struct {
 typedef struct {
-    FS_File_Api file;
-    FS_Dir_Api dir;
-    FS_Common_Api common;
-    FS_Error_Api error;
-    void* context;
+    const FS_File_Api file;
+    const FS_Dir_Api dir;
+    const FS_Common_Api common;
 } FS_Api;
 } FS_Api;
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 16 - 5
applications/storage/storage.c

@@ -11,6 +11,8 @@
 #define ICON_SD_MOUNTED &I_SDcardMounted_11x8
 #define ICON_SD_MOUNTED &I_SDcardMounted_11x8
 #define ICON_SD_ERROR &I_SDcardFail_11x8
 #define ICON_SD_ERROR &I_SDcardFail_11x8
 
 
+#define TAG "Storage"
+
 static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) {
 static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) {
     furi_assert(canvas);
     furi_assert(canvas);
     furi_assert(context);
     furi_assert(context);
@@ -63,15 +65,14 @@ void storage_tick(Storage* app) {
         }
         }
     }
     }
 
 
-    if(app->storage[ST_EXT].status != app->prev_ext_storage_status) {
-        app->prev_ext_storage_status = app->storage[ST_EXT].status;
-        furi_pubsub_publish(app->pubsub, &app->storage[ST_EXT].status);
-    }
-
     // storage not enabled but was enabled (sd card unmount)
     // storage not enabled but was enabled (sd card unmount)
     if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) {
     if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) {
         app->sd_gui.enabled = false;
         app->sd_gui.enabled = false;
         view_port_enabled_set(app->sd_gui.view_port, false);
         view_port_enabled_set(app->sd_gui.view_port, false);
+
+        FURI_LOG_I(TAG, "SD card unmount");
+        StorageEvent event = {.type = StorageEventTypeCardUnmount};
+        furi_pubsub_publish(app->pubsub, &event);
     }
     }
 
 
     // storage enabled (or in error state) but was not enabled (sd card mount)
     // storage enabled (or in error state) but was not enabled (sd card mount)
@@ -83,6 +84,16 @@ void storage_tick(Storage* app) {
        app->sd_gui.enabled == false) {
        app->sd_gui.enabled == false) {
         app->sd_gui.enabled = true;
         app->sd_gui.enabled = true;
         view_port_enabled_set(app->sd_gui.view_port, true);
         view_port_enabled_set(app->sd_gui.view_port, true);
+
+        if(app->storage[ST_EXT].status == StorageStatusOK) {
+            FURI_LOG_I(TAG, "SD card mount");
+            StorageEvent event = {.type = StorageEventTypeCardMount};
+            furi_pubsub_publish(app->pubsub, &event);
+        } else {
+            FURI_LOG_I(TAG, "SD card mount error");
+            StorageEvent event = {.type = StorageEventTypeCardMountError};
+            furi_pubsub_publish(app->pubsub, &event);
+        }
     }
     }
 }
 }
 
 

+ 19 - 1
applications/storage/storage.h

@@ -1,5 +1,6 @@
 #pragma once
 #pragma once
-#include <furi.h>
+#include <stdint.h>
+#include <m-string.h>
 #include "filesystem_api_defines.h"
 #include "filesystem_api_defines.h"
 #include "storage_sd_api.h"
 #include "storage_sd_api.h"
 
 
@@ -18,6 +19,23 @@ File* storage_file_alloc(Storage* storage);
  */
  */
 void storage_file_free(File* file);
 void storage_file_free(File* file);
 
 
+typedef enum {
+    StorageEventTypeCardMount,
+    StorageEventTypeCardUnmount,
+    StorageEventTypeCardMountError,
+    StorageEventTypeFileClose,
+} StorageEventType;
+
+typedef struct {
+    StorageEventType type;
+} StorageEvent;
+
+/**
+ * Get storage pubsub.
+ * Storage will send StorageEvent messages.
+ * @param storage 
+ * @return FuriPubSub* 
+ */
 FuriPubSub* storage_get_pubsub(Storage* storage);
 FuriPubSub* storage_get_pubsub(Storage* storage);
 
 
 /******************* File Functions *******************/
 /******************* File Functions *******************/

+ 72 - 20
applications/storage/storage_external_api.c

@@ -3,6 +3,7 @@
 #include "storage.h"
 #include "storage.h"
 #include "storage_i.h"
 #include "storage_i.h"
 #include "storage_message.h"
 #include "storage_message.h"
+#include <toolbox/stream/file_stream.h>
 
 
 #define MAX_NAME_LENGTH 256
 #define MAX_NAME_LENGTH 256
 
 
@@ -49,9 +50,12 @@
 #define FILE_OPENED 1
 #define FILE_OPENED 1
 #define FILE_CLOSED 0
 #define FILE_CLOSED 0
 
 
+typedef enum {
+    StorageEventFlagFileClose = (1 << 0),
+} StorageEventFlag;
 /****************** FILE ******************/
 /****************** FILE ******************/
 
 
-bool storage_file_open(
+static bool storage_file_open_internal(
     File* file,
     File* file,
     const char* path,
     const char* path,
     FS_AccessMode access_mode,
     FS_AccessMode access_mode,
@@ -75,6 +79,41 @@ bool storage_file_open(
     return S_RETURN_BOOL;
     return S_RETURN_BOOL;
 }
 }
 
 
+static void storage_file_close_callback(const void* message, void* context) {
+    const StorageEvent* storage_event = message;
+
+    if(storage_event->type == StorageEventTypeFileClose) {
+        furi_assert(context);
+        osEventFlagsId_t event = context;
+        osEventFlagsSet(event, StorageEventFlagFileClose);
+    }
+}
+
+bool storage_file_open(
+    File* file,
+    const char* path,
+    FS_AccessMode access_mode,
+    FS_OpenMode open_mode) {
+    bool result;
+    osEventFlagsId_t event = osEventFlagsNew(NULL);
+    FuriPubSubSubscription* subscription = furi_pubsub_subscribe(
+        storage_get_pubsub(file->storage), storage_file_close_callback, event);
+
+    do {
+        result = storage_file_open_internal(file, path, access_mode, open_mode);
+
+        if(!result && file->error_id == FSE_ALREADY_OPEN) {
+            osEventFlagsWait(event, StorageEventFlagFileClose, osFlagsWaitAny, osWaitForever);
+        } else {
+            break;
+        }
+    } while(true);
+
+    furi_pubsub_unsubscribe(storage_get_pubsub(file->storage), subscription);
+    osEventFlagsDelete(event);
+    return result;
+}
+
 bool storage_file_close(File* file) {
 bool storage_file_close(File* file) {
     S_FILE_API_PROLOGUE;
     S_FILE_API_PROLOGUE;
     S_API_PROLOGUE;
     S_API_PROLOGUE;
@@ -259,31 +298,44 @@ FS_Error storage_common_remove(Storage* storage, const char* path) {
 }
 }
 
 
 FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) {
 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,
-        }};
+    FS_Error error = storage_common_copy(storage, old_path, new_path);
+    if(error == FSE_OK) {
+        error = storage_common_remove(storage, old_path);
+    }
 
 
-    S_API_MESSAGE(StorageCommandCommonRename);
-    S_API_EPILOGUE;
-    return S_RETURN_ERROR;
+    return error;
 }
 }
 
 
 FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) {
 FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) {
-    S_API_PROLOGUE;
+    FS_Error error;
 
 
-    SAData data = {
-        .cpaths = {
-            .old = old_path,
-            .new = new_path,
-        }};
+    FileInfo fileinfo;
+    error = storage_common_stat(storage, old_path, &fileinfo);
 
 
-    S_API_MESSAGE(StorageCommandCommonCopy);
-    S_API_EPILOGUE;
-    return S_RETURN_ERROR;
+    if(error == FSE_OK) {
+        if(fileinfo.flags & FSF_DIRECTORY) {
+            error = storage_common_mkdir(storage, new_path);
+        } else {
+            Stream* stream_from = file_stream_alloc(storage);
+            Stream* stream_to = file_stream_alloc(storage);
+
+            do {
+                if(!file_stream_open(stream_from, old_path, FSAM_READ, FSOM_OPEN_EXISTING)) break;
+                if(!file_stream_open(stream_to, new_path, FSAM_WRITE, FSOM_CREATE_NEW)) break;
+                stream_copy_full(stream_from, stream_to);
+            } while(false);
+
+            error = file_stream_get_error(stream_from);
+            if(error == FSE_OK) {
+                error = file_stream_get_error(stream_to);
+            }
+
+            stream_free(stream_from);
+            stream_free(stream_to);
+        }
+    }
+
+    return error;
 }
 }
 
 
 FS_Error storage_common_mkdir(Storage* storage, const char* path) {
 FS_Error storage_common_mkdir(Storage* storage, const char* path) {

+ 2 - 24
applications/storage/storage_glue.c

@@ -103,25 +103,7 @@ bool storage_has_file(const File* file, StorageData* storage_data) {
     return result;
     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 storage_path_already_open(string_t path, StorageFileList_t array) {
     bool open = false;
     bool open = false;
 
 
     StorageFileList_it_t it;
     StorageFileList_it_t it;
@@ -178,11 +160,7 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage) {
     return founded_file->file_data;
     return founded_file->file_data;
 }
 }
 
 
-void storage_push_storage_file(
-    File* file,
-    const char* path,
-    StorageType type,
-    StorageData* storage) {
+void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage) {
     StorageFile* storage_file = StorageFileList_push_new(storage->files);
     StorageFile* storage_file = StorageFileList_push_new(storage->files);
     furi_check(storage_file != NULL);
     furi_check(storage_file != NULL);
 
 

+ 3 - 9
applications/storage/storage_glue.h

@@ -3,7 +3,6 @@
 #include <furi.h>
 #include <furi.h>
 #include "filesystem_api_internal.h"
 #include "filesystem_api_internal.h"
 #include <m-string.h>
 #include <m-string.h>
-#include <m-array.h>
 #include <m-list.h>
 #include <m-list.h>
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
@@ -54,7 +53,7 @@ LIST_DEF(
      CLEAR(API_2(storage_file_clear))))
      CLEAR(API_2(storage_file_clear))))
 
 
 struct StorageData {
 struct StorageData {
-    FS_Api fs_api;
+    const FS_Api* fs_api;
     StorageApi api;
     StorageApi api;
     void* data;
     void* data;
     osMutexId_t mutex;
     osMutexId_t mutex;
@@ -63,17 +62,12 @@ struct StorageData {
 };
 };
 
 
 bool storage_has_file(const File* file, StorageData* storage_data);
 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);
+bool storage_path_already_open(string_t path, StorageFileList_t files);
 
 
 void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage);
 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_get_storage_file_data(const File* file, StorageData* storage);
 
 
-void storage_push_storage_file(
-    File* file,
-    const char* path,
-    StorageType type,
-    StorageData* storage);
+void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage);
 bool storage_pop_storage_file(File* file, StorageData* storage);
 bool storage_pop_storage_file(File* file, StorageData* storage);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus

+ 0 - 1
applications/storage/storage_i.h

@@ -19,7 +19,6 @@ typedef struct {
 struct Storage {
 struct Storage {
     osMessageQueueId_t message_queue;
     osMessageQueueId_t message_queue;
     StorageData storage[STORAGE_COUNT];
     StorageData storage[STORAGE_COUNT];
-    StorageStatus prev_ext_storage_status;
     StorageSDGui sd_gui;
     StorageSDGui sd_gui;
     FuriPubSub* pubsub;
     FuriPubSub* pubsub;
 };
 };

+ 0 - 8
applications/storage/storage_message.h

@@ -47,11 +47,6 @@ typedef struct {
     FileInfo* fileinfo;
     FileInfo* fileinfo;
 } SADataCStat;
 } SADataCStat;
 
 
-typedef struct {
-    const char* old;
-    const char* new;
-} SADataCPaths;
-
 typedef struct {
 typedef struct {
     const char* fs_path;
     const char* fs_path;
     uint64_t* total_space;
     uint64_t* total_space;
@@ -84,7 +79,6 @@ typedef union {
     SADataDRead dread;
     SADataDRead dread;
 
 
     SADataCStat cstat;
     SADataCStat cstat;
-    SADataCPaths cpaths;
     SADataCFSInfo cfsinfo;
     SADataCFSInfo cfsinfo;
 
 
     SADataError error;
     SADataError error;
@@ -120,8 +114,6 @@ typedef enum {
     StorageCommandDirRewind,
     StorageCommandDirRewind,
     StorageCommandCommonStat,
     StorageCommandCommonStat,
     StorageCommandCommonRemove,
     StorageCommandCommonRemove,
-    StorageCommandCommonRename,
-    StorageCommandCommonCopy,
     StorageCommandCommonMkDir,
     StorageCommandCommonMkDir,
     StorageCommandCommonFSInfo,
     StorageCommandCommonFSInfo,
     StorageCommandSDFormat,
     StorageCommandSDFormat,

+ 88 - 108
applications/storage/storage_processing.c

@@ -1,8 +1,11 @@
 #include "storage_processing.h"
 #include "storage_processing.h"
+#include <m-list.h>
+#include <m-dict.h>
+#include <m-string.h>
 
 
 #define FS_CALL(_storage, _fn)   \
 #define FS_CALL(_storage, _fn)   \
     storage_data_lock(_storage); \
     storage_data_lock(_storage); \
-    ret = _storage->fs_api._fn;  \
+    ret = _storage->fs_api->_fn; \
     storage_data_unlock(_storage);
     storage_data_unlock(_storage);
 
 
 #define ST_CALL(_storage, _fn)   \
 #define ST_CALL(_storage, _fn)   \
@@ -11,18 +14,8 @@
     storage_data_unlock(_storage);
     storage_data_unlock(_storage);
 
 
 static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) {
 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];
-
+    furi_check(type == ST_EXT || type == ST_INT);
+    StorageData* storage = &app->storage[type];
     return storage;
     return storage;
 }
 }
 
 
@@ -42,10 +35,55 @@ static StorageData* get_storage_by_file(File* file, StorageData* storages) {
     return storage_data;
     return storage_data;
 }
 }
 
 
-const char* remove_vfs(const char* path) {
+static const char* remove_vfs(const char* path) {
     return path + MIN(4, strlen(path));
     return path + MIN(4, strlen(path));
 }
 }
 
 
+static const char* ext_path = "/ext";
+static const char* int_path = "/int";
+static const char* any_path = "/any";
+
+static StorageType storage_get_type_by_path(Storage* app, const char* path) {
+    StorageType type = ST_ERROR;
+    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;
+    }
+
+    if(type == ST_ANY) {
+        type = ST_INT;
+        if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) {
+            type = ST_EXT;
+        }
+    }
+
+    return type;
+}
+
+static void storage_path_change_to_real_storage(string_t path, StorageType real_storage) {
+    if(memcmp(string_get_cstr(path), any_path, strlen(any_path)) == 0) {
+        switch(real_storage) {
+        case ST_EXT:
+            string_set_char(path, 0, ext_path[0]);
+            string_set_char(path, 1, ext_path[1]);
+            string_set_char(path, 2, ext_path[2]);
+            string_set_char(path, 3, ext_path[3]);
+            break;
+        case ST_INT:
+            string_set_char(path, 0, int_path[0]);
+            string_set_char(path, 1, int_path[1]);
+            string_set_char(path, 2, int_path[2]);
+            string_set_char(path, 3, int_path[3]);
+            break;
+        default:
+            break;
+        }
+    }
+}
+
 /******************* File Functions *******************/
 /******************* File Functions *******************/
 
 
 bool storage_process_file_open(
 bool storage_process_file_open(
@@ -55,7 +93,7 @@ bool storage_process_file_open(
     FS_AccessMode access_mode,
     FS_AccessMode access_mode,
     FS_OpenMode open_mode) {
     FS_OpenMode open_mode) {
     bool ret = false;
     bool ret = false;
-    StorageType type = storage_get_type_by_path(path);
+    StorageType type = storage_get_type_by_path(app, path);
     StorageData* storage;
     StorageData* storage;
     file->error_id = FSE_OK;
     file->error_id = FSE_OK;
 
 
@@ -63,12 +101,18 @@ bool storage_process_file_open(
         file->error_id = FSE_INVALID_NAME;
         file->error_id = FSE_INVALID_NAME;
     } else {
     } else {
         storage = storage_get_storage_by_type(app, type);
         storage = storage_get_storage_by_type(app, type);
-        if(storage_path_already_open(path, storage->files)) {
+        string_t real_path;
+        string_init_set(real_path, path);
+        storage_path_change_to_real_storage(real_path, type);
+
+        if(storage_path_already_open(real_path, storage->files)) {
             file->error_id = FSE_ALREADY_OPEN;
             file->error_id = FSE_ALREADY_OPEN;
         } else {
         } else {
-            storage_push_storage_file(file, path, type, storage);
+            storage_push_storage_file(file, real_path, type, storage);
             FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode));
             FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode));
         }
         }
+
+        string_clear(real_path);
     }
     }
 
 
     return ret;
     return ret;
@@ -83,6 +127,9 @@ bool storage_process_file_close(Storage* app, File* file) {
     } else {
     } else {
         FS_CALL(storage, file.close(storage, file));
         FS_CALL(storage, file.close(storage, file));
         storage_pop_storage_file(file, storage);
         storage_pop_storage_file(file, storage);
+
+        StorageEvent event = {.type = StorageEventTypeFileClose};
+        furi_pubsub_publish(app->pubsub, &event);
     }
     }
 
 
     return ret;
     return ret;
@@ -205,7 +252,7 @@ static bool storage_process_file_eof(Storage* app, File* file) {
 
 
 bool storage_process_dir_open(Storage* app, File* file, const char* path) {
 bool storage_process_dir_open(Storage* app, File* file, const char* path) {
     bool ret = false;
     bool ret = false;
-    StorageType type = storage_get_type_by_path(path);
+    StorageType type = storage_get_type_by_path(app, path);
     StorageData* storage;
     StorageData* storage;
     file->error_id = FSE_OK;
     file->error_id = FSE_OK;
 
 
@@ -213,12 +260,17 @@ bool storage_process_dir_open(Storage* app, File* file, const char* path) {
         file->error_id = FSE_INVALID_NAME;
         file->error_id = FSE_INVALID_NAME;
     } else {
     } else {
         storage = storage_get_storage_by_type(app, type);
         storage = storage_get_storage_by_type(app, type);
-        if(storage_path_already_open(path, storage->files)) {
+        string_t real_path;
+        string_init_set(real_path, path);
+        storage_path_change_to_real_storage(real_path, type);
+
+        if(storage_path_already_open(real_path, storage->files)) {
             file->error_id = FSE_ALREADY_OPEN;
             file->error_id = FSE_ALREADY_OPEN;
         } else {
         } else {
-            storage_push_storage_file(file, path, type, storage);
+            storage_push_storage_file(file, real_path, type, storage);
             FS_CALL(storage, dir.open(storage, file, remove_vfs(path)));
             FS_CALL(storage, dir.open(storage, file, remove_vfs(path)));
         }
         }
+        string_clear(real_path);
     }
     }
 
 
     return ret;
     return ret;
@@ -273,7 +325,7 @@ bool storage_process_dir_rewind(Storage* app, File* file) {
 
 
 static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) {
 static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) {
     FS_Error ret = FSE_OK;
     FS_Error ret = FSE_OK;
-    StorageType type = storage_get_type_by_path(path);
+    StorageType type = storage_get_type_by_path(app, path);
 
 
     if(storage_type_is_not_valid(type)) {
     if(storage_type_is_not_valid(type)) {
         ret = FSE_INVALID_NAME;
         ret = FSE_INVALID_NAME;
@@ -287,7 +339,11 @@ static FS_Error storage_process_common_stat(Storage* app, const char* path, File
 
 
 static FS_Error storage_process_common_remove(Storage* app, const char* path) {
 static FS_Error storage_process_common_remove(Storage* app, const char* path) {
     FS_Error ret = FSE_OK;
     FS_Error ret = FSE_OK;
-    StorageType type = storage_get_type_by_path(path);
+    StorageType type = storage_get_type_by_path(app, path);
+
+    string_t real_path;
+    string_init_set(real_path, path);
+    storage_path_change_to_real_storage(real_path, type);
 
 
     do {
     do {
         if(storage_type_is_not_valid(type)) {
         if(storage_type_is_not_valid(type)) {
@@ -296,7 +352,7 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) {
         }
         }
 
 
         StorageData* storage = storage_get_storage_by_type(app, type);
         StorageData* storage = storage_get_storage_by_type(app, type);
-        if(storage_path_already_open(path, storage->files)) {
+        if(storage_path_already_open(real_path, storage->files)) {
             ret = FSE_ALREADY_OPEN;
             ret = FSE_ALREADY_OPEN;
             break;
             break;
         }
         }
@@ -304,12 +360,14 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) {
         FS_CALL(storage, common.remove(storage, remove_vfs(path)));
         FS_CALL(storage, common.remove(storage, remove_vfs(path)));
     } while(false);
     } while(false);
 
 
+    string_clear(real_path);
+
     return ret;
     return ret;
 }
 }
 
 
 static FS_Error storage_process_common_mkdir(Storage* app, const char* path) {
 static FS_Error storage_process_common_mkdir(Storage* app, const char* path) {
     FS_Error ret = FSE_OK;
     FS_Error ret = FSE_OK;
-    StorageType type = storage_get_type_by_path(path);
+    StorageType type = storage_get_type_by_path(app, path);
 
 
     if(storage_type_is_not_valid(type)) {
     if(storage_type_is_not_valid(type)) {
         ret = FSE_INVALID_NAME;
         ret = FSE_INVALID_NAME;
@@ -321,86 +379,13 @@ static FS_Error storage_process_common_mkdir(Storage* app, const char* path) {
     return ret;
     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;
-
-    FileInfo fileinfo;
-    ret = storage_process_common_stat(app, old, &fileinfo);
-
-    if(ret == FSE_OK) {
-        if(fileinfo.flags & FSF_DIRECTORY) {
-            ret = storage_process_common_mkdir(app, new);
-        } else {
-            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);
-                    storage_process_file_close(app, &file_old);
-                    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_new)) {
-        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_fs_info(
 static FS_Error storage_process_common_fs_info(
     Storage* app,
     Storage* app,
     const char* fs_path,
     const char* fs_path,
     uint64_t* total_space,
     uint64_t* total_space,
     uint64_t* free_space) {
     uint64_t* free_space) {
     FS_Error ret = FSE_OK;
     FS_Error ret = FSE_OK;
-    StorageType type = storage_get_type_by_path(fs_path);
+    StorageType type = storage_get_type_by_path(app, fs_path);
 
 
     if(storage_type_is_not_valid(type)) {
     if(storage_type_is_not_valid(type)) {
         ret = FSE_INVALID_NAME;
         ret = FSE_INVALID_NAME;
@@ -472,8 +457,7 @@ static FS_Error storage_process_sd_status(Storage* app) {
 }
 }
 
 
 /****************** API calls processing ******************/
 /****************** API calls processing ******************/
-
-void storage_process_message(Storage* app, StorageMessage* message) {
+void storage_process_message_internal(Storage* app, StorageMessage* message) {
     switch(message->command) {
     switch(message->command) {
     case StorageCommandFileOpen:
     case StorageCommandFileOpen:
         message->return_data->bool_value = storage_process_file_open(
         message->return_data->bool_value = storage_process_file_open(
@@ -556,14 +540,6 @@ void storage_process_message(Storage* app, StorageMessage* message) {
         message->return_data->error_value =
         message->return_data->error_value =
             storage_process_common_remove(app, message->data->path.path);
             storage_process_common_remove(app, message->data->path.path);
         break;
         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:
     case StorageCommandCommonMkDir:
         message->return_data->error_value =
         message->return_data->error_value =
             storage_process_common_mkdir(app, message->data->path.path);
             storage_process_common_mkdir(app, message->data->path.path);
@@ -592,3 +568,7 @@ void storage_process_message(Storage* app, StorageMessage* message) {
 
 
     osSemaphoreRelease(message->semaphore);
     osSemaphoreRelease(message->semaphore);
 }
 }
+
+void storage_process_message(Storage* app, StorageMessage* message) {
+    storage_process_message_internal(app, message);
+}

+ 5 - 6
applications/storage/storage_sd_api.h

@@ -1,8 +1,6 @@
 #pragma once
 #pragma once
 #include <furi.h>
 #include <furi.h>
 #include "filesystem_api_defines.h"
 #include "filesystem_api_defines.h"
-#include <fatfs.h>
-#include "storage_glue.h"
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -11,10 +9,11 @@ extern "C" {
 #define SD_LABEL_LENGTH 34
 #define SD_LABEL_LENGTH 34
 
 
 typedef enum {
 typedef enum {
-    FST_FAT12 = FS_FAT12,
-    FST_FAT16 = FS_FAT16,
-    FST_FAT32 = FS_FAT32,
-    FST_EXFAT = FS_EXFAT,
+    FST_UNKNOWN,
+    FST_FAT12,
+    FST_FAT16,
+    FST_FAT32,
+    FST_EXFAT,
 } SDFsType;
 } SDFsType;
 
 
 typedef struct {
 typedef struct {

+ 48 - 27
applications/storage/storages/storage_ext.c

@@ -164,7 +164,24 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) {
         sector_size = fs->ssize;
         sector_size = fs->ssize;
 #endif
 #endif
 
 
-        sd_info->fs_type = fs->fs_type;
+        switch(fs->fs_type) {
+        case FS_FAT12:
+            sd_info->fs_type = FST_FAT12;
+            break;
+        case FS_FAT16:
+            sd_info->fs_type = FST_FAT16;
+            break;
+        case FS_FAT32:
+            sd_info->fs_type = FST_FAT32;
+            break;
+        case FS_EXFAT:
+            sd_info->fs_type = FST_EXFAT;
+            break;
+
+        default:
+            sd_info->fs_type = FST_UNKNOWN;
+            break;
+        }
 
 
         sd_info->kb_total = total_sectors / 1024 * sector_size;
         sd_info->kb_total = total_sectors / 1024 * sector_size;
         sd_info->kb_free = free_sectors / 1024 * sector_size;
         sd_info->kb_free = free_sectors / 1024 * sector_size;
@@ -466,11 +483,6 @@ static FS_Error storage_ext_common_remove(void* ctx, const char* path) {
     return storage_ext_parse_error(result);
     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) {
 static FS_Error storage_ext_common_mkdir(void* ctx, const char* path) {
     SDError result = f_mkdir(path);
     SDError result = f_mkdir(path);
     return storage_ext_parse_error(result);
     return storage_ext_parse_error(result);
@@ -510,6 +522,35 @@ static FS_Error storage_ext_common_fs_info(
 }
 }
 
 
 /******************* Init Storage *******************/
 /******************* Init Storage *******************/
+static const FS_Api fs_api = {
+    .file =
+        {
+            .open = storage_ext_file_open,
+            .close = storage_ext_file_close,
+            .read = storage_ext_file_read,
+            .write = storage_ext_file_write,
+            .seek = storage_ext_file_seek,
+            .tell = storage_ext_file_tell,
+            .truncate = storage_ext_file_truncate,
+            .size = storage_ext_file_size,
+            .sync = storage_ext_file_sync,
+            .eof = storage_ext_file_eof,
+        },
+    .dir =
+        {
+            .open = storage_ext_dir_open,
+            .close = storage_ext_dir_close,
+            .read = storage_ext_dir_read,
+            .rewind = storage_ext_dir_rewind,
+        },
+    .common =
+        {
+            .stat = storage_ext_common_stat,
+            .mkdir = storage_ext_common_mkdir,
+            .remove = storage_ext_common_remove,
+            .fs_info = storage_ext_common_fs_info,
+        },
+};
 
 
 void storage_ext_init(StorageData* storage) {
 void storage_ext_init(StorageData* storage) {
     SDData* sd_data = malloc(sizeof(SDData));
     SDData* sd_data = malloc(sizeof(SDData));
@@ -519,27 +560,7 @@ void storage_ext_init(StorageData* storage) {
 
 
     storage->data = sd_data;
     storage->data = sd_data;
     storage->api.tick = storage_ext_tick;
     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;
+    storage->fs_api = &fs_api;
 
 
     hal_sd_detect_init();
     hal_sd_detect_init();
 
 

+ 30 - 28
applications/storage/storages/storage_int.c

@@ -636,13 +636,6 @@ static FS_Error storage_int_common_remove(void* ctx, const char* path) {
     return storage_int_parse_error(result);
     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) {
 static FS_Error storage_int_common_mkdir(void* ctx, const char* path) {
     StorageData* storage = ctx;
     StorageData* storage = ctx;
     lfs_t* lfs = lfs_get_from_storage(storage);
     lfs_t* lfs = lfs_get_from_storage(storage);
@@ -671,6 +664,35 @@ static FS_Error storage_int_common_fs_info(
 }
 }
 
 
 /******************* Init Storage *******************/
 /******************* Init Storage *******************/
+static const FS_Api fs_api = {
+    .file =
+        {
+            .open = storage_int_file_open,
+            .close = storage_int_file_close,
+            .read = storage_int_file_read,
+            .write = storage_int_file_write,
+            .seek = storage_int_file_seek,
+            .tell = storage_int_file_tell,
+            .truncate = storage_int_file_truncate,
+            .size = storage_int_file_size,
+            .sync = storage_int_file_sync,
+            .eof = storage_int_file_eof,
+        },
+    .dir =
+        {
+            .open = storage_int_dir_open,
+            .close = storage_int_dir_close,
+            .read = storage_int_dir_read,
+            .rewind = storage_int_dir_rewind,
+        },
+    .common =
+        {
+            .stat = storage_int_common_stat,
+            .mkdir = storage_int_common_mkdir,
+            .remove = storage_int_common_remove,
+            .fs_info = storage_int_common_fs_info,
+        },
+};
 
 
 void storage_int_init(StorageData* storage) {
 void storage_int_init(StorageData* storage) {
     FURI_LOG_I(TAG, "Starting");
     FURI_LOG_I(TAG, "Starting");
@@ -689,25 +711,5 @@ void storage_int_init(StorageData* storage) {
 
 
     storage->data = lfs_data;
     storage->data = lfs_data;
     storage->api.tick = NULL;
     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;
+    storage->fs_api = &fs_api;
 }
 }

+ 80 - 0
applications/tests/storage/storage_test.c

@@ -0,0 +1,80 @@
+#include "../minunit.h"
+#include <furi.h>
+#include <furi_hal_delay.h>
+#include <storage/storage.h>
+
+#define STORAGE_LOCKED_FILE "/ext/locked_file.test"
+
+static void storage_file_open_lock_setup() {
+    Storage* storage = furi_record_open("storage");
+    File* file = storage_file_alloc(storage);
+    storage_simply_remove(storage, STORAGE_LOCKED_FILE);
+    mu_check(storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_WRITE, FSOM_CREATE_NEW));
+    mu_check(storage_file_write(file, "0123", 4) == 4);
+    mu_check(storage_file_close(file));
+    storage_file_free(file);
+    furi_record_close("storage");
+}
+
+static void storage_file_open_lock_teardown() {
+    Storage* storage = furi_record_open("storage");
+    mu_check(storage_simply_remove(storage, STORAGE_LOCKED_FILE));
+    furi_record_close("storage");
+}
+
+static int32_t storage_file_locker(void* ctx) {
+    Storage* storage = furi_record_open("storage");
+    osSemaphoreId_t semaphore = ctx;
+    File* file = storage_file_alloc(storage);
+    furi_check(storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_READ_WRITE, FSOM_OPEN_EXISTING));
+    osSemaphoreRelease(semaphore);
+    furi_hal_delay_ms(1000);
+
+    furi_check(storage_file_close(file));
+    furi_record_close("storage");
+    storage_file_free(file);
+    return 0;
+}
+
+MU_TEST(storage_file_open_lock) {
+    Storage* storage = furi_record_open("storage");
+    bool result = false;
+    osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL);
+    File* file = storage_file_alloc(storage);
+
+    // file_locker thread start
+    FuriThread* locker_thread = furi_thread_alloc();
+    furi_thread_set_name(locker_thread, "StorageFileLocker");
+    furi_thread_set_stack_size(locker_thread, 2048);
+    furi_thread_set_context(locker_thread, semaphore);
+    furi_thread_set_callback(locker_thread, storage_file_locker);
+    mu_check(furi_thread_start(locker_thread));
+
+    // wait for file lock
+    osSemaphoreAcquire(semaphore, osWaitForever);
+    osSemaphoreDelete(semaphore);
+
+    result = storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_READ_WRITE, FSOM_OPEN_EXISTING);
+    storage_file_close(file);
+
+    // file_locker thread stop
+    mu_check(furi_thread_join(locker_thread) == osOK);
+    furi_thread_free(locker_thread);
+
+    // clean data
+    storage_file_free(file);
+    furi_record_close("storage");
+
+    mu_assert(result, "cannot open locked file");
+}
+
+MU_TEST_SUITE(storage_file) {
+    storage_file_open_lock_setup();
+    MU_RUN_TEST(storage_file_open_lock);
+    storage_file_open_lock_teardown();
+}
+
+int run_minunit_test_storage() {
+    MU_RUN_SUITE(storage_file);
+    return MU_EXIT_CODE;
+}

+ 4 - 2
applications/tests/test_index.c

@@ -16,6 +16,7 @@ int run_minunit_test_rpc();
 int run_minunit_test_flipper_format();
 int run_minunit_test_flipper_format();
 int run_minunit_test_flipper_format_string();
 int run_minunit_test_flipper_format_string();
 int run_minunit_test_stream();
 int run_minunit_test_stream();
+int run_minunit_test_storage();
 
 
 void minunit_print_progress(void) {
 void minunit_print_progress(void) {
     static char progress[] = {'\\', '|', '/', '-'};
     static char progress[] = {'\\', '|', '/', '-'};
@@ -53,11 +54,12 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) {
         uint32_t cycle_counter = DWT->CYCCNT;
         uint32_t cycle_counter = DWT->CYCCNT;
 
 
         test_result |= run_minunit();
         test_result |= run_minunit();
-        test_result |= run_minunit_test_infrared_decoder_encoder();
-        test_result |= run_minunit_test_rpc();
+        test_result |= run_minunit_test_storage();
         test_result |= run_minunit_test_stream();
         test_result |= run_minunit_test_stream();
         test_result |= run_minunit_test_flipper_format();
         test_result |= run_minunit_test_flipper_format();
         test_result |= run_minunit_test_flipper_format_string();
         test_result |= run_minunit_test_flipper_format_string();
+        test_result |= run_minunit_test_infrared_decoder_encoder();
+        test_result |= run_minunit_test_rpc();
         cycle_counter = (DWT->CYCCNT - cycle_counter);
         cycle_counter = (DWT->CYCCNT - cycle_counter);
 
 
         FURI_LOG_I(TAG, "Consumed: %0.2fs", (float)cycle_counter / (SystemCoreClock));
         FURI_LOG_I(TAG, "Consumed: %0.2fs", (float)cycle_counter / (SystemCoreClock));

+ 7 - 0
lib/toolbox/stream/file_stream.c

@@ -61,6 +61,13 @@ bool file_stream_close(Stream* _stream) {
     return storage_file_close(stream->file);
     return storage_file_close(stream->file);
 }
 }
 
 
+FS_Error file_stream_get_error(Stream* _stream) {
+    furi_assert(_stream);
+    FileStream* stream = (FileStream*)_stream;
+    furi_check(stream->stream_base.vtable == &file_stream_vtable);
+    return storage_file_get_error(stream->file);
+}
+
 static void file_stream_free(FileStream* stream) {
 static void file_stream_free(FileStream* stream) {
     storage_file_free(stream->file);
     storage_file_free(stream->file);
     free(stream);
     free(stream);

+ 7 - 0
lib/toolbox/stream/file_stream.h

@@ -35,6 +35,13 @@ bool file_stream_open(
  */
  */
 bool file_stream_close(Stream* stream);
 bool file_stream_close(Stream* stream);
 
 
+/** 
+ * Retrieves the error id from the file object
+ * @param stream pointer to stream object.
+ * @return FS_Error error id
+ */
+FS_Error file_stream_get_error(Stream* stream);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif