[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 | ||||
| #include "file_worker.h" | ||||
| #include <m-array.h> | ||||
| 
 | ||||
| #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) { | ||||
|     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; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -63,6 +63,7 @@ void ibutton_cli_print_key_data(iButtonKey* key) { | ||||
| #define EVENT_FLAG_IBUTTON_COMPLETE (1 << 0) | ||||
| 
 | ||||
| static void ibutton_cli_worker_read_cb(void* context) { | ||||
|     furi_assert(context); | ||||
|     osEventFlagsId_t event = context; | ||||
|     osEventFlagsSet(event, EVENT_FLAG_IBUTTON_COMPLETE); | ||||
| } | ||||
| @ -112,6 +113,7 @@ typedef struct { | ||||
| } iButtonWriteContext; | ||||
| 
 | ||||
| static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult result) { | ||||
|     furi_assert(context); | ||||
|     iButtonWriteContext* write_context = (iButtonWriteContext*)context; | ||||
|     write_context->result = result; | ||||
|     osEventFlagsSet(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE); | ||||
|  | ||||
| @ -9,6 +9,7 @@ typedef enum { | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| static void byte_input_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| #include "../ibutton_app.h" | ||||
| 
 | ||||
| static void widget_callback(GuiButtonType result, InputType type, void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| #include "../ibutton_app.h" | ||||
| 
 | ||||
| static void popup_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
|     event.type = iButtonEvent::Type::EventTypeBack; | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| static void emulate_callback(void* context, bool emulated) { | ||||
|     furi_assert(context); | ||||
|     if(emulated) { | ||||
|         iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|         iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerEmulated}; | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| #include "../ibutton_app.h" | ||||
| 
 | ||||
| static void widget_callback(GuiButtonType result, InputType type, void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| static void read_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerRead}; | ||||
|     app->get_view_manager()->send_event(&event); | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <one_wire/maxim_crc.h> | ||||
| 
 | ||||
| static void dialog_ex_callback(DialogExResult result, void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <one_wire/maxim_crc.h> | ||||
| 
 | ||||
| static void dialog_ex_callback(DialogExResult result, void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| static void dialog_ex_callback(DialogExResult result, void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -9,6 +9,7 @@ typedef enum { | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <lib/toolbox/random_name.h> | ||||
| 
 | ||||
| static void text_input_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| static void popup_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
|     event.type = iButtonEvent::Type::EventTypeBack; | ||||
|  | ||||
| @ -11,6 +11,7 @@ typedef enum { | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -8,6 +8,7 @@ typedef enum { | ||||
| } SubmenuIndex; | ||||
| 
 | ||||
| static void submenu_callback(void* context, uint32_t index) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
| 
 | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| #include "../ibutton_app.h" | ||||
| 
 | ||||
| static void ibutton_worker_write_cb(void* context, iButtonWorkerWriteResult result) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
|     event.type = iButtonEvent::Type::EventTypeWorkerWrite; | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| #include "../ibutton_app.h" | ||||
| 
 | ||||
| static void popup_callback(void* context) { | ||||
|     furi_assert(context); | ||||
|     iButtonApp* app = static_cast<iButtonApp*>(context); | ||||
|     iButtonEvent event; | ||||
|     event.type = iButtonEvent::Type::EventTypeBack; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
|  | ||||
| @ -75,21 +75,21 @@ struct File { | ||||
|  *      @return end of file flag | ||||
|  */ | ||||
| typedef struct { | ||||
|     bool (*open)( | ||||
|     bool (*const open)( | ||||
|         void* context, | ||||
|         File* file, | ||||
|         const char* path, | ||||
|         FS_AccessMode access_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 (*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); | ||||
|     bool (*truncate)(void* context, File* file); | ||||
|     bool (*const truncate)(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; | ||||
| 
 | ||||
| /** Dir api structure
 | ||||
| @ -118,10 +118,15 @@ typedef struct { | ||||
|  *      @return success flag | ||||
|  */ | ||||
| typedef struct { | ||||
|     bool (*open)(void* context, File* file, const char* path); | ||||
|     bool (*close)(void* context, File* file); | ||||
|     bool (*read)(void* context, File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||
|     bool (*rewind)(void* context, File* file); | ||||
|     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; | ||||
| 
 | ||||
| /** Common api structure
 | ||||
| @ -141,12 +146,6 @@ typedef struct { | ||||
|  *      @param path path to file/directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::rename | ||||
|  *      @brief Rename file/directory, | ||||
|  *          file/directory must not be opened | ||||
|  *      @param path path to file/directory | ||||
|  *      @return FS_Error error info | ||||
|  *  | ||||
|  *  @var FS_Common_Api::mkdir | ||||
|  *      @brief Create new directory | ||||
|  *      @param path path to new directory | ||||
| @ -160,31 +159,21 @@ typedef struct { | ||||
|  *      @return FS_Error error info | ||||
|  */ | ||||
| typedef struct { | ||||
|     FS_Error (*stat)(void* context, const char* path, FileInfo* fileinfo); | ||||
|     FS_Error (*remove)(void* context, const char* path); | ||||
|     FS_Error (*rename)(void* context, const char* old_path, const char* new_path); | ||||
|     FS_Error (*mkdir)(void* context, const char* path); | ||||
|     FS_Error ( | ||||
|         *fs_info)(void* context, const char* fs_path, uint64_t* total_space, uint64_t* free_space); | ||||
|     FS_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; | ||||
| 
 | ||||
| /** Errors api structure
 | ||||
|  *  @var FS_Error_Api::get_desc | ||||
|  *      @brief Get error description text | ||||
|  *      @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id) | ||||
|  *      @return pointer to description text | ||||
|  */ | ||||
| typedef struct { | ||||
|     const char* (*get_desc)(void* context, FS_Error error_id); | ||||
| } FS_Error_Api; | ||||
| 
 | ||||
| /** Full filesystem api structure */ | ||||
| typedef struct { | ||||
|     FS_File_Api file; | ||||
|     FS_Dir_Api dir; | ||||
|     FS_Common_Api common; | ||||
|     FS_Error_Api error; | ||||
|     void* context; | ||||
|     const FS_File_Api file; | ||||
|     const FS_Dir_Api dir; | ||||
|     const FS_Common_Api common; | ||||
| } FS_Api; | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
|  | ||||
| @ -11,6 +11,8 @@ | ||||
| #define ICON_SD_MOUNTED &I_SDcardMounted_11x8 | ||||
| #define ICON_SD_ERROR &I_SDcardFail_11x8 | ||||
| 
 | ||||
| #define TAG "Storage" | ||||
| 
 | ||||
| static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) { | ||||
|     furi_assert(canvas); | ||||
|     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)
 | ||||
|     if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) { | ||||
|         app->sd_gui.enabled = false; | ||||
|         view_port_enabled_set(app->sd_gui.view_port, false); | ||||
| 
 | ||||
|         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)
 | ||||
| @ -83,6 +84,16 @@ void storage_tick(Storage* app) { | ||||
|        app->sd_gui.enabled == false) { | ||||
|         app->sd_gui.enabled = 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 | ||||
| #include <furi.h> | ||||
| #include <stdint.h> | ||||
| #include <m-string.h> | ||||
| #include "filesystem_api_defines.h" | ||||
| #include "storage_sd_api.h" | ||||
| 
 | ||||
| @ -18,6 +19,23 @@ File* storage_file_alloc(Storage* storage); | ||||
|  */ | ||||
| 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); | ||||
| 
 | ||||
| /******************* File Functions *******************/ | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| #include "storage.h" | ||||
| #include "storage_i.h" | ||||
| #include "storage_message.h" | ||||
| #include <toolbox/stream/file_stream.h> | ||||
| 
 | ||||
| #define MAX_NAME_LENGTH 256 | ||||
| 
 | ||||
| @ -49,9 +50,12 @@ | ||||
| #define FILE_OPENED 1 | ||||
| #define FILE_CLOSED 0 | ||||
| 
 | ||||
| typedef enum { | ||||
|     StorageEventFlagFileClose = (1 << 0), | ||||
| } StorageEventFlag; | ||||
| /****************** FILE ******************/ | ||||
| 
 | ||||
| bool storage_file_open( | ||||
| static bool storage_file_open_internal( | ||||
|     File* file, | ||||
|     const char* path, | ||||
|     FS_AccessMode access_mode, | ||||
| @ -75,6 +79,41 @@ bool storage_file_open( | ||||
|     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) { | ||||
|     S_FILE_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) { | ||||
|     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 = { | ||||
|         .cpaths = { | ||||
|             .old = old_path, | ||||
|             .new = new_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) { | ||||
|     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) { | ||||
|  | ||||
| @ -103,25 +103,7 @@ bool storage_has_file(const File* file, StorageData* storage_data) { | ||||
|     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; | ||||
| 
 | ||||
|     StorageFileList_it_t it; | ||||
| @ -178,11 +160,7 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage) { | ||||
|     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); | ||||
|     furi_check(storage_file != NULL); | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,6 @@ | ||||
| #include <furi.h> | ||||
| #include "filesystem_api_internal.h" | ||||
| #include <m-string.h> | ||||
| #include <m-array.h> | ||||
| #include <m-list.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| @ -54,7 +53,7 @@ LIST_DEF( | ||||
|      CLEAR(API_2(storage_file_clear)))) | ||||
| 
 | ||||
| struct StorageData { | ||||
|     FS_Api fs_api; | ||||
|     const FS_Api* fs_api; | ||||
|     StorageApi api; | ||||
|     void* data; | ||||
|     osMutexId_t mutex; | ||||
| @ -63,17 +62,12 @@ struct StorageData { | ||||
| }; | ||||
| 
 | ||||
| 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_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); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
|  | ||||
| @ -19,7 +19,6 @@ typedef struct { | ||||
| struct Storage { | ||||
|     osMessageQueueId_t message_queue; | ||||
|     StorageData storage[STORAGE_COUNT]; | ||||
|     StorageStatus prev_ext_storage_status; | ||||
|     StorageSDGui sd_gui; | ||||
|     FuriPubSub* pubsub; | ||||
| }; | ||||
|  | ||||
| @ -47,11 +47,6 @@ typedef struct { | ||||
|     FileInfo* fileinfo; | ||||
| } SADataCStat; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* old; | ||||
|     const char* new; | ||||
| } SADataCPaths; | ||||
| 
 | ||||
| typedef struct { | ||||
|     const char* fs_path; | ||||
|     uint64_t* total_space; | ||||
| @ -84,7 +79,6 @@ typedef union { | ||||
|     SADataDRead dread; | ||||
| 
 | ||||
|     SADataCStat cstat; | ||||
|     SADataCPaths cpaths; | ||||
|     SADataCFSInfo cfsinfo; | ||||
| 
 | ||||
|     SADataError error; | ||||
| @ -120,8 +114,6 @@ typedef enum { | ||||
|     StorageCommandDirRewind, | ||||
|     StorageCommandCommonStat, | ||||
|     StorageCommandCommonRemove, | ||||
|     StorageCommandCommonRename, | ||||
|     StorageCommandCommonCopy, | ||||
|     StorageCommandCommonMkDir, | ||||
|     StorageCommandCommonFSInfo, | ||||
|     StorageCommandSDFormat, | ||||
|  | ||||
| @ -1,8 +1,11 @@ | ||||
| #include "storage_processing.h" | ||||
| #include <m-list.h> | ||||
| #include <m-dict.h> | ||||
| #include <m-string.h> | ||||
| 
 | ||||
| #define FS_CALL(_storage, _fn)   \ | ||||
|     storage_data_lock(_storage); \ | ||||
|     ret = _storage->fs_api._fn;  \ | ||||
|     ret = _storage->fs_api->_fn; \ | ||||
|     storage_data_unlock(_storage); | ||||
| 
 | ||||
| #define ST_CALL(_storage, _fn)   \ | ||||
| @ -11,18 +14,8 @@ | ||||
|     storage_data_unlock(_storage); | ||||
| 
 | ||||
| static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { | ||||
|     StorageData* storage; | ||||
| 
 | ||||
|     if(type == ST_ANY) { | ||||
|         type = ST_INT; | ||||
|         StorageData* ext_storage = &app->storage[ST_EXT]; | ||||
| 
 | ||||
|         if(storage_data_status(ext_storage) == StorageStatusOK) { | ||||
|             type = ST_EXT; | ||||
|         } | ||||
|     } | ||||
|     storage = &app->storage[type]; | ||||
| 
 | ||||
|     furi_check(type == ST_EXT || type == ST_INT); | ||||
|     StorageData* storage = &app->storage[type]; | ||||
|     return storage; | ||||
| } | ||||
| 
 | ||||
| @ -42,10 +35,55 @@ static StorageData* get_storage_by_file(File* file, StorageData* storages) { | ||||
|     return storage_data; | ||||
| } | ||||
| 
 | ||||
| const char* remove_vfs(const char* path) { | ||||
| static const char* remove_vfs(const char* 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 *******************/ | ||||
| 
 | ||||
| bool storage_process_file_open( | ||||
| @ -55,7 +93,7 @@ bool storage_process_file_open( | ||||
|     FS_AccessMode access_mode, | ||||
|     FS_OpenMode open_mode) { | ||||
|     bool ret = false; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
|     StorageType type = storage_get_type_by_path(app, path); | ||||
|     StorageData* storage; | ||||
|     file->error_id = FSE_OK; | ||||
| 
 | ||||
| @ -63,12 +101,18 @@ bool storage_process_file_open( | ||||
|         file->error_id = FSE_INVALID_NAME; | ||||
|     } else { | ||||
|         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; | ||||
|         } 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)); | ||||
|         } | ||||
| 
 | ||||
|         string_clear(real_path); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
| @ -83,6 +127,9 @@ bool storage_process_file_close(Storage* app, File* file) { | ||||
|     } else { | ||||
|         FS_CALL(storage, file.close(storage, file)); | ||||
|         storage_pop_storage_file(file, storage); | ||||
| 
 | ||||
|         StorageEvent event = {.type = StorageEventTypeFileClose}; | ||||
|         furi_pubsub_publish(app->pubsub, &event); | ||||
|     } | ||||
| 
 | ||||
|     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 ret = false; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
|     StorageType type = storage_get_type_by_path(app, path); | ||||
|     StorageData* storage; | ||||
|     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; | ||||
|     } else { | ||||
|         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; | ||||
|         } 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))); | ||||
|         } | ||||
|         string_clear(real_path); | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|     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)) { | ||||
|         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) { | ||||
|     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 { | ||||
|         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); | ||||
|         if(storage_path_already_open(path, storage->files)) { | ||||
|         if(storage_path_already_open(real_path, storage->files)) { | ||||
|             ret = FSE_ALREADY_OPEN; | ||||
|             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))); | ||||
|     } while(false); | ||||
| 
 | ||||
|     string_clear(real_path); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { | ||||
|     FS_Error ret = FSE_OK; | ||||
|     StorageType type = storage_get_type_by_path(path); | ||||
|     StorageType type = storage_get_type_by_path(app, path); | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type)) { | ||||
|         ret = FSE_INVALID_NAME; | ||||
| @ -321,86 +379,13 @@ static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { | ||||
|     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( | ||||
|     Storage* app, | ||||
|     const char* fs_path, | ||||
|     uint64_t* total_space, | ||||
|     uint64_t* free_space) { | ||||
|     FS_Error ret = FSE_OK; | ||||
|     StorageType type = storage_get_type_by_path(fs_path); | ||||
|     StorageType type = storage_get_type_by_path(app, fs_path); | ||||
| 
 | ||||
|     if(storage_type_is_not_valid(type)) { | ||||
|         ret = FSE_INVALID_NAME; | ||||
| @ -472,8 +457,7 @@ static FS_Error storage_process_sd_status(Storage* app) { | ||||
| } | ||||
| 
 | ||||
| /****************** API calls processing ******************/ | ||||
| 
 | ||||
| void storage_process_message(Storage* app, StorageMessage* message) { | ||||
| void storage_process_message_internal(Storage* app, StorageMessage* message) { | ||||
|     switch(message->command) { | ||||
|     case StorageCommandFileOpen: | ||||
|         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 = | ||||
|             storage_process_common_remove(app, message->data->path.path); | ||||
|         break; | ||||
|     case StorageCommandCommonRename: | ||||
|         message->return_data->error_value = storage_process_common_rename( | ||||
|             app, message->data->cpaths.old, message->data->cpaths.new); | ||||
|         break; | ||||
|     case StorageCommandCommonCopy: | ||||
|         message->return_data->error_value = | ||||
|             storage_process_common_copy(app, message->data->cpaths.old, message->data->cpaths.new); | ||||
|         break; | ||||
|     case StorageCommandCommonMkDir: | ||||
|         message->return_data->error_value = | ||||
|             storage_process_common_mkdir(app, message->data->path.path); | ||||
| @ -592,3 +568,7 @@ void storage_process_message(Storage* app, StorageMessage* message) { | ||||
| 
 | ||||
|     osSemaphoreRelease(message->semaphore); | ||||
| } | ||||
| 
 | ||||
| void storage_process_message(Storage* app, StorageMessage* message) { | ||||
|     storage_process_message_internal(app, message); | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,6 @@ | ||||
| #pragma once | ||||
| #include <furi.h> | ||||
| #include "filesystem_api_defines.h" | ||||
| #include <fatfs.h> | ||||
| #include "storage_glue.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| @ -11,10 +9,11 @@ extern "C" { | ||||
| #define SD_LABEL_LENGTH 34 | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
| typedef struct { | ||||
|  | ||||
| @ -164,7 +164,24 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { | ||||
|         sector_size = fs->ssize; | ||||
| #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_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); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_ext_common_rename(void* ctx, const char* old_path, const char* new_path) { | ||||
|     SDError result = f_rename(old_path, new_path); | ||||
|     return storage_ext_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_ext_common_mkdir(void* ctx, const char* path) { | ||||
|     SDError result = f_mkdir(path); | ||||
|     return storage_ext_parse_error(result); | ||||
| @ -510,6 +522,35 @@ static FS_Error storage_ext_common_fs_info( | ||||
| } | ||||
| 
 | ||||
| /******************* 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) { | ||||
|     SDData* sd_data = malloc(sizeof(SDData)); | ||||
| @ -519,27 +560,7 @@ void storage_ext_init(StorageData* storage) { | ||||
| 
 | ||||
|     storage->data = sd_data; | ||||
|     storage->api.tick = storage_ext_tick; | ||||
|     storage->fs_api.file.open = storage_ext_file_open; | ||||
|     storage->fs_api.file.close = storage_ext_file_close; | ||||
|     storage->fs_api.file.read = storage_ext_file_read; | ||||
|     storage->fs_api.file.write = storage_ext_file_write; | ||||
|     storage->fs_api.file.seek = storage_ext_file_seek; | ||||
|     storage->fs_api.file.tell = storage_ext_file_tell; | ||||
|     storage->fs_api.file.truncate = storage_ext_file_truncate; | ||||
|     storage->fs_api.file.size = storage_ext_file_size; | ||||
|     storage->fs_api.file.sync = storage_ext_file_sync; | ||||
|     storage->fs_api.file.eof = storage_ext_file_eof; | ||||
| 
 | ||||
|     storage->fs_api.dir.open = storage_ext_dir_open; | ||||
|     storage->fs_api.dir.close = storage_ext_dir_close; | ||||
|     storage->fs_api.dir.read = storage_ext_dir_read; | ||||
|     storage->fs_api.dir.rewind = storage_ext_dir_rewind; | ||||
| 
 | ||||
|     storage->fs_api.common.stat = storage_ext_common_stat; | ||||
|     storage->fs_api.common.mkdir = storage_ext_common_mkdir; | ||||
|     storage->fs_api.common.rename = storage_ext_common_rename; | ||||
|     storage->fs_api.common.remove = storage_ext_common_remove; | ||||
|     storage->fs_api.common.fs_info = storage_ext_common_fs_info; | ||||
|     storage->fs_api = &fs_api; | ||||
| 
 | ||||
|     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); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_int_common_rename(void* ctx, const char* old_path, const char* new_path) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
|     int result = lfs_rename(lfs, old_path, new_path); | ||||
|     return storage_int_parse_error(result); | ||||
| } | ||||
| 
 | ||||
| static FS_Error storage_int_common_mkdir(void* ctx, const char* path) { | ||||
|     StorageData* storage = ctx; | ||||
|     lfs_t* lfs = lfs_get_from_storage(storage); | ||||
| @ -671,6 +664,35 @@ static FS_Error storage_int_common_fs_info( | ||||
| } | ||||
| 
 | ||||
| /******************* 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) { | ||||
|     FURI_LOG_I(TAG, "Starting"); | ||||
| @ -689,25 +711,5 @@ void storage_int_init(StorageData* storage) { | ||||
| 
 | ||||
|     storage->data = lfs_data; | ||||
|     storage->api.tick = NULL; | ||||
|     storage->fs_api.file.open = storage_int_file_open; | ||||
|     storage->fs_api.file.close = storage_int_file_close; | ||||
|     storage->fs_api.file.read = storage_int_file_read; | ||||
|     storage->fs_api.file.write = storage_int_file_write; | ||||
|     storage->fs_api.file.seek = storage_int_file_seek; | ||||
|     storage->fs_api.file.tell = storage_int_file_tell; | ||||
|     storage->fs_api.file.truncate = storage_int_file_truncate; | ||||
|     storage->fs_api.file.size = storage_int_file_size; | ||||
|     storage->fs_api.file.sync = storage_int_file_sync; | ||||
|     storage->fs_api.file.eof = storage_int_file_eof; | ||||
| 
 | ||||
|     storage->fs_api.dir.open = storage_int_dir_open; | ||||
|     storage->fs_api.dir.close = storage_int_dir_close; | ||||
|     storage->fs_api.dir.read = storage_int_dir_read; | ||||
|     storage->fs_api.dir.rewind = storage_int_dir_rewind; | ||||
| 
 | ||||
|     storage->fs_api.common.stat = storage_int_common_stat; | ||||
|     storage->fs_api.common.mkdir = storage_int_common_mkdir; | ||||
|     storage->fs_api.common.rename = storage_int_common_rename; | ||||
|     storage->fs_api.common.remove = storage_int_common_remove; | ||||
|     storage->fs_api.common.fs_info = storage_int_common_fs_info; | ||||
|     storage->fs_api = &fs_api; | ||||
| } | ||||
|  | ||||
							
								
								
									
										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_string(); | ||||
| int run_minunit_test_stream(); | ||||
| int run_minunit_test_storage(); | ||||
| 
 | ||||
| void minunit_print_progress(void) { | ||||
|     static char progress[] = {'\\', '|', '/', '-'}; | ||||
| @ -53,11 +54,12 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { | ||||
|         uint32_t cycle_counter = DWT->CYCCNT; | ||||
| 
 | ||||
|         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_flipper_format(); | ||||
|         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); | ||||
| 
 | ||||
|         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); | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|     storage_file_free(stream->file); | ||||
|     free(stream); | ||||
|  | ||||
| @ -35,6 +35,13 @@ bool file_stream_open( | ||||
|  */ | ||||
| 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 | ||||
| } | ||||
| #endif | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 SG
						SG