[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…
Reference in New Issue
Block a user