loader.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. #include <callback/loader.h>
  2. #include <alloc/alloc.h>
  3. bool loader_view_alloc(void *context)
  4. {
  5. FlipWorldApp *app = (FlipWorldApp *)context;
  6. furi_check(app, "FlipWorldApp is NULL");
  7. if (app->view_loader)
  8. {
  9. FURI_LOG_E(TAG, "View loader already allocated");
  10. return false;
  11. }
  12. if (app->widget_result)
  13. {
  14. FURI_LOG_E(TAG, "Widget result already allocated");
  15. return false;
  16. }
  17. view_dispatcher_set_custom_event_callback(app->view_dispatcher, loader_custom_event_callback);
  18. if (!easy_flipper_set_view(&app->view_loader, FlipWorldViewLoader, loader_draw_callback, NULL, callback_to_submenu, &app->view_dispatcher, app))
  19. {
  20. return false;
  21. }
  22. loader_init(app->view_loader);
  23. return easy_flipper_set_widget(&app->widget_result, FlipWorldViewWidgetResult, "", callback_to_submenu, &app->view_dispatcher);
  24. }
  25. void loader_view_free(void *context)
  26. {
  27. FlipWorldApp *app = (FlipWorldApp *)context;
  28. furi_check(app, "FlipWorldApp is NULL");
  29. // Free Widget(s)
  30. if (app->widget_result)
  31. {
  32. view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewWidgetResult);
  33. widget_free(app->widget_result);
  34. app->widget_result = NULL;
  35. }
  36. // Free View(s)
  37. if (app->view_loader)
  38. {
  39. view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewLoader);
  40. loader_free_model(app->view_loader);
  41. view_free(app->view_loader);
  42. app->view_loader = NULL;
  43. }
  44. }
  45. static void loader_error_draw(Canvas *canvas, DataLoaderModel *model)
  46. {
  47. if (canvas == NULL)
  48. {
  49. FURI_LOG_E(TAG, "error_draw - canvas is NULL");
  50. DEV_CRASH();
  51. return;
  52. }
  53. if (model->fhttp->last_response != NULL)
  54. {
  55. if (strstr(model->fhttp->last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  56. {
  57. canvas_clear(canvas);
  58. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  59. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  60. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  61. }
  62. else if (strstr(model->fhttp->last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  63. {
  64. canvas_clear(canvas);
  65. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  66. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  67. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  68. }
  69. else if (strstr(model->fhttp->last_response, "[ERROR] GET request failed or returned empty data.") != NULL)
  70. {
  71. canvas_clear(canvas);
  72. canvas_draw_str(canvas, 0, 10, "[ERROR] WiFi error.");
  73. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  74. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  75. }
  76. else if (strstr(model->fhttp->last_response, "[PONG]") != NULL)
  77. {
  78. canvas_clear(canvas);
  79. canvas_draw_str(canvas, 0, 10, "[STATUS]Connecting to AP...");
  80. }
  81. else
  82. {
  83. canvas_clear(canvas);
  84. FURI_LOG_E(TAG, "Received an error: %s", model->fhttp->last_response);
  85. canvas_draw_str(canvas, 0, 10, "[ERROR] Unusual error...");
  86. canvas_draw_str(canvas, 0, 60, "Press BACK and retry.");
  87. }
  88. }
  89. else
  90. {
  91. canvas_clear(canvas);
  92. canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
  93. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  94. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  95. }
  96. }
  97. static void loader_process_callback(void *context)
  98. {
  99. if (context == NULL)
  100. {
  101. FURI_LOG_E(TAG, "loader_process_callback - context is NULL");
  102. DEV_CRASH();
  103. return;
  104. }
  105. FlipWorldApp *app = (FlipWorldApp *)context;
  106. View *view = app->view_loader;
  107. DataState current_data_state;
  108. DataLoaderModel *loader_model = NULL;
  109. with_view_model(
  110. view,
  111. DataLoaderModel * model,
  112. {
  113. current_data_state = model->data_state;
  114. loader_model = model;
  115. },
  116. false);
  117. if (!loader_model || !loader_model->fhttp)
  118. {
  119. FURI_LOG_E(TAG, "Model or fhttp is NULL");
  120. DEV_CRASH();
  121. return;
  122. }
  123. if (current_data_state == DataStateInitial)
  124. {
  125. with_view_model(
  126. view,
  127. DataLoaderModel * model,
  128. {
  129. model->data_state = DataStateRequested;
  130. DataLoaderFetch fetch = model->fetcher;
  131. if (fetch == NULL)
  132. {
  133. FURI_LOG_E(TAG, "Model doesn't have Fetch function assigned.");
  134. model->data_state = DataStateError;
  135. return;
  136. }
  137. // Clear any previous responses
  138. strncpy(model->fhttp->last_response, "", 1);
  139. bool request_status = fetch(model);
  140. if (!request_status)
  141. {
  142. model->data_state = DataStateError;
  143. }
  144. },
  145. true);
  146. }
  147. else if (current_data_state == DataStateRequested || current_data_state == DataStateError)
  148. {
  149. if (loader_model->fhttp->state == IDLE && loader_model->fhttp->last_response != NULL)
  150. {
  151. if (strstr(loader_model->fhttp->last_response, "[PONG]") != NULL)
  152. {
  153. FURI_LOG_DEV(TAG, "PONG received.");
  154. }
  155. else if (strncmp(loader_model->fhttp->last_response, "[SUCCESS]", 9))
  156. {
  157. FURI_LOG_DEV(TAG, "SUCCESS received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  158. }
  159. else if (strncmp(loader_model->fhttp->last_response, "[ERROR]", 9))
  160. {
  161. FURI_LOG_DEV(TAG, "ERROR received. %s", loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  162. }
  163. else if (strlen(loader_model->fhttp->last_response))
  164. {
  165. // Still waiting on response
  166. }
  167. else
  168. {
  169. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateReceived; }, true);
  170. }
  171. }
  172. else if (loader_model->fhttp->state == SENDING || loader_model->fhttp->state == RECEIVING)
  173. {
  174. // continue waiting
  175. }
  176. else if (loader_model->fhttp->state == INACTIVE)
  177. {
  178. // inactive. try again
  179. }
  180. else if (loader_model->fhttp->state == ISSUE)
  181. {
  182. with_view_model(view, DataLoaderModel * model, { model->data_state = DataStateError; }, true);
  183. }
  184. else
  185. {
  186. FURI_LOG_DEV(TAG, "Unexpected state: %d lastresp: %s", loader_model->fhttp->state, loader_model->fhttp->last_response ? loader_model->fhttp->last_response : "NULL");
  187. DEV_CRASH();
  188. }
  189. }
  190. else if (current_data_state == DataStateReceived)
  191. {
  192. with_view_model(
  193. view,
  194. DataLoaderModel * model,
  195. {
  196. char *data_text;
  197. if (model->parser == NULL)
  198. {
  199. data_text = NULL;
  200. FURI_LOG_DEV(TAG, "Parser is NULL");
  201. DEV_CRASH();
  202. }
  203. else
  204. {
  205. data_text = model->parser(model);
  206. }
  207. FURI_LOG_DEV(TAG, "Parsed data: %s\r\ntext: %s", model->fhttp->last_response ? model->fhttp->last_response : "NULL", data_text ? data_text : "NULL");
  208. model->data_text = data_text;
  209. if (data_text == NULL)
  210. {
  211. model->data_state = DataStateParseError;
  212. }
  213. else
  214. {
  215. model->data_state = DataStateParsed;
  216. }
  217. },
  218. true);
  219. }
  220. else if (current_data_state == DataStateParsed)
  221. {
  222. with_view_model(
  223. view,
  224. DataLoaderModel * model,
  225. {
  226. if (++model->request_index < model->request_count)
  227. {
  228. model->data_state = DataStateInitial;
  229. }
  230. else
  231. {
  232. loader_widget_set_text(model->data_text != NULL ? model->data_text : "", &app->widget_result);
  233. if (model->data_text != NULL)
  234. {
  235. free(model->data_text);
  236. model->data_text = NULL;
  237. }
  238. view_set_previous_callback(widget_get_view(app->widget_result), model->back_callback);
  239. view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewWidgetResult);
  240. }
  241. },
  242. true);
  243. }
  244. }
  245. bool loader_custom_event_callback(void *context, uint32_t index)
  246. {
  247. if (context == NULL)
  248. {
  249. FURI_LOG_E(TAG, "custom_event_callback - context is NULL");
  250. DEV_CRASH();
  251. return false;
  252. }
  253. switch (index)
  254. {
  255. case FlipWorldCustomEventProcess:
  256. loader_process_callback(context);
  257. return true;
  258. default:
  259. FURI_LOG_DEV(TAG, "custom_event_callback. Unknown index: %ld", index);
  260. return false;
  261. }
  262. }
  263. void loader_draw_callback(Canvas *canvas, void *model)
  264. {
  265. if (!canvas || !model)
  266. {
  267. FURI_LOG_E(TAG, "loader_draw_callback - canvas or model is NULL");
  268. return;
  269. }
  270. DataLoaderModel *data_loader_model = (DataLoaderModel *)model;
  271. HTTPState http_state = data_loader_model->fhttp->state;
  272. DataState data_state = data_loader_model->data_state;
  273. char *title = data_loader_model->title;
  274. canvas_set_font(canvas, FontSecondary);
  275. if (http_state == INACTIVE)
  276. {
  277. canvas_draw_str(canvas, 0, 7, "Wifi Dev Board disconnected.");
  278. canvas_draw_str(canvas, 0, 17, "Please connect to the board.");
  279. canvas_draw_str(canvas, 0, 32, "If your board is connected,");
  280. canvas_draw_str(canvas, 0, 42, "make sure you have flashed");
  281. canvas_draw_str(canvas, 0, 52, "your WiFi Devboard with the");
  282. canvas_draw_str(canvas, 0, 62, "latest FlipperHTTP flash.");
  283. return;
  284. }
  285. if (data_state == DataStateError || data_state == DataStateParseError)
  286. {
  287. loader_error_draw(canvas, data_loader_model);
  288. return;
  289. }
  290. canvas_draw_str(canvas, 0, 7, title);
  291. canvas_draw_str(canvas, 0, 17, "Loading...");
  292. if (data_state == DataStateInitial)
  293. {
  294. return;
  295. }
  296. if (http_state == SENDING)
  297. {
  298. canvas_draw_str(canvas, 0, 27, "Fetching...");
  299. return;
  300. }
  301. if (http_state == RECEIVING || data_state == DataStateRequested)
  302. {
  303. canvas_draw_str(canvas, 0, 27, "Receiving...");
  304. return;
  305. }
  306. if (http_state == IDLE && data_state == DataStateReceived)
  307. {
  308. canvas_draw_str(canvas, 0, 27, "Processing...");
  309. return;
  310. }
  311. if (http_state == IDLE && data_state == DataStateParsed)
  312. {
  313. canvas_draw_str(canvas, 0, 27, "Processed...");
  314. return;
  315. }
  316. }
  317. static void loader_timer_callback(void *context)
  318. {
  319. if (context == NULL)
  320. {
  321. FURI_LOG_E(TAG, "loader_timer_callback - context is NULL");
  322. DEV_CRASH();
  323. return;
  324. }
  325. FlipWorldApp *app = (FlipWorldApp *)context;
  326. view_dispatcher_send_custom_event(app->view_dispatcher, FlipWorldCustomEventProcess);
  327. }
  328. static void loader_on_enter(void *context)
  329. {
  330. if (context == NULL)
  331. {
  332. FURI_LOG_E(TAG, "loader_on_enter - context is NULL");
  333. DEV_CRASH();
  334. return;
  335. }
  336. FlipWorldApp *app = (FlipWorldApp *)context;
  337. View *view = app->view_loader;
  338. with_view_model(
  339. view,
  340. DataLoaderModel * model,
  341. {
  342. view_set_previous_callback(view, model->back_callback);
  343. if (model->timer == NULL)
  344. {
  345. model->timer = furi_timer_alloc(loader_timer_callback, FuriTimerTypePeriodic, app);
  346. }
  347. furi_timer_start(model->timer, 250);
  348. },
  349. true);
  350. }
  351. static void loader_on_exit(void *context)
  352. {
  353. if (context == NULL)
  354. {
  355. FURI_LOG_E(TAG, "loader_on_exit - context is NULL");
  356. DEV_CRASH();
  357. return;
  358. }
  359. FlipWorldApp *app = (FlipWorldApp *)context;
  360. View *view = app->view_loader;
  361. with_view_model(
  362. view,
  363. DataLoaderModel * model,
  364. {
  365. if (model->timer)
  366. {
  367. furi_timer_stop(model->timer);
  368. }
  369. },
  370. false);
  371. }
  372. void loader_init(View *view)
  373. {
  374. if (view == NULL)
  375. {
  376. FURI_LOG_E(TAG, "loader_init - view is NULL");
  377. DEV_CRASH();
  378. return;
  379. }
  380. view_allocate_model(view, ViewModelTypeLocking, sizeof(DataLoaderModel));
  381. view_set_enter_callback(view, loader_on_enter);
  382. view_set_exit_callback(view, loader_on_exit);
  383. }
  384. void loader_free_model(View *view)
  385. {
  386. if (view == NULL)
  387. {
  388. FURI_LOG_E(TAG, "loader_free_model - view is NULL");
  389. DEV_CRASH();
  390. return;
  391. }
  392. with_view_model(
  393. view,
  394. DataLoaderModel * model,
  395. {
  396. if (model->timer)
  397. {
  398. furi_timer_free(model->timer);
  399. model->timer = NULL;
  400. }
  401. if (model->parser_context)
  402. {
  403. // do not free the context here, it is the app context
  404. // free(model->parser_context);
  405. // model->parser_context = NULL;
  406. }
  407. if (model->fhttp)
  408. {
  409. flipper_http_free(model->fhttp);
  410. model->fhttp = NULL;
  411. }
  412. },
  413. false);
  414. }
  415. void loader_switch_to_view(FlipWorldApp *app, char *title, DataLoaderFetch fetcher, DataLoaderParser parser, size_t request_count, ViewNavigationCallback back, uint32_t view_id)
  416. {
  417. if (app == NULL)
  418. {
  419. FURI_LOG_E(TAG, "loader_switch_to_view - app is NULL");
  420. DEV_CRASH();
  421. return;
  422. }
  423. View *view = app->view_loader;
  424. if (view == NULL)
  425. {
  426. FURI_LOG_E(TAG, "loader_switch_to_view - view is NULL");
  427. DEV_CRASH();
  428. return;
  429. }
  430. with_view_model(
  431. view,
  432. DataLoaderModel * model,
  433. {
  434. model->title = title;
  435. model->fetcher = fetcher;
  436. model->parser = parser;
  437. model->request_index = 0;
  438. model->request_count = request_count;
  439. model->back_callback = back;
  440. model->data_state = DataStateInitial;
  441. model->data_text = NULL;
  442. //
  443. model->parser_context = app;
  444. if (!model->fhttp)
  445. {
  446. model->fhttp = flipper_http_alloc();
  447. }
  448. },
  449. true);
  450. view_dispatcher_switch_to_view(app->view_dispatcher, view_id);
  451. }
  452. void loader_widget_set_text(char *message, Widget **widget)
  453. {
  454. if (widget == NULL)
  455. {
  456. FURI_LOG_E(TAG, "set_widget_text - widget is NULL");
  457. DEV_CRASH();
  458. return;
  459. }
  460. if (message == NULL)
  461. {
  462. FURI_LOG_E(TAG, "set_widget_text - message is NULL");
  463. DEV_CRASH();
  464. return;
  465. }
  466. widget_reset(*widget);
  467. uint32_t message_length = strlen(message); // Length of the message
  468. uint32_t i = 0; // Index tracker
  469. uint32_t formatted_index = 0; // Tracker for where we are in the formatted message
  470. char *formatted_message; // Buffer to hold the final formatted message
  471. // Allocate buffer with double the message length plus one for safety
  472. if (!easy_flipper_set_buffer(&formatted_message, message_length * 2 + 1))
  473. {
  474. return;
  475. }
  476. while (i < message_length)
  477. {
  478. uint32_t max_line_length = 31; // Maximum characters per line
  479. uint32_t remaining_length = message_length - i; // Remaining characters
  480. uint32_t line_length = (remaining_length < max_line_length) ? remaining_length : max_line_length;
  481. // Check for newline character within the current segment
  482. uint32_t newline_pos = i;
  483. bool found_newline = false;
  484. for (; newline_pos < i + line_length && newline_pos < message_length; newline_pos++)
  485. {
  486. if (message[newline_pos] == '\n')
  487. {
  488. found_newline = true;
  489. break;
  490. }
  491. }
  492. if (found_newline)
  493. {
  494. // If newline found, set line_length up to the newline
  495. line_length = newline_pos - i;
  496. }
  497. // Temporary buffer to hold the current line
  498. char line[32];
  499. strncpy(line, message + i, line_length);
  500. line[line_length] = '\0';
  501. // If newline was found, skip it for the next iteration
  502. if (found_newline)
  503. {
  504. i += line_length + 1; // +1 to skip the '\n' character
  505. }
  506. else
  507. {
  508. // Check if the line ends in the middle of a word and adjust accordingly
  509. if (line_length == max_line_length && message[i + line_length] != '\0' && message[i + line_length] != ' ')
  510. {
  511. // Find the last space within the current line to avoid breaking a word
  512. char *last_space = strrchr(line, ' ');
  513. if (last_space != NULL)
  514. {
  515. // Adjust the line_length to avoid cutting the word
  516. line_length = last_space - line;
  517. line[line_length] = '\0'; // Null-terminate at the space
  518. }
  519. }
  520. // Move the index forward by the determined line_length
  521. i += line_length;
  522. // Skip any spaces at the beginning of the next line
  523. while (i < message_length && message[i] == ' ')
  524. {
  525. i++;
  526. }
  527. }
  528. // Manually copy the fixed line into the formatted_message buffer
  529. for (uint32_t j = 0; j < line_length; j++)
  530. {
  531. formatted_message[formatted_index++] = line[j];
  532. }
  533. // Add a newline character for line spacing
  534. formatted_message[formatted_index++] = '\n';
  535. }
  536. // Null-terminate the formatted_message
  537. formatted_message[formatted_index] = '\0';
  538. // Add the formatted message to the widget
  539. widget_add_text_scroll_element(*widget, 0, 0, 128, 64, formatted_message);
  540. }