scene_action_settings.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. #include <furi.h>
  2. #include <gui/view_dispatcher.h>
  3. #include <gui/scene_manager.h>
  4. #include <gui/modules/submenu.h>
  5. #include <lib/toolbox/path.h>
  6. #include "quac.h"
  7. #include "scenes.h"
  8. #include "scene_action_settings.h"
  9. #include "../actions/action.h"
  10. #include "quac_icons.h"
  11. // Define different settings per Action
  12. typedef enum {
  13. ActionSettingsRename, // Rename file or folder
  14. ActionSettingsDelete, // Delete file or folder on SDcard
  15. ActionSettingsImport, // Copy a remote file into "current" folder
  16. ActionSettingsImportLink, // Create a link to a remote file into "current" folder
  17. ActionSettingsCreateGroup, // Create new empty folder in "current" folder
  18. ActionSettingsCreatePlaylist, // Turn this folder into a playlist
  19. ActionSettingsAddToPlaylist, // Append a remote file to this playlist
  20. } ActionSettingsIndex;
  21. // Delete the file of the currently selected item
  22. // Update items_view list before returning so that UI is updated and correct
  23. bool scene_action_settings_delete(App* app) {
  24. bool success = false;
  25. Item* item = ItemArray_get(app->items_view->items, app->selected_item);
  26. DialogMessage* dialog = dialog_message_alloc();
  27. dialog_message_set_header(dialog, "Delete?", 64, 0, AlignCenter, AlignTop);
  28. FuriString* text = furi_string_alloc();
  29. furi_string_printf(text, "%s\n\n%s", furi_string_get_cstr(item->name), "Are you sure?");
  30. dialog_message_set_text(dialog, furi_string_get_cstr(text), 64, 18, AlignCenter, AlignTop);
  31. dialog_message_set_buttons(dialog, "Cancel", NULL, "OK");
  32. DialogMessageButton button = dialog_message_show(app->dialog, dialog);
  33. if(button == DialogMessageButtonRight) {
  34. FuriString* current_path = furi_string_alloc();
  35. path_extract_dirname(furi_string_get_cstr(item->path), current_path);
  36. FS_Error fs_result = storage_common_remove(app->storage, furi_string_get_cstr(item->path));
  37. if(fs_result == FSE_OK) {
  38. success = true;
  39. FURI_LOG_I(TAG, "Deleted file: %s", furi_string_get_cstr(item->path));
  40. // ItemsView* new_items = item_get_items_view_from_path(app, current_path);
  41. // item_items_view_free(app->items_view);
  42. // app->items_view = new_items;
  43. } else {
  44. FURI_LOG_E(
  45. TAG, "Error deleting file! Error=%s", filesystem_api_error_get_desc(fs_result));
  46. FuriString* error_msg = furi_string_alloc();
  47. furi_string_printf(
  48. error_msg, "Delete failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
  49. dialog_message_show_storage_error(app->dialog, furi_string_get_cstr(error_msg));
  50. furi_string_free(error_msg);
  51. }
  52. furi_string_free(current_path);
  53. } else {
  54. // FURI_LOG_I(TAG, "Used cancelled Delete");
  55. }
  56. furi_string_free(text);
  57. dialog_message_free(dialog);
  58. return success;
  59. }
  60. static bool scene_action_settings_import_file_browser_callback(
  61. FuriString* path,
  62. void* context,
  63. uint8_t** icon,
  64. FuriString* item_name) {
  65. UNUSED(context);
  66. UNUSED(item_name);
  67. char ext[MAX_EXT_LEN];
  68. path_extract_extension(path, ext, MAX_EXT_LEN);
  69. if(!strcmp(ext, ".sub")) {
  70. memcpy(*icon, icon_get_frame_data(&I_SubGHz_10px, 0), 32); // TODO: find the right size!
  71. } else if(!strcmp(ext, ".rfid")) {
  72. memcpy(*icon, icon_get_frame_data(&I_RFID_10px, 0), 32);
  73. } else if(!strcmp(ext, ".ir")) {
  74. memcpy(*icon, icon_get_frame_data(&I_IR_10px, 0), 32);
  75. } else if(!strcmp(ext, ".nfc")) {
  76. memcpy(*icon, icon_get_frame_data(&I_NFC_10px, 0), 32);
  77. } else if(!strcmp(ext, ".ibtn")) {
  78. memcpy(*icon, icon_get_frame_data(&I_iButton_10px, 0), 32);
  79. } else if(!strcmp(ext, ".qpl")) {
  80. memcpy(*icon, icon_get_frame_data(&I_Playlist_10px, 0), 32);
  81. } else {
  82. return false;
  83. }
  84. return true;
  85. }
  86. // Ask user for file to import from elsewhere on the SD card
  87. FuriString* scene_action_get_file_to_import_alloc(App* app) {
  88. // Setup our file browser options
  89. DialogsFileBrowserOptions fb_options;
  90. dialog_file_browser_set_basic_options(&fb_options, "", NULL);
  91. fb_options.base_path = STORAGE_EXT_PATH_PREFIX;
  92. fb_options.skip_assets = true;
  93. furi_string_set_str(app->temp_str, fb_options.base_path);
  94. fb_options.item_loader_callback = scene_action_settings_import_file_browser_callback;
  95. fb_options.item_loader_context = app;
  96. FuriString* full_path = NULL;
  97. if(dialog_file_browser_show(app->dialog, app->temp_str, app->temp_str, &fb_options)) {
  98. full_path = furi_string_alloc_set(app->temp_str);
  99. }
  100. return full_path;
  101. }
  102. // Import a file from elsewhere on the SD card
  103. // Update items_view list before returning so that UI is updated and correct
  104. bool scene_action_settings_import(App* app) {
  105. bool success = false;
  106. FuriString* current_path = furi_string_alloc();
  107. if(app->selected_item != EMPTY_ACTION_INDEX) {
  108. Item* item = ItemArray_get(app->items_view->items, app->selected_item);
  109. path_extract_dirname(furi_string_get_cstr(item->path), current_path);
  110. } else {
  111. furi_string_set(current_path, app->items_view->path);
  112. }
  113. // Setup our file browser options
  114. DialogsFileBrowserOptions fb_options;
  115. dialog_file_browser_set_basic_options(&fb_options, "", NULL);
  116. fb_options.base_path = furi_string_get_cstr(current_path);
  117. fb_options.skip_assets = true;
  118. furi_string_set_str(app->temp_str, fb_options.base_path);
  119. fb_options.item_loader_callback = scene_action_settings_import_file_browser_callback;
  120. fb_options.item_loader_context = app;
  121. if(dialog_file_browser_show(app->dialog, app->temp_str, app->temp_str, &fb_options)) {
  122. // FURI_LOG_I(TAG, "Selected file is %s", furi_string_get_cstr(app->temp_str));
  123. FuriString* file_name = furi_string_alloc();
  124. path_extract_filename(app->temp_str, file_name, false);
  125. // FURI_LOG_I(TAG, "Importing file %s", furi_string_get_cstr(file_name));
  126. FuriString* full_path;
  127. full_path = furi_string_alloc_printf(
  128. "%s/%s", furi_string_get_cstr(current_path), furi_string_get_cstr(file_name));
  129. // FURI_LOG_I(TAG, "New path is %s", furi_string_get_cstr(full_path));
  130. FS_Error fs_result = storage_common_copy(
  131. app->storage, furi_string_get_cstr(app->temp_str), furi_string_get_cstr(full_path));
  132. if(fs_result == FSE_OK) {
  133. success = true;
  134. // FURI_LOG_I(TAG, "File copied / updating items view list");
  135. // ItemsView* new_items = item_get_items_view_from_path(app, current_path);
  136. // item_items_view_free(app->items_view);
  137. // app->items_view = new_items;
  138. } else {
  139. FURI_LOG_E(TAG, "File copy failed! %s", filesystem_api_error_get_desc(fs_result));
  140. FuriString* error_msg = furi_string_alloc_printf(
  141. "File copy failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
  142. dialog_message_show_storage_error(app->dialog, furi_string_get_cstr(error_msg));
  143. furi_string_free(error_msg);
  144. }
  145. furi_string_free(file_name);
  146. furi_string_free(full_path);
  147. } else {
  148. // FURI_LOG_I(TAG, "User cancelled");
  149. }
  150. furi_string_free(current_path);
  151. return success;
  152. }
  153. // Prompt user for the name of the new Group
  154. // Update items_view list before returning so that UI is updated and correct
  155. bool scene_action_settings_create_group(App* app) {
  156. UNUSED(app);
  157. return false;
  158. }
  159. void scene_action_settings_callback(void* context, uint32_t index) {
  160. App* app = context;
  161. view_dispatcher_send_custom_event(app->view_dispatcher, index);
  162. }
  163. void scene_action_settings_on_enter(void* context) {
  164. App* app = context;
  165. Submenu* menu = app->sub_menu;
  166. submenu_reset(menu);
  167. if(app->selected_item >= 0) {
  168. Item* item = ItemArray_get(app->items_view->items, app->selected_item);
  169. submenu_set_header(menu, furi_string_get_cstr(item->name));
  170. submenu_add_item(
  171. menu, "Rename", ActionSettingsRename, scene_action_settings_callback, app);
  172. submenu_add_item(
  173. menu, "Delete", ActionSettingsDelete, scene_action_settings_callback, app);
  174. } else {
  175. submenu_set_header(menu, furi_string_get_cstr(app->items_view->name));
  176. }
  177. submenu_add_item(
  178. menu, "Import Here", ActionSettingsImport, scene_action_settings_callback, app);
  179. submenu_add_item(
  180. menu, "Import Link Here", ActionSettingsImportLink, scene_action_settings_callback, app);
  181. submenu_add_item(
  182. menu, "Create Group", ActionSettingsCreateGroup, scene_action_settings_callback, app);
  183. view_dispatcher_switch_to_view(app->view_dispatcher, QView_SubMenu);
  184. }
  185. bool scene_action_settings_on_event(void* context, SceneManagerEvent event) {
  186. App* app = context;
  187. bool consumed = false;
  188. if(event.type == SceneManagerEventTypeCustom) {
  189. switch(event.event) {
  190. case ActionSettingsRename:
  191. consumed = true;
  192. scene_manager_next_scene(app->scene_manager, QScene_ActionRename);
  193. break;
  194. case ActionSettingsDelete:
  195. consumed = true;
  196. if(scene_action_settings_delete(app)) {
  197. scene_manager_previous_scene(app->scene_manager);
  198. }
  199. break;
  200. case ActionSettingsImport: {
  201. consumed = true;
  202. // get the filename to import
  203. FuriString* import_file = scene_action_get_file_to_import_alloc(app);
  204. if(import_file) {
  205. FURI_LOG_I(TAG, "Importing %s", furi_string_get_cstr(import_file));
  206. // if it's a .ir file, switch to a scene that lets user pick the command from the file
  207. // only if there's more than one command in the file. then copy that relevant chunk
  208. // to the local directory
  209. char ext[MAX_EXT_LEN] = {0};
  210. path_extract_extension(import_file, ext, MAX_EXT_LEN);
  211. if(!strcmp(ext, ".ir")) {
  212. FURI_LOG_I(TAG, "Loading ir file %s", furi_string_get_cstr(app->temp_str));
  213. // load scene that takes filename and lists all commands
  214. // the scene should write the new file, eh?
  215. scene_manager_next_scene(app->scene_manager, QScene_ActionIRList);
  216. } else {
  217. // just copy the file here
  218. FuriString* current_path = furi_string_alloc();
  219. if(app->selected_item != EMPTY_ACTION_INDEX) {
  220. Item* item = ItemArray_get(app->items_view->items, app->selected_item);
  221. path_extract_dirname(furi_string_get_cstr(item->path), current_path);
  222. } else {
  223. furi_string_set(current_path, app->items_view->path);
  224. }
  225. // TODO: this should be a method
  226. FuriString* file_name = furi_string_alloc();
  227. path_extract_filename(import_file, file_name, false);
  228. // FURI_LOG_I(TAG, "Importing file %s", furi_string_get_cstr(file_name));
  229. FuriString* full_path;
  230. full_path = furi_string_alloc_printf(
  231. "%s/%s",
  232. furi_string_get_cstr(current_path),
  233. furi_string_get_cstr(file_name));
  234. // FURI_LOG_I(TAG, "New path is %s", furi_string_get_cstr(full_path));
  235. FURI_LOG_I(
  236. TAG,
  237. "Copy: %s to %s",
  238. furi_string_get_cstr(import_file),
  239. furi_string_get_cstr(full_path));
  240. FS_Error fs_result = storage_common_copy(
  241. app->storage,
  242. furi_string_get_cstr(import_file),
  243. furi_string_get_cstr(full_path));
  244. if(fs_result != FSE_OK) {
  245. FURI_LOG_E(
  246. TAG, "Copy file failed! %s", filesystem_api_error_get_desc(fs_result));
  247. FuriString* error_msg = furi_string_alloc_printf(
  248. "Copy failed!\nError: %s", filesystem_api_error_get_desc(fs_result));
  249. dialog_message_show_storage_error(
  250. app->dialog, furi_string_get_cstr(error_msg));
  251. furi_string_free(error_msg);
  252. }
  253. furi_string_free(file_name);
  254. furi_string_free(full_path);
  255. }
  256. furi_string_free(import_file);
  257. } else {
  258. scene_manager_previous_scene(app->scene_manager);
  259. }
  260. // if(scene_action_settings_import(app)) {
  261. // scene_manager_previous_scene(app->scene_manager);
  262. // }
  263. } break;
  264. case ActionSettingsImportLink: {
  265. consumed = true;
  266. // get the filename to import as a link
  267. FuriString* import_file = scene_action_get_file_to_import_alloc(app);
  268. if(import_file) {
  269. FURI_LOG_I(TAG, "Importing as link %s", furi_string_get_cstr(import_file));
  270. char ext[MAX_EXT_LEN] = {0};
  271. path_extract_extension(import_file, ext, MAX_EXT_LEN);
  272. if(!strcmp(ext, ".ir")) {
  273. dialog_message_show_storage_error(
  274. app->dialog, "Can't import IR file as link at this time");
  275. } else if(!strcmp(ext, ".ql")) {
  276. FURI_LOG_E(TAG, "Can't import link file as a link!");
  277. dialog_message_show_storage_error(
  278. app->dialog, "Can't import link file as a link!");
  279. } else {
  280. FuriString* current_path = furi_string_alloc();
  281. if(app->selected_item != EMPTY_ACTION_INDEX) {
  282. Item* item = ItemArray_get(app->items_view->items, app->selected_item);
  283. path_extract_dirname(furi_string_get_cstr(item->path), current_path);
  284. } else {
  285. furi_string_set(current_path, app->items_view->path);
  286. }
  287. FuriString* file_name = furi_string_alloc();
  288. path_extract_filename(import_file, file_name, false);
  289. FuriString* full_path;
  290. full_path = furi_string_alloc_printf(
  291. "%s/%s.ql", // path/filename.ext.ql
  292. furi_string_get_cstr(current_path),
  293. furi_string_get_cstr(file_name));
  294. FURI_LOG_I(
  295. TAG,
  296. "Copy as link: %s to %s",
  297. furi_string_get_cstr(import_file),
  298. furi_string_get_cstr(full_path));
  299. File* file_link = storage_file_alloc(app->storage);
  300. if(storage_file_open(
  301. file_link,
  302. furi_string_get_cstr(full_path),
  303. FSAM_WRITE,
  304. FSOM_CREATE_NEW)) {
  305. const char* cimport_file = furi_string_get_cstr(import_file);
  306. size_t bytes_written =
  307. storage_file_write(file_link, cimport_file, strlen(cimport_file));
  308. if(bytes_written != strlen(cimport_file)) {
  309. FURI_LOG_E(
  310. TAG,
  311. "Copy as link failure: incorrect bytes written. Expected %d, wrote %d",
  312. strlen(cimport_file),
  313. bytes_written);
  314. }
  315. } else {
  316. dialog_message_show_storage_error(app->dialog, "Error writing link file!");
  317. FURI_LOG_E(
  318. TAG,
  319. "Copy file as link failed! File %s already exists",
  320. furi_string_get_cstr(full_path));
  321. }
  322. storage_file_close(file_link);
  323. storage_file_free(file_link);
  324. furi_string_free(file_name);
  325. furi_string_free(full_path);
  326. }
  327. furi_string_free(import_file);
  328. } else {
  329. scene_manager_previous_scene(app->scene_manager);
  330. }
  331. } break;
  332. case ActionSettingsCreateGroup:
  333. consumed = true;
  334. scene_manager_next_scene(app->scene_manager, QScene_ActionCreateGroup);
  335. break;
  336. }
  337. }
  338. return consumed;
  339. }
  340. void scene_action_settings_on_exit(void* context) {
  341. App* app = context;
  342. submenu_reset(app->sub_menu);
  343. // Rebuild our list on exit, to pick up any renames
  344. ItemsView* new_items = item_get_items_view_from_path(app, app->items_view->path);
  345. item_items_view_free(app->items_view);
  346. app->items_view = new_items;
  347. }