manifest.c 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #include "manifest.h"
  2. #include <toolbox/stream/buffered_file_stream.h>
  3. #include <toolbox/hex.h>
  4. struct ResourceManifestReader {
  5. Storage* storage;
  6. Stream* stream;
  7. FuriString* linebuf;
  8. ResourceManifestEntry entry;
  9. };
  10. ResourceManifestReader* resource_manifest_reader_alloc(Storage* storage) {
  11. ResourceManifestReader* resource_manifest =
  12. (ResourceManifestReader*)malloc(sizeof(ResourceManifestReader));
  13. resource_manifest->storage = storage;
  14. resource_manifest->stream = buffered_file_stream_alloc(resource_manifest->storage);
  15. memset(&resource_manifest->entry, 0, sizeof(ResourceManifestEntry));
  16. resource_manifest->entry.name = furi_string_alloc();
  17. resource_manifest->linebuf = furi_string_alloc();
  18. return resource_manifest;
  19. }
  20. void resource_manifest_reader_free(ResourceManifestReader* resource_manifest) {
  21. furi_assert(resource_manifest);
  22. furi_string_free(resource_manifest->linebuf);
  23. furi_string_free(resource_manifest->entry.name);
  24. buffered_file_stream_close(resource_manifest->stream);
  25. stream_free(resource_manifest->stream);
  26. free(resource_manifest);
  27. }
  28. bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, const char* filename) {
  29. furi_assert(resource_manifest);
  30. return buffered_file_stream_open(
  31. resource_manifest->stream, filename, FSAM_READ, FSOM_OPEN_EXISTING);
  32. }
  33. /* Read entries in format of
  34. * F:<hash>:<size>:<name>
  35. * D:<name>
  36. */
  37. ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest) {
  38. furi_assert(resource_manifest);
  39. furi_string_reset(resource_manifest->entry.name);
  40. resource_manifest->entry.type = ResourceManifestEntryTypeUnknown;
  41. resource_manifest->entry.size = 0;
  42. memset(resource_manifest->entry.hash, 0, sizeof(resource_manifest->entry.hash));
  43. do {
  44. if(!stream_read_line(resource_manifest->stream, resource_manifest->linebuf)) {
  45. return NULL;
  46. }
  47. /* Trim end of line */
  48. furi_string_trim(resource_manifest->linebuf);
  49. char type_code = furi_string_get_char(resource_manifest->linebuf, 0);
  50. switch(type_code) {
  51. case 'V':
  52. resource_manifest->entry.type = ResourceManifestEntryTypeVersion;
  53. break;
  54. case 'T':
  55. resource_manifest->entry.type = ResourceManifestEntryTypeTimestamp;
  56. break;
  57. case 'F':
  58. resource_manifest->entry.type = ResourceManifestEntryTypeFile;
  59. break;
  60. case 'D':
  61. resource_manifest->entry.type = ResourceManifestEntryTypeDirectory;
  62. break;
  63. default: /* Skip other entries - version, timestamp, etc */
  64. continue;
  65. };
  66. if(resource_manifest->entry.type == ResourceManifestEntryTypeFile) {
  67. /* Parse file entry
  68. F:<hash>:<size>:<name> */
  69. /* Remove entry type code */
  70. furi_string_right(resource_manifest->linebuf, 2);
  71. if(furi_string_search_char(resource_manifest->linebuf, ':') !=
  72. sizeof(resource_manifest->entry.hash) * 2) {
  73. /* Invalid hash */
  74. continue;
  75. }
  76. /* Read hash */
  77. hex_chars_to_uint8(
  78. furi_string_get_cstr(resource_manifest->linebuf), resource_manifest->entry.hash);
  79. /* Remove hash */
  80. furi_string_right(
  81. resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1);
  82. resource_manifest->entry.size = atoi(furi_string_get_cstr(resource_manifest->linebuf));
  83. /* Remove size */
  84. size_t offs = furi_string_search_char(resource_manifest->linebuf, ':');
  85. furi_string_right(resource_manifest->linebuf, offs + 1);
  86. furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf);
  87. } else { //-V547
  88. /* Everything else is plain key value. Parse version, timestamp or directory entry
  89. <Type>:<Value> */
  90. /* Remove entry type code */
  91. furi_string_right(resource_manifest->linebuf, 2);
  92. furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf);
  93. }
  94. return &resource_manifest->entry;
  95. } while(true);
  96. return NULL;
  97. }
  98. ResourceManifestEntry*
  99. resource_manifest_reader_previous(ResourceManifestReader* resource_manifest) {
  100. furi_assert(resource_manifest);
  101. // Snapshot position for rollback
  102. const size_t previous_position = stream_tell(resource_manifest->stream);
  103. // We need to jump 2 lines back
  104. size_t jumps = 2;
  105. // Special case: end of the file.
  106. const bool was_eof = stream_eof(resource_manifest->stream);
  107. if(was_eof) {
  108. jumps = 1;
  109. }
  110. while(jumps) {
  111. if(!stream_seek_to_char(resource_manifest->stream, '\n', StreamDirectionBackward)) {
  112. break;
  113. }
  114. if(stream_tell(resource_manifest->stream) < (previous_position - 1)) {
  115. jumps--;
  116. }
  117. }
  118. // Special case: first line. Force seek to zero
  119. if(jumps == 1) {
  120. jumps = 0;
  121. stream_seek(resource_manifest->stream, 0, StreamOffsetFromStart);
  122. }
  123. if(jumps == 0) {
  124. ResourceManifestEntry* entry = resource_manifest_reader_next(resource_manifest);
  125. // Special case: was end of the file, prevent loop
  126. if(was_eof) {
  127. stream_seek(resource_manifest->stream, -1, StreamOffsetFromCurrent);
  128. }
  129. return entry;
  130. } else {
  131. stream_seek(resource_manifest->stream, previous_position, StreamOffsetFromStart);
  132. return NULL;
  133. }
  134. }