flip_store_apps.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. #include <apps/flip_store_apps.h>
  2. FlipStoreAppInfo *flip_catalog = NULL;
  3. uint32_t app_selected_index = 0;
  4. bool flip_store_sent_request = false;
  5. bool flip_store_success = false;
  6. bool flip_store_saved_data = false;
  7. bool flip_store_saved_success = false;
  8. uint32_t flip_store_category_index = 0;
  9. FlipStoreAppInfo *flip_catalog_alloc()
  10. {
  11. FlipStoreAppInfo *app_catalog = (FlipStoreAppInfo *)malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
  12. if (!app_catalog)
  13. {
  14. FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
  15. return NULL;
  16. }
  17. for (int i = 0; i < MAX_APP_COUNT; i++)
  18. {
  19. app_catalog[i].app_name = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
  20. if (!app_catalog[i].app_name)
  21. {
  22. FURI_LOG_E(TAG, "Failed to allocate memory for app_name.");
  23. return NULL;
  24. }
  25. app_catalog[i].app_id = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
  26. if (!app_catalog[i].app_id)
  27. {
  28. FURI_LOG_E(TAG, "Failed to allocate memory for app_id.");
  29. return NULL;
  30. }
  31. app_catalog[i].app_build_id = (char *)malloc(MAX_ID_LENGTH * sizeof(char));
  32. if (!app_catalog[i].app_build_id)
  33. {
  34. FURI_LOG_E(TAG, "Failed to allocate memory for app_build_id.");
  35. return NULL;
  36. }
  37. app_catalog[i].app_version = (char *)malloc(MAX_APP_VERSION_LENGTH * sizeof(char));
  38. if (!app_catalog[i].app_version)
  39. {
  40. FURI_LOG_E(TAG, "Failed to allocate memory for app_version.");
  41. return NULL;
  42. }
  43. app_catalog[i].app_description = (char *)malloc(MAX_APP_DESCRIPTION_LENGTH * sizeof(char));
  44. if (!app_catalog[i].app_description)
  45. {
  46. FURI_LOG_E(TAG, "Failed to allocate memory for app_description.");
  47. return NULL;
  48. }
  49. }
  50. return app_catalog;
  51. }
  52. void flip_catalog_free()
  53. {
  54. if (!flip_catalog)
  55. {
  56. return;
  57. }
  58. for (int i = 0; i < MAX_APP_COUNT; i++)
  59. {
  60. if (flip_catalog[i].app_name)
  61. {
  62. free(flip_catalog[i].app_name);
  63. }
  64. if (flip_catalog[i].app_id)
  65. {
  66. free(flip_catalog[i].app_id);
  67. }
  68. if (flip_catalog[i].app_build_id)
  69. {
  70. free(flip_catalog[i].app_build_id);
  71. }
  72. if (flip_catalog[i].app_version)
  73. {
  74. free(flip_catalog[i].app_version);
  75. }
  76. if (flip_catalog[i].app_description)
  77. {
  78. free(flip_catalog[i].app_description);
  79. }
  80. }
  81. }
  82. bool flip_store_process_app_list()
  83. {
  84. // Initialize the flip_catalog
  85. flip_catalog = flip_catalog_alloc();
  86. if (!flip_catalog)
  87. {
  88. FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
  89. return false;
  90. }
  91. FuriString *feed_data = flipper_http_load_from_file(fhttp.file_path);
  92. if (feed_data == NULL)
  93. {
  94. FURI_LOG_E(TAG, "Failed to load received data from file.");
  95. return false;
  96. }
  97. char *data_cstr = (char *)furi_string_get_cstr(feed_data);
  98. if (data_cstr == NULL)
  99. {
  100. FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
  101. furi_string_free(feed_data);
  102. return false;
  103. }
  104. // Parser state variables
  105. bool in_string = false;
  106. bool is_escaped = false;
  107. bool reading_key = false;
  108. bool reading_value = false;
  109. bool inside_app_object = false;
  110. bool found_name = false, found_id = false, found_build_id = false, found_version = false, found_description = false;
  111. char current_key[MAX_KEY_LENGTH] = {0};
  112. size_t key_index = 0;
  113. char current_value[MAX_VALUE_LENGTH] = {0};
  114. size_t value_index = 0;
  115. int app_count = 0;
  116. enum ObjectState object_state = OBJECT_EXPECT_KEY;
  117. enum
  118. {
  119. STATE_SEARCH_APPS_KEY,
  120. STATE_SEARCH_ARRAY_START,
  121. STATE_READ_ARRAY_ELEMENTS,
  122. STATE_DONE
  123. } state = STATE_SEARCH_APPS_KEY;
  124. // Iterate through the data
  125. for (size_t i = 0; data_cstr[i] != '\0' && state != STATE_DONE; ++i)
  126. {
  127. char c = data_cstr[i];
  128. if (is_escaped)
  129. {
  130. is_escaped = false;
  131. if (reading_key && key_index < MAX_KEY_LENGTH - 1)
  132. {
  133. current_key[key_index++] = c;
  134. }
  135. else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
  136. {
  137. current_value[value_index++] = c;
  138. }
  139. continue;
  140. }
  141. if (c == '\\')
  142. {
  143. is_escaped = true;
  144. continue;
  145. }
  146. if (c == '\"')
  147. {
  148. in_string = !in_string;
  149. if (in_string)
  150. {
  151. if (!reading_key && !reading_value)
  152. {
  153. if (state == STATE_SEARCH_APPS_KEY || object_state == OBJECT_EXPECT_KEY)
  154. {
  155. reading_key = true;
  156. key_index = 0;
  157. current_key[0] = '\0';
  158. }
  159. else if (object_state == OBJECT_EXPECT_VALUE)
  160. {
  161. reading_value = true;
  162. value_index = 0;
  163. current_value[0] = '\0';
  164. }
  165. }
  166. }
  167. else
  168. {
  169. if (reading_key)
  170. {
  171. reading_key = false;
  172. current_key[key_index] = '\0';
  173. if (state == STATE_SEARCH_APPS_KEY && strcmp(current_key, "apps") == 0)
  174. {
  175. state = STATE_SEARCH_ARRAY_START;
  176. }
  177. else if (inside_app_object)
  178. {
  179. object_state = OBJECT_EXPECT_COLON;
  180. }
  181. }
  182. else if (reading_value)
  183. {
  184. reading_value = false;
  185. current_value[value_index] = '\0';
  186. if (inside_app_object)
  187. {
  188. if (strcmp(current_key, "name") == 0)
  189. {
  190. snprintf(flip_catalog[app_count].app_name, MAX_APP_NAME_LENGTH, "%.31s", current_value);
  191. found_name = true;
  192. }
  193. else if (strcmp(current_key, "id") == 0)
  194. {
  195. snprintf(flip_catalog[app_count].app_id, MAX_ID_LENGTH, "%.31s", current_value);
  196. found_id = true;
  197. }
  198. else if (strcmp(current_key, "build_id") == 0)
  199. {
  200. snprintf(flip_catalog[app_count].app_build_id, MAX_ID_LENGTH, "%.31s", current_value);
  201. found_build_id = true;
  202. }
  203. else if (strcmp(current_key, "version") == 0)
  204. {
  205. snprintf(flip_catalog[app_count].app_version, MAX_APP_VERSION_LENGTH, "%.3s", current_value);
  206. found_version = true;
  207. }
  208. else if (strcmp(current_key, "description") == 0)
  209. {
  210. snprintf(flip_catalog[app_count].app_description, MAX_APP_DESCRIPTION_LENGTH, "%.99s", current_value);
  211. found_description = true;
  212. }
  213. if (found_name && found_id && found_build_id && found_version && found_description)
  214. {
  215. app_count++;
  216. if (app_count >= MAX_APP_COUNT)
  217. {
  218. FURI_LOG_I(TAG, "Reached maximum app count.");
  219. state = STATE_DONE;
  220. break;
  221. }
  222. found_name = found_id = found_build_id = found_version = found_description = false;
  223. }
  224. object_state = OBJECT_EXPECT_COMMA_OR_END;
  225. }
  226. }
  227. }
  228. continue;
  229. }
  230. if (in_string)
  231. {
  232. if (reading_key && key_index < MAX_KEY_LENGTH - 1)
  233. {
  234. current_key[key_index++] = c;
  235. }
  236. else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
  237. {
  238. current_value[value_index++] = c;
  239. }
  240. continue;
  241. }
  242. if (state == STATE_SEARCH_ARRAY_START && c == '[')
  243. {
  244. state = STATE_READ_ARRAY_ELEMENTS;
  245. continue;
  246. }
  247. if (state == STATE_READ_ARRAY_ELEMENTS)
  248. {
  249. if (c == '{')
  250. {
  251. inside_app_object = true;
  252. object_state = OBJECT_EXPECT_KEY;
  253. }
  254. else if (c == '}')
  255. {
  256. inside_app_object = false;
  257. }
  258. else if (c == ':')
  259. {
  260. object_state = OBJECT_EXPECT_VALUE;
  261. }
  262. else if (c == ',')
  263. {
  264. object_state = OBJECT_EXPECT_KEY;
  265. }
  266. else if (c == ']')
  267. {
  268. state = STATE_DONE;
  269. break;
  270. }
  271. }
  272. }
  273. // Clean up
  274. furi_string_free(feed_data);
  275. free(data_cstr);
  276. return app_count > 0;
  277. }
  278. bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
  279. {
  280. char url[128];
  281. fhttp.save_received_data = false;
  282. fhttp.is_bytes_request = true;
  283. snprintf(url, sizeof(url), "https://catalog.flipperzero.one/api/v0/application/version/%s/build/compatible?target=f%d&api=%d.%d", build_id, target, api_major, api_minor);
  284. char *headers = jsmn("Content-Type", "application/octet-stream");
  285. bool sent_request = flipper_http_get_request_bytes(url, headers);
  286. free(headers);
  287. return sent_request;
  288. }
  289. void flip_store_request_error(Canvas *canvas)
  290. {
  291. if (fhttp.last_response != NULL)
  292. {
  293. if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  294. {
  295. canvas_clear(canvas);
  296. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  297. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  298. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  299. }
  300. else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  301. {
  302. canvas_clear(canvas);
  303. canvas_draw_str(canvas, 0, 10, "[ERROR] Not connected to Wifi.");
  304. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  305. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  306. }
  307. else
  308. {
  309. FURI_LOG_E(TAG, "Received an error: %s", fhttp.last_response);
  310. canvas_draw_str(canvas, 0, 42, "Unusual error...");
  311. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  312. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  313. }
  314. }
  315. else
  316. {
  317. canvas_clear(canvas);
  318. canvas_draw_str(canvas, 0, 10, "[ERROR] Unknown error.");
  319. canvas_draw_str(canvas, 0, 50, "Update your WiFi settings.");
  320. canvas_draw_str(canvas, 0, 60, "Press BACK to return.");
  321. }
  322. }
  323. // function to handle the entire installation process "asynchronously"
  324. bool flip_store_install_app(Canvas *canvas, char *category)
  325. {
  326. // create /apps/FlipStore directory if it doesn't exist
  327. char directory_path[128];
  328. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s", category);
  329. // Create the directory
  330. Storage *storage = furi_record_open(RECORD_STORAGE);
  331. storage_common_mkdir(storage, directory_path);
  332. // Adjusted to access flip_catalog as an array of structures
  333. char installation_text[64];
  334. snprintf(installation_text, sizeof(installation_text), "Installing %s", flip_catalog[app_selected_index].app_name);
  335. snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
  336. canvas_draw_str(canvas, 0, 10, installation_text);
  337. canvas_draw_str(canvas, 0, 20, "Sending request..");
  338. uint8_t target = furi_hal_version_get_hw_target();
  339. uint16_t api_major, api_minor;
  340. furi_hal_info_get_api_version(&api_major, &api_minor);
  341. if (fhttp.state != INACTIVE && flip_store_get_fap_file(flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor))
  342. {
  343. canvas_draw_str(canvas, 0, 30, "Request sent.");
  344. fhttp.state = RECEIVING;
  345. canvas_draw_str(canvas, 0, 40, "Receiving...");
  346. }
  347. else
  348. {
  349. FURI_LOG_E(TAG, "Failed to send the request");
  350. flip_store_success = false;
  351. return false;
  352. }
  353. while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
  354. {
  355. // Wait for the feed to be received
  356. furi_delay_ms(10);
  357. }
  358. // furi_timer_stop(fhttp.get_timeout_timer);
  359. if (fhttp.state == ISSUE)
  360. {
  361. flip_store_request_error(canvas);
  362. flip_store_success = false;
  363. return false;
  364. }
  365. flip_store_success = true;
  366. return true;
  367. }
  368. // process the app list and return view
  369. int32_t flip_store_handle_app_list(FlipStoreApp *app, int32_t success_view, char *category, Submenu **submenu)
  370. {
  371. // reset the flip_catalog
  372. flip_catalog_free();
  373. if (!app)
  374. {
  375. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  376. return FlipStoreViewPopup;
  377. }
  378. snprintf(
  379. fhttp.file_path,
  380. sizeof(fhttp.file_path),
  381. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s.json", category);
  382. fhttp.save_received_data = true;
  383. fhttp.is_bytes_request = false;
  384. char url[128];
  385. snprintf(url, sizeof(url), "https://www.flipsocial.net/api/flipper/apps/%s/max/", category);
  386. // async call to the app list with timer
  387. if (fhttp.state != INACTIVE && flipper_http_get_request_with_headers(url, "{\"Content-Type\":\"application/json\"}"))
  388. {
  389. furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
  390. fhttp.state = RECEIVING;
  391. }
  392. else
  393. {
  394. FURI_LOG_E(TAG, "Failed to send the request");
  395. fhttp.state = ISSUE;
  396. return FlipStoreViewPopup;
  397. }
  398. while (fhttp.state == RECEIVING && furi_timer_is_running(fhttp.get_timeout_timer) > 0)
  399. {
  400. // Wait for the feed to be received
  401. furi_delay_ms(10);
  402. }
  403. furi_timer_stop(fhttp.get_timeout_timer);
  404. if (fhttp.state == ISSUE)
  405. {
  406. FURI_LOG_E(TAG, "Failed to receive data");
  407. if (fhttp.last_response == NULL)
  408. {
  409. if (fhttp.last_response != NULL)
  410. {
  411. if (strstr(fhttp.last_response, "[ERROR] Not connected to Wifi. Failed to reconnect.") != NULL)
  412. {
  413. popup_set_text(app->popup, "[ERROR] WiFi Disconnected.\n\n\nUpdate your WiFi settings.\nPress BACK to return.", 0, 10, AlignLeft, AlignTop);
  414. }
  415. else if (strstr(fhttp.last_response, "[ERROR] Failed to connect to Wifi.") != NULL)
  416. {
  417. popup_set_text(app->popup, "[ERROR] WiFi Disconnected.\n\n\nUpdate your WiFi settings.\nPress BACK to return.", 0, 10, AlignLeft, AlignTop);
  418. }
  419. else
  420. {
  421. popup_set_text(app->popup, fhttp.last_response, 0, 50, AlignLeft, AlignTop);
  422. }
  423. }
  424. else
  425. {
  426. popup_set_text(app->popup, "[ERROR] Unknown Error.\n\n\nUpdate your WiFi settings.\nPress BACK to return.", 0, 10, AlignLeft, AlignTop);
  427. }
  428. return FlipStoreViewPopup;
  429. }
  430. else
  431. {
  432. popup_set_text(app->popup, "Failed to received data.", 0, 50, AlignLeft, AlignTop);
  433. return FlipStoreViewPopup;
  434. }
  435. }
  436. else
  437. {
  438. // process the app list
  439. if (flip_store_process_app_list() && submenu && flip_catalog)
  440. {
  441. submenu_reset(*submenu);
  442. // add each app name to submenu
  443. for (int i = 0; i < MAX_APP_COUNT; i++)
  444. {
  445. if (strlen(flip_catalog[i].app_name) > 0)
  446. {
  447. submenu_add_item(*submenu, flip_catalog[i].app_name, FlipStoreSubmenuIndexStartAppList + i, callback_submenu_choices, app);
  448. }
  449. else
  450. {
  451. break;
  452. }
  453. }
  454. return success_view;
  455. }
  456. else
  457. {
  458. FURI_LOG_E(TAG, "Failed to process the app list");
  459. popup_set_text(app->popup, "Failed to process the app list", 0, 10, AlignLeft, AlignTop);
  460. return FlipStoreViewPopup;
  461. }
  462. }
  463. }