archive.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. #include "archive_i.h"
  2. static bool archive_get_filenames(ArchiveApp* archive);
  3. static void update_offset(ArchiveApp* archive) {
  4. furi_assert(archive);
  5. with_view_model(
  6. archive->view_archive_main, (ArchiveViewModel * model) {
  7. size_t array_size = files_array_size(model->files);
  8. uint16_t bounds = array_size > 3 ? 2 : array_size;
  9. if(model->list_offset < model->idx - bounds) {
  10. model->list_offset = CLAMP(model->list_offset + 1, array_size - (bounds + 2), 0);
  11. } else if(model->list_offset > model->idx - bounds) {
  12. model->list_offset = CLAMP(model->idx - 1, array_size - (bounds), 0);
  13. }
  14. return true;
  15. });
  16. }
  17. static void archive_update_last_idx(ArchiveApp* archive) {
  18. furi_assert(archive);
  19. with_view_model(
  20. archive->view_archive_main, (ArchiveViewModel * model) {
  21. archive->browser.last_idx[archive->browser.depth] =
  22. CLAMP(model->idx, files_array_size(model->files) - 1, 0);
  23. model->idx = 0;
  24. return true;
  25. });
  26. }
  27. static void archive_switch_dir(ArchiveApp* archive, const char* path) {
  28. furi_assert(archive);
  29. furi_assert(path);
  30. string_set(archive->browser.path, path);
  31. archive_get_filenames(archive);
  32. }
  33. static void archive_switch_tab(ArchiveApp* archive) {
  34. furi_assert(archive);
  35. with_view_model(
  36. archive->view_archive_main, (ArchiveViewModel * model) {
  37. model->tab_idx = archive->browser.tab_id;
  38. model->idx = 0;
  39. return true;
  40. });
  41. archive->browser.depth = 0;
  42. archive_switch_dir(archive, tab_default_paths[archive->browser.tab_id]);
  43. update_offset(archive);
  44. }
  45. static void archive_leave_dir(ArchiveApp* archive) {
  46. furi_assert(archive);
  47. size_t last_char =
  48. string_search_rchar(archive->browser.path, '/', string_size(archive->browser.path));
  49. if(last_char) {
  50. string_right(archive->browser.path, last_char);
  51. }
  52. archive->browser.depth = CLAMP(archive->browser.depth - 1, MAX_DEPTH, 0);
  53. with_view_model(
  54. archive->view_archive_main, (ArchiveViewModel * model) {
  55. model->idx = archive->browser.last_idx[archive->browser.depth];
  56. return true;
  57. });
  58. archive_switch_dir(archive, string_get_cstr(archive->browser.path));
  59. update_offset(archive);
  60. }
  61. static void archive_enter_dir(ArchiveApp* archive, string_t name) {
  62. furi_assert(archive);
  63. furi_assert(name);
  64. archive_update_last_idx(archive);
  65. archive->browser.depth = CLAMP(archive->browser.depth + 1, MAX_DEPTH, 0);
  66. string_cat(archive->browser.path, "/");
  67. string_cat(archive->browser.path, archive->browser.name);
  68. archive_switch_dir(archive, string_get_cstr(archive->browser.path));
  69. update_offset(archive);
  70. }
  71. static bool filter_by_extension(ArchiveApp* archive, FileInfo* file_info, const char* name) {
  72. furi_assert(archive);
  73. furi_assert(file_info);
  74. furi_assert(name);
  75. bool result = false;
  76. const char* filter_ext_ptr = get_tab_ext(archive->browser.tab_id);
  77. if(strcmp(filter_ext_ptr, "*") == 0) {
  78. result = true;
  79. } else if(strstr(name, filter_ext_ptr) != NULL) {
  80. result = true;
  81. }
  82. return result;
  83. }
  84. static void set_file_type(ArchiveFile_t* file, FileInfo* file_info) {
  85. furi_assert(file);
  86. furi_assert(file_info);
  87. for(size_t i = 0; i < SIZEOF_ARRAY(known_ext); i++) {
  88. if(string_search_str(file->name, known_ext[i], 0) != STRING_FAILURE) {
  89. file->type = i;
  90. return;
  91. }
  92. }
  93. if(file_info->flags & FSF_DIRECTORY) {
  94. file->type = ArchiveFileTypeFolder;
  95. } else {
  96. file->type = ArchiveFileTypeUnknown;
  97. }
  98. }
  99. static bool archive_get_filenames(ArchiveApp* archive) {
  100. furi_assert(archive);
  101. FS_Dir_Api* dir_api = &archive->fs_api->dir;
  102. ArchiveFile_t item;
  103. FileInfo file_info;
  104. File directory;
  105. char name[MAX_NAME_LEN];
  106. bool result;
  107. result = dir_api->open(&directory, string_get_cstr(archive->browser.path));
  108. with_view_model(
  109. archive->view_archive_main, (ArchiveViewModel * model) {
  110. files_array_clean(model->files);
  111. return true;
  112. });
  113. if(!result) {
  114. dir_api->close(&directory);
  115. return false;
  116. }
  117. while(1) {
  118. result = dir_api->read(&directory, &file_info, name, MAX_NAME_LEN);
  119. if(directory.error_id == FSE_NOT_EXIST || name[0] == 0) {
  120. break;
  121. }
  122. if(result) {
  123. uint16_t files_cnt;
  124. with_view_model(
  125. archive->view_archive_main, (ArchiveViewModel * model) {
  126. files_cnt = files_array_size(model->files);
  127. return true;
  128. });
  129. if(files_cnt > MAX_FILES) {
  130. break;
  131. } else if(directory.error_id == FSE_OK) {
  132. if(filter_by_extension(archive, &file_info, name)) {
  133. ArchiveFile_t_init(&item);
  134. string_init_set(item.name, name);
  135. set_file_type(&item, &file_info);
  136. with_view_model(
  137. archive->view_archive_main, (ArchiveViewModel * model) {
  138. files_array_push_back(model->files, item);
  139. return true;
  140. });
  141. ArchiveFile_t_clear(&item);
  142. }
  143. } else {
  144. dir_api->close(&directory);
  145. return false;
  146. }
  147. }
  148. }
  149. dir_api->close(&directory);
  150. return true;
  151. }
  152. static void archive_exit_callback(ArchiveApp* archive) {
  153. furi_assert(archive);
  154. AppEvent event;
  155. event.type = EventTypeExit;
  156. furi_check(osMessageQueuePut(archive->event_queue, &event, 0, osWaitForever) == osOK);
  157. }
  158. static uint32_t archive_previous_callback(void* context) {
  159. return ArchiveViewMain;
  160. }
  161. /* file menu */
  162. static void archive_add_to_favourites(ArchiveApp* archive) {
  163. furi_assert(archive);
  164. FS_Common_Api* common_api = &archive->fs_api->common;
  165. string_t buffer_src;
  166. string_t buffer_dst;
  167. string_init_set(buffer_src, archive->browser.path);
  168. string_cat(buffer_src, "/");
  169. string_cat(buffer_src, archive->browser.name);
  170. string_init_set_str(buffer_dst, "/favourites/");
  171. string_cat(buffer_dst, archive->browser.name);
  172. common_api->rename(string_get_cstr(buffer_src), string_get_cstr(buffer_dst));
  173. string_clear(buffer_src);
  174. string_clear(buffer_dst);
  175. }
  176. static void archive_text_input_callback(void* context) {
  177. furi_assert(context);
  178. ArchiveApp* archive = (ArchiveApp*)context;
  179. FS_Common_Api* common_api = &archive->fs_api->common;
  180. string_t buffer_src;
  181. string_t buffer_dst;
  182. string_init_set(buffer_src, archive->browser.path);
  183. string_init_set(buffer_dst, archive->browser.path);
  184. string_cat(buffer_src, "/");
  185. string_cat(buffer_dst, "/");
  186. string_cat(buffer_src, archive->browser.name);
  187. string_cat_str(buffer_dst, archive->browser.text_input_buffer);
  188. // append extension
  189. ArchiveFile_t* file;
  190. with_view_model(
  191. archive->view_archive_main, (ArchiveViewModel * model) {
  192. file = files_array_get(
  193. model->files, CLAMP(model->idx, files_array_size(model->files) - 1, 0));
  194. return true;
  195. });
  196. string_cat(buffer_dst, known_ext[file->type]);
  197. common_api->rename(string_get_cstr(buffer_src), string_get_cstr(buffer_dst));
  198. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewMain);
  199. string_clear(buffer_src);
  200. string_clear(buffer_dst);
  201. archive_get_filenames(archive);
  202. }
  203. static void archive_enter_text_input(ArchiveApp* archive) {
  204. furi_assert(archive);
  205. *archive->browser.text_input_buffer = '\0';
  206. strlcpy(
  207. archive->browser.text_input_buffer,
  208. string_get_cstr(archive->browser.name),
  209. string_size(archive->browser.name));
  210. archive_trim_file_ext(archive->browser.text_input_buffer);
  211. text_input_set_header_text(archive->text_input, "Rename:");
  212. text_input_set_result_callback(
  213. archive->text_input,
  214. archive_text_input_callback,
  215. archive,
  216. archive->browser.text_input_buffer,
  217. MAX_NAME_LEN);
  218. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput);
  219. }
  220. static void archive_show_file_menu(ArchiveApp* archive) {
  221. furi_assert(archive);
  222. archive->browser.menu = true;
  223. with_view_model(
  224. archive->view_archive_main, (ArchiveViewModel * model) {
  225. model->menu = true;
  226. model->menu_idx = 0;
  227. return true;
  228. });
  229. }
  230. static void archive_close_file_menu(ArchiveApp* archive) {
  231. furi_assert(archive);
  232. archive->browser.menu = false;
  233. with_view_model(
  234. archive->view_archive_main, (ArchiveViewModel * model) {
  235. model->menu = false;
  236. model->menu_idx = 0;
  237. return true;
  238. });
  239. }
  240. static void archive_open_app(ArchiveApp* archive, const char* app_name, const char* args) {
  241. furi_assert(archive);
  242. furi_assert(app_name);
  243. app_loader_start(app_name, args);
  244. }
  245. static void archive_delete_file(ArchiveApp* archive, string_t name) {
  246. furi_assert(archive);
  247. furi_assert(name);
  248. FS_Common_Api* common_api = &archive->fs_api->common;
  249. string_t path;
  250. string_init_set(path, archive->browser.path);
  251. string_cat(path, "/");
  252. string_cat(path, name);
  253. common_api->remove(string_get_cstr(path));
  254. string_clear(path);
  255. archive_get_filenames(archive);
  256. update_offset(archive);
  257. with_view_model(
  258. archive->view_archive_main, (ArchiveViewModel * model) {
  259. model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0);
  260. return true;
  261. });
  262. }
  263. static void archive_file_menu_callback(ArchiveApp* archive) {
  264. furi_assert(archive);
  265. ArchiveFile_t* selected;
  266. uint8_t idx = 0;
  267. with_view_model(
  268. archive->view_archive_main, (ArchiveViewModel * model) {
  269. selected = files_array_get(model->files, model->idx);
  270. idx = model->menu_idx;
  271. return true;
  272. });
  273. switch(idx) {
  274. case 0:
  275. if((selected->type != ArchiveFileTypeFolder && selected->type != ArchiveFileTypeUnknown)) {
  276. string_t full_path;
  277. string_init_set(full_path, archive->browser.path);
  278. string_cat(full_path, "/");
  279. string_cat(full_path, selected->name);
  280. archive_open_app(
  281. archive, flipper_app_name[selected->type], string_get_cstr(full_path));
  282. string_clear(full_path);
  283. }
  284. break;
  285. case 1:
  286. string_set(archive->browser.name, selected->name);
  287. archive_add_to_favourites(archive);
  288. archive_close_file_menu(archive);
  289. break;
  290. case 2:
  291. // open rename view
  292. archive_enter_text_input(archive);
  293. break;
  294. case 3:
  295. // confirmation?
  296. archive_delete_file(archive, selected->name);
  297. archive_close_file_menu(archive);
  298. break;
  299. default:
  300. archive_close_file_menu(archive);
  301. break;
  302. }
  303. selected = NULL;
  304. }
  305. static void menu_input_handler(ArchiveApp* archive, InputEvent* event) {
  306. furi_assert(archive);
  307. furi_assert(archive);
  308. if(event->type == InputTypeShort) {
  309. if(event->key == InputKeyUp || event->key == InputKeyDown) {
  310. with_view_model(
  311. archive->view_archive_main, (ArchiveViewModel * model) {
  312. if(event->key == InputKeyUp) {
  313. model->menu_idx = CLAMP(model->menu_idx - 1, MENU_ITEMS - 1, 0);
  314. } else if(event->key == InputKeyDown) {
  315. model->menu_idx = CLAMP(model->menu_idx + 1, MENU_ITEMS - 1, 0);
  316. }
  317. return true;
  318. });
  319. }
  320. if(event->key == InputKeyOk) {
  321. archive_file_menu_callback(archive);
  322. } else if(event->key == InputKeyBack) {
  323. archive_close_file_menu(archive);
  324. }
  325. }
  326. }
  327. /* main controls */
  328. static bool archive_view_input(InputEvent* event, void* context) {
  329. furi_assert(event);
  330. furi_assert(context);
  331. ArchiveApp* archive = context;
  332. bool in_menu = archive->browser.menu;
  333. if(in_menu) {
  334. menu_input_handler(archive, event);
  335. return true;
  336. }
  337. if(event->type == InputTypeShort) {
  338. if(event->key == InputKeyLeft) {
  339. archive->browser.tab_id = CLAMP(archive->browser.tab_id - 1, ArchiveTabTotal, 0);
  340. archive_switch_tab(archive);
  341. return true;
  342. } else if(event->key == InputKeyRight) {
  343. archive->browser.tab_id = CLAMP(archive->browser.tab_id + 1, ArchiveTabTotal - 1, 0);
  344. archive_switch_tab(archive);
  345. return true;
  346. } else if(event->key == InputKeyBack) {
  347. if(archive->browser.depth == 0) {
  348. archive_exit_callback(archive);
  349. } else {
  350. archive_leave_dir(archive);
  351. }
  352. return true;
  353. }
  354. }
  355. if(event->key == InputKeyUp || event->key == InputKeyDown) {
  356. with_view_model(
  357. archive->view_archive_main, (ArchiveViewModel * model) {
  358. size_t num_elements = files_array_size(model->files) - 1;
  359. if((event->type == InputTypeShort || event->type == InputTypeRepeat)) {
  360. if(event->key == InputKeyUp) {
  361. model->idx = CLAMP(model->idx - 1, num_elements, 0);
  362. } else if(event->key == InputKeyDown) {
  363. model->idx = CLAMP(model->idx + 1, num_elements, 0);
  364. }
  365. }
  366. return true;
  367. });
  368. update_offset(archive);
  369. }
  370. if(event->key == InputKeyOk) {
  371. ArchiveFile_t* selected;
  372. with_view_model(
  373. archive->view_archive_main, (ArchiveViewModel * model) {
  374. if(files_array_size(model->files) > 0) {
  375. selected = files_array_get(model->files, model->idx);
  376. }
  377. return true;
  378. });
  379. string_set(archive->browser.name, selected->name);
  380. if(selected->type == ArchiveFileTypeFolder) {
  381. if(event->type == InputTypeShort) {
  382. archive_enter_dir(archive, archive->browser.name);
  383. } else if(event->type == InputTypeLong) {
  384. archive_show_file_menu(archive);
  385. }
  386. } else {
  387. if(event->type == InputTypeShort) {
  388. archive_show_file_menu(archive);
  389. }
  390. }
  391. }
  392. update_offset(archive);
  393. return true;
  394. }
  395. void archive_free(ArchiveApp* archive) {
  396. furi_assert(archive);
  397. view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewMain);
  398. view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput);
  399. view_dispatcher_free(archive->view_dispatcher);
  400. with_view_model(
  401. archive->view_archive_main, (ArchiveViewModel * model) {
  402. files_array_clear(model->files);
  403. return false;
  404. });
  405. view_free(archive->view_archive_main);
  406. string_clear(archive->browser.name);
  407. string_clear(archive->browser.path);
  408. text_input_free(archive->text_input);
  409. furi_record_close("sdcard");
  410. archive->fs_api = NULL;
  411. furi_record_close("gui");
  412. archive->gui = NULL;
  413. furi_thread_free(archive->app_thread);
  414. furi_check(osMessageQueueDelete(archive->event_queue) == osOK);
  415. free(archive);
  416. }
  417. ArchiveApp* archive_alloc() {
  418. ArchiveApp* archive = furi_alloc(sizeof(ArchiveApp));
  419. archive->event_queue = osMessageQueueNew(8, sizeof(AppEvent), NULL);
  420. archive->app_thread = furi_thread_alloc();
  421. archive->gui = furi_record_open("gui");
  422. archive->fs_api = furi_record_open("sdcard");
  423. archive->text_input = text_input_alloc();
  424. archive->view_archive_main = view_alloc();
  425. furi_check(archive->event_queue);
  426. view_allocate_model(
  427. archive->view_archive_main, ViewModelTypeLocking, sizeof(ArchiveViewModel));
  428. with_view_model(
  429. archive->view_archive_main, (ArchiveViewModel * model) {
  430. files_array_init(model->files);
  431. return false;
  432. });
  433. view_set_context(archive->view_archive_main, archive);
  434. view_set_draw_callback(archive->view_archive_main, archive_view_render);
  435. view_set_input_callback(archive->view_archive_main, archive_view_input);
  436. view_set_previous_callback(
  437. text_input_get_view(archive->text_input), archive_previous_callback);
  438. // View Dispatcher
  439. archive->view_dispatcher = view_dispatcher_alloc();
  440. view_dispatcher_add_view(
  441. archive->view_dispatcher, ArchiveViewMain, archive->view_archive_main);
  442. view_dispatcher_add_view(
  443. archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input));
  444. view_dispatcher_attach_to_gui(
  445. archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen);
  446. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveTabFavourites);
  447. return archive;
  448. }
  449. int32_t app_archive(void* p) {
  450. ArchiveApp* archive = archive_alloc();
  451. // default tab
  452. archive_switch_tab(archive);
  453. AppEvent event;
  454. while(1) {
  455. furi_check(osMessageQueueGet(archive->event_queue, &event, NULL, osWaitForever) == osOK);
  456. if(event.type == EventTypeExit) {
  457. break;
  458. }
  459. }
  460. archive_free(archive);
  461. return 0;
  462. }