flip_store_apps.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. // define the list of categories
  10. char *categories[] = {
  11. "Bluetooth",
  12. "Games",
  13. "GPIO",
  14. "Infrared",
  15. "iButton",
  16. "Media",
  17. "NFC",
  18. "RFID",
  19. "Sub-GHz",
  20. "Tools",
  21. "USB",
  22. };
  23. FlipStoreAppInfo *flip_catalog_alloc()
  24. {
  25. FlipStoreAppInfo *app_catalog = (FlipStoreAppInfo *)malloc(MAX_APP_COUNT * sizeof(FlipStoreAppInfo));
  26. if (!app_catalog)
  27. {
  28. FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
  29. return NULL;
  30. }
  31. for (int i = 0; i < MAX_APP_COUNT; i++)
  32. {
  33. app_catalog[i].app_name = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
  34. if (!app_catalog[i].app_name)
  35. {
  36. FURI_LOG_E(TAG, "Failed to allocate memory for app_name.");
  37. return NULL;
  38. }
  39. app_catalog[i].app_id = (char *)malloc(MAX_APP_NAME_LENGTH * sizeof(char));
  40. if (!app_catalog[i].app_id)
  41. {
  42. FURI_LOG_E(TAG, "Failed to allocate memory for app_id.");
  43. return NULL;
  44. }
  45. app_catalog[i].app_build_id = (char *)malloc(MAX_ID_LENGTH * sizeof(char));
  46. if (!app_catalog[i].app_build_id)
  47. {
  48. FURI_LOG_E(TAG, "Failed to allocate memory for app_build_id.");
  49. return NULL;
  50. }
  51. app_catalog[i].app_version = (char *)malloc(MAX_APP_VERSION_LENGTH * sizeof(char));
  52. if (!app_catalog[i].app_version)
  53. {
  54. FURI_LOG_E(TAG, "Failed to allocate memory for app_version.");
  55. return NULL;
  56. }
  57. app_catalog[i].app_description = (char *)malloc(MAX_APP_DESCRIPTION_LENGTH * sizeof(char));
  58. if (!app_catalog[i].app_description)
  59. {
  60. FURI_LOG_E(TAG, "Failed to allocate memory for app_description.");
  61. return NULL;
  62. }
  63. }
  64. return app_catalog;
  65. }
  66. void flip_catalog_free()
  67. {
  68. if (flip_catalog)
  69. {
  70. free(flip_catalog);
  71. }
  72. }
  73. bool flip_store_process_app_list()
  74. {
  75. // Initialize the flip_catalog
  76. flip_catalog = flip_catalog_alloc();
  77. if (!flip_catalog)
  78. {
  79. FURI_LOG_E(TAG, "Failed to allocate memory for flip_catalog.");
  80. return false;
  81. }
  82. FuriString *feed_data = flipper_http_load_from_file(fhttp.file_path);
  83. if (feed_data == NULL)
  84. {
  85. FURI_LOG_E(TAG, "Failed to load received data from file.");
  86. return false;
  87. }
  88. // free the resources
  89. flipper_http_deinit();
  90. char *data_cstr = (char *)furi_string_get_cstr(feed_data);
  91. if (data_cstr == NULL)
  92. {
  93. FURI_LOG_E(TAG, "Failed to get C-string from FuriString.");
  94. furi_string_free(feed_data);
  95. return false;
  96. }
  97. // Parser state variables
  98. bool in_string = false;
  99. bool is_escaped = false;
  100. bool reading_key = false;
  101. bool reading_value = false;
  102. bool inside_app_object = false;
  103. bool found_name = false, found_id = false, found_build_id = false, found_version = false, found_description = false;
  104. char current_key[MAX_KEY_LENGTH] = {0};
  105. size_t key_index = 0;
  106. char current_value[MAX_VALUE_LENGTH] = {0};
  107. size_t value_index = 0;
  108. int app_count = 0;
  109. enum ObjectState object_state = OBJECT_EXPECT_KEY;
  110. enum
  111. {
  112. STATE_SEARCH_APPS_KEY,
  113. STATE_SEARCH_ARRAY_START,
  114. STATE_READ_ARRAY_ELEMENTS,
  115. STATE_DONE
  116. } state = STATE_SEARCH_APPS_KEY;
  117. // Iterate through the data
  118. for (size_t i = 0; data_cstr[i] != '\0' && state != STATE_DONE; ++i)
  119. {
  120. char c = data_cstr[i];
  121. if (is_escaped)
  122. {
  123. is_escaped = false;
  124. if (reading_key && key_index < MAX_KEY_LENGTH - 1)
  125. {
  126. current_key[key_index++] = c;
  127. }
  128. else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
  129. {
  130. current_value[value_index++] = c;
  131. }
  132. continue;
  133. }
  134. if (c == '\\')
  135. {
  136. is_escaped = true;
  137. continue;
  138. }
  139. if (c == '\"')
  140. {
  141. in_string = !in_string;
  142. if (in_string)
  143. {
  144. if (!reading_key && !reading_value)
  145. {
  146. if (state == STATE_SEARCH_APPS_KEY || object_state == OBJECT_EXPECT_KEY)
  147. {
  148. reading_key = true;
  149. key_index = 0;
  150. current_key[0] = '\0';
  151. }
  152. else if (object_state == OBJECT_EXPECT_VALUE)
  153. {
  154. reading_value = true;
  155. value_index = 0;
  156. current_value[0] = '\0';
  157. }
  158. }
  159. }
  160. else
  161. {
  162. if (reading_key)
  163. {
  164. reading_key = false;
  165. current_key[key_index] = '\0';
  166. if (state == STATE_SEARCH_APPS_KEY && strcmp(current_key, "apps") == 0)
  167. {
  168. state = STATE_SEARCH_ARRAY_START;
  169. }
  170. else if (inside_app_object)
  171. {
  172. object_state = OBJECT_EXPECT_COLON;
  173. }
  174. }
  175. else if (reading_value)
  176. {
  177. reading_value = false;
  178. current_value[value_index] = '\0';
  179. if (inside_app_object)
  180. {
  181. if (strcmp(current_key, "name") == 0)
  182. {
  183. snprintf(flip_catalog[app_count].app_name, MAX_APP_NAME_LENGTH, "%.31s", current_value);
  184. found_name = true;
  185. }
  186. else if (strcmp(current_key, "id") == 0)
  187. {
  188. snprintf(flip_catalog[app_count].app_id, MAX_ID_LENGTH, "%.31s", current_value);
  189. found_id = true;
  190. }
  191. else if (strcmp(current_key, "build_id") == 0)
  192. {
  193. snprintf(flip_catalog[app_count].app_build_id, MAX_ID_LENGTH, "%.31s", current_value);
  194. found_build_id = true;
  195. }
  196. else if (strcmp(current_key, "version") == 0)
  197. {
  198. snprintf(flip_catalog[app_count].app_version, MAX_APP_VERSION_LENGTH, "%.3s", current_value);
  199. found_version = true;
  200. }
  201. else if (strcmp(current_key, "description") == 0)
  202. {
  203. snprintf(flip_catalog[app_count].app_description, MAX_APP_DESCRIPTION_LENGTH, "%.99s", current_value);
  204. found_description = true;
  205. }
  206. if (found_name && found_id && found_build_id && found_version && found_description)
  207. {
  208. app_count++;
  209. if (app_count >= MAX_APP_COUNT)
  210. {
  211. FURI_LOG_I(TAG, "Reached maximum app count.");
  212. state = STATE_DONE;
  213. break;
  214. }
  215. found_name = found_id = found_build_id = found_version = found_description = false;
  216. }
  217. object_state = OBJECT_EXPECT_COMMA_OR_END;
  218. }
  219. }
  220. }
  221. continue;
  222. }
  223. if (in_string)
  224. {
  225. if (reading_key && key_index < MAX_KEY_LENGTH - 1)
  226. {
  227. current_key[key_index++] = c;
  228. }
  229. else if (reading_value && value_index < MAX_VALUE_LENGTH - 1)
  230. {
  231. current_value[value_index++] = c;
  232. }
  233. continue;
  234. }
  235. if (state == STATE_SEARCH_ARRAY_START && c == '[')
  236. {
  237. state = STATE_READ_ARRAY_ELEMENTS;
  238. continue;
  239. }
  240. if (state == STATE_READ_ARRAY_ELEMENTS)
  241. {
  242. if (c == '{')
  243. {
  244. inside_app_object = true;
  245. object_state = OBJECT_EXPECT_KEY;
  246. }
  247. else if (c == '}')
  248. {
  249. inside_app_object = false;
  250. }
  251. else if (c == ':')
  252. {
  253. object_state = OBJECT_EXPECT_VALUE;
  254. }
  255. else if (c == ',')
  256. {
  257. object_state = OBJECT_EXPECT_KEY;
  258. }
  259. else if (c == ']')
  260. {
  261. state = STATE_DONE;
  262. break;
  263. }
  264. }
  265. }
  266. // Clean up
  267. furi_string_free(feed_data);
  268. free(data_cstr);
  269. return app_count > 0;
  270. }
  271. bool flip_store_get_fap_file(char *build_id, uint8_t target, uint16_t api_major, uint16_t api_minor)
  272. {
  273. if (!app_instance)
  274. {
  275. FURI_LOG_E(TAG, "FlipStoreApp is NULL");
  276. return false;
  277. }
  278. // initialize the http
  279. if (!flipper_http_init(flipper_http_rx_callback, app_instance))
  280. {
  281. FURI_LOG_E(TAG, "Failed to initialize FlipperHTTP.");
  282. return false;
  283. }
  284. fhttp.state = IDLE;
  285. char url[128];
  286. fhttp.save_received_data = false;
  287. fhttp.is_bytes_request = true;
  288. 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);
  289. return flipper_http_get_request_bytes(url, "{\"Content-Type\": \"application/octet-stream\"}");
  290. }
  291. bool flip_store_install_app(char *category)
  292. {
  293. // create /apps/FlipStore directory if it doesn't exist
  294. char directory_path[128];
  295. snprintf(directory_path, sizeof(directory_path), STORAGE_EXT_PATH_PREFIX "/apps/%s", category);
  296. // Create the directory
  297. Storage *storage = furi_record_open(RECORD_STORAGE);
  298. storage_common_mkdir(storage, directory_path);
  299. snprintf(fhttp.file_path, sizeof(fhttp.file_path), STORAGE_EXT_PATH_PREFIX "/apps/%s/%s.fap", category, flip_catalog[app_selected_index].app_id);
  300. uint8_t target = furi_hal_version_get_hw_target();
  301. uint16_t api_major, api_minor;
  302. furi_hal_info_get_api_version(&api_major, &api_minor);
  303. if (fhttp.state != INACTIVE && flip_store_get_fap_file(flip_catalog[app_selected_index].app_build_id, target, api_major, api_minor))
  304. {
  305. fhttp.state = RECEIVING;
  306. return true;
  307. }
  308. else
  309. {
  310. FURI_LOG_E(TAG, "Failed to send the request");
  311. flip_store_success = false;
  312. return false;
  313. }
  314. }