flipper-file.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. #include <furi.h>
  2. #include "flipper-file.h"
  3. #include <toolbox/hex.h>
  4. #include <inttypes.h>
  5. struct FlipperFile {
  6. File* file;
  7. };
  8. const char* flipper_file_filetype_key = "Filetype";
  9. const char* flipper_file_version_key = "Version";
  10. const char flipper_file_eoln = '\n';
  11. const char flipper_file_eolr = '\r';
  12. const char flipper_file_delimiter = ':';
  13. const char flipper_file_comment = '#';
  14. /**
  15. * Writes data to a file as a hexadecimal array.
  16. * @param file
  17. * @param data
  18. * @param data_size
  19. * @return true on success write
  20. */
  21. bool flipper_file_write_hex_internal(File* file, const uint8_t* data, const uint16_t data_size) {
  22. const uint8_t byte_text_size = 3;
  23. char byte_text[byte_text_size];
  24. bool result = true;
  25. uint16_t bytes_written;
  26. for(uint8_t i = 0; i < data_size; i++) {
  27. snprintf(byte_text, byte_text_size, "%02X", data[i]);
  28. if(i != 0) {
  29. // space
  30. const char space = ' ';
  31. bytes_written = storage_file_write(file, &space, sizeof(space));
  32. if(bytes_written != sizeof(space)) {
  33. result = false;
  34. break;
  35. }
  36. }
  37. bytes_written = storage_file_write(file, &byte_text, strlen(byte_text));
  38. if(bytes_written != strlen(byte_text)) {
  39. result = false;
  40. break;
  41. }
  42. }
  43. return result;
  44. }
  45. /**
  46. * Reads a valid key from a file as a string.
  47. * After reading, the rw pointer will be on the flipper_file_delimiter symbol.
  48. * Optimized not to read comments and values into RAM.
  49. * @param file
  50. * @param key
  51. * @return true on success read
  52. */
  53. bool flipper_file_read_valid_key(File* file, string_t key) {
  54. string_clean(key);
  55. bool found = false;
  56. bool error = false;
  57. const uint8_t buffer_size = 32;
  58. uint8_t buffer[buffer_size];
  59. bool accumulate = true;
  60. bool new_line = true;
  61. while(true) {
  62. uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size);
  63. if(bytes_were_read == 0) break;
  64. for(uint16_t i = 0; i < bytes_were_read; i++) {
  65. if(buffer[i] == flipper_file_eoln) {
  66. // EOL found, clean data, start accumulating data and set the new_line flag
  67. string_clean(key);
  68. accumulate = true;
  69. new_line = true;
  70. } else if(buffer[i] == flipper_file_eolr) {
  71. // Ignore
  72. } else if(buffer[i] == flipper_file_comment && new_line) {
  73. // if there is a comment character and we are at the beginning of a new line
  74. // do not accumulate comment data and reset the new_line flag
  75. accumulate = false;
  76. new_line = false;
  77. } else if(buffer[i] == flipper_file_delimiter) {
  78. if(new_line) {
  79. // we are on a "new line" and found the delimiter
  80. // this can only be if we have previously found some kind of key, so
  81. // clear the data, set the flag that we no longer want to accumulate data
  82. // and reset the new_line flag
  83. string_clean(key);
  84. accumulate = false;
  85. new_line = false;
  86. } else {
  87. // parse the delimiter only if we are accumulating data
  88. if(accumulate) {
  89. // we found the delimiter, move the rw pointer to the correct location
  90. // and signal that we have found something
  91. // TODO negative seek
  92. uint64_t position = storage_file_tell(file);
  93. position = position - bytes_were_read + i;
  94. if(!storage_file_seek(file, position, true)) {
  95. error = true;
  96. break;
  97. }
  98. found = true;
  99. break;
  100. }
  101. }
  102. } else {
  103. // just new symbol, reset the new_line flag
  104. new_line = false;
  105. if(accumulate) {
  106. // and accumulate data if we want
  107. string_push_back(key, buffer[i]);
  108. }
  109. }
  110. }
  111. if(found || error) break;
  112. }
  113. return found;
  114. }
  115. /**
  116. * Sets rw pointer to the data after the key
  117. * @param file
  118. * @param key
  119. * @return true if key was found
  120. */
  121. bool flipper_file_seek_to_key(File* file, const char* key) {
  122. bool found = false;
  123. string_t readed_key;
  124. string_init(readed_key);
  125. // TODO optimize this to search from a stored rw pointer
  126. if(storage_file_seek(file, 0, true)) {
  127. while(!storage_file_eof(file)) {
  128. if(flipper_file_read_valid_key(file, readed_key)) {
  129. if(string_cmp_str(readed_key, key) == 0) {
  130. uint64_t position = storage_file_tell(file);
  131. if(!storage_file_seek(file, position + 2, true)) break;
  132. found = true;
  133. break;
  134. }
  135. }
  136. }
  137. }
  138. string_clear(readed_key);
  139. return found;
  140. }
  141. /**
  142. * Reads data as a string from the stored rw pointer to the \r or \n symbol position
  143. * @param file
  144. * @param str_result
  145. * @return true on success read
  146. */
  147. bool flipper_file_read_until(File* file, string_t str_result) {
  148. string_clean(str_result);
  149. const uint8_t buffer_size = 32;
  150. uint8_t buffer[buffer_size];
  151. do {
  152. uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size);
  153. if(bytes_were_read == 0) break;
  154. bool result = false;
  155. bool error = false;
  156. for(uint16_t i = 0; i < bytes_were_read; i++) {
  157. if(buffer[i] == flipper_file_eoln) {
  158. // TODO negative seek
  159. uint64_t position = storage_file_tell(file);
  160. position = position - bytes_were_read + i;
  161. if(!storage_file_seek(file, position, true)) {
  162. error = true;
  163. break;
  164. }
  165. result = true;
  166. break;
  167. } else if(buffer[i] == flipper_file_eolr) {
  168. // Ignore
  169. } else {
  170. string_push_back(str_result, buffer[i]);
  171. }
  172. }
  173. if(result || error) {
  174. break;
  175. }
  176. } while(true);
  177. return string_size(str_result) != 0;
  178. }
  179. /**
  180. * Reads single hexadecimal data from a file to byte
  181. * @param file
  182. * @param byte
  183. * @return bool
  184. */
  185. bool flipper_file_read_hex_byte(File* file, uint8_t* byte) {
  186. uint8_t hi_nibble_value, low_nibble_value;
  187. uint8_t text[3];
  188. bool result = false;
  189. uint16_t bytes_were_read = storage_file_read(file, text, 3);
  190. if(bytes_were_read >= 2) {
  191. if(text[0] != ' ') {
  192. if(hex_char_to_hex_nibble(text[0], &hi_nibble_value) &&
  193. hex_char_to_hex_nibble(text[1], &low_nibble_value)) {
  194. *byte = (hi_nibble_value << 4) | low_nibble_value;
  195. result = true;
  196. }
  197. } else {
  198. if(hex_char_to_hex_nibble(text[1], &hi_nibble_value) &&
  199. hex_char_to_hex_nibble(text[2], &low_nibble_value)) {
  200. *byte = (hi_nibble_value << 4) | low_nibble_value;
  201. result = true;
  202. }
  203. }
  204. }
  205. return result;
  206. }
  207. FlipperFile* flipper_file_alloc(Storage* storage) {
  208. FlipperFile* flipper_file = malloc(sizeof(FlipperFile));
  209. flipper_file->file = storage_file_alloc(storage);
  210. return flipper_file;
  211. }
  212. void flipper_file_free(FlipperFile* flipper_file) {
  213. furi_assert(flipper_file);
  214. if(storage_file_is_open(flipper_file->file)) {
  215. storage_file_close(flipper_file->file);
  216. }
  217. storage_file_free(flipper_file->file);
  218. free(flipper_file);
  219. }
  220. bool flipper_file_open_read(FlipperFile* flipper_file, const char* filename) {
  221. furi_assert(flipper_file);
  222. bool result = storage_file_open(flipper_file->file, filename, FSAM_READ, FSOM_OPEN_EXISTING);
  223. return result;
  224. }
  225. bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename) {
  226. furi_assert(flipper_file);
  227. bool result = storage_file_open(flipper_file->file, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS);
  228. return result;
  229. }
  230. bool flipper_file_close(FlipperFile* flipper_file) {
  231. furi_assert(flipper_file);
  232. if(storage_file_is_open(flipper_file->file)) {
  233. return storage_file_close(flipper_file->file);
  234. }
  235. return true;
  236. }
  237. bool flipper_file_read_header(FlipperFile* flipper_file, string_t filetype, uint32_t* version) {
  238. bool result = false;
  239. do {
  240. result = flipper_file_read_string(flipper_file, flipper_file_filetype_key, filetype);
  241. if(!result) break;
  242. result = flipper_file_read_uint32(flipper_file, flipper_file_version_key, version);
  243. if(!result) break;
  244. } while(false);
  245. return result;
  246. }
  247. bool flipper_file_write_header(
  248. FlipperFile* flipper_file,
  249. string_t filetype,
  250. const uint32_t version) {
  251. bool result = false;
  252. do {
  253. result = flipper_file_write_string(flipper_file, flipper_file_filetype_key, filetype);
  254. if(!result) break;
  255. result = flipper_file_write_uint32(flipper_file, flipper_file_version_key, version);
  256. if(!result) break;
  257. } while(false);
  258. return result;
  259. }
  260. bool flipper_file_write_header_cstr(
  261. FlipperFile* flipper_file,
  262. const char* filetype,
  263. const uint32_t version) {
  264. bool result = false;
  265. string_t value;
  266. string_init_set(value, filetype);
  267. result = flipper_file_write_header(flipper_file, value, version);
  268. string_clear(value);
  269. return result;
  270. }
  271. bool flipper_file_read_string(FlipperFile* flipper_file, const char* key, string_t data) {
  272. furi_assert(flipper_file);
  273. bool result = false;
  274. if(flipper_file_seek_to_key(flipper_file->file, key)) {
  275. if(flipper_file_read_until(flipper_file->file, data)) {
  276. result = true;
  277. }
  278. }
  279. return result;
  280. }
  281. bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, string_t data) {
  282. furi_assert(flipper_file);
  283. bool result = false;
  284. do {
  285. uint16_t bytes_written;
  286. bytes_written = storage_file_write(flipper_file->file, key, strlen(key));
  287. if(bytes_written != strlen(key)) break;
  288. const char delimiter_buffer[2] = {flipper_file_delimiter, ' '};
  289. bytes_written =
  290. storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer));
  291. if(bytes_written != sizeof(delimiter_buffer)) break;
  292. bytes_written =
  293. storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data));
  294. if(bytes_written != string_size(data)) break;
  295. bytes_written =
  296. storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln));
  297. if(bytes_written != sizeof(flipper_file_eoln)) break;
  298. result = true;
  299. } while(false);
  300. return result;
  301. }
  302. bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) {
  303. bool result = false;
  304. string_t value;
  305. string_init_set(value, data);
  306. result = flipper_file_write_string(flipper_file, key, value);
  307. string_clear(value);
  308. return result;
  309. }
  310. bool flipper_file_read_uint32(FlipperFile* flipper_file, const char* key, uint32_t* data) {
  311. bool result = false;
  312. string_t value;
  313. string_init(value);
  314. result = flipper_file_read_string(flipper_file, key, value);
  315. if(result) {
  316. int ret = sscanf(string_get_cstr(value), "%" PRIu32, data);
  317. if(ret != 1) result = false;
  318. }
  319. string_clear(value);
  320. return result;
  321. }
  322. bool flipper_file_write_uint32(FlipperFile* flipper_file, const char* key, const uint32_t data) {
  323. bool result = false;
  324. string_t value;
  325. string_init_printf(value, "%" PRIu32, data);
  326. result = flipper_file_write_string(flipper_file, key, value);
  327. string_clear(value);
  328. return result;
  329. }
  330. bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data) {
  331. furi_assert(flipper_file);
  332. bool result = false;
  333. do {
  334. uint16_t bytes_written;
  335. const char comment_buffer[2] = {flipper_file_comment, ' '};
  336. bytes_written =
  337. storage_file_write(flipper_file->file, comment_buffer, sizeof(comment_buffer));
  338. if(bytes_written != sizeof(comment_buffer)) break;
  339. bytes_written =
  340. storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data));
  341. if(bytes_written != string_size(data)) break;
  342. bytes_written =
  343. storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln));
  344. if(bytes_written != sizeof(flipper_file_eoln)) break;
  345. result = true;
  346. } while(false);
  347. return result;
  348. }
  349. bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data) {
  350. bool result = false;
  351. string_t value;
  352. string_init_set(value, data);
  353. result = flipper_file_write_comment(flipper_file, value);
  354. string_clear(value);
  355. return result;
  356. }
  357. bool flipper_file_write_hex_array(
  358. FlipperFile* flipper_file,
  359. const char* key,
  360. const uint8_t* data,
  361. const uint16_t data_size) {
  362. furi_assert(flipper_file);
  363. bool result = false;
  364. do {
  365. uint16_t bytes_written;
  366. bytes_written = storage_file_write(flipper_file->file, key, strlen(key));
  367. if(bytes_written != strlen(key)) break;
  368. const char delimiter_buffer[2] = {flipper_file_delimiter, ' '};
  369. bytes_written =
  370. storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer));
  371. if(bytes_written != sizeof(delimiter_buffer)) break;
  372. if(!flipper_file_write_hex_internal(flipper_file->file, data, data_size)) break;
  373. bytes_written =
  374. storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln));
  375. if(bytes_written != sizeof(flipper_file_eoln)) break;
  376. result = true;
  377. } while(false);
  378. return result;
  379. }
  380. bool flipper_file_read_hex_array(
  381. FlipperFile* flipper_file,
  382. const char* key,
  383. uint8_t* data,
  384. const uint16_t data_size) {
  385. furi_assert(flipper_file);
  386. bool result = false;
  387. if(flipper_file_seek_to_key(flipper_file->file, key)) {
  388. result = true;
  389. for(uint16_t i = 0; i < data_size; i++) {
  390. if(!flipper_file_read_hex_byte(flipper_file->file, &data[i])) {
  391. result = false;
  392. break;
  393. }
  394. }
  395. }
  396. return result;
  397. }