sd-filesystem.c 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. #include "fatfs.h"
  2. #include "filesystem-api.h"
  3. #include "sd-filesystem.h"
  4. #include "menu/menu.h"
  5. #include "menu/menu_item.h"
  6. #include "cli/cli.h"
  7. #include "api-hal-sd.h"
  8. #include <gui/modules/dialog_ex.h>
  9. #include <gui/modules/file_select.h>
  10. typedef enum {
  11. FST_FAT12 = FS_FAT12,
  12. FST_FAT16 = FS_FAT16,
  13. FST_FAT32 = FS_FAT32,
  14. FST_EXFAT = FS_EXFAT,
  15. } SDFsType;
  16. typedef struct {
  17. SDFsType fs_type;
  18. uint32_t kb_total;
  19. uint32_t kb_free;
  20. uint16_t cluster_size;
  21. uint16_t sector_size;
  22. char label[34];
  23. SDError error;
  24. } SDInfo;
  25. typedef enum {
  26. SdAppEventTypeBack,
  27. SdAppEventTypeOK,
  28. SdAppEventTypeFormat,
  29. SdAppEventTypeInfo,
  30. SdAppEventTypeEject,
  31. SdAppEventTypeFileSelect,
  32. SdAppEventTypeCheckError,
  33. SdAppEventTypeShowError,
  34. } SdAppEventType;
  35. typedef struct {
  36. const char* path;
  37. const char* extension;
  38. char* result;
  39. uint8_t result_size;
  40. const char* selected_filename;
  41. } SdAppFileSelectData;
  42. typedef struct {
  43. bool result;
  44. } SdAppFileSelectResultEvent;
  45. typedef struct {
  46. SdAppEventType type;
  47. union {
  48. SdAppFileSelectData file_select_data;
  49. const char* error_text;
  50. } payload;
  51. } SdAppEvent;
  52. static void sd_icon_draw_callback(Canvas* canvas, void* context);
  53. bool sd_api_file_select(
  54. SdApp* sd_app,
  55. const char* path,
  56. const char* extension,
  57. char* result,
  58. uint8_t result_size,
  59. const char* selected_filename);
  60. void sd_api_check_error(SdApp* sd_app);
  61. void sd_api_show_error(SdApp* sd_app, const char* error_text);
  62. /******************* Allocators *******************/
  63. FS_Api* fs_api_alloc() {
  64. FS_Api* fs_api = furi_alloc(sizeof(FS_Api));
  65. // fill file api
  66. fs_api->file.open = fs_file_open;
  67. fs_api->file.close = fs_file_close;
  68. fs_api->file.read = fs_file_read;
  69. fs_api->file.write = fs_file_write;
  70. fs_api->file.seek = fs_file_seek;
  71. fs_api->file.tell = fs_file_tell;
  72. fs_api->file.truncate = fs_file_truncate;
  73. fs_api->file.size = fs_file_size;
  74. fs_api->file.sync = fs_file_sync;
  75. fs_api->file.eof = fs_file_eof;
  76. // fill dir api
  77. fs_api->dir.open = fs_dir_open;
  78. fs_api->dir.close = fs_dir_close;
  79. fs_api->dir.read = fs_dir_read;
  80. fs_api->dir.rewind = fs_dir_rewind;
  81. // fill common api
  82. fs_api->common.info = fs_common_info;
  83. fs_api->common.remove = fs_common_remove;
  84. fs_api->common.rename = fs_common_rename;
  85. fs_api->common.set_attr = fs_common_set_attr;
  86. fs_api->common.mkdir = fs_common_mkdir;
  87. fs_api->common.set_time = fs_common_set_time;
  88. fs_api->common.get_fs_info = fs_get_fs_info;
  89. // fill errors api
  90. fs_api->error.get_desc = fs_error_get_desc;
  91. fs_api->error.get_internal_desc = fs_error_get_internal_desc;
  92. return fs_api;
  93. }
  94. SdApp* sd_app_alloc() {
  95. SdApp* sd_app = furi_alloc(sizeof(SdApp));
  96. // init inner fs data
  97. furi_check(_fs_init(&sd_app->info));
  98. sd_app->event_queue = osMessageQueueNew(8, sizeof(SdAppEvent), NULL);
  99. sd_app->result_receiver = osMessageQueueNew(1, sizeof(SdAppFileSelectResultEvent), NULL);
  100. // init icon view_port
  101. sd_app->icon.view_port = view_port_alloc();
  102. sd_app->icon.mounted = &I_SDcardMounted_11x8;
  103. sd_app->icon.fail = &I_SDcardFail_11x8;
  104. view_port_set_width(sd_app->icon.view_port, icon_get_width(sd_app->icon.mounted));
  105. view_port_draw_callback_set(sd_app->icon.view_port, sd_icon_draw_callback, sd_app);
  106. view_port_enabled_set(sd_app->icon.view_port, false);
  107. // init sd card api
  108. sd_app->sd_card_api.context = sd_app;
  109. sd_app->sd_card_api.file_select = sd_api_file_select;
  110. sd_app->sd_card_api.check_error = sd_api_check_error;
  111. sd_app->sd_card_api.show_error = sd_api_show_error;
  112. sd_app->sd_app_state = SdAppStateBackground;
  113. string_init(sd_app->text_holder);
  114. return sd_app;
  115. }
  116. /******************* Internal sd card related fns *******************/
  117. void get_sd_info(SdApp* sd_app, SDInfo* sd_info) {
  118. uint32_t free_clusters, free_sectors, total_sectors;
  119. FATFS* fs;
  120. // clean data
  121. memset(sd_info, 0, sizeof(SDInfo));
  122. // get fs info
  123. _fs_lock(&sd_app->info);
  124. sd_info->error = f_getlabel(sd_app->info.path, sd_info->label, NULL);
  125. if(sd_info->error == SD_OK) {
  126. sd_info->error = f_getfree(sd_app->info.path, &free_clusters, &fs);
  127. }
  128. _fs_unlock(&sd_app->info);
  129. if(sd_info->error == SD_OK) {
  130. // calculate size
  131. total_sectors = (fs->n_fatent - 2) * fs->csize;
  132. free_sectors = free_clusters * fs->csize;
  133. uint16_t sector_size = _MAX_SS;
  134. #if _MAX_SS != _MIN_SS
  135. sector_size = fs->ssize;
  136. #endif
  137. sd_info->fs_type = fs->fs_type;
  138. sd_info->kb_total = total_sectors / 1024 * sector_size;
  139. sd_info->kb_free = free_sectors / 1024 * sector_size;
  140. sd_info->cluster_size = fs->csize;
  141. sd_info->sector_size = sector_size;
  142. }
  143. }
  144. const char* get_fs_type_text(SDFsType fs_type) {
  145. switch(fs_type) {
  146. case(FST_FAT12):
  147. return "FAT12";
  148. break;
  149. case(FST_FAT16):
  150. return "FAT16";
  151. break;
  152. case(FST_FAT32):
  153. return "FAT32";
  154. break;
  155. case(FST_EXFAT):
  156. return "EXFAT";
  157. break;
  158. default:
  159. return "UNKNOWN";
  160. break;
  161. }
  162. }
  163. void app_sd_format_internal(SdApp* sd_app) {
  164. uint8_t* work_area;
  165. _fs_lock(&sd_app->info);
  166. work_area = malloc(_MAX_SS);
  167. if(work_area == NULL) {
  168. sd_app->info.status = SD_NOT_ENOUGH_CORE;
  169. } else {
  170. sd_app->info.status = f_mkfs(sd_app->info.path, FM_ANY, 0, work_area, _MAX_SS);
  171. free(work_area);
  172. if(sd_app->info.status == SD_OK) {
  173. // set label and mount card
  174. f_setlabel("Flipper SD");
  175. sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1);
  176. }
  177. }
  178. _fs_unlock(&sd_app->info);
  179. }
  180. const NotificationSequence sd_sequence_success = {
  181. &message_green_255,
  182. &message_delay_50,
  183. &message_green_0,
  184. &message_delay_50,
  185. &message_green_255,
  186. &message_delay_50,
  187. &message_green_0,
  188. &message_delay_50,
  189. &message_green_255,
  190. &message_delay_50,
  191. &message_green_0,
  192. &message_delay_50,
  193. NULL,
  194. };
  195. const NotificationSequence sd_sequence_error = {
  196. &message_red_255,
  197. &message_delay_50,
  198. &message_red_0,
  199. &message_delay_50,
  200. &message_red_255,
  201. &message_delay_50,
  202. &message_red_0,
  203. &message_delay_50,
  204. &message_red_255,
  205. &message_delay_50,
  206. &message_red_0,
  207. &message_delay_50,
  208. NULL,
  209. };
  210. const NotificationSequence sd_sequence_eject = {
  211. &message_blue_255,
  212. &message_delay_50,
  213. &message_blue_0,
  214. &message_delay_50,
  215. &message_blue_255,
  216. &message_delay_50,
  217. &message_blue_0,
  218. &message_delay_50,
  219. &message_blue_255,
  220. &message_delay_50,
  221. &message_blue_0,
  222. &message_delay_50,
  223. NULL,
  224. };
  225. const NotificationSequence sd_sequence_wait = {
  226. &message_red_255,
  227. &message_blue_255,
  228. &message_do_not_reset,
  229. NULL,
  230. };
  231. const NotificationSequence sd_sequence_wait_off = {
  232. &message_red_0,
  233. &message_blue_0,
  234. NULL,
  235. };
  236. void app_sd_notify_wait(SdApp* sd_app) {
  237. notification_message(sd_app->notifications, &sd_sequence_wait);
  238. }
  239. void app_sd_notify_wait_off(SdApp* sd_app) {
  240. notification_message(sd_app->notifications, &sd_sequence_wait_off);
  241. }
  242. void app_sd_notify_success(SdApp* sd_app) {
  243. notification_message(sd_app->notifications, &sd_sequence_success);
  244. }
  245. void app_sd_notify_eject(SdApp* sd_app) {
  246. notification_message(sd_app->notifications, &sd_sequence_eject);
  247. }
  248. void app_sd_notify_error(SdApp* sd_app) {
  249. notification_message(sd_app->notifications, &sd_sequence_error);
  250. }
  251. bool app_sd_mount_card(SdApp* sd_app) {
  252. bool result = false;
  253. const uint8_t max_init_counts = 10;
  254. uint8_t counter = max_init_counts;
  255. uint8_t bsp_result;
  256. _fs_lock(&sd_app->info);
  257. while(result == false && counter > 0 && hal_sd_detect()) {
  258. app_sd_notify_wait(sd_app);
  259. if((counter % 10) == 0) {
  260. // power reset sd card
  261. bsp_result = BSP_SD_Init(true);
  262. } else {
  263. bsp_result = BSP_SD_Init(false);
  264. }
  265. if(bsp_result) {
  266. // bsp error
  267. sd_app->info.status = SD_LOW_LEVEL_ERR;
  268. } else {
  269. sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1);
  270. if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) {
  271. FATFS* fs;
  272. uint32_t free_clusters;
  273. sd_app->info.status = f_getfree(sd_app->info.path, &free_clusters, &fs);
  274. if(sd_app->info.status == SD_OK || sd_app->info.status == SD_NO_FILESYSTEM) {
  275. result = true;
  276. }
  277. }
  278. }
  279. app_sd_notify_wait_off(sd_app);
  280. if(!result) {
  281. delay(1000);
  282. FURI_LOG_E(
  283. "SD FILESYSTEM",
  284. "init(%d), error: %s\r\n",
  285. counter,
  286. fs_error_get_internal_desc(sd_app->info.status));
  287. counter--;
  288. }
  289. }
  290. _fs_unlock(&sd_app->info);
  291. return result;
  292. }
  293. void app_sd_unmount_card(SdApp* sd_app) {
  294. _fs_lock(&sd_app->info);
  295. // set status
  296. sd_app->info.status = SD_NO_CARD;
  297. view_port_enabled_set(sd_app->icon.view_port, false);
  298. // close files
  299. for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) {
  300. FileData* filedata = &sd_app->info.files[index];
  301. if(filedata->thread_id != NULL) {
  302. if(filedata->is_dir) {
  303. f_closedir(&filedata->data.dir);
  304. } else {
  305. f_close(&filedata->data.file);
  306. }
  307. filedata->thread_id = NULL;
  308. }
  309. }
  310. // unmount volume
  311. f_mount(0, sd_app->info.path, 0);
  312. _fs_unlock(&sd_app->info);
  313. }
  314. bool app_sd_make_path(const char* path) {
  315. furi_assert(path);
  316. if(*path) {
  317. char* file_path = strdup(path);
  318. // Make parent directories
  319. for(char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) {
  320. *p = '\0';
  321. SDError result = f_mkdir(file_path);
  322. if(result != SD_OK) {
  323. if(result != SD_EXIST) {
  324. *p = '/';
  325. free(file_path);
  326. return false;
  327. }
  328. }
  329. *p = '/';
  330. }
  331. // Make origin directory
  332. SDError result = f_mkdir(file_path);
  333. if(result != SD_OK) {
  334. if(result != SD_EXIST) {
  335. free(file_path);
  336. return false;
  337. }
  338. }
  339. free(file_path);
  340. }
  341. return true;
  342. }
  343. /******************* Draw callbacks *******************/
  344. static void sd_icon_draw_callback(Canvas* canvas, void* context) {
  345. furi_assert(canvas);
  346. furi_assert(context);
  347. SdApp* sd_app = context;
  348. switch(sd_app->info.status) {
  349. case SD_NO_CARD:
  350. break;
  351. case SD_OK:
  352. canvas_draw_icon(canvas, 0, 0, sd_app->icon.mounted);
  353. break;
  354. default:
  355. canvas_draw_icon(canvas, 0, 0, sd_app->icon.fail);
  356. break;
  357. }
  358. }
  359. /******************* SD-api callbacks *******************/
  360. bool sd_api_file_select(
  361. SdApp* sd_app,
  362. const char* path,
  363. const char* extension,
  364. char* result,
  365. uint8_t result_size,
  366. const char* selected_filename) {
  367. bool retval = false;
  368. SdAppEvent message = {
  369. .type = SdAppEventTypeFileSelect,
  370. .payload = {
  371. .file_select_data = {
  372. .path = path,
  373. .extension = extension,
  374. .result = result,
  375. .result_size = result_size,
  376. .selected_filename = selected_filename,
  377. }}};
  378. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  379. SdAppFileSelectResultEvent event;
  380. while(1) {
  381. osStatus_t event_status =
  382. osMessageQueueGet(sd_app->result_receiver, &event, NULL, osWaitForever);
  383. if(event_status == osOK) {
  384. retval = event.result;
  385. break;
  386. }
  387. }
  388. if(!retval) {
  389. sd_api_check_error(sd_app);
  390. }
  391. return retval;
  392. }
  393. void sd_api_check_error(SdApp* sd_app) {
  394. SdAppEvent message = {.type = SdAppEventTypeCheckError};
  395. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  396. }
  397. void sd_api_show_error(SdApp* sd_app, const char* error_text) {
  398. SdAppEvent message = {.type = SdAppEventTypeShowError, .payload.error_text = error_text};
  399. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  400. }
  401. /******************* View callbacks *******************/
  402. void app_view_back_callback(void* context) {
  403. furi_assert(context);
  404. SdApp* sd_app = context;
  405. SdAppEvent message = {.type = SdAppEventTypeBack};
  406. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  407. }
  408. void app_view_dialog_callback(DialogExResult result, void* context) {
  409. furi_assert(context);
  410. SdApp* sd_app = context;
  411. if(result == DialogExResultLeft) {
  412. SdAppEvent message = {.type = SdAppEventTypeBack};
  413. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  414. } else if(result == DialogExResultRight) {
  415. SdAppEvent message = {.type = SdAppEventTypeOK};
  416. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  417. }
  418. }
  419. void app_view_file_select_callback(bool result, void* context) {
  420. furi_assert(context);
  421. SdApp* sd_app = context;
  422. if(result) {
  423. SdAppEvent message = {.type = SdAppEventTypeOK};
  424. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  425. } else {
  426. SdAppEvent message = {.type = SdAppEventTypeBack};
  427. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  428. }
  429. }
  430. /******************* Menu callbacks *******************/
  431. void app_sd_info_callback(void* context) {
  432. furi_assert(context);
  433. SdApp* sd_app = context;
  434. SdAppEvent message = {.type = SdAppEventTypeInfo};
  435. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  436. }
  437. void app_sd_format_callback(void* context) {
  438. furi_assert(context);
  439. SdApp* sd_app = context;
  440. SdAppEvent message = {.type = SdAppEventTypeFormat};
  441. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  442. }
  443. void app_sd_eject_callback(void* context) {
  444. furi_assert(context);
  445. SdApp* sd_app = context;
  446. SdAppEvent message = {.type = SdAppEventTypeEject};
  447. furi_check(osMessageQueuePut(sd_app->event_queue, &message, 0, osWaitForever) == osOK);
  448. }
  449. /******************* Cli callbacks *******************/
  450. static void cli_sd_format(Cli* cli, string_t args, void* _ctx) {
  451. SdApp* sd_app = (SdApp*)_ctx;
  452. printf("Formatting SD card, all data will be lost. Are you sure (y/n)?\r\n");
  453. char c = cli_getc(cli);
  454. if(c == 'y' || c == 'Y') {
  455. printf("Formatting, please wait...\r\n");
  456. // format card
  457. app_sd_format_internal(sd_app);
  458. if(sd_app->info.status != SD_OK) {
  459. printf("SD card format error: ");
  460. printf(fs_error_get_internal_desc(sd_app->info.status));
  461. printf("\r\n");
  462. } else {
  463. printf("SD card was successfully formatted.\r\n");
  464. }
  465. } else {
  466. printf("Cancelled.\r\n");
  467. }
  468. }
  469. static void cli_sd_info(Cli* cli, string_t args, void* _ctx) {
  470. SdApp* sd_app = (SdApp*)_ctx;
  471. SDInfo sd_info;
  472. get_sd_info(sd_app, &sd_info);
  473. printf("SD Status: %s\r\n", fs_error_get_internal_desc(sd_app->info.status));
  474. if(sd_info.error == SD_OK) {
  475. const char* fs_type = get_fs_type_text(sd_info.fs_type);
  476. printf("Label: %s\r\n", sd_info.label);
  477. printf("Filesystem: %s\r\n", fs_type);
  478. printf("Cluster: %d sectors\r\n", sd_info.cluster_size);
  479. printf("Sector: %d bytes\r\n", sd_info.sector_size);
  480. printf("%lu KB total\r\n", sd_info.kb_total);
  481. printf("%lu KB free\r\n", sd_info.kb_free);
  482. } else {
  483. printf("SD Info error: %s\r\n", fs_error_get_internal_desc(sd_info.error));
  484. }
  485. }
  486. /******************* Test *******************/
  487. bool try_to_alloc_view_holder(SdApp* sd_app, Gui* gui) {
  488. bool result = false;
  489. _fs_lock(&sd_app->info);
  490. if(sd_app->view_holder == NULL) {
  491. sd_app->view_holder = view_holder_alloc();
  492. view_holder_attach_to_gui(sd_app->view_holder, gui);
  493. view_holder_set_back_callback(sd_app->view_holder, app_view_back_callback, sd_app);
  494. result = true;
  495. }
  496. _fs_unlock(&sd_app->info);
  497. return result;
  498. }
  499. DialogEx* alloc_and_attach_dialog(SdApp* sd_app) {
  500. DialogEx* dialog = dialog_ex_alloc();
  501. dialog_ex_set_context(dialog, sd_app);
  502. dialog_ex_set_result_callback(dialog, app_view_dialog_callback);
  503. view_holder_set_view(sd_app->view_holder, dialog_ex_get_view(dialog));
  504. view_holder_set_free_callback(sd_app->view_holder, (FreeCallback)dialog_ex_free, dialog);
  505. return dialog;
  506. }
  507. FileSelect* alloc_and_attach_file_select(SdApp* sd_app) {
  508. FileSelect* file_select = file_select_alloc();
  509. file_select_set_callback(file_select, app_view_file_select_callback, sd_app);
  510. view_holder_set_view(sd_app->view_holder, file_select_get_view(file_select));
  511. view_holder_set_free_callback(
  512. sd_app->view_holder, (FreeCallback)file_select_free, file_select);
  513. return file_select;
  514. }
  515. void free_view_holder(SdApp* sd_app) {
  516. _fs_lock(&sd_app->info);
  517. if(sd_app->view_holder) {
  518. view_holder_free(sd_app->view_holder);
  519. sd_app->view_holder = NULL;
  520. }
  521. _fs_unlock(&sd_app->info);
  522. }
  523. void app_reset_state(SdApp* sd_app) {
  524. _fs_lock(&sd_app->info);
  525. if(sd_app->view_holder) {
  526. view_holder_stop(sd_app->view_holder);
  527. }
  528. _fs_unlock(&sd_app->info);
  529. free_view_holder(sd_app);
  530. string_set_str(sd_app->text_holder, "");
  531. sd_app->sd_app_state = SdAppStateBackground;
  532. }
  533. /******************* Main app *******************/
  534. int32_t sd_filesystem(void* p) {
  535. SdApp* sd_app = sd_app_alloc();
  536. FS_Api* fs_api = fs_api_alloc();
  537. Gui* gui = furi_record_open("gui");
  538. Cli* cli = furi_record_open("cli");
  539. sd_app->notifications = furi_record_open("notification");
  540. ValueMutex* menu_vm = furi_record_open("menu");
  541. gui_add_view_port(gui, sd_app->icon.view_port, GuiLayerStatusBarLeft);
  542. cli_add_command(cli, "sd_format", CliCommandFlagDefault, cli_sd_format, sd_app);
  543. cli_add_command(cli, "sd_info", CliCommandFlagDefault, cli_sd_info, sd_app);
  544. // add api record
  545. furi_record_create("sdcard", fs_api);
  546. // init menu
  547. // TODO menu icon
  548. MenuItem* menu_item;
  549. menu_item = menu_item_alloc_menu("SD Card", icon_animation_alloc(&I_SDcardMounted_11x8));
  550. menu_item_subitem_add(
  551. menu_item, menu_item_alloc_function("Info", NULL, app_sd_info_callback, sd_app));
  552. menu_item_subitem_add(
  553. menu_item, menu_item_alloc_function("Format", NULL, app_sd_format_callback, sd_app));
  554. menu_item_subitem_add(
  555. menu_item, menu_item_alloc_function("Eject", NULL, app_sd_eject_callback, sd_app));
  556. // add item to menu
  557. furi_check(menu_vm);
  558. with_value_mutex(
  559. menu_vm, (Menu * menu) { menu_item_add(menu, menu_item); });
  560. FURI_LOG_I("SD FILESYSTEM", "start");
  561. // add api record
  562. furi_record_create("sdcard", fs_api);
  563. furi_record_create("sdcard-ex", &sd_app->sd_card_api);
  564. // sd card cycle
  565. bool sd_was_present = true;
  566. // init detect pins
  567. hal_sd_detect_init();
  568. while(true) {
  569. if(sd_was_present) {
  570. if(hal_sd_detect()) {
  571. FURI_LOG_I("SD FILESYSTEM", "Card detected");
  572. app_sd_mount_card(sd_app);
  573. if(sd_app->info.status != SD_OK) {
  574. FURI_LOG_E(
  575. "SD FILESYSTEM",
  576. "sd init error: %s",
  577. fs_error_get_internal_desc(sd_app->info.status));
  578. app_sd_notify_error(sd_app);
  579. } else {
  580. FURI_LOG_I("SD FILESYSTEM", "sd init ok");
  581. app_sd_notify_success(sd_app);
  582. }
  583. view_port_enabled_set(sd_app->icon.view_port, true);
  584. sd_was_present = false;
  585. if(!hal_sd_detect()) {
  586. FURI_LOG_I("SD FILESYSTEM", "card removed");
  587. view_port_enabled_set(sd_app->icon.view_port, false);
  588. app_sd_unmount_card(sd_app);
  589. sd_was_present = true;
  590. }
  591. }
  592. } else {
  593. if(!hal_sd_detect()) {
  594. FURI_LOG_I("SD FILESYSTEM", "card removed");
  595. view_port_enabled_set(sd_app->icon.view_port, false);
  596. app_sd_unmount_card(sd_app);
  597. sd_was_present = true;
  598. app_sd_notify_eject(sd_app);
  599. }
  600. }
  601. SdAppEvent event;
  602. osStatus_t event_status = osMessageQueueGet(sd_app->event_queue, &event, NULL, 1000);
  603. const uint8_t y_1_line = 32;
  604. const uint8_t y_2_line = 32;
  605. const uint8_t y_4_line = 26;
  606. if(event_status == osOK) {
  607. switch(event.type) {
  608. case SdAppEventTypeOK:
  609. switch(sd_app->sd_app_state) {
  610. case SdAppStateFormat: {
  611. DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder);
  612. dialog_ex_set_left_button_text(dialog, NULL);
  613. dialog_ex_set_right_button_text(dialog, NULL);
  614. dialog_ex_set_header(
  615. dialog, "Formatting...", 64, y_1_line, AlignCenter, AlignCenter);
  616. dialog_ex_set_text(dialog, NULL, 0, 0, AlignCenter, AlignCenter);
  617. sd_app->sd_app_state = SdAppStateFormatInProgress;
  618. delay(100);
  619. app_sd_format_internal(sd_app);
  620. app_sd_notify_success(sd_app);
  621. dialog_ex_set_left_button_text(dialog, "Back");
  622. dialog_ex_set_header(
  623. dialog, "SD card formatted", 64, 10, AlignCenter, AlignCenter);
  624. dialog_ex_set_text(
  625. dialog, "Press back to return", 64, y_1_line, AlignCenter, AlignCenter);
  626. sd_app->sd_app_state = SdAppStateFormatCompleted;
  627. }; break;
  628. case SdAppStateEject: {
  629. DialogEx* dialog = view_holder_get_free_context(sd_app->view_holder);
  630. dialog_ex_set_right_button_text(dialog, NULL);
  631. dialog_ex_set_header(
  632. dialog, "SD card ejected", 64, 10, AlignCenter, AlignCenter);
  633. dialog_ex_set_text(
  634. dialog,
  635. "Now the SD card\ncan be removed.",
  636. 64,
  637. y_2_line,
  638. AlignCenter,
  639. AlignCenter);
  640. sd_app->sd_app_state = SdAppStateEjected;
  641. app_sd_unmount_card(sd_app);
  642. app_sd_notify_eject(sd_app);
  643. }; break;
  644. case SdAppStateFileSelect: {
  645. SdAppFileSelectResultEvent retval = {.result = true};
  646. furi_check(
  647. osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
  648. osOK);
  649. app_reset_state(sd_app);
  650. }; break;
  651. default:
  652. break;
  653. }
  654. break;
  655. case SdAppEventTypeBack:
  656. switch(sd_app->sd_app_state) {
  657. case SdAppStateFormatInProgress:
  658. break;
  659. case SdAppStateFileSelect: {
  660. SdAppFileSelectResultEvent retval = {.result = false};
  661. furi_check(
  662. osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
  663. osOK);
  664. app_reset_state(sd_app);
  665. }; break;
  666. default:
  667. app_reset_state(sd_app);
  668. break;
  669. }
  670. break;
  671. case SdAppEventTypeFormat:
  672. if(try_to_alloc_view_holder(sd_app, gui)) {
  673. DialogEx* dialog = alloc_and_attach_dialog(sd_app);
  674. dialog_ex_set_left_button_text(dialog, "Back");
  675. dialog_ex_set_right_button_text(dialog, "Format");
  676. dialog_ex_set_header(
  677. dialog, "Format SD card?", 64, 10, AlignCenter, AlignCenter);
  678. dialog_ex_set_text(
  679. dialog, "All data will be lost.", 64, y_1_line, AlignCenter, AlignCenter);
  680. view_holder_start(sd_app->view_holder);
  681. sd_app->sd_app_state = SdAppStateFormat;
  682. }
  683. break;
  684. case SdAppEventTypeInfo:
  685. if(try_to_alloc_view_holder(sd_app, gui)) {
  686. DialogEx* dialog = alloc_and_attach_dialog(sd_app);
  687. dialog_ex_set_left_button_text(dialog, "Back");
  688. SDInfo sd_info;
  689. get_sd_info(sd_app, &sd_info);
  690. if(sd_info.error == SD_OK) {
  691. string_printf(
  692. sd_app->text_holder,
  693. "Label: %s\nType: %s\n%lu KB total\n%lu KB free",
  694. sd_info.label,
  695. get_fs_type_text(sd_info.fs_type),
  696. sd_info.kb_total,
  697. sd_info.kb_free);
  698. dialog_ex_set_text(
  699. dialog,
  700. string_get_cstr(sd_app->text_holder),
  701. 4,
  702. y_4_line,
  703. AlignLeft,
  704. AlignCenter);
  705. view_holder_start(sd_app->view_holder);
  706. } else {
  707. string_printf(
  708. sd_app->text_holder,
  709. "SD status: %s\n SD info: %s",
  710. fs_error_get_internal_desc(_fs_status(&sd_app->info)),
  711. fs_error_get_internal_desc(sd_info.error));
  712. dialog_ex_set_header(dialog, "Error", 64, 10, AlignCenter, AlignCenter);
  713. dialog_ex_set_text(
  714. dialog,
  715. string_get_cstr(sd_app->text_holder),
  716. 64,
  717. y_2_line,
  718. AlignCenter,
  719. AlignCenter);
  720. view_holder_start(sd_app->view_holder);
  721. }
  722. sd_app->sd_app_state = SdAppStateInfo;
  723. }
  724. break;
  725. case SdAppEventTypeEject:
  726. if(try_to_alloc_view_holder(sd_app, gui)) {
  727. DialogEx* dialog = alloc_and_attach_dialog(sd_app);
  728. dialog_ex_set_left_button_text(dialog, "Back");
  729. dialog_ex_set_right_button_text(dialog, "Eject");
  730. dialog_ex_set_header(
  731. dialog, "Eject SD card?", 64, 10, AlignCenter, AlignCenter);
  732. dialog_ex_set_text(
  733. dialog,
  734. "SD card will be\nunavailable",
  735. 64,
  736. y_2_line,
  737. AlignCenter,
  738. AlignCenter);
  739. view_holder_start(sd_app->view_holder);
  740. sd_app->sd_app_state = SdAppStateEject;
  741. }
  742. break;
  743. case SdAppEventTypeFileSelect:
  744. if(!app_sd_make_path(event.payload.file_select_data.path)) {
  745. SdAppFileSelectResultEvent retval = {.result = false};
  746. furi_check(
  747. osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
  748. osOK);
  749. break;
  750. }
  751. if(try_to_alloc_view_holder(sd_app, gui)) {
  752. FileSelect* file_select = alloc_and_attach_file_select(sd_app);
  753. SdAppFileSelectData* file_select_data = &event.payload.file_select_data;
  754. file_select_set_api(file_select, fs_api);
  755. file_select_set_filter(
  756. file_select, file_select_data->path, file_select_data->extension);
  757. file_select_set_result_buffer(
  758. file_select, file_select_data->result, file_select_data->result_size);
  759. if(!file_select_init(file_select)) {
  760. SdAppFileSelectResultEvent retval = {.result = false};
  761. furi_check(
  762. osMessageQueuePut(
  763. sd_app->result_receiver, &retval, 0, osWaitForever) == osOK);
  764. app_reset_state(sd_app);
  765. } else {
  766. sd_app->sd_app_state = SdAppStateFileSelect;
  767. if(file_select_data->selected_filename != NULL) {
  768. file_select_set_selected_file(
  769. file_select, file_select_data->selected_filename);
  770. }
  771. view_holder_start(sd_app->view_holder);
  772. }
  773. } else {
  774. SdAppFileSelectResultEvent retval = {.result = false};
  775. furi_check(
  776. osMessageQueuePut(sd_app->result_receiver, &retval, 0, osWaitForever) ==
  777. osOK);
  778. }
  779. break;
  780. case SdAppEventTypeCheckError:
  781. if(sd_app->info.status != SD_OK) {
  782. if(try_to_alloc_view_holder(sd_app, gui)) {
  783. DialogEx* dialog = alloc_and_attach_dialog(sd_app);
  784. dialog_ex_set_left_button_text(dialog, "Back");
  785. if(sd_app->info.status == SD_NO_CARD) {
  786. dialog_ex_set_text(
  787. dialog,
  788. "SD card\nnot found",
  789. 88,
  790. y_1_line,
  791. AlignCenter,
  792. AlignCenter);
  793. dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43);
  794. } else {
  795. dialog_ex_set_text(
  796. dialog, "SD card\nerror", 88, y_1_line, AlignCenter, AlignCenter);
  797. dialog_ex_set_icon(dialog, 5, 10, &I_SDError_43x35);
  798. }
  799. sd_app->sd_app_state = SdAppStateCheckError;
  800. view_holder_start(sd_app->view_holder);
  801. }
  802. }
  803. break;
  804. case SdAppEventTypeShowError:
  805. if(try_to_alloc_view_holder(sd_app, gui)) {
  806. DialogEx* dialog = alloc_and_attach_dialog(sd_app);
  807. dialog_ex_set_left_button_text(dialog, "Back");
  808. dialog_ex_set_text(
  809. dialog, event.payload.error_text, 88, y_1_line, AlignCenter, AlignCenter);
  810. dialog_ex_set_icon(dialog, 5, 6, &I_SDQuestion_35x43);
  811. sd_app->sd_app_state = SdAppStateShowError;
  812. view_holder_start(sd_app->view_holder);
  813. }
  814. break;
  815. }
  816. }
  817. }
  818. return 0;
  819. }