Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/flipperzero-firmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikolay Minaylov <nm29719@gmail.com>2022-07-14 19:44:34 +0300
committerGitHub <noreply@github.com>2022-07-14 19:44:34 +0300
commit2caa5c3064b88d529a8c2e011c79f27799a9b1f2 (patch)
tree2bb30073ee2d40e502627ddfc6a8c9344d7ab658
parentfd498bdfcfad7806c37674622e32244d659a16ea (diff)
[FL-2633] Move files from /int to /ext on SD mount #1384
Co-authored-by: あく <alleteam@gmail.com>
-rw-r--r--applications/storage/storage.h8
-rw-r--r--applications/storage/storage_external_api.c128
-rw-r--r--applications/storage_move_to_sd/application.fam19
-rw-r--r--applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.c30
-rw-r--r--applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.h29
-rw-r--r--applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h2
-rw-r--r--applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c70
-rw-r--r--applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c32
-rw-r--r--applications/storage_move_to_sd/storage_move_to_sd.c177
-rw-r--r--applications/storage_move_to_sd/storage_move_to_sd.h49
-rw-r--r--fbt_options.py1
11 files changed, 545 insertions, 0 deletions
diff --git a/applications/storage/storage.h b/applications/storage/storage.h
index 1e6ced23..dcb8deee 100644
--- a/applications/storage/storage.h
+++ b/applications/storage/storage.h
@@ -190,6 +190,14 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha
*/
FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path);
+/** Copy one folder contents into another with rename of all conflicting files
+ * @param app pointer to the api
+ * @param old_path old path
+ * @param new_path new path
+ * @return FS_Error operation result
+ */
+FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path);
+
/** Creates a directory
* @param app pointer to the api
* @param path directory path
diff --git a/applications/storage/storage_external_api.c b/applications/storage/storage_external_api.c
index 77bb6550..dc29faa6 100644
--- a/applications/storage/storage_external_api.c
+++ b/applications/storage/storage_external_api.c
@@ -1,3 +1,4 @@
+#include "furi/log.h"
#include <furi/record.h>
#include <m-string.h>
#include "storage.h"
@@ -5,8 +6,10 @@
#include "storage_message.h"
#include <toolbox/stream/file_stream.h>
#include <toolbox/dir_walk.h>
+#include "toolbox/path.h"
#define MAX_NAME_LENGTH 256
+#define MAX_EXT_LEN 16
#define TAG "StorageAPI"
@@ -436,6 +439,131 @@ FS_Error storage_common_copy(Storage* storage, const char* old_path, const char*
return error;
}
+static FS_Error
+ storage_merge_recursive(Storage* storage, const char* old_path, const char* new_path) {
+ FS_Error error = storage_common_mkdir(storage, new_path);
+ DirWalk* dir_walk = dir_walk_alloc(storage);
+ string_t path;
+ string_t tmp_new_path;
+ string_t tmp_old_path;
+ FileInfo fileinfo;
+ string_init(path);
+ string_init(tmp_new_path);
+ string_init(tmp_old_path);
+
+ do {
+ if((error != FSE_OK) && (error != FSE_EXIST)) break;
+
+ if(!dir_walk_open(dir_walk, old_path)) {
+ error = dir_walk_get_error(dir_walk);
+ break;
+ }
+
+ while(1) {
+ DirWalkResult res = dir_walk_read(dir_walk, path, &fileinfo);
+
+ if(res == DirWalkError) {
+ error = dir_walk_get_error(dir_walk);
+ break;
+ } else if(res == DirWalkLast) {
+ break;
+ } else {
+ string_set(tmp_old_path, path);
+ string_right(path, strlen(old_path));
+ string_printf(tmp_new_path, "%s%s", new_path, string_get_cstr(path));
+
+ if(fileinfo.flags & FSF_DIRECTORY) {
+ if(storage_common_stat(storage, string_get_cstr(tmp_new_path), &fileinfo) ==
+ FSE_OK) {
+ if(fileinfo.flags & FSF_DIRECTORY) {
+ error = storage_common_mkdir(storage, string_get_cstr(tmp_new_path));
+ }
+ }
+ } else {
+ error = storage_common_merge(
+ storage, string_get_cstr(tmp_old_path), string_get_cstr(tmp_new_path));
+ }
+
+ if(error != FSE_OK) break;
+ }
+ }
+
+ } while(false);
+
+ string_clear(tmp_new_path);
+ string_clear(tmp_old_path);
+ string_clear(path);
+ dir_walk_free(dir_walk);
+ return error;
+}
+
+FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path) {
+ FS_Error error;
+ const char* new_path_tmp;
+ string_t new_path_next;
+ string_init(new_path_next);
+
+ FileInfo fileinfo;
+ error = storage_common_stat(storage, old_path, &fileinfo);
+
+ if(error == FSE_OK) {
+ if(fileinfo.flags & FSF_DIRECTORY) {
+ error = storage_merge_recursive(storage, old_path, new_path);
+ } else {
+ error = storage_common_stat(storage, new_path, &fileinfo);
+ if(error == FSE_OK) {
+ string_set_str(new_path_next, new_path);
+ string_t dir_path;
+ string_t filename;
+ char extension[MAX_EXT_LEN];
+
+ string_init(dir_path);
+ string_init(filename);
+
+ path_extract_filename(new_path_next, filename, true);
+ path_extract_dirname(new_path, dir_path);
+ path_extract_extension(new_path_next, extension, MAX_EXT_LEN);
+
+ storage_get_next_filename(
+ storage,
+ string_get_cstr(dir_path),
+ string_get_cstr(filename),
+ extension,
+ new_path_next,
+ 255);
+ string_cat_printf(dir_path, "/%s%s", string_get_cstr(new_path_next), extension);
+ string_set(new_path_next, dir_path);
+
+ string_clear(dir_path);
+ string_clear(filename);
+ new_path_tmp = string_get_cstr(new_path_next);
+ } else {
+ new_path_tmp = new_path;
+ }
+ 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_tmp, 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);
+ }
+ }
+
+ string_clear(new_path_next);
+
+ return error;
+}
+
FS_Error storage_common_mkdir(Storage* storage, const char* path) {
S_API_PROLOGUE;
S_API_DATA_PATH;
diff --git a/applications/storage_move_to_sd/application.fam b/applications/storage_move_to_sd/application.fam
new file mode 100644
index 00000000..60a6d987
--- /dev/null
+++ b/applications/storage_move_to_sd/application.fam
@@ -0,0 +1,19 @@
+App(
+ appid="storage_move_to_sd",
+ name="StorageMoveToSd",
+ apptype=FlipperAppType.SYSTEM,
+ entry_point="storage_move_to_sd_app",
+ requires=["gui","storage"],
+ provides=["storage_move_to_sd_start"],
+ stack_size=2 * 1024,
+ order=30,
+)
+
+App(
+ appid="storage_move_to_sd_start",
+ apptype=FlipperAppType.STARTUP,
+ entry_point="storage_move_to_sd_start",
+ requires=["storage"],
+ order=120,
+)
+
diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.c b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.c
new file mode 100644
index 00000000..011bf47d
--- /dev/null
+++ b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.c
@@ -0,0 +1,30 @@
+#include "storage_move_to_sd_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const storage_move_to_sd_on_enter_handlers[])(void*) = {
+#include "storage_move_to_sd_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const storage_move_to_sd_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "storage_move_to_sd_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const storage_move_to_sd_on_exit_handlers[])(void* context) = {
+#include "storage_move_to_sd_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers storage_move_to_sd_scene_handlers = {
+ .on_enter_handlers = storage_move_to_sd_on_enter_handlers,
+ .on_event_handlers = storage_move_to_sd_on_event_handlers,
+ .on_exit_handlers = storage_move_to_sd_on_exit_handlers,
+ .scene_num = StorageMoveToSdSceneNum,
+};
diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.h b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.h
new file mode 100644
index 00000000..bdeb4a84
--- /dev/null
+++ b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) StorageMoveToSd##id,
+typedef enum {
+#include "storage_move_to_sd_scene_config.h"
+ StorageMoveToSdSceneNum,
+} StorageMoveToSdScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers storage_move_to_sd_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "storage_move_to_sd_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+ bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "storage_move_to_sd_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "storage_move_to_sd_scene_config.h"
+#undef ADD_SCENE
diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h
new file mode 100644
index 00000000..1d7b2d25
--- /dev/null
+++ b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_config.h
@@ -0,0 +1,2 @@
+ADD_SCENE(storage_move_to_sd, confirm, Confirm)
+ADD_SCENE(storage_move_to_sd, progress, Progress)
diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c
new file mode 100644
index 00000000..71ce5a7c
--- /dev/null
+++ b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_confirm.c
@@ -0,0 +1,70 @@
+#include "../storage_move_to_sd.h"
+#include "gui/canvas.h"
+#include "gui/modules/widget_elements/widget_element_i.h"
+#include "storage/storage.h"
+
+static void storage_move_to_sd_scene_confirm_widget_callback(
+ GuiButtonType result,
+ InputType type,
+ void* context) {
+ StorageMoveToSd* app = context;
+ furi_assert(app);
+ if(type == InputTypeShort) {
+ if(result == GuiButtonTypeRight) {
+ view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventConfirm);
+ } else if(result == GuiButtonTypeLeft) {
+ view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit);
+ }
+ }
+}
+
+void storage_move_to_sd_scene_confirm_on_enter(void* context) {
+ StorageMoveToSd* app = context;
+
+ widget_add_button_element(
+ app->widget,
+ GuiButtonTypeLeft,
+ "Cancel",
+ storage_move_to_sd_scene_confirm_widget_callback,
+ app);
+ widget_add_button_element(
+ app->widget,
+ GuiButtonTypeRight,
+ "Confirm",
+ storage_move_to_sd_scene_confirm_widget_callback,
+ app);
+
+ widget_add_string_element(
+ app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "SD card inserted");
+ widget_add_string_multiline_element(
+ app->widget,
+ 64,
+ 32,
+ AlignCenter,
+ AlignCenter,
+ FontSecondary,
+ "Move data from\ninternal storage to SD card?");
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, StorageMoveToSdViewWidget);
+}
+
+bool storage_move_to_sd_scene_confirm_on_event(void* context, SceneManagerEvent event) {
+ StorageMoveToSd* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ if(event.event == MoveToSdCustomEventConfirm) {
+ scene_manager_next_scene(app->scene_manager, StorageMoveToSdProgress);
+ consumed = true;
+ } else if(event.event == MoveToSdCustomEventExit) {
+ view_dispatcher_stop(app->view_dispatcher);
+ }
+ }
+
+ return consumed;
+}
+
+void storage_move_to_sd_scene_confirm_on_exit(void* context) {
+ StorageMoveToSd* app = context;
+ widget_reset(app->widget);
+}
diff --git a/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c
new file mode 100644
index 00000000..d44b25c3
--- /dev/null
+++ b/applications/storage_move_to_sd/scenes/storage_move_to_sd_scene_progress.c
@@ -0,0 +1,32 @@
+#include "../storage_move_to_sd.h"
+#include "cmsis_os2.h"
+
+void storage_move_to_sd_scene_progress_on_enter(void* context) {
+ StorageMoveToSd* app = context;
+
+ widget_add_string_element(
+ app->widget, 64, 10, AlignCenter, AlignCenter, FontPrimary, "Moving...");
+
+ view_dispatcher_switch_to_view(app->view_dispatcher, StorageMoveToSdViewWidget);
+
+ storage_move_to_sd_perform();
+ view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit);
+}
+
+bool storage_move_to_sd_scene_progress_on_event(void* context, SceneManagerEvent event) {
+ StorageMoveToSd* app = context;
+ bool consumed = false;
+
+ if(event.type == SceneManagerEventTypeCustom) {
+ view_dispatcher_stop(app->view_dispatcher);
+ } else if(event.type == SceneManagerEventTypeBack) {
+ consumed = true;
+ }
+
+ return consumed;
+}
+
+void storage_move_to_sd_scene_progress_on_exit(void* context) {
+ StorageMoveToSd* app = context;
+ widget_reset(app->widget);
+}
diff --git a/applications/storage_move_to_sd/storage_move_to_sd.c b/applications/storage_move_to_sd/storage_move_to_sd.c
new file mode 100644
index 00000000..f703321d
--- /dev/null
+++ b/applications/storage_move_to_sd/storage_move_to_sd.c
@@ -0,0 +1,177 @@
+#include "storage_move_to_sd.h"
+#include "cmsis_os2.h"
+#include "furi/common_defines.h"
+#include "furi/log.h"
+#include "loader/loader.h"
+#include "m-string.h"
+#include <stdint.h>
+
+#define TAG "MoveToSd"
+
+#define MOVE_SRC "/int"
+#define MOVE_DST "/ext"
+
+static const char* app_dirs[] = {
+ "subghz",
+ "lfrfid",
+ "nfc",
+ "infrared",
+ "ibutton",
+ "badusb",
+};
+
+bool storage_move_to_sd_perform(void) {
+ Storage* storage = furi_record_open("storage");
+ string_t path_src;
+ string_t path_dst;
+ string_init(path_src);
+ string_init(path_dst);
+
+ for(uint32_t i = 0; i < COUNT_OF(app_dirs); i++) {
+ string_printf(path_src, "%s/%s", MOVE_SRC, app_dirs[i]);
+ string_printf(path_dst, "%s/%s", MOVE_DST, app_dirs[i]);
+ storage_common_merge(storage, string_get_cstr(path_src), string_get_cstr(path_dst));
+ storage_simply_remove_recursive(storage, string_get_cstr(path_src));
+ }
+
+ string_clear(path_src);
+ string_clear(path_dst);
+
+ furi_record_close("storage");
+
+ return false;
+}
+
+static bool storage_move_to_sd_check(void) {
+ Storage* storage = furi_record_open("storage");
+
+ FileInfo file_info;
+ bool state = false;
+ string_t path;
+ string_init(path);
+
+ for(uint32_t i = 0; i < COUNT_OF(app_dirs); i++) {
+ string_printf(path, "%s/%s", MOVE_SRC, app_dirs[i]);
+ if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) {
+ if((file_info.flags & FSF_DIRECTORY) != 0) {
+ state = true;
+ break;
+ }
+ }
+ }
+
+ string_clear(path);
+
+ furi_record_close("storage");
+
+ return state;
+}
+
+static bool storage_move_to_sd_custom_event_callback(void* context, uint32_t event) {
+ furi_assert(context);
+ StorageMoveToSd* app = context;
+ return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool storage_move_to_sd_back_event_callback(void* context) {
+ furi_assert(context);
+ StorageMoveToSd* app = context;
+ return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void storage_move_to_sd_unmount_callback(const void* message, void* context) {
+ StorageMoveToSd* app = context;
+ furi_assert(app);
+ const StorageEvent* storage_event = message;
+
+ if((storage_event->type == StorageEventTypeCardUnmount) ||
+ (storage_event->type == StorageEventTypeCardMountError)) {
+ view_dispatcher_send_custom_event(app->view_dispatcher, MoveToSdCustomEventExit);
+ }
+}
+
+static StorageMoveToSd* storage_move_to_sd_alloc() {
+ StorageMoveToSd* app = malloc(sizeof(StorageMoveToSd));
+
+ app->gui = furi_record_open("gui");
+ app->notifications = furi_record_open("notification");
+
+ app->view_dispatcher = view_dispatcher_alloc();
+ app->scene_manager = scene_manager_alloc(&storage_move_to_sd_scene_handlers, app);
+
+ view_dispatcher_enable_queue(app->view_dispatcher);
+ view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+
+ view_dispatcher_set_custom_event_callback(
+ app->view_dispatcher, storage_move_to_sd_custom_event_callback);
+ view_dispatcher_set_navigation_event_callback(
+ app->view_dispatcher, storage_move_to_sd_back_event_callback);
+
+ view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+ app->widget = widget_alloc();
+ view_dispatcher_add_view(
+ app->view_dispatcher, StorageMoveToSdViewWidget, widget_get_view(app->widget));
+
+ scene_manager_next_scene(app->scene_manager, StorageMoveToSdConfirm);
+
+ Storage* storage = furi_record_open("storage");
+ app->sub = furi_pubsub_subscribe(
+ storage_get_pubsub(storage), storage_move_to_sd_unmount_callback, app);
+ furi_record_close("storage");
+
+ return app;
+}
+
+static void storage_move_to_sd_free(StorageMoveToSd* app) {
+ Storage* storage = furi_record_open("storage");
+ furi_pubsub_unsubscribe(storage_get_pubsub(storage), app->sub);
+ furi_record_close("storage");
+ furi_record_close("notification");
+
+ view_dispatcher_remove_view(app->view_dispatcher, StorageMoveToSdViewWidget);
+ widget_free(app->widget);
+ view_dispatcher_free(app->view_dispatcher);
+ scene_manager_free(app->scene_manager);
+
+ furi_record_close("gui");
+
+ free(app);
+}
+
+int32_t storage_move_to_sd_app(void* p) {
+ UNUSED(p);
+
+ if(storage_move_to_sd_check()) {
+ StorageMoveToSd* app = storage_move_to_sd_alloc();
+ notification_message(app->notifications, &sequence_display_backlight_on);
+ view_dispatcher_run(app->view_dispatcher);
+ storage_move_to_sd_free(app);
+ } else {
+ FURI_LOG_I(TAG, "Nothing to move");
+ }
+
+ return 0;
+}
+
+static void storage_move_to_sd_mount_callback(const void* message, void* context) {
+ UNUSED(context);
+
+ const StorageEvent* storage_event = message;
+
+ if(storage_event->type == StorageEventTypeCardMount) {
+ Loader* loader = furi_record_open("loader");
+ loader_start(loader, "StorageMoveToSd", NULL);
+ furi_record_close("loader");
+ }
+}
+
+int32_t storage_move_to_sd_start(void* p) {
+ UNUSED(p);
+ Storage* storage = furi_record_open("storage");
+
+ furi_pubsub_subscribe(storage_get_pubsub(storage), storage_move_to_sd_mount_callback, NULL);
+
+ furi_record_close("storage");
+ return 0;
+}
diff --git a/applications/storage_move_to_sd/storage_move_to_sd.h b/applications/storage_move_to_sd/storage_move_to_sd.h
new file mode 100644
index 00000000..dc1d669b
--- /dev/null
+++ b/applications/storage_move_to_sd/storage_move_to_sd.h
@@ -0,0 +1,49 @@
+#pragma once
+#include "gui/modules/widget_elements/widget_element_i.h"
+#include <furi.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <notification/notification_messages.h>
+
+#include <gui/modules/widget.h>
+#include <gui/modules/popup.h>
+
+#include <storage/storage.h>
+#include <storage/storage_sd_api.h>
+
+#include "scenes/storage_move_to_sd_scene.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ MoveToSdCustomEventExit,
+ MoveToSdCustomEventConfirm,
+} MoveToSdCustomEvent;
+
+typedef struct {
+ // records
+ Gui* gui;
+ Widget* widget;
+ NotificationApp* notifications;
+
+ // view managment
+ SceneManager* scene_manager;
+ ViewDispatcher* view_dispatcher;
+
+ FuriPubSubSubscription* sub;
+
+} StorageMoveToSd;
+
+typedef enum {
+ StorageMoveToSdViewWidget,
+} StorageMoveToSdView;
+
+bool storage_move_to_sd_perform(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/fbt_options.py b/fbt_options.py
index fb06cecd..fb2a0d36 100644
--- a/fbt_options.py
+++ b/fbt_options.py
@@ -67,6 +67,7 @@ FIRMWARE_APPS = {
# Apps
"basic_apps",
"updater_app",
+ "storage_move_to_sd",
"archive",
# Settings
"passport",