archive.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  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. string_set(archive->browser.name, archive->browser.text_input_buffer);
  337. // append extension
  338. ArchiveFile_t* file;
  339. with_view_model(
  340. archive->view_archive_main, (ArchiveViewModel * model) {
  341. file = files_array_get(
  342. model->files, CLAMP(model->idx, files_array_size(model->files) - 1, 0));
  343. file->fav = archive_is_favorite(archive, file);
  344. return true;
  345. });
  346. string_cat(buffer_dst, known_ext[file->type]);
  347. storage_common_rename(archive->api, string_get_cstr(buffer_src), string_get_cstr(buffer_dst));
  348. if(file->fav) {
  349. archive_favorites_rename(archive, file, string_get_cstr(buffer_dst));
  350. }
  351. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewMain);
  352. archive_get_filenames(archive);
  353. with_view_model(
  354. archive->view_archive_main, (ArchiveViewModel * model) {
  355. model->idx = 0;
  356. while(model->idx < files_array_size(model->files)) {
  357. ArchiveFile_t* current = files_array_get(model->files, model->idx);
  358. if(!string_search(current->name, archive->browser.text_input_buffer)) {
  359. break;
  360. }
  361. ++model->idx;
  362. }
  363. return true;
  364. });
  365. update_offset(archive);
  366. string_clear(buffer_src);
  367. string_clear(buffer_dst);
  368. }
  369. static void archive_enter_text_input(ArchiveApp* archive) {
  370. furi_assert(archive);
  371. *archive->browser.text_input_buffer = '\0';
  372. strlcpy(
  373. archive->browser.text_input_buffer, string_get_cstr(archive->browser.name), MAX_NAME_LEN);
  374. archive_trim_file_ext(archive->browser.text_input_buffer);
  375. text_input_set_header_text(archive->text_input, "Rename:");
  376. text_input_set_result_callback(
  377. archive->text_input,
  378. archive_text_input_callback,
  379. archive,
  380. archive->browser.text_input_buffer,
  381. MAX_NAME_LEN,
  382. false);
  383. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput);
  384. }
  385. static void archive_show_file_menu(ArchiveApp* archive) {
  386. furi_assert(archive);
  387. archive->browser.menu = true;
  388. with_view_model(
  389. archive->view_archive_main, (ArchiveViewModel * model) {
  390. ArchiveFile_t* selected;
  391. selected = files_array_get(model->files, model->idx);
  392. model->menu = true;
  393. model->menu_idx = 0;
  394. selected->fav = is_known_app(selected->type) ? archive_is_favorite(archive, selected) :
  395. false;
  396. return true;
  397. });
  398. }
  399. static void archive_close_file_menu(ArchiveApp* archive) {
  400. furi_assert(archive);
  401. archive->browser.menu = false;
  402. with_view_model(
  403. archive->view_archive_main, (ArchiveViewModel * model) {
  404. model->menu = false;
  405. model->menu_idx = 0;
  406. return true;
  407. });
  408. }
  409. static void archive_open_app(ArchiveApp* archive, const char* app_name, const char* args) {
  410. furi_assert(archive);
  411. furi_assert(app_name);
  412. loader_start(archive->loader, app_name, args);
  413. }
  414. static void archive_delete_file(ArchiveApp* archive, ArchiveFile_t* file) {
  415. furi_assert(archive);
  416. furi_assert(file);
  417. string_t path;
  418. string_init(path);
  419. string_printf(
  420. path, "%s/%s", string_get_cstr(archive->browser.path), string_get_cstr(file->name));
  421. if(archive_is_favorite(archive, file)) { // remove from favorites
  422. archive_favorites_delete(archive, file);
  423. }
  424. file_worker_remove(archive->file_worker, string_get_cstr(path));
  425. string_clear(path);
  426. archive_get_filenames(archive);
  427. with_view_model(
  428. archive->view_archive_main, (ArchiveViewModel * model) {
  429. model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0);
  430. return true;
  431. });
  432. update_offset(archive);
  433. }
  434. static void
  435. archive_run_in_app(ArchiveApp* archive, ArchiveFile_t* selected, bool full_path_provided) {
  436. string_t full_path;
  437. if(!full_path_provided) {
  438. string_init_printf(
  439. full_path,
  440. "%s/%s",
  441. string_get_cstr(archive->browser.path),
  442. string_get_cstr(selected->name));
  443. } else {
  444. string_init_set(full_path, selected->name);
  445. }
  446. archive_open_app(archive, flipper_app_name[selected->type], string_get_cstr(full_path));
  447. string_clear(full_path);
  448. }
  449. static void archive_file_menu_callback(ArchiveApp* archive) {
  450. furi_assert(archive);
  451. ArchiveFile_t* selected;
  452. uint8_t idx = 0;
  453. with_view_model(
  454. archive->view_archive_main, (ArchiveViewModel * model) {
  455. selected = files_array_get(model->files, model->idx);
  456. idx = model->menu_idx;
  457. return true;
  458. });
  459. switch(idx) {
  460. case 0:
  461. if(is_known_app(selected->type)) {
  462. archive_run_in_app(archive, selected, false);
  463. }
  464. break;
  465. case 1:
  466. if(is_known_app(selected->type)) {
  467. if(!archive_is_favorite(archive, selected)) {
  468. string_set(archive->browser.name, selected->name);
  469. archive_add_to_favorites(archive);
  470. } else {
  471. // delete from favorites
  472. archive_favorites_delete(archive, selected);
  473. }
  474. archive_close_file_menu(archive);
  475. }
  476. break;
  477. case 2:
  478. // open rename view
  479. if(is_known_app(selected->type)) {
  480. archive_enter_text_input(archive);
  481. }
  482. break;
  483. case 3:
  484. // confirmation?
  485. archive_delete_file(archive, selected);
  486. archive_close_file_menu(archive);
  487. break;
  488. default:
  489. archive_close_file_menu(archive);
  490. break;
  491. }
  492. selected = NULL;
  493. }
  494. static void menu_input_handler(ArchiveApp* archive, InputEvent* event) {
  495. furi_assert(archive);
  496. furi_assert(archive);
  497. if(event->type == InputTypeShort) {
  498. if(event->key == InputKeyUp || event->key == InputKeyDown) {
  499. with_view_model(
  500. archive->view_archive_main, (ArchiveViewModel * model) {
  501. if(event->key == InputKeyUp) {
  502. model->menu_idx = ((model->menu_idx - 1) + MENU_ITEMS) % MENU_ITEMS;
  503. } else if(event->key == InputKeyDown) {
  504. model->menu_idx = (model->menu_idx + 1) % MENU_ITEMS;
  505. }
  506. return true;
  507. });
  508. }
  509. if(event->key == InputKeyOk) {
  510. archive_file_menu_callback(archive);
  511. } else if(event->key == InputKeyBack) {
  512. archive_close_file_menu(archive);
  513. }
  514. }
  515. }
  516. /* main controls */
  517. static bool archive_view_input(InputEvent* event, void* context) {
  518. furi_assert(event);
  519. furi_assert(context);
  520. ArchiveApp* archive = context;
  521. bool in_menu = archive->browser.menu;
  522. if(in_menu) {
  523. menu_input_handler(archive, event);
  524. return true;
  525. }
  526. if(event->type == InputTypeShort) {
  527. if(event->key == InputKeyLeft) {
  528. if(archive->browser.tab_id > 0) {
  529. archive->browser.tab_id = CLAMP(archive->browser.tab_id - 1, ArchiveTabTotal, 0);
  530. archive_switch_tab(archive);
  531. return true;
  532. }
  533. } else if(event->key == InputKeyRight) {
  534. if(archive->browser.tab_id < ArchiveTabTotal - 1) {
  535. archive->browser.tab_id =
  536. CLAMP(archive->browser.tab_id + 1, ArchiveTabTotal - 1, 0);
  537. archive_switch_tab(archive);
  538. return true;
  539. }
  540. } else if(event->key == InputKeyBack) {
  541. if(archive->browser.depth == 0) {
  542. archive_exit_callback(archive);
  543. } else {
  544. archive_leave_dir(archive);
  545. }
  546. return true;
  547. }
  548. }
  549. if(event->key == InputKeyUp || event->key == InputKeyDown) {
  550. with_view_model(
  551. archive->view_archive_main, (ArchiveViewModel * model) {
  552. uint16_t num_elements = (uint16_t)files_array_size(model->files);
  553. if((event->type == InputTypeShort || event->type == InputTypeRepeat)) {
  554. if(event->key == InputKeyUp) {
  555. model->idx = ((model->idx - 1) + num_elements) % num_elements;
  556. } else if(event->key == InputKeyDown) {
  557. model->idx = (model->idx + 1) % num_elements;
  558. }
  559. }
  560. return true;
  561. });
  562. update_offset(archive);
  563. }
  564. if(event->key == InputKeyOk) {
  565. ArchiveFile_t* selected;
  566. with_view_model(
  567. archive->view_archive_main, (ArchiveViewModel * model) {
  568. selected = files_array_size(model->files) > 0 ?
  569. files_array_get(model->files, model->idx) :
  570. NULL;
  571. return true;
  572. });
  573. if(selected) {
  574. string_set(archive->browser.name, selected->name);
  575. if(selected->type == ArchiveFileTypeFolder) {
  576. if(event->type == InputTypeShort) {
  577. archive_enter_dir(archive, archive->browser.name);
  578. } else if(event->type == InputTypeLong) {
  579. archive_show_file_menu(archive);
  580. }
  581. } else {
  582. if(event->type == InputTypeShort) {
  583. if(archive->browser.tab_id == ArchiveTabFavorites) {
  584. if(is_known_app(selected->type)) {
  585. archive_run_in_app(archive, selected, true);
  586. }
  587. } else {
  588. archive_show_file_menu(archive);
  589. }
  590. }
  591. }
  592. }
  593. }
  594. update_offset(archive);
  595. return true;
  596. }
  597. void archive_free(ArchiveApp* archive) {
  598. furi_assert(archive);
  599. file_worker_free(archive->file_worker);
  600. view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewMain);
  601. view_dispatcher_remove_view(archive->view_dispatcher, ArchiveViewTextInput);
  602. view_dispatcher_free(archive->view_dispatcher);
  603. with_view_model(
  604. archive->view_archive_main, (ArchiveViewModel * model) {
  605. files_array_clear(model->files);
  606. return false;
  607. });
  608. view_free(archive->view_archive_main);
  609. string_clear(archive->browser.name);
  610. string_clear(archive->browser.path);
  611. text_input_free(archive->text_input);
  612. furi_record_close("storage");
  613. archive->api = NULL;
  614. furi_record_close("gui");
  615. archive->gui = NULL;
  616. furi_record_close("loader");
  617. archive->loader = NULL;
  618. furi_thread_free(archive->app_thread);
  619. furi_check(osMessageQueueDelete(archive->event_queue) == osOK);
  620. free(archive);
  621. }
  622. ArchiveApp* archive_alloc() {
  623. ArchiveApp* archive = furi_alloc(sizeof(ArchiveApp));
  624. archive->event_queue = osMessageQueueNew(8, sizeof(AppEvent), NULL);
  625. archive->app_thread = furi_thread_alloc();
  626. archive->gui = furi_record_open("gui");
  627. archive->loader = furi_record_open("loader");
  628. archive->api = furi_record_open("storage");
  629. archive->text_input = text_input_alloc();
  630. archive->view_archive_main = view_alloc();
  631. archive->file_worker = file_worker_alloc(true);
  632. furi_check(archive->event_queue);
  633. view_allocate_model(
  634. archive->view_archive_main, ViewModelTypeLocking, sizeof(ArchiveViewModel));
  635. with_view_model(
  636. archive->view_archive_main, (ArchiveViewModel * model) {
  637. files_array_init(model->files);
  638. return false;
  639. });
  640. view_set_context(archive->view_archive_main, archive);
  641. view_set_draw_callback(archive->view_archive_main, archive_view_render);
  642. view_set_input_callback(archive->view_archive_main, archive_view_input);
  643. view_set_previous_callback(
  644. text_input_get_view(archive->text_input), archive_previous_callback);
  645. // View Dispatcher
  646. archive->view_dispatcher = view_dispatcher_alloc();
  647. view_dispatcher_add_view(
  648. archive->view_dispatcher, ArchiveViewMain, archive->view_archive_main);
  649. view_dispatcher_add_view(
  650. archive->view_dispatcher, ArchiveViewTextInput, text_input_get_view(archive->text_input));
  651. view_dispatcher_attach_to_gui(
  652. archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen);
  653. view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveTabFavorites);
  654. return archive;
  655. }
  656. int32_t archive_app(void* p) {
  657. ArchiveApp* archive = archive_alloc();
  658. // default tab
  659. archive_switch_tab(archive);
  660. AppEvent event;
  661. while(1) {
  662. furi_check(osMessageQueueGet(archive->event_queue, &event, NULL, osWaitForever) == osOK);
  663. if(event.type == EventTypeExit) {
  664. break;
  665. }
  666. }
  667. archive_free(archive);
  668. return 0;
  669. }