flip_store_github.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. // flip_store_github.c
  2. #include <github/flip_store_github.h>
  3. #include <flip_storage/flip_store_storage.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <stdbool.h>
  7. #define MAX_RECURSION_DEPTH 5 // maximum allowed "/" characters in repo path
  8. #define MAX_PENDING_FOLDERS 20 // maximum number of folders to process iteratively
  9. // --- Pending Folder Queue for iterative folder processing ---
  10. typedef struct
  11. {
  12. char file_path[256]; // Folder JSON file path (downloaded folder info)
  13. char repo[256]; // New repository path, e.g. "repo/subfolder"
  14. } PendingFolder;
  15. static PendingFolder pendingFolders[MAX_PENDING_FOLDERS];
  16. static int pendingFoldersCount = 0;
  17. static int count_char(const char *s, char c)
  18. {
  19. int count = 0;
  20. for (; *s; s++)
  21. {
  22. if (*s == c)
  23. {
  24. count++;
  25. }
  26. }
  27. return count;
  28. }
  29. // Enqueue a folder for later processing.
  30. // currentRepo is the repository path received so far (e.g. "repo" at top level)
  31. // folderName is the name of the folder (e.g. "alloc") that is appended.
  32. static bool enqueue_folder(const char *file_path, const char *currentRepo, const char *folderName)
  33. {
  34. if (pendingFoldersCount >= MAX_PENDING_FOLDERS)
  35. {
  36. FURI_LOG_E(TAG, "Pending folder queue full!");
  37. return false;
  38. }
  39. PendingFolder *pf = &pendingFolders[pendingFoldersCount++];
  40. strncpy(pf->file_path, file_path, sizeof(pf->file_path) - 1);
  41. pf->file_path[sizeof(pf->file_path) - 1] = '\0';
  42. // New repo path = currentRepo + "/" + folderName.
  43. snprintf(pf->repo, sizeof(pf->repo), "%s/%s", currentRepo, folderName);
  44. FURI_LOG_I(TAG, "Enqueued folder: %s (file: %s)", pf->repo, pf->file_path);
  45. return true;
  46. }
  47. // Process all enqueued folders iteratively.
  48. static void process_pending_folders(const char *author)
  49. {
  50. int i = 0;
  51. while (i < pendingFoldersCount)
  52. {
  53. PendingFolder pf = pendingFolders[i];
  54. FURI_LOG_I(TAG, "Processing pending folder: %s", pf.repo);
  55. if (!flip_store_parse_github_contents(pf.file_path, author, pf.repo))
  56. {
  57. FURI_LOG_E(TAG, "Failed to process pending folder: %s", pf.repo);
  58. }
  59. i++;
  60. }
  61. pendingFoldersCount = 0; // Reset queue after processing.
  62. }
  63. // Helper to download a file from Github and save it to storage.
  64. bool flip_store_download_github_file(
  65. FlipperHTTP *fhttp,
  66. const char *filename,
  67. const char *author,
  68. const char *repo,
  69. const char *link)
  70. {
  71. if (!fhttp || !filename || !author || !repo || !link)
  72. {
  73. FURI_LOG_E(TAG, "Invalid arguments.");
  74. return false;
  75. }
  76. snprintf(fhttp->file_path, sizeof(fhttp->file_path),
  77. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s.txt",
  78. author, repo, filename);
  79. fhttp->state = IDLE;
  80. fhttp->save_received_data = false;
  81. fhttp->is_bytes_request = true;
  82. // return flipper_http_get_request_bytes(fhttp, link, "{\"Content-Type\":\"application/octet-stream\"}");
  83. return flipper_http_request(fhttp, BYTES, link, "{\"Content-Type\":\"application/octet-stream\"}", NULL);
  84. }
  85. static bool save_directory(const char *dir)
  86. {
  87. Storage *storage = furi_record_open(RECORD_STORAGE);
  88. if (!storage_common_exists(storage, dir) && storage_common_mkdir(storage, dir) != FSE_OK)
  89. {
  90. FURI_LOG_E(TAG, "Failed to create directory %s", dir);
  91. furi_record_close(RECORD_STORAGE);
  92. return false;
  93. }
  94. furi_record_close(RECORD_STORAGE);
  95. return true;
  96. }
  97. bool flip_store_get_github_contents(FlipperHTTP *fhttp, const char *author, const char *repo)
  98. {
  99. // Create Initial directories
  100. char dir[256];
  101. // create a data directory: /ext/apps_data/flip_store/data
  102. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data");
  103. save_directory(STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data");
  104. // create a data directory for the author: /ext/apps_data/flip_store/data/author
  105. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s", author);
  106. save_directory(dir);
  107. // info path: /ext/apps_data/flip_store/data/author/info.json
  108. snprintf(fhttp->file_path, sizeof(fhttp->file_path),
  109. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/info.json",
  110. author);
  111. // create a data directory for the repo: /ext/apps_data/flip_store/data/author/repo
  112. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s", author, repo);
  113. save_directory(dir);
  114. // create author folder: /ext/apps_data/flip_store/author
  115. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s", author);
  116. save_directory(dir);
  117. // create repo folder: /ext/apps_data/flip_store/author/repo
  118. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s", author, repo);
  119. save_directory(dir);
  120. // get the contents of the repo
  121. char link[256];
  122. snprintf(link, sizeof(link), "https://api.github.com/repos/%s/%s/contents", author, repo);
  123. fhttp->save_received_data = true;
  124. // return flipper_http_get_request_with_headers(fhttp, link, "{\"Content-Type\":\"application/json\"}");
  125. return flipper_http_request(fhttp, GET, link, "{\"Content-Type\":\"application/json\"}", NULL);
  126. }
  127. // Recursively (but now iteratively, via queue) parse GitHub contents.
  128. // 'repo' is the repository path relative to the author’s root.
  129. bool flip_store_parse_github_contents(char *file_path, const char *author, const char *repo)
  130. {
  131. // Check recursion depth by counting '/' characters.
  132. if (count_char(repo, '/') >= MAX_RECURSION_DEPTH)
  133. {
  134. FURI_LOG_I(TAG, "Max recursion depth reached for repo path: %s", repo);
  135. return true;
  136. }
  137. FURI_LOG_I(TAG, "Parsing Github contents from %s - %s.", author, repo);
  138. if (!file_path || !author || !repo)
  139. {
  140. FURI_LOG_E(TAG, "Invalid arguments.");
  141. return false;
  142. }
  143. // Load JSON data from file.
  144. FuriString *git_data = flipper_http_load_from_file(file_path);
  145. if (git_data == NULL)
  146. {
  147. FURI_LOG_E(TAG, "Failed to load received data from file.");
  148. return false;
  149. }
  150. // Wrap the JSON array in an object for easier parsing.
  151. FuriString *git_data_str = furi_string_alloc();
  152. if (!git_data_str)
  153. {
  154. FURI_LOG_E(TAG, "Failed to allocate git_data_str.");
  155. furi_string_free(git_data);
  156. return false;
  157. }
  158. furi_string_cat_str(git_data_str, "{\"json_data\":");
  159. furi_string_cat(git_data_str, git_data);
  160. furi_string_cat_str(git_data_str, "}");
  161. furi_string_free(git_data);
  162. const size_t additional_bytes = strlen("{\"json_data\":") + 1;
  163. if (memmgr_heap_get_max_free_block() < furi_string_size(git_data_str) + additional_bytes)
  164. {
  165. FURI_LOG_E(TAG, "Not enough memory to allocate git_data_str.");
  166. furi_string_free(git_data_str);
  167. return false;
  168. }
  169. int file_count = 0;
  170. int folder_count = 0;
  171. char dir[256];
  172. FURI_LOG_I(TAG, "Looping through Github files/folders. Available memory: %d bytes", memmgr_heap_get_max_free_block());
  173. char *data = (char *)furi_string_get_cstr(git_data_str);
  174. size_t data_len = furi_string_size(git_data_str);
  175. size_t pos = 0;
  176. char *array_start = strchr(data, '[');
  177. if (!array_start)
  178. {
  179. FURI_LOG_E(TAG, "Invalid JSON format: '[' not found.");
  180. furi_string_free(git_data_str);
  181. return false;
  182. }
  183. pos = array_start - data;
  184. size_t brace_count = 0;
  185. size_t obj_start = 0;
  186. bool in_string = false;
  187. while (pos < data_len && file_count < MAX_GITHUB_FILES)
  188. {
  189. char current = data[pos];
  190. if (current == '"' && (pos == 0 || data[pos - 1] != '\\'))
  191. {
  192. in_string = !in_string;
  193. }
  194. if (!in_string)
  195. {
  196. if (current == '{')
  197. {
  198. if (brace_count == 0)
  199. {
  200. obj_start = pos;
  201. }
  202. brace_count++;
  203. }
  204. else if (current == '}')
  205. {
  206. brace_count--;
  207. if (brace_count == 0)
  208. {
  209. size_t obj_end = pos;
  210. size_t obj_length = obj_end - obj_start + 1;
  211. char *obj_str = malloc(obj_length + 1);
  212. if (!obj_str)
  213. {
  214. FURI_LOG_E(TAG, "Memory allocation failed for obj_str.");
  215. break;
  216. }
  217. strncpy(obj_str, data + obj_start, obj_length);
  218. obj_str[obj_length] = '\0';
  219. FuriString *json_data_array = furi_string_alloc();
  220. furi_string_set(json_data_array, obj_str);
  221. free(obj_str);
  222. if (!json_data_array)
  223. {
  224. FURI_LOG_E(TAG, "Failed to initialize json_data_array.");
  225. break;
  226. }
  227. FURI_LOG_I(TAG, "Loaded json data object #%d. Available memory: %d bytes",
  228. file_count, memmgr_heap_get_max_free_block());
  229. FuriString *type = get_json_value_furi("type", json_data_array);
  230. if (!type)
  231. {
  232. FURI_LOG_E(TAG, "Failed to get type.");
  233. furi_string_free(json_data_array);
  234. break;
  235. }
  236. // If not a file, assume it is a folder.
  237. if (strcmp(furi_string_get_cstr(type), "file") != 0)
  238. {
  239. FuriString *name = get_json_value_furi("name", json_data_array);
  240. if (!name)
  241. {
  242. FURI_LOG_E(TAG, "Failed to get name.");
  243. furi_string_free(type);
  244. furi_string_free(json_data_array);
  245. break;
  246. }
  247. // skip undesired folders (e.g. ".git").
  248. if (strcmp(furi_string_get_cstr(name), ".git") == 0)
  249. {
  250. FURI_LOG_I(TAG, "Skipping folder %s.", furi_string_get_cstr(name));
  251. furi_string_free(name);
  252. furi_string_free(type);
  253. furi_string_free(json_data_array);
  254. size_t remaining_length = data_len - (obj_end + 1);
  255. memmove(data + obj_start, data + obj_end + 1, remaining_length + 1);
  256. data_len -= (obj_end + 1 - obj_start);
  257. pos = obj_start;
  258. continue;
  259. }
  260. FuriString *url = get_json_value_furi("url", json_data_array);
  261. if (!url)
  262. {
  263. FURI_LOG_E(TAG, "Failed to get url.");
  264. furi_string_free(name);
  265. furi_string_free(type);
  266. furi_string_free(json_data_array);
  267. break;
  268. }
  269. // Create the folder on storage.
  270. snprintf(dir, sizeof(dir),
  271. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s",
  272. author, repo, furi_string_get_cstr(name));
  273. save_directory(dir);
  274. snprintf(dir, sizeof(dir),
  275. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/%s",
  276. author, repo, furi_string_get_cstr(name));
  277. save_directory(dir);
  278. // Save folder JSON for later downloading its contents.
  279. snprintf(dir, sizeof(dir),
  280. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/folder%d.json",
  281. author, repo, folder_count);
  282. FuriString *json = furi_string_alloc();
  283. if (!json)
  284. {
  285. FURI_LOG_E(TAG, "Failed to allocate json.");
  286. furi_string_free(name);
  287. furi_string_free(url);
  288. furi_string_free(type);
  289. furi_string_free(json_data_array);
  290. break;
  291. }
  292. furi_string_cat_str(json, "{\"name\":\"");
  293. furi_string_cat(json, name);
  294. furi_string_cat_str(json, "\",\"link\":\"");
  295. furi_string_cat(json, url);
  296. furi_string_cat_str(json, "\"}");
  297. if (!save_char_with_path(dir, furi_string_get_cstr(json)))
  298. {
  299. FURI_LOG_E(TAG, "Failed to save folder json.");
  300. }
  301. FURI_LOG_I(TAG, "Saved folder %s.", furi_string_get_cstr(name));
  302. // Enqueue the folder instead of recursing.
  303. enqueue_folder(dir, repo, furi_string_get_cstr(name));
  304. furi_string_free(name);
  305. furi_string_free(url);
  306. furi_string_free(type);
  307. furi_string_free(json);
  308. furi_string_free(json_data_array);
  309. folder_count++;
  310. size_t remaining_length = data_len - (obj_end + 1);
  311. memmove(data + obj_start, data + obj_end + 1, remaining_length + 1);
  312. data_len -= (obj_end + 1 - obj_start);
  313. pos = obj_start;
  314. continue;
  315. }
  316. furi_string_free(type);
  317. // Process file: extract download_url and name.
  318. FuriString *download_url = get_json_value_furi("download_url", json_data_array);
  319. if (!download_url)
  320. {
  321. FURI_LOG_E(TAG, "Failed to get download_url.");
  322. furi_string_free(json_data_array);
  323. break;
  324. }
  325. FuriString *name = get_json_value_furi("name", json_data_array);
  326. if (!name)
  327. {
  328. FURI_LOG_E(TAG, "Failed to get name.");
  329. furi_string_free(json_data_array);
  330. furi_string_free(download_url);
  331. break;
  332. }
  333. furi_string_free(json_data_array);
  334. FURI_LOG_I(TAG, "Received file %s and download_url. Available memory: %d bytes",
  335. furi_string_get_cstr(name), memmgr_heap_get_max_free_block());
  336. FuriString *json = furi_string_alloc();
  337. if (!json)
  338. {
  339. FURI_LOG_E(TAG, "Failed to allocate json.");
  340. furi_string_free(download_url);
  341. furi_string_free(name);
  342. break;
  343. }
  344. furi_string_cat_str(json, "{\"name\":\"");
  345. furi_string_cat(json, name);
  346. furi_string_cat_str(json, "\",\"link\":\"");
  347. furi_string_cat(json, download_url);
  348. furi_string_cat_str(json, "\"}");
  349. snprintf(dir, sizeof(dir),
  350. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/file%d.json",
  351. author, repo, file_count);
  352. if (!save_char_with_path(dir, furi_string_get_cstr(json)))
  353. {
  354. FURI_LOG_E(TAG, "Failed to save file json.");
  355. }
  356. FURI_LOG_I(TAG, "Saved file %s.", furi_string_get_cstr(name));
  357. furi_string_free(name);
  358. furi_string_free(download_url);
  359. furi_string_free(json);
  360. file_count++;
  361. size_t remaining_length = data_len - (obj_end + 1);
  362. memmove(data + obj_start, data + obj_end + 1, remaining_length + 1);
  363. data_len -= (obj_end + 1 - obj_start);
  364. pos = obj_start;
  365. continue;
  366. }
  367. }
  368. }
  369. pos++;
  370. }
  371. // Save file count.
  372. char file_count_str[16];
  373. snprintf(file_count_str, sizeof(file_count_str), "%d", file_count);
  374. char file_count_dir[256];
  375. snprintf(file_count_dir, sizeof(file_count_dir),
  376. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/file_count.txt", author);
  377. if (!save_char_with_path(file_count_dir, file_count_str))
  378. {
  379. FURI_LOG_E(TAG, "Failed to save file count.");
  380. furi_string_free(git_data_str);
  381. return false;
  382. }
  383. // Save folder count.
  384. char folder_count_str[16];
  385. snprintf(folder_count_str, sizeof(folder_count_str), "%d", folder_count);
  386. char folder_count_dir[256];
  387. snprintf(folder_count_dir, sizeof(folder_count_dir),
  388. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/folder_count.txt",
  389. author, repo);
  390. if (!save_char_with_path(folder_count_dir, folder_count_str))
  391. {
  392. FURI_LOG_E(TAG, "Failed to save folder count.");
  393. furi_string_free(git_data_str);
  394. return false;
  395. }
  396. furi_string_free(git_data_str);
  397. FURI_LOG_I(TAG, "Successfully parsed %d files and %d folders.", file_count, folder_count);
  398. return true;
  399. }
  400. bool flip_store_install_all_github_files(FlipperHTTP *fhttp, const char *author, const char *repo)
  401. {
  402. FURI_LOG_I(TAG, "Installing all Github files.");
  403. if (!fhttp || !author || !repo)
  404. {
  405. FURI_LOG_E(TAG, "Invalid arguments.");
  406. return false;
  407. }
  408. fhttp->state = RECEIVING;
  409. // --- Install files first ---
  410. char file_count_dir[256];
  411. snprintf(file_count_dir, sizeof(file_count_dir),
  412. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/file_count.txt", author);
  413. FuriString *file_count = flipper_http_load_from_file(file_count_dir);
  414. if (file_count == NULL)
  415. {
  416. FURI_LOG_E(TAG, "Failed to load file count.");
  417. return false;
  418. }
  419. int count = atoi(furi_string_get_cstr(file_count));
  420. furi_string_free(file_count);
  421. char file_dir[256];
  422. FURI_LOG_I(TAG, "Installing %d files.", count);
  423. for (int i = 0; i < count; i++)
  424. {
  425. snprintf(file_dir, sizeof(file_dir),
  426. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/file%d.json",
  427. author, repo, i);
  428. FuriString *file = flipper_http_load_from_file(file_dir);
  429. if (!file)
  430. {
  431. FURI_LOG_E(TAG, "Failed to load file.");
  432. return false;
  433. }
  434. FURI_LOG_I(TAG, "Loaded file %s.", file_dir);
  435. FuriString *name = get_json_value_furi("name", file);
  436. if (!name)
  437. {
  438. FURI_LOG_E(TAG, "Failed to get name.");
  439. furi_string_free(file);
  440. return false;
  441. }
  442. FuriString *link = get_json_value_furi("link", file);
  443. if (!link)
  444. {
  445. FURI_LOG_E(TAG, "Failed to get link.");
  446. furi_string_free(file);
  447. furi_string_free(name);
  448. return false;
  449. }
  450. furi_string_free(file);
  451. FURI_LOG_I(TAG, "Downloading file %s", furi_string_get_cstr(name));
  452. // fetch_file callback
  453. bool fetch_file_result = false;
  454. {
  455. bool fetch_file()
  456. {
  457. return flip_store_download_github_file(
  458. fhttp,
  459. furi_string_get_cstr(name),
  460. author,
  461. repo,
  462. furi_string_get_cstr(link));
  463. }
  464. // parse_file callback
  465. bool parse_file()
  466. {
  467. char current_file_path[256];
  468. char new_file_path[256];
  469. snprintf(current_file_path, sizeof(current_file_path),
  470. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s.txt",
  471. author, repo, furi_string_get_cstr(name));
  472. snprintf(new_file_path, sizeof(new_file_path),
  473. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s",
  474. author, repo, furi_string_get_cstr(name));
  475. Storage *storage = furi_record_open(RECORD_STORAGE);
  476. if (!storage_file_exists(storage, current_file_path))
  477. {
  478. FURI_LOG_E(TAG, "Failed to download file.");
  479. furi_record_close(RECORD_STORAGE);
  480. return false;
  481. }
  482. if (storage_common_rename(storage, current_file_path, new_file_path) != FSE_OK)
  483. {
  484. FURI_LOG_E(TAG, "Failed to rename file.");
  485. furi_record_close(RECORD_STORAGE);
  486. return false;
  487. }
  488. furi_record_close(RECORD_STORAGE);
  489. return true;
  490. }
  491. fetch_file_result = flipper_http_process_response_async(fhttp, fetch_file, parse_file);
  492. }
  493. if (!fetch_file_result)
  494. {
  495. FURI_LOG_E(TAG, "Failed to download file.");
  496. furi_string_free(name);
  497. furi_string_free(link);
  498. return false;
  499. }
  500. FURI_LOG_I(TAG, "Downloaded file %s", furi_string_get_cstr(name));
  501. furi_string_free(name);
  502. furi_string_free(link);
  503. }
  504. fhttp->state = IDLE;
  505. // --- Now install folders ---
  506. char folder_count_dir[256];
  507. snprintf(folder_count_dir, sizeof(folder_count_dir),
  508. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/folder_count.txt",
  509. author, repo);
  510. FuriString *folder_count = flipper_http_load_from_file(folder_count_dir);
  511. if (folder_count == NULL)
  512. {
  513. FURI_LOG_E(TAG, "Failed to load folder count.");
  514. return false;
  515. }
  516. count = atoi(furi_string_get_cstr(folder_count));
  517. furi_string_free(folder_count);
  518. char folder_dir[256];
  519. FURI_LOG_I(TAG, "Installing %d folders.", count);
  520. for (int i = 0; i < count; i++)
  521. {
  522. snprintf(folder_dir, sizeof(folder_dir),
  523. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/folder%d.json",
  524. author, repo, i);
  525. FuriString *folder = flipper_http_load_from_file(folder_dir);
  526. if (!folder)
  527. {
  528. FURI_LOG_E(TAG, "Failed to load folder.");
  529. return false;
  530. }
  531. FURI_LOG_I(TAG, "Loaded folder %s.", folder_dir);
  532. FuriString *name = get_json_value_furi("name", folder);
  533. if (!name)
  534. {
  535. FURI_LOG_E(TAG, "Failed to get name.");
  536. furi_string_free(folder);
  537. return false;
  538. }
  539. FuriString *link = get_json_value_furi("link", folder);
  540. if (!link)
  541. {
  542. FURI_LOG_E(TAG, "Failed to get link.");
  543. furi_string_free(folder);
  544. furi_string_free(name);
  545. return false;
  546. }
  547. furi_string_free(folder);
  548. FURI_LOG_I(TAG, "Downloading folder %s", furi_string_get_cstr(name));
  549. // fetch_folder callback
  550. bool fetch_folder_result = false;
  551. {
  552. bool fetch_folder()
  553. {
  554. fhttp->save_received_data = true;
  555. snprintf(fhttp->file_path, sizeof(fhttp->file_path),
  556. STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/folder%d.json",
  557. author, repo, i);
  558. // return flipper_http_get_request_with_headers(
  559. // fhttp,
  560. // furi_string_get_cstr(link),
  561. // "{\"Content-Type\":\"application/json\"}");
  562. return flipper_http_request(fhttp, GET, furi_string_get_cstr(link), "{\"Content-Type\":\"application/json\"}", NULL);
  563. }
  564. // parse_folder callback (just enqueue)
  565. bool parse_folder()
  566. {
  567. return enqueue_folder(fhttp->file_path, repo, furi_string_get_cstr(name));
  568. }
  569. fetch_folder_result = flipper_http_process_response_async(fhttp, fetch_folder, parse_folder);
  570. }
  571. if (!fetch_folder_result)
  572. {
  573. FURI_LOG_E(TAG, "Failed to download folder.");
  574. furi_string_free(name);
  575. furi_string_free(link);
  576. return false;
  577. }
  578. FURI_LOG_I(TAG, "Downloaded folder %s", furi_string_get_cstr(name));
  579. furi_string_free(name);
  580. furi_string_free(link);
  581. }
  582. fhttp->state = IDLE;
  583. // Finally, process all pending (enqueued) folders iteratively.
  584. process_pending_folders(author);
  585. return true;
  586. }