archive_browser.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. #include "archive/views/archive_browser_view.h"
  2. #include "archive_files.h"
  3. #include "archive_apps.h"
  4. #include "archive_browser.h"
  5. #include <core/common_defines.h>
  6. #include <core/log.h>
  7. #include "gui/modules/file_browser_worker.h"
  8. #include "m-string.h"
  9. #include <math.h>
  10. static void
  11. archive_folder_open_cb(void* context, uint32_t item_cnt, int32_t file_idx, bool is_root) {
  12. furi_assert(context);
  13. ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
  14. int32_t load_offset = 0;
  15. browser->is_root = is_root;
  16. ArchiveTabEnum tab = archive_get_tab(browser);
  17. if((item_cnt == 0) && (archive_is_home(browser)) && (tab != ArchiveTabBrowser)) {
  18. archive_switch_tab(browser, browser->last_tab_switch_dir);
  19. } else if(!string_start_with_str_p(browser->path, "/app:")) {
  20. with_view_model(
  21. browser->view, (ArchiveBrowserViewModel * model) {
  22. files_array_reset(model->files);
  23. model->item_cnt = item_cnt;
  24. model->item_idx = (file_idx > 0) ? file_idx : 0;
  25. load_offset =
  26. CLAMP(model->item_idx - FILE_LIST_BUF_LEN / 2, (int32_t)model->item_cnt, 0);
  27. model->array_offset = 0;
  28. model->list_offset = 0;
  29. model->list_loading = true;
  30. model->folder_loading = false;
  31. return false;
  32. });
  33. archive_update_offset(browser);
  34. file_browser_worker_load(browser->worker, load_offset, FILE_LIST_BUF_LEN);
  35. }
  36. }
  37. static void archive_list_load_cb(void* context, uint32_t list_load_offset) {
  38. furi_assert(context);
  39. ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
  40. with_view_model(
  41. browser->view, (ArchiveBrowserViewModel * model) {
  42. files_array_reset(model->files);
  43. model->array_offset = list_load_offset;
  44. return false;
  45. });
  46. }
  47. static void archive_list_item_cb(void* context, string_t item_path, bool is_folder, bool is_last) {
  48. furi_assert(context);
  49. ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
  50. if(!is_last) {
  51. archive_add_file_item(browser, is_folder, string_get_cstr(item_path));
  52. } else {
  53. with_view_model(
  54. browser->view, (ArchiveBrowserViewModel * model) {
  55. model->list_loading = false;
  56. return true;
  57. });
  58. }
  59. }
  60. static void archive_long_load_cb(void* context) {
  61. furi_assert(context);
  62. ArchiveBrowserView* browser = (ArchiveBrowserView*)context;
  63. with_view_model(
  64. browser->view, (ArchiveBrowserViewModel * model) {
  65. model->folder_loading = true;
  66. return true;
  67. });
  68. }
  69. static void archive_file_browser_set_path(
  70. ArchiveBrowserView* browser,
  71. string_t path,
  72. const char* filter_ext,
  73. bool skip_assets) {
  74. furi_assert(browser);
  75. if(!browser->worker_running) {
  76. browser->worker = file_browser_worker_alloc(path, filter_ext, skip_assets);
  77. file_browser_worker_set_callback_context(browser->worker, browser);
  78. file_browser_worker_set_folder_callback(browser->worker, archive_folder_open_cb);
  79. file_browser_worker_set_list_callback(browser->worker, archive_list_load_cb);
  80. file_browser_worker_set_item_callback(browser->worker, archive_list_item_cb);
  81. file_browser_worker_set_long_load_callback(browser->worker, archive_long_load_cb);
  82. browser->worker_running = true;
  83. } else {
  84. furi_assert(browser->worker);
  85. file_browser_worker_set_config(browser->worker, path, filter_ext, skip_assets);
  86. }
  87. }
  88. bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) {
  89. size_t array_size = files_array_size(model->files);
  90. if((idx >= (uint32_t)model->array_offset + array_size) ||
  91. (idx < (uint32_t)model->array_offset)) {
  92. return false;
  93. }
  94. return true;
  95. }
  96. void archive_update_offset(ArchiveBrowserView* browser) {
  97. furi_assert(browser);
  98. with_view_model(
  99. browser->view, (ArchiveBrowserViewModel * model) {
  100. uint16_t bounds = model->item_cnt > 3 ? 2 : model->item_cnt;
  101. if((model->item_cnt > 3u) && (model->item_idx >= ((int32_t)model->item_cnt - 1))) {
  102. model->list_offset = model->item_idx - 3;
  103. } else if(model->list_offset < model->item_idx - bounds) {
  104. model->list_offset =
  105. CLAMP(model->item_idx - 2, (int32_t)model->item_cnt - bounds, 0);
  106. } else if(model->list_offset > model->item_idx - bounds) {
  107. model->list_offset =
  108. CLAMP(model->item_idx - 1, (int32_t)model->item_cnt - bounds, 0);
  109. }
  110. return true;
  111. });
  112. }
  113. void archive_update_focus(ArchiveBrowserView* browser, const char* target) {
  114. furi_assert(browser);
  115. furi_assert(target);
  116. archive_get_items(browser, string_get_cstr(browser->path));
  117. if(!archive_file_get_array_size(browser) && archive_is_home(browser)) {
  118. archive_switch_tab(browser, TAB_RIGHT);
  119. } else {
  120. with_view_model(
  121. browser->view, (ArchiveBrowserViewModel * model) {
  122. uint16_t idx = 0;
  123. while(idx < files_array_size(model->files)) {
  124. ArchiveFile_t* current = files_array_get(model->files, idx);
  125. if(!string_search(current->path, target)) {
  126. model->item_idx = idx + model->array_offset;
  127. break;
  128. }
  129. ++idx;
  130. }
  131. return false;
  132. });
  133. archive_update_offset(browser);
  134. }
  135. }
  136. size_t archive_file_get_array_size(ArchiveBrowserView* browser) {
  137. furi_assert(browser);
  138. uint16_t size = 0;
  139. with_view_model(
  140. browser->view, (ArchiveBrowserViewModel * model) {
  141. size = files_array_size(model->files);
  142. return false;
  143. });
  144. return size;
  145. }
  146. void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count) {
  147. furi_assert(browser);
  148. with_view_model(
  149. browser->view, (ArchiveBrowserViewModel * model) {
  150. model->item_cnt = count;
  151. model->item_idx = CLAMP(model->item_idx, (int32_t)model->item_cnt - 1, 0);
  152. return false;
  153. });
  154. archive_update_offset(browser);
  155. }
  156. void archive_file_array_rm_selected(ArchiveBrowserView* browser) {
  157. furi_assert(browser);
  158. uint32_t items_cnt = 0;
  159. with_view_model(
  160. browser->view, (ArchiveBrowserViewModel * model) {
  161. files_array_remove_v(
  162. model->files,
  163. model->item_idx - model->array_offset,
  164. model->item_idx - model->array_offset + 1);
  165. model->item_cnt--;
  166. model->item_idx = CLAMP(model->item_idx, (int32_t)model->item_cnt - 1, 0);
  167. items_cnt = model->item_cnt;
  168. return false;
  169. });
  170. if((items_cnt == 0) && (archive_is_home(browser))) {
  171. archive_switch_tab(browser, TAB_RIGHT);
  172. }
  173. archive_update_offset(browser);
  174. }
  175. void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir) {
  176. furi_assert(browser);
  177. with_view_model(
  178. browser->view, (ArchiveBrowserViewModel * model) {
  179. ArchiveFile_t temp;
  180. size_t array_size = files_array_size(model->files) - 1;
  181. uint8_t swap_idx = CLAMP((size_t)(model->item_idx + dir), array_size, 0u);
  182. if(model->item_idx == 0 && dir < 0) {
  183. ArchiveFile_t_init(&temp);
  184. files_array_pop_at(&temp, model->files, array_size);
  185. files_array_push_at(model->files, model->item_idx, temp);
  186. ArchiveFile_t_clear(&temp);
  187. } else if(((uint32_t)model->item_idx == array_size) && (dir > 0)) {
  188. ArchiveFile_t_init(&temp);
  189. files_array_pop_at(&temp, model->files, 0);
  190. files_array_push_at(model->files, array_size, temp);
  191. ArchiveFile_t_clear(&temp);
  192. } else {
  193. files_array_swap_at(model->files, model->item_idx, swap_idx);
  194. }
  195. return false;
  196. });
  197. }
  198. void archive_file_array_rm_all(ArchiveBrowserView* browser) {
  199. furi_assert(browser);
  200. with_view_model(
  201. browser->view, (ArchiveBrowserViewModel * model) {
  202. files_array_reset(model->files);
  203. return false;
  204. });
  205. }
  206. void archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) {
  207. furi_assert(browser);
  208. int32_t offset_new = 0;
  209. with_view_model(
  210. browser->view, (ArchiveBrowserViewModel * model) {
  211. if(model->item_cnt > FILE_LIST_BUF_LEN) {
  212. if(dir < 0) {
  213. offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 3;
  214. } else if(dir == 0) {
  215. offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 2;
  216. } else {
  217. offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1;
  218. }
  219. if(offset_new > 0) {
  220. offset_new =
  221. CLAMP(offset_new, (int32_t)model->item_cnt - FILE_LIST_BUF_LEN, 0);
  222. } else {
  223. offset_new = 0;
  224. }
  225. }
  226. return false;
  227. });
  228. file_browser_worker_load(browser->worker, offset_new, FILE_LIST_BUF_LEN);
  229. }
  230. ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) {
  231. furi_assert(browser);
  232. ArchiveFile_t* selected;
  233. with_view_model(
  234. browser->view, (ArchiveBrowserViewModel * model) {
  235. selected = files_array_size(model->files) ?
  236. files_array_get(model->files, model->item_idx - model->array_offset) :
  237. NULL;
  238. return false;
  239. });
  240. return selected;
  241. }
  242. ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) {
  243. furi_assert(browser);
  244. ArchiveFile_t* selected;
  245. with_view_model(
  246. browser->view, (ArchiveBrowserViewModel * model) {
  247. idx = CLAMP(idx - model->array_offset, files_array_size(model->files), 0u);
  248. selected = files_array_size(model->files) ? files_array_get(model->files, idx) : NULL;
  249. return false;
  250. });
  251. return selected;
  252. }
  253. ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) {
  254. furi_assert(browser);
  255. ArchiveTabEnum tab_id;
  256. with_view_model(
  257. browser->view, (ArchiveBrowserViewModel * model) {
  258. tab_id = model->tab_idx;
  259. return false;
  260. });
  261. return tab_id;
  262. }
  263. bool archive_is_home(ArchiveBrowserView* browser) {
  264. furi_assert(browser);
  265. if(browser->is_root) {
  266. return true;
  267. }
  268. const char* default_path = archive_get_default_path(archive_get_tab(browser));
  269. return (string_cmp_str(browser->path, default_path) == 0);
  270. }
  271. const char* archive_get_name(ArchiveBrowserView* browser) {
  272. ArchiveFile_t* selected = archive_get_current_file(browser);
  273. return string_get_cstr(selected->path);
  274. }
  275. void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) {
  276. furi_assert(browser);
  277. with_view_model(
  278. browser->view, (ArchiveBrowserViewModel * model) {
  279. model->tab_idx = tab;
  280. return false;
  281. });
  282. }
  283. void archive_add_app_item(ArchiveBrowserView* browser, const char* name) {
  284. furi_assert(browser);
  285. furi_assert(name);
  286. ArchiveFile_t item;
  287. ArchiveFile_t_init(&item);
  288. string_set_str(item.path, name);
  289. archive_set_file_type(&item, name, false, true);
  290. with_view_model(
  291. browser->view, (ArchiveBrowserViewModel * model) {
  292. files_array_push_back(model->files, item);
  293. model->item_cnt = files_array_size(model->files);
  294. return false;
  295. });
  296. ArchiveFile_t_clear(&item);
  297. }
  298. void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const char* name) {
  299. furi_assert(browser);
  300. furi_assert(name);
  301. ArchiveFile_t item;
  302. ArchiveFile_t_init(&item);
  303. string_init_set_str(item.path, name);
  304. archive_set_file_type(&item, string_get_cstr(browser->path), is_folder, false);
  305. with_view_model(
  306. browser->view, (ArchiveBrowserViewModel * model) {
  307. files_array_push_back(model->files, item);
  308. return false;
  309. });
  310. ArchiveFile_t_clear(&item);
  311. }
  312. void archive_show_file_menu(ArchiveBrowserView* browser, bool show) {
  313. furi_assert(browser);
  314. with_view_model(
  315. browser->view, (ArchiveBrowserViewModel * model) {
  316. if(show) {
  317. if(archive_is_item_in_array(model, model->item_idx)) {
  318. model->menu = true;
  319. model->menu_idx = 0;
  320. ArchiveFile_t* selected =
  321. files_array_get(model->files, model->item_idx - model->array_offset);
  322. selected->fav = archive_is_favorite("%s", string_get_cstr(selected->path));
  323. }
  324. } else {
  325. model->menu = false;
  326. model->menu_idx = 0;
  327. }
  328. return true;
  329. });
  330. }
  331. void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) {
  332. furi_assert(browser);
  333. with_view_model(
  334. browser->view, (ArchiveBrowserViewModel * model) {
  335. model->move_fav = active;
  336. return true;
  337. });
  338. }
  339. static bool archive_is_dir_exists(string_t path) {
  340. if(string_equal_str_p(path, STORAGE_ANY_PATH_PREFIX)) {
  341. return true;
  342. }
  343. bool state = false;
  344. FileInfo file_info;
  345. Storage* storage = furi_record_open(RECORD_STORAGE);
  346. if(storage_common_stat(storage, string_get_cstr(path), &file_info) == FSE_OK) {
  347. if(file_info.flags & FSF_DIRECTORY) {
  348. state = true;
  349. }
  350. }
  351. furi_record_close(RECORD_STORAGE);
  352. return state;
  353. }
  354. void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) {
  355. furi_assert(browser);
  356. ArchiveTabEnum tab = archive_get_tab(browser);
  357. browser->last_tab_switch_dir = key;
  358. if(key == InputKeyLeft) {
  359. tab = ((tab - 1) + ArchiveTabTotal) % ArchiveTabTotal;
  360. } else {
  361. tab = (tab + 1) % ArchiveTabTotal;
  362. }
  363. browser->is_root = true;
  364. archive_set_tab(browser, tab);
  365. string_set_str(browser->path, archive_get_default_path(tab));
  366. bool tab_empty = true;
  367. if(tab == ArchiveTabFavorites) {
  368. if(archive_favorites_count(browser) > 0) {
  369. tab_empty = false;
  370. }
  371. } else if(string_start_with_str_p(browser->path, "/app:")) {
  372. char* app_name = strchr(string_get_cstr(browser->path), ':');
  373. if(app_name != NULL) {
  374. if(archive_app_is_available(browser, string_get_cstr(browser->path))) {
  375. tab_empty = false;
  376. }
  377. }
  378. } else {
  379. tab = archive_get_tab(browser);
  380. if(archive_is_dir_exists(browser->path)) {
  381. bool skip_assets = (strcmp(archive_get_tab_ext(tab), "*") == 0) ? false : true;
  382. archive_file_browser_set_path(
  383. browser, browser->path, archive_get_tab_ext(tab), skip_assets);
  384. tab_empty = false; // Empty check will be performed later
  385. } else {
  386. tab_empty = true;
  387. }
  388. }
  389. if((tab_empty) && (tab != ArchiveTabBrowser)) {
  390. archive_switch_tab(browser, key);
  391. } else {
  392. with_view_model(
  393. browser->view, (ArchiveBrowserViewModel * model) {
  394. model->item_idx = 0;
  395. model->array_offset = 0;
  396. return false;
  397. });
  398. archive_get_items(browser, string_get_cstr(browser->path));
  399. archive_update_offset(browser);
  400. }
  401. }
  402. void archive_enter_dir(ArchiveBrowserView* browser, string_t path) {
  403. furi_assert(browser);
  404. furi_assert(path);
  405. int32_t idx_temp = 0;
  406. with_view_model(
  407. browser->view, (ArchiveBrowserViewModel * model) {
  408. idx_temp = model->item_idx;
  409. return false;
  410. });
  411. string_set(browser->path, path);
  412. file_browser_worker_folder_enter(browser->worker, path, idx_temp);
  413. }
  414. void archive_leave_dir(ArchiveBrowserView* browser) {
  415. furi_assert(browser);
  416. file_browser_worker_folder_exit(browser->worker);
  417. }
  418. void archive_refresh_dir(ArchiveBrowserView* browser) {
  419. furi_assert(browser);
  420. int32_t idx_temp = 0;
  421. with_view_model(
  422. browser->view, (ArchiveBrowserViewModel * model) {
  423. idx_temp = model->item_idx;
  424. return false;
  425. });
  426. file_browser_worker_folder_refresh(browser->worker, idx_temp);
  427. }