Просмотр исходного кода

[FL-935] Queue-based SD app cycle. File select widget. (#388)

* SD App: more specific sd api
* Gui: view dispatcher fix api documentation
* Gui: view holder thingy
* SD App: do not sleep when working with sd card bus
* SD App: queue-based lifecycle
* Assets: sd-card assets
* SD App: init file select api

Co-authored-by: あく <alleteam@gmail.com>
SG 4 лет назад
Родитель
Сommit
cc263d743b

+ 34 - 16
applications/gui/modules/file_select.c

@@ -8,13 +8,16 @@ struct FileSelect {
     // public
     View* view;
     FS_Api* fs_api;
-    char* path;
-    char* extension;
+    const char* path;
+    const char* extension;
 
     bool init_completed;
 
     FileSelectCallback callback;
     void* context;
+
+    char* buffer;
+    uint8_t buffer_size;
 };
 
 typedef struct {
@@ -28,7 +31,7 @@ typedef struct {
 
 bool file_select_fill_strings(FileSelect* file_select);
 bool file_select_fill_count(FileSelect* file_select);
-static bool file_select_init(FileSelect* file_select);
+static bool file_select_init_inner(FileSelect* file_select);
 
 static void file_select_draw_callback(Canvas* canvas, void* _model) {
     FileSelectModel* model = _model;
@@ -66,8 +69,8 @@ static bool file_select_input_callback(InputEvent* event, void* context) {
 
     if(event->type == InputTypeShort) {
         if(!file_select->init_completed) {
-            if(!file_select_init(file_select)) {
-                file_select->callback(NULL, file_select->context);
+            if(!file_select_init_inner(file_select)) {
+                file_select->callback(false, file_select->context);
             }
         } else if(event->key == InputKeyUp) {
             with_view_model(
@@ -127,20 +130,24 @@ static bool file_select_input_callback(InputEvent* event, void* context) {
                         return false;
                     });
 
-                file_select->callback(result, file_select->context);
+                if(file_select->buffer) {
+                    strlcpy(file_select->buffer, result, file_select->buffer_size);
+                };
+
+                file_select->callback(true, file_select->context);
             }
             consumed = true;
         }
 
         if(!file_select_fill_strings(file_select)) {
-            file_select->callback(NULL, file_select->context);
+            file_select->callback(false, file_select->context);
         }
     }
 
     return consumed;
 }
 
-static bool file_select_init(FileSelect* file_select) {
+static bool file_select_init_inner(FileSelect* file_select) {
     bool result = false;
     if(file_select->path && file_select->extension && file_select->fs_api) {
         if(file_select_fill_count(file_select)) {
@@ -162,13 +169,6 @@ FileSelect* file_select_alloc() {
     view_set_draw_callback(file_select->view, file_select_draw_callback);
     view_set_input_callback(file_select->view, file_select_input_callback);
 
-    file_select->fs_api = NULL;
-    file_select->path = NULL;
-    file_select->extension = NULL;
-    file_select->init_completed = false;
-    file_select->callback = NULL;
-    file_select->context = NULL;
-
     with_view_model(
         file_select->view, (FileSelectModel * model) {
             for(uint8_t i = 0; i < FILENAME_COUNT; i++) {
@@ -209,12 +209,30 @@ void file_select_set_api(FileSelect* file_select, FS_Api* fs_api) {
 void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context) {
 }
 
-void file_select_set_filter(FileSelect* file_select, char* path, char* extension) {
+void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension) {
     furi_assert(file_select);
     file_select->path = path;
     file_select->extension = extension;
 }
 
+void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size) {
+    file_select->buffer = buffer;
+    file_select->buffer_size = buffer_size;
+
+    if(file_select->buffer) {
+        strlcpy(file_select->buffer, "", file_select->buffer_size);
+    }
+}
+
+bool file_select_init(FileSelect* file_select) {
+    if(!file_select_init_inner(file_select)) {
+        file_select->callback(false, file_select->context);
+        return false;
+    } else {
+        return true;
+    }
+}
+
 static bool filter_file(FileSelect* file_select, FileInfo* file_info, char* name) {
     bool result = false;
 

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

@@ -8,7 +8,7 @@ extern "C" {
 
 typedef struct FileSelect FileSelect;
 
-typedef void (*FileSelectCallback)(const char* result, void* context);
+typedef void (*FileSelectCallback)(bool result, void* context);
 
 FileSelect* file_select_alloc();
 
@@ -17,7 +17,9 @@ View* file_select_get_view(FileSelect* file_select);
 
 void file_select_set_api(FileSelect* file_select, FS_Api* fs_api);
 void file_select_set_callback(FileSelect* file_select, FileSelectCallback callback, void* context);
-void file_select_set_filter(FileSelect* file_select, char* path, char* extension);
+void file_select_set_filter(FileSelect* file_select, const char* path, const char* extension);
+void file_select_set_result_buffer(FileSelect* file_select, char* buffer, uint8_t buffer_size);
+bool file_select_init(FileSelect* file_select);
 
 #ifdef __cplusplus
 }

+ 27 - 19
applications/gui/view_dispatcher.h

@@ -7,47 +7,55 @@
 extern "C" {
 #endif
 
-/* ViewDispatcher view_port placement */
+/**
+ * @brief ViewDispatcher view_port placement
+ */
 typedef enum {
-    ViewDispatcherTypeNone, /* Special layer for internal use only */
-    ViewDispatcherTypeWindow, /* Main view_port layer, status bar is shown */
-    ViewDispatcherTypeFullscreen /* Fullscreen view_port layer */
+    ViewDispatcherTypeNone, /**< Special layer for internal use only */
+    ViewDispatcherTypeWindow, /**< Main view_port layer, status bar is shown */
+    ViewDispatcherTypeFullscreen /**< Fullscreen view_port layer */
 } ViewDispatcherType;
 
 typedef struct ViewDispatcher ViewDispatcher;
 
-/* Allocate ViewDispatcher
+/**
+ * @brief Allocate ViewDispatcher
  * @return pointer to ViewDispatcher instance
  */
 ViewDispatcher* view_dispatcher_alloc();
 
-/* Free ViewDispatcher
- * @param pointer to View
+/**
+ * @brief Free ViewDispatcher
+ * @param view_dispatcher pointer to ViewDispatcher
  */
 void view_dispatcher_free(ViewDispatcher* view_dispatcher);
 
-/* Add view to ViewDispatcher
+/**
+ * @brief Add view to ViewDispatcher
  * @param view_dispatcher, ViewDispatcher instance
- * @param view_id, View id to register
- * @param view, View instance
+ * @param view_id View id to register
+ * @param view View instance
  */
 void view_dispatcher_add_view(ViewDispatcher* view_dispatcher, uint32_t view_id, View* view);
 
-/* Remove view from ViewDispatcher
- * @param view_dispatcher, ViewDispatcher instance
- * @param view_id, View id to remove
+/**
+ * @brief Remove view from ViewDispatcher
+ * @param view_dispatcher ViewDispatcher instance
+ * @param view_id View id to remove
  */
 void view_dispatcher_remove_view(ViewDispatcher* view_dispatcher, uint32_t view_id);
 
-/* Switch to View
- * @param view_dispatcher, ViewDispatcher instance
- * @param view_id, View id to register
+/**
+ * @brief Switch to View
+ * @param view_dispatcher ViewDispatcher instance
+ * @param view_id View id to register
  */
 void view_dispatcher_switch_to_view(ViewDispatcher* view_dispatcher, uint32_t view_id);
 
-/* Attach ViewDispatcher to GUI
- * @param view_dispatcher, ViewDispatcher instance
- * @param gui, GUI instance to attach to
+/**
+ * @brief Attach ViewDispatcher to GUI
+ * @param view_dispatcher ViewDispatcher instance
+ * @param gui GUI instance to attach to
  */
 void view_dispatcher_attach_to_gui(
     ViewDispatcher* view_dispatcher,

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

@@ -27,10 +27,12 @@ bool _fs_init(SdFsInfo* _fs_info) {
 }
 
 bool _fs_lock(SdFsInfo* fs_info) {
+    api_hal_power_insomnia_enter();
     return (osMutexAcquire(fs_info->mutex, osWaitForever) == osOK);
 }
 
 bool _fs_unlock(SdFsInfo* fs_info) {
+    api_hal_power_insomnia_exit();
     return (osMutexRelease(fs_info->mutex) == osOK);
 }
 

+ 476 - 298
applications/sd-filesystem/sd-filesystem.c

@@ -6,6 +6,65 @@
 #include "cli/cli.h"
 #include "api-hal-sd.h"
 
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/file_select.h>
+
+typedef enum {
+    FST_FAT12 = FS_FAT12,
+    FST_FAT16 = FS_FAT16,
+    FST_FAT32 = FS_FAT32,
+    FST_EXFAT = FS_EXFAT,
+} SDFsType;
+
+typedef struct {
+    SDFsType fs_type;
+    uint32_t kb_total;
+    uint32_t kb_free;
+    uint16_t cluster_size;
+    uint16_t sector_size;
+    char label[34];
+    SDError error;
+} SDInfo;
+
+typedef enum {
+    SdAppEventTypeBack,
+    SdAppEventTypeOK,
+    SdAppEventTypeFormat,
+    SdAppEventTypeInfo,
+    SdAppEventTypeEject,
+    SdAppEventTypeFileSelect,
+    SdAppEventTypeCheckError,
+} SdAppEventType;
+
+typedef struct {
+    const char* path;
+    const char* extension;
+    char* result;
+    uint8_t result_size;
+} SdAppFileSelectData;
+
+typedef struct {
+    bool result;
+} SdAppFileSelectResultEvent;
+
+typedef struct {
+    SdAppEventType type;
+    osMessageQueueId_t result_receiver;
+    union {
+        SdAppFileSelectData file_select_data;
+    } payload;
+} SdAppEvent;
+
+static void sd_icon_draw_callback(Canvas* canvas, void* context);
+bool sd_api_file_select(
+    SdApp* sd_app,
+    const char* path,
+    const char* extension,
+    char* result,
+    uint8_t result_size);
+
+/******************* Allocators *******************/
+
 FS_Api* fs_api_alloc() {
     FS_Api* fs_api = furi_alloc(sizeof(FS_Api));
 
@@ -43,77 +102,13 @@ FS_Api* fs_api_alloc() {
     return fs_api;
 }
 
-void sd_set_lines(SdApp* sd_app, uint8_t count, ...) {
-    va_list argptr;
-    count = min(count, SD_STATE_LINES_COUNT);
-
-    for(uint8_t i = 0; i < SD_STATE_LINES_COUNT; i++) {
-        sd_app->line[i] = "";
-    }
-
-    va_start(argptr, count);
-
-    for(uint8_t i = 0; i < count; i++) {
-        sd_app->line[i] = va_arg(argptr, char*);
-    }
-
-    va_end(argptr);
-}
-
-void sd_icon_draw_callback(Canvas* canvas, void* context) {
-    furi_assert(canvas);
-    furi_assert(context);
-    SdApp* sd_app = context;
-
-    switch(sd_app->info.status) {
-    case SD_NO_CARD:
-        break;
-    case SD_OK:
-        canvas_draw_icon(canvas, 0, 0, sd_app->icon.mounted);
-        break;
-    default:
-        canvas_draw_icon(canvas, 0, 0, sd_app->icon.fail);
-        break;
-    }
-}
-
-void sd_app_draw_callback(Canvas* canvas, void* context) {
-    furi_assert(canvas);
-    furi_assert(context);
-    SdApp* sd_app = context;
-
-    canvas_clear(canvas);
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontPrimary);
-
-    for(uint8_t i = 0; i < SD_STATE_LINES_COUNT; i++) {
-        canvas_draw_str(canvas, 0, (i + 1) * 10, sd_app->line[i]);
-    }
-}
-
-void sd_app_input_callback(InputEvent* event, void* context) {
-    furi_assert(context);
-    SdApp* sd_app = context;
-
-    osMessageQueuePut(sd_app->event_queue, event, 0, 0);
-}
-
 SdApp* sd_app_alloc() {
     SdApp* sd_app = furi_alloc(sizeof(SdApp));
 
     // init inner fs data
     furi_check(_fs_init(&sd_app->info));
 
-    sd_app->event_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL);
-
-    // init view_port
-    sd_app->view_port = view_port_alloc();
-    view_port_draw_callback_set(sd_app->view_port, sd_app_draw_callback, sd_app);
-    view_port_input_callback_set(sd_app->view_port, sd_app_input_callback, sd_app);
-    view_port_enabled_set(sd_app->view_port, false);
-
-    // init lines
-    sd_set_lines(sd_app, 0);
+    sd_app->event_queue = osMessageQueueNew(8, sizeof(SdAppEvent), NULL);
 
     // init icon view_port
     sd_app->icon.view_port = view_port_alloc();
@@ -123,143 +118,68 @@ SdApp* sd_app_alloc() {
     view_port_draw_callback_set(sd_app->icon.view_port, sd_icon_draw_callback, sd_app);
     view_port_enabled_set(sd_app->icon.view_port, false);
 
-    return sd_app;
-}
-
-bool app_sd_ask(SdApp* sd_app, InputKey input_true, InputKey input_false) {
-    bool result;
+    // init sd card api
+    sd_app->sd_card_api.context = sd_app;
+    sd_app->sd_card_api.file_select = sd_api_file_select;
+    sd_app->sd_app_state = SdAppStateBackground;
+    string_init(sd_app->text_holder);
 
-    InputEvent event;
-    while(1) {
-        osStatus_t event_status =
-            osMessageQueueGet(sd_app->event_queue, &event, NULL, osWaitForever);
-
-        if(event_status == osOK) {
-            if(event.type == InputTypeShort && event.key == input_true) {
-                result = true;
-                break;
-            }
-            if(event.type == InputTypeShort && event.key == InputKeyBack) {
-                result = false;
-                break;
-            }
-        }
-    }
-
-    return result;
+    return sd_app;
 }
 
-void app_sd_info_callback(void* context) {
-    furi_assert(context);
-    SdApp* sd_app = context;
-    view_port_enabled_set(sd_app->view_port, true);
-
-    // dynamic strings
-    const uint8_t str_buffer_size = 26;
-    const uint8_t str_count = 6;
-    char* str_buffer[str_count];
-    bool memory_error = false;
+/******************* Internal sd card related fns *******************/
 
-    // info vars
-    uint32_t serial_num;
-    SDError get_label_result, get_free_result;
-    FATFS* fs;
+void get_sd_info(SdApp* sd_app, SDInfo* sd_info) {
     uint32_t free_clusters, free_sectors, total_sectors;
-    char volume_label[34];
+    FATFS* fs;
 
-    // init strings
-    for(uint8_t i = 0; i < str_count; i++) {
-        str_buffer[i] = malloc(str_buffer_size + 1);
-        if(str_buffer[i] == NULL) {
-            memory_error = true;
-        } else {
-            str_buffer[i][0] = 0;
-        }
-    }
+    // clean data
+    memset(sd_info, 0, sizeof(SDInfo));
 
-    if(memory_error) {
-        sd_set_lines(sd_app, 1, "not enough memory");
-    } else {
-        // get fs info
-        _fs_lock(&sd_app->info);
-        get_label_result = f_getlabel(sd_app->info.path, volume_label, &serial_num);
-        get_free_result = f_getfree(sd_app->info.path, &free_clusters, &fs);
-        _fs_unlock(&sd_app->info);
+    // get fs info
+    _fs_lock(&sd_app->info);
+    sd_info->error = f_getlabel(sd_app->info.path, sd_info->label, NULL);
+    if(sd_info->error == SD_OK) {
+        sd_info->error = f_getfree(sd_app->info.path, &free_clusters, &fs);
+    }
+    _fs_unlock(&sd_app->info);
 
+    if(sd_info->error == SD_OK) {
         // calculate size
         total_sectors = (fs->n_fatent - 2) * fs->csize;
         free_sectors = free_clusters * fs->csize;
+
         uint16_t sector_size = _MAX_SS;
 #if _MAX_SS != _MIN_SS
         sector_size = fs->ssize;
 #endif
 
-        // output info to dynamic strings
-        if(get_label_result == SD_OK && get_free_result == SD_OK) {
-            snprintf(str_buffer[0], str_buffer_size, "%s", volume_label);
+        sd_info->fs_type = fs->fs_type;
 
-            const char* fs_type = "";
-
-            switch(fs->fs_type) {
-            case(FS_FAT12):
-                fs_type = "FAT12";
-                break;
-            case(FS_FAT16):
-                fs_type = "FAT16";
-                break;
-            case(FS_FAT32):
-                fs_type = "FAT32";
-                break;
-            case(FS_EXFAT):
-                fs_type = "EXFAT";
-                break;
-            default:
-                fs_type = "UNKNOWN";
-                break;
-            }
-
-            snprintf(str_buffer[1], str_buffer_size, "%s, S/N: %lu", fs_type, serial_num);
-
-            snprintf(str_buffer[2], str_buffer_size, "Cluster: %d sectors", fs->csize);
-            snprintf(str_buffer[3], str_buffer_size, "Sector: %d bytes", sector_size);
-            snprintf(
-                str_buffer[4], str_buffer_size, "%lu KB total", total_sectors / 1024 * sector_size);
-            snprintf(
-                str_buffer[5], str_buffer_size, "%lu KB free", free_sectors / 1024 * sector_size);
-        } else {
-            snprintf(str_buffer[0], str_buffer_size, "SD status error:");
-            snprintf(
-                str_buffer[1],
-                str_buffer_size,
-                "%s",
-                fs_error_get_internal_desc(_fs_status(&sd_app->info)));
-            snprintf(str_buffer[2], str_buffer_size, "Label error:");
-            snprintf(
-                str_buffer[3], str_buffer_size, "%s", fs_error_get_internal_desc(get_label_result));
-            snprintf(str_buffer[4], str_buffer_size, "Get free error:");
-            snprintf(
-                str_buffer[5], str_buffer_size, "%s", fs_error_get_internal_desc(get_free_result));
-        }
-
-        // dynamic strings to screen
-        sd_set_lines(
-            sd_app,
-            6,
-            str_buffer[0],
-            str_buffer[1],
-            str_buffer[2],
-            str_buffer[3],
-            str_buffer[4],
-            str_buffer[5]);
+        sd_info->kb_total = total_sectors / 1024 * sector_size;
+        sd_info->kb_free = free_sectors / 1024 * sector_size;
+        sd_info->cluster_size = fs->csize;
+        sd_info->sector_size = sector_size;
     }
+}
 
-    app_sd_ask(sd_app, InputKeyBack, InputKeyBack);
-
-    sd_set_lines(sd_app, 0);
-    view_port_enabled_set(sd_app->view_port, false);
-
-    for(uint8_t i = 0; i < str_count; i++) {
-        free(str_buffer[i]);
+const char* get_fs_type_text(SDFsType fs_type) {
+    switch(fs_type) {
+    case(FST_FAT12):
+        return "FAT12";
+        break;
+    case(FST_FAT16):
+        return "FAT16";
+        break;
+    case(FST_FAT32):
+        return "FAT32";
+        break;
+    case(FST_EXFAT):
+        return "EXFAT";
+        break;
+    default:
+        return "UNKNOWN";
+        break;
     }
 }
 
@@ -284,39 +204,6 @@ void app_sd_format_internal(SdApp* sd_app) {
     _fs_unlock(&sd_app->info);
 }
 
-void app_sd_format_callback(void* context) {
-    furi_assert(context);
-    SdApp* sd_app = context;
-
-    // ask to really format
-    sd_set_lines(sd_app, 2, "Press UP to format", "or BACK to exit");
-    view_port_enabled_set(sd_app->view_port, true);
-
-    // wait for input
-    if(!app_sd_ask(sd_app, InputKeyUp, InputKeyBack)) {
-        view_port_enabled_set(sd_app->view_port, false);
-        return;
-    }
-
-    // show warning
-    sd_set_lines(sd_app, 3, "formatting SD card", "procedure can be lengthy", "please wait");
-
-    // format card
-    app_sd_format_internal(sd_app);
-
-    if(sd_app->info.status != SD_OK) {
-        sd_set_lines(
-            sd_app, 2, "SD card format error", fs_error_get_internal_desc(sd_app->info.status));
-    } else {
-        sd_set_lines(sd_app, 1, "SD card formatted");
-    }
-
-    // wait for BACK
-    app_sd_ask(sd_app, InputKeyBack, InputKeyBack);
-
-    view_port_enabled_set(sd_app->view_port, false);
-}
-
 void app_sd_notify_wait_on() {
     api_hal_light_set(LightRed, 0xFF);
     api_hal_light_set(LightBlue, 0xFF);
@@ -433,23 +320,149 @@ void app_sd_unmount_card(SdApp* sd_app) {
     _fs_unlock(&sd_app->info);
 }
 
-void app_sd_eject_callback(void* context) {
+bool app_sd_make_path(const char* path) {
+    furi_assert(path);
+
+    if(*path) {
+        char* file_path = strdup(path);
+
+        for(char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) {
+            *p = '\0';
+            SDError result = f_mkdir(file_path);
+
+            if(result != SD_OK) {
+                if(result != SD_EXIST) {
+                    *p = '/';
+                    free(file_path);
+                    return false;
+                }
+            }
+            *p = '/';
+        }
+
+        free(file_path);
+    }
+
+    return true;
+}
+
+/******************* Draw callbacks *******************/
+
+static void sd_icon_draw_callback(Canvas* canvas, void* context) {
+    furi_assert(canvas);
     furi_assert(context);
     SdApp* sd_app = context;
 
-    sd_set_lines(sd_app, 1, "ejecting SD card");
-    view_port_enabled_set(sd_app->view_port, true);
+    switch(sd_app->info.status) {
+    case SD_NO_CARD:
+        break;
+    case SD_OK:
+        canvas_draw_icon(canvas, 0, 0, sd_app->icon.mounted);
+        break;
+    default:
+        canvas_draw_icon(canvas, 0, 0, sd_app->icon.fail);
+        break;
+    }
+}
+
+/******************* SD-api callbacks *******************/
+
+bool sd_api_file_select(
+    SdApp* sd_app,
+    const char* path,
+    const char* extension,
+    char* result,
+    uint8_t result_size) {
+    bool retval = false;
+
+    osMessageQueueId_t return_event_queue =
+        osMessageQueueNew(1, sizeof(SdAppFileSelectResultEvent), NULL);
 
-    app_sd_unmount_card(sd_app);
+    SdAppEvent message = {
+        .type = SdAppEventTypeFileSelect,
+        .result_receiver = return_event_queue,
+        .payload = {
+            .file_select_data = {
+                .path = path,
+                .extension = extension,
+                .result = result,
+                .result_size = result_size}}};
 
-    sd_set_lines(sd_app, 1, "SD card can be pulled out");
+    furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
 
-    // wait for BACK
-    app_sd_ask(sd_app, InputKeyBack, InputKeyBack);
+    SdAppFileSelectResultEvent event;
+    while(1) {
+        osStatus_t event_status =
+            osMessageQueueGet(sd_app->event_queue, &event, NULL, osWaitForever);
+        if(event_status == osOK) {
+            retval = event.result;
+            break;
+        }
+    }
 
-    view_port_enabled_set(sd_app->view_port, false);
+    return retval;
+}
+
+/******************* View callbacks *******************/
+
+void app_view_back_callback(void* context) {
+    furi_assert(context);
+    SdApp* sd_app = context;
+    SdAppEvent message = {.type = SdAppEventTypeBack};
+    furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
+}
+
+void app_view_dialog_callback(DialogExResult result, void* context) {
+    furi_assert(context);
+    SdApp* sd_app = context;
+
+    if(result == DialogExResultLeft) {
+        SdAppEvent message = {.type = SdAppEventTypeBack};
+        furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
+    } else if(result == DialogExResultRight) {
+        SdAppEvent message = {.type = SdAppEventTypeOK};
+        furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
+    }
+}
+
+void app_view_file_select_callback(bool result, void* context) {
+    furi_assert(context);
+    SdApp* sd_app = context;
+
+    if(result) {
+        SdAppEvent message = {.type = SdAppEventTypeOK};
+        furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
+    } else {
+        SdAppEvent message = {.type = SdAppEventTypeBack};
+        furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
+    }
+}
+
+/******************* Menu callbacks *******************/
+
+void app_sd_info_callback(void* context) {
+    furi_assert(context);
+    SdApp* sd_app = context;
+    SdAppEvent message = {.type = SdAppEventTypeInfo};
+    furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
+}
+
+void app_sd_format_callback(void* context) {
+    furi_assert(context);
+    SdApp* sd_app = context;
+    SdAppEvent message = {.type = SdAppEventTypeFormat};
+    furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
 }
 
+void app_sd_eject_callback(void* context) {
+    furi_assert(context);
+    SdApp* sd_app = context;
+    SdAppEvent message = {.type = SdAppEventTypeEject};
+    furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
+}
+
+/******************* Cli callbacks *******************/
+
 static void cli_sd_status(string_t args, void* _ctx) {
     SdApp* sd_app = (SdApp*)_ctx;
 
@@ -477,93 +490,81 @@ static void cli_sd_format(string_t args, void* _ctx) {
 
 static void cli_sd_info(string_t args, void* _ctx) {
     SdApp* sd_app = (SdApp*)_ctx;
+    SDInfo sd_info;
+
+    get_sd_info(sd_app, &sd_info);
+
+    if(sd_info.error == SD_OK) {
+        const char* fs_type = get_fs_type_text(sd_info.fs_type);
+        printf("Label: %s\r\n", sd_info.label);
+        printf("%s\r\n", fs_type);
+        printf("Cluster: %d sectors\r\n", sd_info.cluster_size);
+        printf("Sector: %d bytes\r\n", sd_info.sector_size);
+        printf("%lu KB total\r\n", sd_info.kb_total);
+        printf("%lu KB free\r\n", sd_info.kb_free);
+    } else {
+        printf("SD status error: %s\r\n", fs_error_get_internal_desc(_fs_status(&sd_app->info)));
+        printf("SD info error: %s\r\n", fs_error_get_internal_desc(sd_info.error));
+    }
+}
 
-    const uint8_t str_buffer_size = 64;
-    char str_buffer[str_buffer_size];
+/******************* Test *******************/
 
-    // info vars
-    uint32_t serial_num;
-    SDError get_label_result, get_free_result;
-    FATFS* fs;
-    uint32_t free_clusters, free_sectors, total_sectors;
-    char volume_label[34];
+bool try_to_alloc_view_holder(SdApp* sd_app, Gui* gui) {
+    bool result = false;
 
-    // get fs info
     _fs_lock(&sd_app->info);
-    get_label_result = f_getlabel(sd_app->info.path, volume_label, &serial_num);
-    get_free_result = f_getfree(sd_app->info.path, &free_clusters, &fs);
-    _fs_unlock(&sd_app->info);
 
-    // calculate size
-    total_sectors = (fs->n_fatent - 2) * fs->csize;
-    free_sectors = free_clusters * fs->csize;
-    uint16_t sector_size = _MAX_SS;
-#if _MAX_SS != _MIN_SS
-    sector_size = fs->ssize;
-#endif
+    if(sd_app->view_holder == NULL) {
+        sd_app->view_holder = view_holder_alloc();
+        view_holder_attach_to_gui(sd_app->view_holder, gui);
+        view_holder_set_back_callback(sd_app->view_holder, app_view_back_callback, sd_app);
+        result = true;
+    }
 
-    // output info to dynamic strings
-    if(get_label_result == SD_OK && get_free_result == SD_OK) {
-        const char* fs_type = "";
+    _fs_unlock(&sd_app->info);
 
-        switch(fs->fs_type) {
-        case(FS_FAT12):
-            fs_type = "FAT12";
-            break;
-        case(FS_FAT16):
-            fs_type = "FAT16";
-            break;
-        case(FS_FAT32):
-            fs_type = "FAT32";
-            break;
-        case(FS_EXFAT):
-            fs_type = "EXFAT";
-            break;
-        default:
-            fs_type = "UNKNOWN";
-            break;
-        }
+    return result;
+}
 
-        snprintf(str_buffer, str_buffer_size, "Label: %s\r\n", volume_label);
-        printf(str_buffer);
+DialogEx* alloc_and_attach_dialog(SdApp* sd_app) {
+    DialogEx* dialog = dialog_ex_alloc();
+    dialog_ex_set_context(dialog, sd_app);
+    dialog_ex_set_result_callback(dialog, app_view_dialog_callback);
+    view_holder_set_view(sd_app->view_holder, dialog_ex_get_view(dialog));
+    view_holder_set_free_callback(sd_app->view_holder, (FreeCallback)dialog_ex_free, dialog);
+    return dialog;
+}
 
-        snprintf(str_buffer, str_buffer_size, "%s, S/N: %lu\r\n", fs_type, serial_num);
-        printf(str_buffer);
+FileSelect* alloc_and_attach_file_select(SdApp* sd_app) {
+    FileSelect* file_select = file_select_alloc();
+    file_select_set_callback(file_select, app_view_file_select_callback, sd_app);
+    view_holder_set_view(sd_app->view_holder, file_select_get_view(file_select));
+    view_holder_set_free_callback(
+        sd_app->view_holder, (FreeCallback)file_select_free, file_select);
+    return file_select;
+}
 
-        snprintf(str_buffer, str_buffer_size, "Cluster: %d sectors\r\n", fs->csize);
-        printf(str_buffer);
+void free_view_holder(SdApp* sd_app) {
+    _fs_lock(&sd_app->info);
 
-        snprintf(str_buffer, str_buffer_size, "Sector: %d bytes\r\n", sector_size);
-        printf(str_buffer);
+    if(sd_app->view_holder) {
+        view_holder_free(sd_app->view_holder);
+        sd_app->view_holder = NULL;
+    }
 
-        snprintf(
-            str_buffer, str_buffer_size, "%lu KB total\r\n", total_sectors / 1024 * sector_size);
-        printf(str_buffer);
+    _fs_unlock(&sd_app->info);
+}
 
-        snprintf(
-            str_buffer, str_buffer_size, "%lu KB free\r\n", free_sectors / 1024 * sector_size);
-        printf(str_buffer);
-    } else {
-        printf("SD status error: ");
-        snprintf(
-            str_buffer,
-            str_buffer_size,
-            "%s\r\n",
-            fs_error_get_internal_desc(_fs_status(&sd_app->info)));
-        printf(str_buffer);
-
-        printf("Label error: ");
-        snprintf(
-            str_buffer, str_buffer_size, "%s\r\n", fs_error_get_internal_desc(get_label_result));
-        printf(str_buffer);
-
-        printf("Get free error: ");
-        snprintf(
-            str_buffer, str_buffer_size, "%s\r\n", fs_error_get_internal_desc(get_free_result));
-        printf(str_buffer);
-    }
+void app_reset_state(SdApp* sd_app) {
+    view_holder_stop(sd_app->view_holder);
+    free_view_holder(sd_app);
+    string_set_str(sd_app->text_holder, "");
+    sd_app->sd_app_state = SdAppStateBackground;
 }
 
+/******************* Main app *******************/
+
 int32_t sd_filesystem(void* p) {
     SdApp* sd_app = sd_app_alloc();
     FS_Api* fs_api = fs_api_alloc();
@@ -572,7 +573,6 @@ int32_t sd_filesystem(void* p) {
     Cli* cli = furi_record_open("cli");
     ValueMutex* menu_vm = furi_record_open("menu");
 
-    gui_add_view_port(gui, sd_app->view_port, GuiLayerFullscreen);
     gui_add_view_port(gui, sd_app->icon.view_port, GuiLayerStatusBarLeft);
 
     cli_add_command(cli, "sd_status", cli_sd_status, sd_app);
@@ -603,6 +603,7 @@ int32_t sd_filesystem(void* p) {
 
     // add api record
     furi_record_create("sdcard", fs_api);
+    furi_record_create("sdcard-ex", &sd_app->sd_card_api);
 
     // sd card cycle
     bool sd_was_present = true;
@@ -648,7 +649,184 @@ int32_t sd_filesystem(void* p) {
             }
         }
 
-        delay(1000);
+        SdAppEvent event;
+        osStatus_t event_status = osMessageQueueGet(sd_app->event_queue, &event, NULL, 1000);
+
+        const uint8_t y_1_line = 32;
+        const uint8_t y_2_line = 32;
+        const uint8_t y_4_line = 26;
+
+        if(event_status == osOK) {
+            switch(event.type) {
+            case SdAppEventTypeOK:
+                switch(sd_app->sd_app_state) {
+                case SdAppStateFormat: {
+                    DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder);
+                    dialog_ex_set_left_button_text(dialog, NULL);
+                    dialog_ex_set_right_button_text(dialog, NULL);
+                    dialog_ex_set_header(
+                        dialog, "Formatting...", 64, y_1_line, AlignCenter, AlignCenter);
+                    dialog_ex_set_text(dialog, NULL, 0, 0, AlignCenter, AlignCenter);
+                    sd_app->sd_app_state = SdAppStateFormatInProgress;
+                    delay(100);
+                    app_sd_format_internal(sd_app);
+                    app_sd_notify_success();
+                    dialog_ex_set_left_button_text(dialog, "Back");
+                    dialog_ex_set_header(
+                        dialog, "SD card formatted", 64, 10, AlignCenter, AlignCenter);
+                    dialog_ex_set_text(
+                        dialog, "Press back to return", 64, y_1_line, AlignCenter, AlignCenter);
+                    sd_app->sd_app_state = SdAppStateFormatCompleted;
+                }; break;
+                case SdAppStateEject: {
+                    DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder);
+                    dialog_ex_set_right_button_text(dialog, NULL);
+                    dialog_ex_set_header(
+                        dialog, "SD card ejected", 64, 10, AlignCenter, AlignCenter);
+                    dialog_ex_set_text(
+                        dialog,
+                        "Now the SD card\ncan be removed.",
+                        64,
+                        y_2_line,
+                        AlignCenter,
+                        AlignCenter);
+                    sd_app->sd_app_state = SdAppStateEjected;
+                    app_sd_unmount_card(sd_app);
+                    app_sd_notify_eject();
+                }; break;
+                case SdAppStateFileSelect: {
+                    SdAppFileSelectResultEvent retval = {.result = true};
+                    furi_check(
+                        osMessageQueuePut(event.result_receiver, &retval, 0, osWaitForever) ==
+                        osOK);
+                    app_reset_state(sd_app);
+                }; break;
+                default:
+                    break;
+                }
+                break;
+            case SdAppEventTypeBack:
+                switch(sd_app->sd_app_state) {
+                case SdAppStateFormatInProgress:
+                    break;
+                case SdAppStateFileSelect: {
+                    SdAppFileSelectResultEvent retval = {.result = false};
+                    furi_check(
+                        osMessageQueuePut(event.result_receiver, &retval, 0, osWaitForever) ==
+                        osOK);
+                    app_reset_state(sd_app);
+                }; break;
+
+                default:
+                    app_reset_state(sd_app);
+                    break;
+                }
+                break;
+            case SdAppEventTypeFormat:
+                if(try_to_alloc_view_holder(sd_app, gui)) {
+                    DialogEx* dialog = alloc_and_attach_dialog(sd_app);
+                    dialog_ex_set_left_button_text(dialog, "Back");
+                    dialog_ex_set_right_button_text(dialog, "Format");
+                    dialog_ex_set_header(
+                        dialog, "Format SD card?", 64, 10, AlignCenter, AlignCenter);
+                    dialog_ex_set_text(
+                        dialog, "All data will be lost.", 64, y_1_line, AlignCenter, AlignCenter);
+                    view_holder_start(sd_app->view_holder);
+                    sd_app->sd_app_state = SdAppStateFormat;
+                }
+                break;
+            case SdAppEventTypeInfo:
+                if(try_to_alloc_view_holder(sd_app, gui)) {
+                    DialogEx* dialog = alloc_and_attach_dialog(sd_app);
+                    dialog_ex_set_left_button_text(dialog, "Back");
+
+                    SDInfo sd_info;
+                    get_sd_info(sd_app, &sd_info);
+
+                    if(sd_info.error == SD_OK) {
+                        string_printf(
+                            sd_app->text_holder,
+                            "Label: %s\nType: %s\n%lu KB total\n%lu KB free",
+                            sd_info.label,
+                            get_fs_type_text(sd_info.fs_type),
+                            sd_info.kb_total,
+                            sd_info.kb_free);
+                        dialog_ex_set_text(
+                            dialog,
+                            string_get_cstr(sd_app->text_holder),
+                            4,
+                            y_4_line,
+                            AlignLeft,
+                            AlignCenter);
+                        view_holder_start(sd_app->view_holder);
+                    } else {
+                        string_printf(
+                            sd_app->text_holder,
+                            "SD status: %s\n SD info: %s",
+                            fs_error_get_internal_desc(_fs_status(&sd_app->info)),
+                            fs_error_get_internal_desc(sd_info.error));
+                        dialog_ex_set_header(dialog, "Error", 64, 10, AlignCenter, AlignCenter);
+                        dialog_ex_set_text(
+                            dialog,
+                            string_get_cstr(sd_app->text_holder),
+                            64,
+                            y_2_line,
+                            AlignCenter,
+                            AlignCenter);
+                        view_holder_start(sd_app->view_holder);
+                    }
+
+                    sd_app->sd_app_state = SdAppStateInfo;
+                }
+                break;
+            case SdAppEventTypeEject:
+                if(try_to_alloc_view_holder(sd_app, gui)) {
+                    DialogEx* dialog = alloc_and_attach_dialog(sd_app);
+                    dialog_ex_set_left_button_text(dialog, "Back");
+                    dialog_ex_set_right_button_text(dialog, "Eject");
+                    dialog_ex_set_header(
+                        dialog, "Eject SD card?", 64, 10, AlignCenter, AlignCenter);
+                    dialog_ex_set_text(
+                        dialog,
+                        "SD card will be\nunavailable",
+                        64,
+                        y_2_line,
+                        AlignCenter,
+                        AlignCenter);
+                    view_holder_start(sd_app->view_holder);
+                    sd_app->sd_app_state = SdAppStateEject;
+                }
+                break;
+            case SdAppEventTypeFileSelect:
+                if(!app_sd_make_path(event.payload.file_select_data.path)) {
+                }
+
+                if(try_to_alloc_view_holder(sd_app, gui)) {
+                    sd_app->result_receiver = event.result_receiver;
+                    FileSelect* file_select = alloc_and_attach_file_select(sd_app);
+                    file_select_set_api(file_select, fs_api);
+                    file_select_set_filter(
+                        file_select,
+                        event.payload.file_select_data.path,
+                        event.payload.file_select_data.extension);
+                    file_select_set_result_buffer(
+                        file_select,
+                        event.payload.file_select_data.result,
+                        event.payload.file_select_data.result_size);
+                    if(!file_select_init(file_select)) {
+                    }
+                    sd_app->sd_app_state = SdAppStateFileSelect;
+                } else {
+                    SdAppFileSelectResultEvent retval = {.result = false};
+                    furi_check(
+                        osMessageQueuePut(event.result_receiver, &retval, 0, osWaitForever) ==
+                        osOK);
+                }
+                break;
+            case SdAppEventTypeCheckError:
+                break;
+            }
+        }
     }
 
     return 0;

+ 23 - 4
applications/sd-filesystem/sd-filesystem.h

@@ -4,6 +4,9 @@
 #include <api-hal.h>
 #include <gui/gui.h>
 #include <input/input.h>
+#include <m-string.h>
+#include "sd-card-api.h"
+#include "view_holder.h"
 
 #define SD_FS_MAX_FILES _FS_LOCK
 #define SD_STATE_LINES_COUNT 6
@@ -77,14 +80,30 @@ typedef struct {
     FATFS fat_fs;
 } SdFsInfo;
 
-typedef struct {
+typedef enum {
+    SdAppStateBackground,
+    SdAppStateFormat,
+    SdAppStateFormatInProgress,
+    SdAppStateFormatCompleted,
+    SdAppStateInfo,
+    SdAppStateEject,
+    SdAppStateEjected,
+    SdAppStateFileSelect,
+} SdAppState;
+
+struct SdApp {
     SdFsInfo info;
     SdFsIcon icon;
 
-    ViewPort* view_port;
-    const char* line[SD_STATE_LINES_COUNT];
+    SdCard_Api sd_card_api;
+    SdAppState sd_app_state;
+
+    ViewHolder* view_holder;
+    osMessageQueueId_t result_receiver;
+
     osMessageQueueId_t event_queue;
-} SdApp;
+    string_t text_holder;
+};
 
 /* core api fns */
 bool _fs_init(SdFsInfo* _fs_info);

+ 110 - 0
applications/sd-filesystem/view_holder.c

@@ -0,0 +1,110 @@
+#include "view_holder.h"
+#include <gui/view_i.h>
+
+struct ViewHolder {
+    View* view;
+    ViewPort* view_port;
+    Gui* gui;
+
+    FreeCallback free_callback;
+    void* free_context;
+
+    BackCallback back_callback;
+    void* back_context;
+};
+
+static void view_holder_draw_callback(Canvas* canvas, void* context);
+static void view_holder_input_callback(InputEvent* event, void* context);
+
+ViewHolder* view_holder_alloc() {
+    ViewHolder* view_holder = furi_alloc(sizeof(ViewHolder));
+
+    view_holder->view_port = view_port_alloc();
+    view_port_draw_callback_set(view_holder->view_port, view_holder_draw_callback, view_holder);
+    view_port_input_callback_set(view_holder->view_port, view_holder_input_callback, view_holder);
+    view_port_enabled_set(view_holder->view_port, false);
+
+    return view_holder;
+}
+
+void view_holder_free(ViewHolder* view_holder) {
+    furi_assert(view_holder);
+
+    if(view_holder->gui) {
+        gui_remove_view_port(view_holder->gui, view_holder->view_port);
+    }
+
+    view_port_free(view_holder->view_port);
+
+    if(view_holder->free_callback) {
+        view_holder->free_callback(view_holder->free_context);
+    }
+
+    free(view_holder);
+}
+
+void view_holder_set_view(ViewHolder* view_holder, View* view) {
+    furi_assert(view_holder);
+    view_holder->view = view;
+}
+
+void view_holder_set_free_callback(
+    ViewHolder* view_holder,
+    FreeCallback free_callback,
+    void* free_context) {
+    furi_assert(view_holder);
+    view_holder->free_callback = free_callback;
+    view_holder->free_context = free_context;
+}
+
+void* view_holder_get_free_context(ViewHolder* view_holder) {
+    return view_holder->free_context;
+}
+
+void view_holder_set_back_callback(
+    ViewHolder* view_holder,
+    BackCallback back_callback,
+    void* back_context) {
+    furi_assert(view_holder);
+    view_holder->back_callback = back_callback;
+    view_holder->back_context = back_context;
+}
+
+void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui) {
+    furi_assert(gui);
+    furi_assert(view_holder);
+    view_holder->gui = gui;
+    gui_add_view_port(gui, view_holder->view_port, GuiLayerFullscreen);
+}
+
+void view_holder_start(ViewHolder* view_holder) {
+    view_port_enabled_set(view_holder->view_port, true);
+}
+
+void view_holder_stop(ViewHolder* view_holder) {
+    view_port_enabled_set(view_holder->view_port, false);
+}
+
+static void view_holder_draw_callback(Canvas* canvas, void* context) {
+    ViewHolder* view_holder = context;
+    if(view_holder->view) {
+        view_draw(view_holder->view, canvas);
+    }
+}
+
+static void view_holder_input_callback(InputEvent* event, void* context) {
+    ViewHolder* view_holder = context;
+    bool is_consumed = false;
+
+    if(view_holder->view) {
+        is_consumed = view_input(view_holder->view, event);
+    }
+
+    if(!is_consumed && event->type == InputTypeShort) {
+        if(event->key == InputKeyBack) {
+            if(view_holder->back_callback) {
+                view_holder->back_callback(view_holder->back_context);
+            }
+        }
+    }
+}

+ 92 - 0
applications/sd-filesystem/view_holder.h

@@ -0,0 +1,92 @@
+#pragma once
+
+#include <gui/view.h>
+#include <gui/gui.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct ViewHolder ViewHolder;
+
+/** 
+ * @brief Free callback type
+ */
+typedef void (*FreeCallback)(void* free_context);
+
+/** 
+ * @brief Back callback type
+ * @warning comes from GUI thread
+ */
+typedef void (*BackCallback)(void* back_context);
+
+/**
+ * @brief Allocate ViewHolder
+ * @return pointer to ViewHolder instance
+ */
+ViewHolder* view_holder_alloc();
+
+/**
+ * @brief Free ViewHolder and call Free callback
+ * @param view_holder pointer to ViewHolder
+ */
+void view_holder_free(ViewHolder* view_holder);
+
+/**
+ * @brief Set view for ViewHolder
+ * 
+ * @param view_holder ViewHolder instance
+ * @param view View instance
+ */
+void view_holder_set_view(ViewHolder* view_holder, View* view);
+
+/**
+ * @brief Set Free callback
+ * 
+ * @param view_holder ViewHolder instance
+ * @param free_callback callback pointer
+ * @param free_context callback context
+ */
+void view_holder_set_free_callback(
+    ViewHolder* view_holder,
+    FreeCallback free_callback,
+    void* free_context);
+
+/**
+ * @brief Free callback context getter. Useful if your Free callback is a module destructor, so you can get an instance of the module using this method.
+ * 
+ * @param view_holder ViewHolder instance
+ * @return void* free callback context
+ */
+void* view_holder_get_free_context(ViewHolder* view_holder);
+
+void view_holder_set_back_callback(
+    ViewHolder* view_holder,
+    BackCallback back_callback,
+    void* back_context);
+
+/**
+ * @brief Attach ViewHolder to GUI
+ * 
+ * @param view_holder ViewHolder instance
+ * @param gui GUI instance to attach to
+ */
+void view_holder_attach_to_gui(ViewHolder* view_holder, Gui* gui);
+
+/**
+ * @brief Enable view processing
+ * 
+ * @param view_holder 
+ */
+void view_holder_start(ViewHolder* view_holder);
+
+/**
+ * @brief Disable view processing
+ * 
+ * @param view_holder 
+ */
+void view_holder_stop(ViewHolder* view_holder);
+
+#ifdef __cplusplus
+}
+#endif

BIN
assets/icons/SDCard/SDError_43x35.png


BIN
assets/icons/SDCard/SDQuestion_35x43.png


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

@@ -0,0 +1,22 @@
+#pragma once
+#include <furi.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct SdApp SdApp;
+
+typedef struct {
+    SdApp* context;
+    bool (*file_select)(
+        SdApp* context,
+        const char* path,
+        const char* extension,
+        char* result,
+        uint8_t result_size);
+} SdCard_Api;
+
+#ifdef __cplusplus
+}
+#endif