flip_store_github.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. #include <github/flip_store_github.h>
  2. #include <flip_storage/flip_store_storage.h>
  3. // Helper to download a file from Github and save it to the storage
  4. bool flip_store_download_github_file(
  5. FlipperHTTP *fhttp,
  6. const char *filename,
  7. const char *author,
  8. const char *repo,
  9. const char *link)
  10. {
  11. if (!fhttp || !filename || !author || !repo || !link)
  12. {
  13. FURI_LOG_E(TAG, "Invalid arguments.");
  14. return false;
  15. }
  16. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s.txt", author, repo, filename);
  17. fhttp->state = IDLE;
  18. fhttp->save_received_data = false;
  19. fhttp->is_bytes_request = true;
  20. return flipper_http_get_request_bytes(fhttp, link, "{\"Content-Type\":\"application/octet-stream\"}");
  21. }
  22. bool flip_store_get_github_contents(FlipperHTTP *fhttp, const char *author, const char *repo)
  23. {
  24. // Create Initial directory
  25. Storage *storage = furi_record_open(RECORD_STORAGE);
  26. char dir[256];
  27. // create a data directory: /ext/apps_data/flip_store/data
  28. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data");
  29. storage_common_mkdir(storage, dir);
  30. // create a data directory for the author: /ext/apps_data/flip_store/data/author
  31. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s", author);
  32. storage_common_mkdir(storage, dir);
  33. // example path: /ext/apps_data/flip_store/data/author/info.json
  34. snprintf(fhttp->file_path, sizeof(fhttp->file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/info.json", author);
  35. // create a data directory for the repo: /ext/apps_data/flip_store/data/author/repo
  36. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s", author, repo);
  37. storage_common_mkdir(storage, dir);
  38. // example path: /ext/apps_data/flip_store/author
  39. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s", author);
  40. storage_common_mkdir(storage, dir);
  41. // example path: /ext/apps_data/flip_store/author/repo
  42. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s", author, repo);
  43. storage_common_mkdir(storage, dir);
  44. furi_record_close(RECORD_STORAGE);
  45. // get the contents of the repo
  46. char link[256];
  47. snprintf(link, sizeof(link), "https://api.github.com/repos/%s/%s/contents", author, repo);
  48. fhttp->save_received_data = true;
  49. return flipper_http_get_request_with_headers(fhttp, link, "{\"Content-Type\":\"application/json\"}");
  50. }
  51. #include <stdio.h>
  52. #include <string.h>
  53. #include <stdbool.h>
  54. // Assuming necessary headers and definitions for FuriString, FURI_LOG, etc., are included.
  55. bool flip_store_parse_github_contents(char *file_path, const char *author, const char *repo)
  56. {
  57. FURI_LOG_I(TAG, "Parsing Github contents from %s - %s.", author, repo);
  58. if (!file_path || !author || !repo)
  59. {
  60. FURI_LOG_E(TAG, "Invalid arguments.");
  61. return false;
  62. }
  63. // Load JSON data from file
  64. FuriString *git_data = flipper_http_load_from_file(file_path);
  65. if (git_data == NULL)
  66. {
  67. FURI_LOG_E(TAG, "Failed to load received data from file.");
  68. return false;
  69. }
  70. // Allocate a new FuriString to hold the entire JSON structure
  71. FuriString *git_data_str = furi_string_alloc();
  72. if (!git_data_str)
  73. {
  74. FURI_LOG_E(TAG, "Failed to allocate git_data_str.");
  75. furi_string_free(git_data);
  76. return false;
  77. }
  78. // Construct the full JSON string
  79. furi_string_cat_str(git_data_str, "{\"json_data\":");
  80. furi_string_cat(git_data_str, git_data);
  81. furi_string_cat_str(git_data_str, "}");
  82. furi_string_free(git_data); // Free the original git_data as it's now part of git_data_str
  83. // Check available memory
  84. const size_t additional_bytes = strlen("{\"json_data\":") + 1; // +1 for the closing "}"
  85. if (memmgr_get_free_heap() < furi_string_size(git_data_str) + additional_bytes)
  86. {
  87. FURI_LOG_E(TAG, "Not enough memory to allocate git_data_str.");
  88. furi_string_free(git_data_str);
  89. return false;
  90. }
  91. int file_count = 0;
  92. char dir[512]; // Increased size to accommodate longer paths if necessary
  93. FURI_LOG_I(TAG, "Looping through Github files.");
  94. FURI_LOG_I(TAG, "Available memory: %d bytes", memmgr_get_free_heap());
  95. // Get the C-string and its length for processing
  96. char *data = (char *)furi_string_get_cstr(git_data_str);
  97. size_t data_len = furi_string_size(git_data_str);
  98. size_t pos = 0; // Current position in the data string
  99. // Locate the start of the JSON array
  100. char *array_start = strchr(data, '[');
  101. if (!array_start)
  102. {
  103. FURI_LOG_E(TAG, "Invalid JSON format: '[' not found.");
  104. furi_string_free(git_data_str);
  105. return false;
  106. }
  107. pos = array_start - data; // Update position to the start of the array
  108. size_t brace_count = 0;
  109. size_t obj_start = 0;
  110. bool in_string = false; // To handle braces inside strings
  111. while (pos < data_len && file_count < MAX_GITHUB_FILES)
  112. {
  113. char current = data[pos];
  114. // Toggle in_string flag if a quote is found (handling escaped quotes)
  115. if (current == '"' && (pos == 0 || data[pos - 1] != '\\'))
  116. {
  117. in_string = !in_string;
  118. }
  119. if (!in_string)
  120. {
  121. if (current == '{')
  122. {
  123. if (brace_count == 0)
  124. {
  125. obj_start = pos; // Potential start of a JSON object
  126. }
  127. brace_count++;
  128. }
  129. else if (current == '}')
  130. {
  131. brace_count--;
  132. if (brace_count == 0)
  133. {
  134. size_t obj_end = pos;
  135. size_t obj_length = obj_end - obj_start + 1;
  136. // Extract the JSON object substring
  137. char *obj_str = malloc(obj_length + 1);
  138. if (!obj_str)
  139. {
  140. FURI_LOG_E(TAG, "Memory allocation failed for obj_str.");
  141. break;
  142. }
  143. strncpy(obj_str, data + obj_start, obj_length);
  144. obj_str[obj_length] = '\0'; // Null-terminate
  145. FuriString *json_data_array = furi_string_alloc();
  146. furi_string_set(json_data_array, obj_str); // Set the string to the allocated memory
  147. free(obj_str); // Free the temporary C-string
  148. if (!json_data_array)
  149. {
  150. FURI_LOG_E(TAG, "Failed to initialize json_data_array.");
  151. break;
  152. }
  153. FURI_LOG_I(TAG, "Loaded json data array value %d. Available memory: %d bytes", file_count, memmgr_get_free_heap());
  154. // Extract "type" field
  155. FuriString *type = get_json_value_furi("type", json_data_array);
  156. if (!type)
  157. {
  158. FURI_LOG_E(TAG, "Failed to get type.");
  159. furi_string_free(json_data_array);
  160. break;
  161. }
  162. // Skip non-file types (e.g., directories)
  163. if (strcmp(furi_string_get_cstr(type), "file") != 0)
  164. {
  165. furi_string_free(type);
  166. furi_string_free(json_data_array);
  167. pos = obj_end + 1; // Move past this object
  168. continue;
  169. }
  170. furi_string_free(type);
  171. // Extract "download_url" and "name"
  172. FuriString *download_url = get_json_value_furi("download_url", json_data_array);
  173. if (!download_url)
  174. {
  175. FURI_LOG_E(TAG, "Failed to get download_url.");
  176. furi_string_free(json_data_array);
  177. break;
  178. }
  179. FuriString *name = get_json_value_furi("name", json_data_array);
  180. if (!name)
  181. {
  182. FURI_LOG_E(TAG, "Failed to get name.");
  183. furi_string_free(json_data_array);
  184. furi_string_free(download_url);
  185. break;
  186. }
  187. furi_string_free(json_data_array);
  188. FURI_LOG_I(TAG, "Received name and download_url. Available memory: %d bytes", memmgr_get_free_heap());
  189. // Create JSON to save
  190. FuriString *json = furi_string_alloc();
  191. if (!json)
  192. {
  193. FURI_LOG_E(TAG, "Failed to allocate json.");
  194. furi_string_free(download_url);
  195. furi_string_free(name);
  196. break;
  197. }
  198. furi_string_cat_str(json, "{\"name\":\"");
  199. furi_string_cat(json, name);
  200. furi_string_cat_str(json, "\",\"link\":\"");
  201. furi_string_cat(json, download_url);
  202. furi_string_cat_str(json, "\"}");
  203. FURI_LOG_I(TAG, "Created json. Available memory: %d bytes", memmgr_get_free_heap());
  204. // Save the JSON to the data folder: /ext/apps_data/flip_store/data/author/repo/fileX.json
  205. snprintf(dir, sizeof(dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/file%d.json", author, repo, file_count);
  206. if (!save_char_with_path(dir, furi_string_get_cstr(json)))
  207. {
  208. FURI_LOG_E(TAG, "Failed to save json.");
  209. }
  210. FURI_LOG_I(TAG, "Saved file %s.", furi_string_get_cstr(name));
  211. // Free allocated resources
  212. furi_string_free(name);
  213. furi_string_free(download_url);
  214. furi_string_free(json);
  215. file_count++;
  216. // This can be expensive for large strings; consider memory constraints
  217. size_t remaining_length = data_len - (obj_end + 1);
  218. memmove(data + obj_start, data + obj_end + 1, remaining_length + 1); // +1 to include null terminator
  219. data_len -= (obj_end + 1 - obj_start);
  220. pos = obj_start; // Reset position to the start of the modified string
  221. continue;
  222. }
  223. }
  224. }
  225. pos++;
  226. }
  227. furi_string_free(git_data_str);
  228. // Save file count
  229. char file_count_str[16];
  230. snprintf(file_count_str, sizeof(file_count_str), "%d", file_count);
  231. char file_count_dir[512]; // Increased size for longer paths
  232. snprintf(file_count_dir, sizeof(file_count_dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/file_count.txt", author);
  233. FURI_LOG_I(TAG, "Successfully parsed %d files.", file_count);
  234. return save_char_with_path(file_count_dir, file_count_str);
  235. }
  236. bool flip_store_install_all_github_files(FlipperHTTP *fhttp, const char *author, const char *repo)
  237. {
  238. FURI_LOG_I(TAG, "Installing all Github files.");
  239. if (!fhttp || !author || !repo)
  240. {
  241. FURI_LOG_E(TAG, "Invalid arguments.");
  242. return false;
  243. }
  244. fhttp->state = RECEIVING;
  245. // get the file count
  246. char file_count_dir[256]; // /ext/apps_data/flip_store/data/author/file_count.txt
  247. snprintf(file_count_dir, sizeof(file_count_dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/file_count.txt", author);
  248. FuriString *file_count = flipper_http_load_from_file(file_count_dir);
  249. if (file_count == NULL)
  250. {
  251. FURI_LOG_E(TAG, "Failed to load file count.");
  252. return false;
  253. }
  254. int count = atoi(furi_string_get_cstr(file_count));
  255. furi_string_free(file_count);
  256. // install all files
  257. char file_dir[256]; // /ext/apps_data/flip_store/data/author/repo/file.json
  258. FURI_LOG_I(TAG, "Installing %d files.", count);
  259. for (int i = 0; i < count; i++)
  260. {
  261. snprintf(file_dir, sizeof(file_dir), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/data/%s/%s/file%d.json", author, repo, i);
  262. FURI_LOG_I(TAG, "Loading file %s. Available memory: %d bytes", file_dir, memmgr_get_free_heap());
  263. FuriString *file = flipper_http_load_from_file_with_limit(file_dir, 512);
  264. if (!file)
  265. {
  266. FURI_LOG_E(TAG, "Failed to load file.");
  267. return false;
  268. }
  269. FURI_LOG_I(TAG, "Loaded file %s.", file_dir);
  270. FuriString *name = get_json_value_furi("name", file);
  271. if (!name)
  272. {
  273. FURI_LOG_E(TAG, "Failed to get name.");
  274. furi_string_free(file);
  275. return false;
  276. }
  277. FuriString *link = get_json_value_furi("link", file);
  278. if (!link)
  279. {
  280. FURI_LOG_E(TAG, "Failed to get link.");
  281. furi_string_free(file);
  282. furi_string_free(name);
  283. return false;
  284. }
  285. furi_string_free(file);
  286. bool fetch_file()
  287. {
  288. return flip_store_download_github_file(fhttp, furi_string_get_cstr(name), author, repo, furi_string_get_cstr(link));
  289. }
  290. bool parse()
  291. {
  292. // remove .txt from the filename
  293. char current_file_path[512];
  294. char new_file_path[512];
  295. snprintf(current_file_path, sizeof(current_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s.txt", author, repo, furi_string_get_cstr(name));
  296. snprintf(new_file_path, sizeof(new_file_path), STORAGE_EXT_PATH_PREFIX "/apps_data/flip_store/%s/%s/%s", author, repo, furi_string_get_cstr(name));
  297. Storage *storage = furi_record_open(RECORD_STORAGE);
  298. if (!storage_file_exists(storage, current_file_path))
  299. {
  300. FURI_LOG_E(TAG, "Failed to download file.");
  301. furi_record_close(RECORD_STORAGE);
  302. return false;
  303. }
  304. if (storage_common_rename(storage, current_file_path, new_file_path) != FSE_OK)
  305. {
  306. FURI_LOG_E(TAG, "Failed to rename file.");
  307. furi_record_close(RECORD_STORAGE);
  308. return false;
  309. }
  310. furi_record_close(RECORD_STORAGE);
  311. return true;
  312. }
  313. // download the file and wait until it is downloaded
  314. FURI_LOG_I(TAG, "Downloading file %s", furi_string_get_cstr(name));
  315. if (!flipper_http_process_response_async(fhttp, fetch_file, parse))
  316. {
  317. FURI_LOG_E(TAG, "Failed to download file.");
  318. furi_string_free(name);
  319. furi_string_free(link);
  320. return false;
  321. }
  322. FURI_LOG_I(TAG, "Downloaded file %s", furi_string_get_cstr(name));
  323. furi_string_free(name);
  324. furi_string_free(link);
  325. }
  326. fhttp->state = IDLE;
  327. return true;
  328. }