archive.c 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  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(array_size > 3 && model->idx >= array_size - 1) {
  10. model->list_offset = model->idx - 3;
  11. } else if(model->list_offset < model->idx - bounds) {
  12. model->list_offset = CLAMP(model->list_offset + 1, array_size - bounds, 0);
  13. } else if(model->list_offset > model->idx - bounds) {
  14. model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0);
  15. }
  16. return true;
  17. });
  18. }
  19. static void archive_update_last_idx(ArchiveApp* archive) {
  20. furi_assert(archive);
  21. with_view_model(
  22. archive->view_archive_main, (ArchiveViewModel * model) {
  23. archive->browser.last_idx[archive->browser.depth] =
  24. CLAMP(model->idx, files_array_size(model->files) - 1, 0);
  25. model->idx = 0;
  26. return true;
  27. });
  28. }
  29. static void archive_switch_dir(ArchiveApp* archive, const char* path) {
  30. furi_assert(archive);
  31. furi_assert(path);
  32. string_set(archive->browser.path, path);
  33. archive_get_filenames(archive);
  34. update_offset(archive);
  35. }
  36. static void archive_switch_tab(ArchiveApp* archive) {
  37. furi_assert(archive);
  38. with_view_model(
  39. archive->view_archive_main, (ArchiveViewModel * model) {
  40. model->tab_idx = archive->browser.tab_id;
  41. model->idx = 0;
  42. return true;
  43. });
  44. archive->browser.depth = 0;
  45. archive_switch_dir(archive, tab_default_paths[archive->browser.tab_id]);
  46. }
  47. static void archive_leave_dir(ArchiveApp* archive) {
  48. furi_assert(archive);
  49. char* last_char_ptr = strrchr(string_get_cstr(archive->browser.path), '/');
  50. if(last_char_ptr) {
  51. size_t pos = last_char_ptr - string_get_cstr(archive->browser.path);
  52. string_left(archive->browser.path, pos);
  53. }
  54. archive->browser.depth = CLAMP(archive->browser.depth - 1, MAX_DEPTH, 0);
  55. with_view_model(
  56. archive->view_archive_main, (ArchiveViewModel * model) {
  57. model->idx = archive->browser.last_idx[archive->browser.depth];
  58. model->list_offset =
  59. model->idx -
  60. (files_array_size(model->files) > 3 ? 3 : files_array_size(model->files));
  61. return true;
  62. });
  63. archive_switch_dir(archive, string_get_cstr(archive->browser.path));
  64. update_offset(archive);
  65. }
  66. static void archive_enter_dir(ArchiveApp* archive, string_t name) {
  67. furi_assert(archive);
  68. furi_assert(name);
  69. archive_update_last_idx(archive);
  70. archive->browser.depth = CLAMP(archive->browser.depth + 1, MAX_DEPTH, 0);
  71. string_cat(archive->browser.path, "/");
  72. string_cat(archive->browser.path, archive->browser.name);
  73. archive_switch_dir(archive, string_get_cstr(archive->browser.path));
  74. }
  75. static bool filter_by_extension(ArchiveApp* archive, FileInfo* file_info, const char* name) {
  76. furi_assert(archive);
  77. furi_assert(file_info);
  78. furi_assert(name);
  79. bool result = false;
  80. const char* filter_ext_ptr = get_tab_ext(archive->browser.tab_id);
  81. if(strcmp(filter_ext_ptr, "*") == 0) {
  82. result = true;
  83. } else if(strstr(name, filter_ext_ptr) != NULL) {
  84. result = true;
  85. } else if(file_info->flags & FSF_DIRECTORY) {
  86. result = true;
  87. }
  88. return result;
  89. }
  90. static void set_file_type(ArchiveFile_t* file, FileInfo* file_info) {
  91. furi_assert(file);
  92. furi_assert(file_info);
  93. for(size_t i = 0; i < SIZEOF_ARRAY(known_ext); i++) {
  94. if(string_search_str(file->name, known_ext[i], 0) != STRING_FAILURE) {
  95. file->type = i;
  96. return;
  97. }
  98. }
  99. if(file_info->flags & FSF_DIRECTORY) {
  100. file->type = ArchiveFileTypeFolder;
  101. } else {
  102. file->type = ArchiveFileTypeUnknown;
  103. }
  104. }
  105. static void archive_file_append(ArchiveApp* archive, const char* path, string_t string) {
  106. furi_assert(archive);
  107. furi_assert(path);
  108. furi_assert(string);
  109. FileWorker* file_worker = file_worker_alloc(false);
  110. if(!file_worker_open(file_worker, path, FSAM_WRITE, FSOM_OPEN_APPEND)) {
  111. FURI_LOG_E("Archive", "Append open error");
  112. }
  113. if(!file_worker_write(file_worker, string_get_cstr(string), string_size(string))) {
  114. FURI_LOG_E("Archive", "Append write error");
  115. }
  116. file_worker_close(file_worker);
  117. file_worker_free(file_worker);
  118. }
  119. static void archive_view_add_item(ArchiveApp* archive, FileInfo* file_info, const char* name) {
  120. furi_assert(archive);
  121. furi_assert(file_info);
  122. furi_assert(name);
  123. ArchiveFile_t item;
  124. if(filter_by_extension(archive, file_info, name)) {
  125. ArchiveFile_t_init(&item);
  126. string_init_set_str(item.name, name);
  127. set_file_type(&item, file_info);
  128. with_view_model(
  129. archive->view_archive_main, (ArchiveViewModel * model) {
  130. files_array_push_back(model->files, item);
  131. return true;
  132. });
  133. ArchiveFile_t_clear(&item);
  134. }
  135. }
  136. static bool archive_is_favorite(ArchiveApp* archive, ArchiveFile_t* selected) {
  137. furi_assert(selected);
  138. string_t path;
  139. string_t buffer;
  140. string_init(buffer);
  141. bool found = false;
  142. string_init_printf(
  143. path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name));
  144. bool load_result =
  145. file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_ALWAYS);
  146. if(load_result) {
  147. while(1) {
  148. if(!file_worker_read_until(archive->file_worker, buffer, '\n')) {
  149. break;
  150. }
  151. if(!string_size(buffer)) {
  152. break;
  153. }
  154. if(!string_search(buffer, path)) {
  155. found = true;
  156. break;
  157. }
  158. }
  159. }
  160. string_clear(buffer);
  161. string_clear(path);
  162. file_worker_close(archive->file_worker);
  163. return found;
  164. }
  165. static bool archive_favorites_read(ArchiveApp* archive) {
  166. string_t buffer;
  167. FileInfo file_info;
  168. string_init(buffer);
  169. bool load_result =
  170. file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
  171. if(load_result) {
  172. while(1) {
  173. if(!file_worker_read_until(archive->file_worker, buffer, '\n')) {
  174. break;
  175. }
  176. if(!string_size(buffer)) {
  177. break;
  178. }
  179. archive_view_add_item(archive, &file_info, string_get_cstr(buffer));
  180. string_clean(buffer);
  181. }
  182. }
  183. string_clear(buffer);
  184. file_worker_close(archive->file_worker);
  185. return load_result;
  186. }
  187. static bool
  188. archive_favorites_rename(ArchiveApp* archive, ArchiveFile_t* selected, const char* dst) {
  189. furi_assert(selected);
  190. string_t path;
  191. string_t buffer;
  192. string_t temp;
  193. string_init(buffer);
  194. string_init(temp);
  195. string_init_printf(
  196. path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name));
  197. bool load_result =
  198. file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
  199. if(load_result) {
  200. while(1) {
  201. if(!file_worker_read_until(archive->file_worker, buffer, '\n')) {
  202. break;
  203. }
  204. if(!string_size(buffer)) {
  205. break;
  206. }
  207. string_printf(
  208. temp, "%s\r\n", string_search(buffer, path) ? string_get_cstr(buffer) : dst);
  209. archive_file_append(archive, ARCHIVE_FAV_TEMP_PATH, temp);
  210. string_clean(temp);
  211. }
  212. }
  213. string_clear(temp);
  214. string_clear(buffer);
  215. string_clear(path);
  216. file_worker_close(archive->file_worker);
  217. file_worker_remove(archive->file_worker, ARCHIVE_FAV_PATH);
  218. file_worker_rename(archive->file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
  219. return load_result;
  220. }
  221. static bool archive_favorites_delete(ArchiveApp* archive, ArchiveFile_t* selected) {
  222. furi_assert(selected);
  223. string_t path;
  224. string_t buffer;
  225. string_init(buffer);
  226. string_init_printf(
  227. path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(selected->name));
  228. bool load_result =
  229. file_worker_open(archive->file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
  230. if(load_result) {
  231. while(1) {
  232. if(!file_worker_read_until(archive->file_worker, buffer, '\n')) {
  233. break;
  234. }
  235. if(!string_size(buffer)) {
  236. break;
  237. }
  238. if(string_search(buffer, path)) {
  239. string_t temp;
  240. string_init_printf(temp, "%s\r\n", string_get_cstr(buffer));
  241. archive_file_append(archive, ARCHIVE_FAV_TEMP_PATH, temp);
  242. string_clear(temp);
  243. }
  244. }
  245. }
  246. string_clear(buffer);
  247. string_clear(path);
  248. file_worker_close(archive->file_worker);
  249. file_worker_remove(archive->file_worker, ARCHIVE_FAV_PATH);
  250. file_worker_rename(archive->file_worker, ARCHIVE_FAV_TEMP_PATH, ARCHIVE_FAV_PATH);
  251. return load_result;
  252. }
  253. static bool archive_read_dir(ArchiveApp* archive) {
  254. FileInfo file_info;
  255. File* directory = storage_file_alloc(archive->api);
  256. char name[MAX_NAME_LEN];
  257. if(!storage_dir_open(directory, string_get_cstr(archive->browser.path))) {
  258. storage_dir_close(directory);
  259. storage_file_free(directory);
  260. return false;
  261. }
  262. while(1) {
  263. if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) {
  264. break;
  265. }
  266. uint16_t files_cnt;
  267. with_view_model(
  268. archive->view_archive_main, (ArchiveViewModel * model) {
  269. files_cnt = files_array_size(model->files);
  270. return true;
  271. });
  272. if(files_cnt > MAX_FILES) {
  273. break;
  274. } else if(storage_file_get_error(directory) == FSE_OK) {
  275. archive_view_add_item(archive, &file_info, name);
  276. } else {
  277. storage_dir_close(directory);
  278. storage_file_free(directory);
  279. return false;
  280. }
  281. }
  282. storage_dir_close(directory);
  283. storage_file_free(directory);
  284. return true;
  285. }
  286. static bool archive_get_filenames(ArchiveApp* archive) {
  287. furi_assert(archive);
  288. with_view_model(
  289. archive->view_archive_main, (ArchiveViewModel * model) {
  290. files_array_clean(model->files);
  291. return true;
  292. });
  293. if(archive->browser.tab_id != ArchiveTabFavorites) {
  294. archive_read_dir(archive);
  295. } else {
  296. archive_favorites_read(archive);
  297. }
  298. return true;
  299. }
  300. static void archive_exit_callback(ArchiveApp* archive) {
  301. furi_assert(archive);
  302. AppEvent event;
  303. event.type = EventTypeExit;
  304. furi_check(osMessageQueuePut(archive->event_queue, &event, 0, osWaitForever) == osOK);
  305. }
  306. static uint32_t archive_previous_callback(void* context) {
  307. return ArchiveViewMain;
  308. }
  309. /* file menu */
  310. static void archive_add_to_favorites(ArchiveApp* archive) {
  311. furi_assert(archive);
  312. string_t buffer_src;
  313. string_init_printf(
  314. buffer_src,
  315. "%s/%s\r\n",
  316. string_get_cstr(archive->browser.path),
  317. string_get_cstr(archive->browser.name));
  318. archive_file_append(archive, ARCHIVE_FAV_PATH, buffer_src);
  319. string_clear(buffer_src);
  320. }
  321. static void archive_text_input_callback(void* context) {
  322. furi_assert(context);
  323. ArchiveApp* archive = (ArchiveApp*)context;
  324. string_t buffer_src;
  325. string_t buffer_dst;
  326. string_init_printf(
  327. buffer_src,
  328. "%s/%s",
  329. string_get_cstr(archive->browser.path),
  330. string_get_cstr(archive->browser.name));
  331. string_init_printf(
  332. buffer_dst,
  333. "%s/%s",
  334. string_get_cstr(archive->browser.path),
  335. archive->browser.text_input_buffer);
  336. // append extension
  337. ArchiveFile_t* file;
  338. with_view_model(
  339. archive->view_archive_main, (ArchiveViewModel * model) {
  340. file = files_array_get(
  341. model->files, CLAMP(model->idx, files_array_size(model->files) - 1, 0));
  342. file->fav = archive_is_favorite(archive, file);
  343. return true;
  344. });
  345. string_cat(buffer_dst, known_ext[file->type]);
  346. storage_common_rename(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst));
  347. if(file->fav) {
  348. archive_favorites_rename(archive, file, string_get_cstr(buffer_dst));
  349. }
  350. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewMain);
  351. string_clear(buffer_src);
  352. string_clear(buffer_dst);
  353. archive_get_filenames(archive);
  354. }
  355. static void archive_enter_text_input(ArchiveApp* archive) {
  356. furi_assert(archive);
  357. *archive->browser.text_input_buffer = '\0';
  358. strlcpy(
  359. archive->browser.text_input_buffer, string_get_cstr(archive->browser.name), MAX_NAME_LEN);
  360. archive_trim_file_ext(archive->browser.text_input_buffer);
  361. text_input_set_header_text(archive->text_input, "Rename:");
  362. text_input_set_result_callback(
  363. archive->text_input,
  364. archive_text_input_callback,
  365. archive,
  366. archive->browser.text_input_buffer,
  367. MAX_NAME_LEN,
  368. false);
  369. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput);
  370. }
  371. static void archive_show_file_menu(ArchiveApp* archive) {
  372. furi_assert(archive);
  373. archive->browser.menu = true;
  374. with_view_model(
  375. archive->view_archive_main, (ArchiveViewModel * model) {
  376. ArchiveFile_t* selected;
  377. selected = files_array_get(model->files, model->idx);
  378. model->menu = true;
  379. model->menu_idx = 0;
  380. selected->fav = is_known_app(selected->type) ? archive_is_favorite(archive, selected) :
  381. false;
  382. return true;
  383. });
  384. }
  385. static void archive_close_file_menu(ArchiveApp* archive) {
  386. furi_assert(archive);
  387. archive->browser.menu = false;
  388. with_view_model(
  389. archive->view_archive_main, (ArchiveViewModel * model) {
  390. model->menu = false;
  391. model->menu_idx = 0;
  392. return true;
  393. });
  394. }
  395. static void archive_open_app(ArchiveApp* archive, const char* app_name, const char* args) {
  396. furi_assert(archive);
  397. furi_assert(app_name);
  398. loader_start(archive->loader, app_name, args);
  399. }
  400. static void archive_delete_file(ArchiveApp* archive, ArchiveFile_t* file) {
  401. furi_assert(archive);
  402. furi_assert(file);
  403. string_t path;
  404. string_init(path);
  405. string_printf(
  406. path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(file->name));
  407. if(archive_is_favorite(archive, file)) { // remove from favorites
  408. archive_favorites_delete(archive, file);
  409. }
  410. file_worker_remove(archive->file_worker, string_get_cstr(path));
  411. string_clear(path);
  412. archive_get_filenames(archive);
  413. with_view_model(
  414. archive->view_archive_main, (ArchiveViewModel * model) {
  415. model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0);
  416. return true;
  417. });
  418. update_offset(archive);
  419. }
  420. static void
  421. archive_run_in_app(ArchiveApp* archive, ArchiveFile_t* selected, bool full_path_provided) {
  422. string_t full_path;
  423. if(!full_path_provided) {
  424. string_init_printf(
  425. full_path,
  426. "%s/%s",
  427. string_get_cstr(archive->browser.path),
  428. string_get_cstr(selected->name));
  429. } else {
  430. string_init_set(full_path, selected->name);
  431. }
  432. archive_open_app(archive, flipper_app_name[selected->type], string_get_cstr(full_path));
  433. string_clear(full_path);
  434. }
  435. static void archive_file_menu_callback(ArchiveApp* archive) {
  436. furi_assert(archive);
  437. ArchiveFile_t* selected;
  438. uint8_t idx = 0;
  439. with_view_model(
  440. archive->view_archive_main, (ArchiveViewModel * model) {
  441. selected = files_array_get(model->files, model->idx);
  442. idx = model->menu_idx;
  443. return true;
  444. });
  445. switch(idx) {
  446. case 0:
  447. if(is_known_app(selected->type)) {
  448. archive_run_in_app(archive, selected, false);
  449. }
  450. break;
  451. case 1:
  452. if(is_known_app(selected->type)) {
  453. if(!archive_is_favorite(archive, selected)) {
  454. string_set(archive->browser.name, selected->name);
  455. archive_add_to_favorites(archive);
  456. } else {
  457. // delete from favorites
  458. archive_favorites_delete(archive, selected);
  459. }
  460. archive_close_file_menu(archive);
  461. }
  462. break;
  463. case 2:
  464. // open rename view
  465. if(is_known_app(selected->type)) {
  466. archive_enter_text_input(archive);
  467. }
  468. break;
  469. case 3:
  470. // confirmation?
  471. archive_delete_file(archive, selected);
  472. archive_close_file_menu(archive);
  473. break;
  474. default:
  475. archive_close_file_menu(archive);
  476. break;
  477. }
  478. selected = NULL;
  479. }
  480. static void menu_input_handler(ArchiveApp* archive, InputEvent* event) {
  481. furi_assert(archive);
  482. furi_assert(archive);
  483. if(event->type == InputTypeShort) {
  484. if(event->key == InputKeyUp || event->key == InputKeyDown) {
  485. with_view_model(
  486. archive->view_archive_main, (ArchiveViewModel * model) {
  487. if(event->key == InputKeyUp) {
  488. model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS;
  489. } else if(event->key == InputKeyDown) {
  490. model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS;
  491. }
  492. return true;
  493. });
  494. }
  495. if(event->key == InputKeyOk) {
  496. archive_file_menu_callback(archive);
  497. } else if(event->key == InputKeyBack) {
  498. archive_close_file_menu(archive);
  499. }
  500. }
  501. }
  502. /* main controls */
  503. static bool archive_view_input(InputEvent* event, void* context) {
  504. furi_assert(event);
  505. furi_assert(context);
  506. ArchiveApp* archive = context;
  507. bool in_menu = archive->browser.menu;
  508. if(in_menu) {
  509. menu_input_handler(archive, event);
  510. return true;
  511. }
  512. if(event->type == InputTypeShort) {
  513. if(event->key == InputKeyLeft) {
  514. if(archive->browser.tab_id > 0) {
  515. archive->browser.tab_id = CLAMP(archive->browser.tab_id - 1, ArchiveTabTotal, 0);
  516. archive_switch_tab(archive);
  517. return true;
  518. }
  519. } else if(event->key == InputKeyRight) {
  520. if(archive->browser.tab_id < ArchiveTabTotal - 1) {
  521. archive->browser.tab_id =
  522. CLAMP(archive->browser.tab_id + 1, ArchiveTabTotal - 1, 0);
  523. archive_switch_tab(archive);
  524. return true;
  525. }
  526. } else if(event->key == InputKeyBack) {
  527. if(archive->browser.depth == 0) {
  528. archive_exit_callback(archive);
  529. } else {
  530. archive_leave_dir(archive);
  531. }
  532. return true;
  533. }
  534. }
  535. if(event->key == InputKeyUp || event->key == InputKeyDown) {
  536. with_view_model(
  537. archive->view_archive_main, (ArchiveViewModel * model) {
  538. uint16_t num_elements = (uint16_t)files_array_size(model->files);
  539. if((event->type == InputTypeShort || event->type == InputTypeRepeat)) {
  540. if(event->key == InputKeyUp) {
  541. model->idx = ((model->idx - 1) + num_elements) % num_elements;
  542. } else if(event->key == InputKeyDown) {
  543. model->idx = (model->idx + 1) % num_elements;
  544. }
  545. }
  546. return true;
  547. });
  548. update_offset(archive);
  549. }
  550. if(event->key == InputKeyOk) {
  551. ArchiveFile_t* selected;
  552. with_view_model(
  553. archive->view_archive_main, (ArchiveViewModel * model) {
  554. selected = files_array_size(model->files) > 0 ?
  555. files_array_get(model->files, model->idx) :
  556. NULL;
  557. return true;
  558. });
  559. if(selected) {
  560. string_set(archive->browser.name, selected->name);
  561. if(selected->type == ArchiveFileTypeFolder) {
  562. if(event->type == InputTypeShort) {
  563. archive_enter_dir(archive, archive->browser.name);
  564. } else if(event->type == InputTypeLong) {
  565. archive_show_file_menu(archive);
  566. }
  567. } else {
  568. if(event->type == InputTypeShort) {
  569. if(archive->browser.tab_id == ArchiveTabFavorites) {
  570. if(is_known_app(selected->type)) {
  571. archive_run_in_app(archive, selected, true);
  572. }
  573. } else {
  574. archive_show_file_menu(archive);
  575. }
  576. }
  577. }
  578. }
  579. }
  580. update_offset(archive);
  581. return true;
  582. }
  583. void archive_free(ArchiveApp* archive) {
  584. furi_assert(archive);
  585. file_worker_free(archive->file_worker);
  586. view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewMain);
  587. view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput);
  588. view_dispatcher_free(archive->view_dispatcher);
  589. with_view_model(
  590. archive->view_archive_main, (ArchiveViewModel * model) {
  591. files_array_clear(model->files);
  592. return false;
  593. });
  594. view_free(archive->view_archive_main);
  595. string_clear(archive->browser.name);
  596. string_clear(archive->browser.path);
  597. text_input_free(archive->text_input);
  598. furi_record_close("storage");
  599. archive->api = NULL;
  600. furi_record_close("gui");
  601. archive->gui = NULL;
  602. furi_record_close("loader");
  603. archive->loader = NULL;
  604. furi_thread_free(archive->app_thread);
  605. furi_check(osMessageQueueDelete(archive->event_queue) == osOK);
  606. free(archive);
  607. }
  608. ArchiveApp* archive_alloc() {
  609. ArchiveApp* archive = furi_alloc(sizeof(ArchiveApp));
  610. archive->event_queue = osMessageQueueNew(8, sizeof(AppEvent), NULL);
  611. archive->app_thread = furi_thread_alloc();
  612. archive->gui = furi_record_open("gui");
  613. archive->loader = furi_record_open("loader");
  614. archive->api = furi_record_open("storage");
  615. archive->text_input = text_input_alloc();
  616. archive->view_archive_main = view_alloc();
  617. archive->file_worker = file_worker_alloc(true);
  618. furi_check(archive->event_queue);
  619. view_allocate_model(
  620. archive->view_archive_main, ViewModelTypeLocking, sizeof(ArchiveViewModel));
  621. with_view_model(
  622. archive->view_archive_main, (ArchiveViewModel * model) {
  623. files_array_init(model->files);
  624. return false;
  625. });
  626. view_set_context(archive->view_archive_main, archive);
  627. view_set_draw_callback(archive->view_archive_main, archive_view_render);
  628. view_set_input_callback(archive->view_archive_main, archive_view_input);
  629. view_set_previous_callback(
  630. text_input_get_view(archive->text_input), archive_previous_callback);
  631. // View Dispatcher
  632. archive->view_dispatcher = view_dispatcher_alloc();
  633. view_dispatcher_add_view(
  634. archive->view_dispatcher, ArchiveViewMain, archive->view_archive_main);
  635. view_dispatcher_add_view(
  636. archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input));
  637. view_dispatcher_attach_to_gui(
  638. archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen);
  639. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveTabFavorites);
  640. return archive;
  641. }
  642. int32_t archive_app(void* p) {
  643. ArchiveApp* archive = archive_alloc();
  644. // default tab
  645. archive_switch_tab(archive);
  646. AppEvent event;
  647. while(1) {
  648. furi_check(osMessageQueueGet(archive->event_queue, &event, NULL, osWaitForever) == osOK);
  649. if(event.type == EventTypeExit) {
  650. break;
  651. }
  652. }
  653. archive_free(archive);
  654. return 0;
  655. }