[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>
This commit is contained in:
		
							parent
							
								
									cb7d43f7e1
								
							
						
					
					
						commit
						855f2584ab
					
				| @ -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
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); |     const StorageEvent* storage_event = message; | ||||||
|     AnimationManager* animation_manager = context; | 
 | ||||||
|     if(animation_manager->check_blocking_callback) { |     switch(storage_event->type) { | ||||||
|         animation_manager->check_blocking_callback(animation_manager->context); |     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; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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}; | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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; | ||||||
|  | |||||||
| @ -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,5 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <furi.h> | #include <stdint.h> | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| extern "C" { | extern "C" { | ||||||
|  | |||||||
| @ -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 (*const sync)(void* context, File* file); | ||||||
|     bool (*eof)(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 (*const open)(void* context, File* file, const char* path); | ||||||
|     bool (*close)(void* context, File* file); |     bool (*const close)(void* context, File* file); | ||||||
|     bool (*read)(void* context, File* file, FileInfo* fileinfo, char* name, uint16_t name_length); |     bool (*const read)( | ||||||
|     bool (*rewind)(void* context, File* file); |         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 (*const stat)(void* context, const char* path, FileInfo* fileinfo); | ||||||
|     FS_Error (*remove)(void* context, const char* path); |     FS_Error (*const remove)(void* context, const char* path); | ||||||
|     FS_Error (*rename)(void* context, const char* old_path, const char* new_path); |     FS_Error (*const mkdir)(void* context, const char* path); | ||||||
|     FS_Error (*mkdir)(void* context, const char* path); |     FS_Error (*const fs_info)( | ||||||
|     FS_Error ( |         void* context, | ||||||
|         *fs_info)(void* context, const char* fs_path, uint64_t* total_space, uint64_t* free_space); |         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; |     const FS_File_Api file; | ||||||
|     FS_Dir_Api dir; |     const FS_Dir_Api dir; | ||||||
|     FS_Common_Api common; |     const FS_Common_Api common; | ||||||
|     FS_Error_Api error; |  | ||||||
|     void* context; |  | ||||||
| } FS_Api; | } FS_Api; | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
|  | |||||||
| @ -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); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 *******************/ | ||||||
|  | |||||||
| @ -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; |     FS_Error error = storage_common_copy(storage, old_path, new_path); | ||||||
|  |     if(error == FSE_OK) { | ||||||
|  |         error = storage_common_remove(storage, old_path); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     SAData data = { |     return error; | ||||||
|         .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) { | FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) { | ||||||
|     S_API_PROLOGUE; |     FS_Error error; | ||||||
| 
 | 
 | ||||||
|     SAData data = { |     FileInfo fileinfo; | ||||||
|         .cpaths = { |     error = storage_common_stat(storage, old_path, &fileinfo); | ||||||
|             .old = old_path, |  | ||||||
|             .new = new_path, |  | ||||||
|         }}; |  | ||||||
| 
 | 
 | ||||||
|     S_API_MESSAGE(StorageCommandCommonCopy); |     if(error == FSE_OK) { | ||||||
|     S_API_EPILOGUE; |         if(fileinfo.flags & FSF_DIRECTORY) { | ||||||
|     return S_RETURN_ERROR; |             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) { | ||||||
|  | |||||||
| @ -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) { | bool storage_path_already_open(string_t path, StorageFileList_t array) { | ||||||
|     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; |     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( | void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage) { | ||||||
|     File* file, |  | ||||||
|     const char* 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,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(string_t path, StorageFileList_t files); | ||||||
| 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_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( | void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage); | ||||||
|     File* file, |  | ||||||
|     const char* 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 | ||||||
|  | |||||||
| @ -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; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -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, | ||||||
|  | |||||||
| @ -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; |     furi_check(type == ST_EXT || type == ST_INT); | ||||||
| 
 |     StorageData* storage = &app->storage[type]; | ||||||
|     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; |     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_internal(Storage* app, StorageMessage* message) { | ||||||
| void storage_process_message(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); | ||||||
|  | } | ||||||
|  | |||||||
| @ -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_UNKNOWN, | ||||||
|     FST_FAT16 = FS_FAT16, |     FST_FAT12, | ||||||
|     FST_FAT32 = FS_FAT32, |     FST_FAT16, | ||||||
|     FST_EXFAT = FS_EXFAT, |     FST_FAT32, | ||||||
|  |     FST_EXFAT, | ||||||
| } SDFsType; | } SDFsType; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|  | |||||||
| @ -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 = &fs_api; | ||||||
|     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(); |     hal_sd_detect_init(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -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 = &fs_api; | ||||||
|     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; |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										80
									
								
								applications/tests/storage/storage_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								applications/tests/storage/storage_test.c
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||||
|  | } | ||||||
| @ -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_storage(); | ||||||
|         test_result |= run_minunit_test_rpc(); |  | ||||||
|         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)); | ||||||
|  | |||||||
| @ -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); | ||||||
|  | |||||||
| @ -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 | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 SG
						SG