Various improvements: Toolbox, Updater and Unit Tests. (#2250)

* Toolbox: add seek to character stream method. UpdateUtils: reverse manifest iterator. UnitTests: more unit tests.
* Target: bump API version. Updater: delete empty folders from manifest before resource deployment.
* UnitTests: use manifest from unit_tests folder instead of global one
* Make PVS happy
* sector cache: allocate always
* Better PVS config for manifest.c
* PVS: Move exception outside of condition
* PVS: remove confusing condition

Co-authored-by: SG <who.just.the.doctor@gmail.com>
This commit is contained in:
あく 2023-01-06 15:31:17 +09:00 committed by GitHub
parent b8dd75884c
commit 41c43f4805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 398 additions and 24 deletions

View File

@ -0,0 +1,75 @@
#include <furi.c>
#include "../minunit.h"
#include <update_util/resources/manifest.h>
#define TAG "Manifest"
MU_TEST(manifest_type_test) {
mu_assert(ResourceManifestEntryTypeUnknown == 0, "ResourceManifestEntryTypeUnknown != 0\r\n");
mu_assert(ResourceManifestEntryTypeVersion == 1, "ResourceManifestEntryTypeVersion != 1\r\n");
mu_assert(
ResourceManifestEntryTypeTimestamp == 2, "ResourceManifestEntryTypeTimestamp != 2\r\n");
mu_assert(
ResourceManifestEntryTypeDirectory == 3, "ResourceManifestEntryTypeDirectory != 3\r\n");
mu_assert(ResourceManifestEntryTypeFile == 4, "ResourceManifestEntryTypeFile != 4\r\n");
}
MU_TEST(manifest_iteration_test) {
bool result = true;
size_t counters[5] = {0};
Storage* storage = furi_record_open(RECORD_STORAGE);
ResourceManifestReader* manifest_reader = resource_manifest_reader_alloc(storage);
do {
// Open manifest file
if(!resource_manifest_reader_open(manifest_reader, EXT_PATH("unit_tests/Manifest"))) {
result = false;
break;
}
// Iterate forward
ResourceManifestEntry* entry_ptr = NULL;
while((entry_ptr = resource_manifest_reader_next(manifest_reader))) {
FURI_LOG_D(TAG, "F:%u:%s", entry_ptr->type, furi_string_get_cstr(entry_ptr->name));
if(entry_ptr->type > 4) {
mu_fail("entry_ptr->type > 4\r\n");
result = false;
break;
}
counters[entry_ptr->type]++;
}
if(!result) break;
// Iterate backward
while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) {
FURI_LOG_D(TAG, "B:%u:%s", entry_ptr->type, furi_string_get_cstr(entry_ptr->name));
if(entry_ptr->type > 4) {
mu_fail("entry_ptr->type > 4\r\n");
result = false;
break;
}
counters[entry_ptr->type]--;
}
} while(false);
resource_manifest_reader_free(manifest_reader);
furi_record_close(RECORD_STORAGE);
mu_assert(counters[ResourceManifestEntryTypeUnknown] == 0, "Unknown counter != 0\r\n");
mu_assert(counters[ResourceManifestEntryTypeVersion] == 0, "Version counter != 0\r\n");
mu_assert(counters[ResourceManifestEntryTypeTimestamp] == 0, "Timestamp counter != 0\r\n");
mu_assert(counters[ResourceManifestEntryTypeDirectory] == 0, "Directory counter != 0\r\n");
mu_assert(counters[ResourceManifestEntryTypeFile] == 0, "File counter != 0\r\n");
mu_assert(result, "Manifest forward iterate failed\r\n");
}
MU_TEST_SUITE(manifest_suite) {
MU_RUN_TEST(manifest_type_test);
MU_RUN_TEST(manifest_iteration_test);
}
int run_minunit_test_manifest() {
MU_RUN_SUITE(manifest_suite);
return MU_EXIT_CODE;
}

View File

@ -72,8 +72,32 @@ MU_TEST_1(stream_composite_subtest, Stream* stream) {
mu_check(stream_seek(stream, -3, StreamOffsetFromEnd));
mu_check(stream_tell(stream) == 4);
// test seeks to char. content: '1337_69'
stream_rewind(stream);
mu_check(stream_seek_to_char(stream, '3', StreamDirectionForward));
mu_check(stream_tell(stream) == 1);
mu_check(stream_seek_to_char(stream, '3', StreamDirectionForward));
mu_check(stream_tell(stream) == 2);
mu_check(stream_seek_to_char(stream, '_', StreamDirectionForward));
mu_check(stream_tell(stream) == 4);
mu_check(stream_seek_to_char(stream, '9', StreamDirectionForward));
mu_check(stream_tell(stream) == 6);
mu_check(!stream_seek_to_char(stream, '9', StreamDirectionForward));
mu_check(stream_tell(stream) == 6);
mu_check(stream_seek_to_char(stream, '_', StreamDirectionBackward));
mu_check(stream_tell(stream) == 4);
mu_check(stream_seek_to_char(stream, '3', StreamDirectionBackward));
mu_check(stream_tell(stream) == 2);
mu_check(stream_seek_to_char(stream, '3', StreamDirectionBackward));
mu_check(stream_tell(stream) == 1);
mu_check(!stream_seek_to_char(stream, '3', StreamDirectionBackward));
mu_check(stream_tell(stream) == 1);
mu_check(stream_seek_to_char(stream, '1', StreamDirectionBackward));
mu_check(stream_tell(stream) == 0);
// write string with replacemet
// "1337_69" -> "1337lee"
mu_check(stream_seek(stream, 4, StreamOffsetFromStart));
mu_check(stream_write_string(stream, string_lee) == 3);
mu_check(stream_size(stream) == 7);
mu_check(stream_tell(stream) == 7);

View File

@ -13,6 +13,7 @@ int run_minunit_test_furi_hal();
int run_minunit_test_furi_string();
int run_minunit_test_infrared();
int run_minunit_test_rpc();
int run_minunit_test_manifest();
int run_minunit_test_flipper_format();
int run_minunit_test_flipper_format_string();
int run_minunit_test_stream();
@ -41,6 +42,7 @@ const UnitTest unit_tests[] = {
{.name = "storage", .entry = run_minunit_test_storage},
{.name = "stream", .entry = run_minunit_test_stream},
{.name = "dirwalk", .entry = run_minunit_test_dirwalk},
{.name = "manifest", .entry = run_minunit_test_manifest},
{.name = "flipper_format", .entry = run_minunit_test_flipper_format},
{.name = "flipper_format_string", .entry = run_minunit_test_flipper_format_string},
{.name = "rpc", .entry = run_minunit_test_rpc},

View File

@ -79,8 +79,8 @@ static void
update_task_set_progress(
update_task,
UpdateTaskStageProgress,
/* For this stage, first 30% of progress = cleanup */
(n_processed_files++ * 30) / (n_approx_file_entries + 1));
/* For this stage, first 20% of progress = cleanup files */
(n_processed_files++ * 20) / (n_approx_file_entries + 1));
FuriString* file_path = furi_string_alloc();
path_concat(
@ -90,6 +90,46 @@ static void
furi_string_free(file_path);
}
}
while((entry_ptr = resource_manifest_reader_previous(manifest_reader))) {
if(entry_ptr->type == ResourceManifestEntryTypeDirectory) {
update_task_set_progress(
update_task,
UpdateTaskStageProgress,
/* For this stage, second 10% of progress = cleanup directories */
(n_processed_files++ * 10) / (n_approx_file_entries + 1));
FuriString* folder_path = furi_string_alloc();
File* folder_file = storage_file_alloc(update_task->storage);
do {
path_concat(
STORAGE_EXT_PATH_PREFIX,
furi_string_get_cstr(entry_ptr->name),
folder_path);
FURI_LOG_D(TAG, "Removing folder %s", furi_string_get_cstr(folder_path));
if(!storage_dir_open(folder_file, furi_string_get_cstr(folder_path))) {
FURI_LOG_W(
TAG,
"%s can't be opened, skipping",
furi_string_get_cstr(folder_path));
break;
}
if(storage_dir_read(folder_file, NULL, NULL, 0)) {
FURI_LOG_I(
TAG, "%s is not empty, skipping", furi_string_get_cstr(folder_path));
break;
}
storage_simply_remove(update_task->storage, furi_string_get_cstr(folder_path));
} while(false);
storage_file_free(folder_file);
furi_string_free(folder_path);
}
}
} while(false);
resource_manifest_reader_free(manifest_reader);
}

View File

@ -0,0 +1,76 @@
V:0
T:1672935435
D:infrared
D:nfc
D:subghz
F:4bff70f2a2ae771f81de5cfb090b3d74:3952:infrared/test_kaseikyo.irtest
F:8556d32d7c54e66771d9da78d007d379:21463:infrared/test_nec.irtest
F:860c0c475573878842180a6cb50c85c7:2012:infrared/test_nec42.irtest
F:2b3cbf3fe7d3642190dfb8362dcc0ed6:3522:infrared/test_nec42ext.irtest
F:c74bbd7f885ab8fbc3b3363598041bc1:18976:infrared/test_necext.irtest
F:cab5e604abcb233bcb27903baec24462:7460:infrared/test_rc5.irtest
F:3d22b3ec2531bb8f4842c9c0c6a8d97c:547:infrared/test_rc5x.irtest
F:c9cb9fa4decbdd077741acb845f21343:8608:infrared/test_rc6.irtest
F:97de943385bc6ad1c4a58fc4fedb5244:16975:infrared/test_samsung32.irtest
F:4eb36c62d4f2e737a3e4a64b5ff0a8e7:41623:infrared/test_sirc.irtest
F:e4ec3299cbe1f528fb1b9b45aac53556:4182:nfc/nfc_nfca_signal_long.nfc
F:af4d10974834c2703ad29e859eea78c2:1020:nfc/nfc_nfca_signal_short.nfc
F:224d12457a26774d8d2aa0d4b3a15652:160:subghz/ansonic.sub
F:ce9fc98dc01230387a340332316774f1:13642:subghz/ansonic_raw.sub
F:f958927b656d0804036c28b4a31ff856:157:subghz/bett.sub
F:b4b17b2603fa3a144dbea4d9ede9f61d:5913:subghz/bett_raw.sub
F:370a0c62be967b420da5e60ffcdc078b:157:subghz/came.sub
F:0156915c656d8c038c6d555d34349a36:6877:subghz/came_atomo_raw.sub
F:111a8b796661f3cbd6f49f756cf91107:8614:subghz/came_raw.sub
F:2101b0a5a72c87f9dce77223b2885aa7:162:subghz/came_twee.sub
F:c608b78b8e4646eeb94db37644623254:10924:subghz/came_twee_raw.sub
F:c4a55acddb68fc3111d592c9292022a8:21703:subghz/cenmax_raw.sub
F:51d6bd600345954b9c84a5bc6e999313:159:subghz/clemsa.sub
F:14fa0d5931a32674bfb2ddf288f3842b:21499:subghz/clemsa_raw.sub
F:f38b6dfa0920199200887b2cd5c0a385:161:subghz/doitrand.sub
F:c7e53da8e3588a2c0721aa794699ccd4:24292:subghz/doitrand_raw.sub
F:cc73b6f4d05bfe30c67a0d18b63e58d9:159:subghz/doorhan.sub
F:22fec89c5cc43504ad4391e61e12c7e0:10457:subghz/doorhan_raw.sub
F:3a97d8bd32ddaff42932b4c3033ee2d2:12732:subghz/faac_slh_raw.sub
F:06d3226f5330665f48d41c49e34fed15:159:subghz/gate_tx.sub
F:8b150a8d38ac7c4f7063ee0d42050399:13827:subghz/gate_tx_raw.sub
F:a7904e17b0c18c083ae1acbefc330c7a:159:subghz/holtek.sub
F:72bb528255ef1c135cb3f436414897d3:173:subghz/holtek_ht12x.sub
F:54ceacb8c156f9534fc7ee0a0911f4da:11380:subghz/holtek_ht12x_raw.sub
F:4a9567c1543cf3e7bb5350b635d9076f:31238:subghz/holtek_raw.sub
F:ca86c0d78364d704ff62b0698093d396:162:subghz/honeywell_wdb.sub
F:f606548c935adc8d8bc804326ef67543:38415:subghz/honeywell_wdb_raw.sub
F:20bba4b0aec006ced7e82513f9459e31:15532:subghz/hormann_hsm_raw.sub
F:3392f2db6aa7777e937db619b86203bb:10637:subghz/ido_117_111_raw.sub
F:cc5c7968527cc233ef11a08986e31bf2:167:subghz/intertechno_v3.sub
F:70bceb941739260ab9f6162cfdeb0347:18211:subghz/intertechno_v3_raw.sub
F:bc9a4622f3e22fd7f82eb3f26e61f59b:44952:subghz/kia_seed_raw.sub
F:6b6e95fc70ea481dc6184d291466d16a:159:subghz/linear.sub
F:77aaa9005db54c0357451ced081857b2:14619:subghz/linear_raw.sub
F:1a618e21e6ffa9984d465012e704c450:161:subghz/magellan.sub
F:bf43cb85d79e20644323d6acad87e028:5808:subghz/magellan_raw.sub
F:4ef17320f936ee88e92582a9308b2faa:161:subghz/marantec.sub
F:507a8413a1603ad348eea945123fb7cc:21155:subghz/marantec_raw.sub
F:22b69dc490d5425488342b5c5a838d55:161:subghz/megacode.sub
F:4f8fe9bef8bdd9c52f3f77e829f8986f:6205:subghz/megacode_raw.sub
F:b39f62cb108c2fa9916e0a466596ab87:18655:subghz/nero_radio_raw.sub
F:d0d70f8183032096805a41e1808c093b:26436:subghz/nero_sketch_raw.sub
F:c6999bd0eefd0fccf34820e17bcbc8ba:161:subghz/nice_flo.sub
F:9b1200600b9ec2a73166797ff243fbfc:3375:subghz/nice_flo_raw.sub
F:b52bafb098282676d1c7163bfb0d6e73:8773:subghz/nice_flor_s_raw.sub
F:e4df94dfdee2efadf2ed9a1e9664f8b2:163:subghz/phoenix_v2.sub
F:8ec066976df93fba6335b3f6dc47014c:8548:subghz/phoenix_v2_raw.sub
F:2b1192e4898aaf274caebbb493b9f96e:164:subghz/power_smart.sub
F:8b8195cab1d9022fe38e802383fb923a:3648:subghz/power_smart_raw.sub
F:1ccf1289533e0486a1d010d934ad7b06:170:subghz/princeton.sub
F:8bccc506a61705ec429aecb879e5d7ce:7344:subghz/princeton_raw.sub
F:0bda91d783e464165190c3b3d16666a7:38724:subghz/scher_khan_magic_code.sub
F:116d7e1a532a0c9e00ffeee105f7138b:166:subghz/security_pls_1_0.sub
F:441fc7fc6fa11ce0068fde3f6145177b:69413:subghz/security_pls_1_0_raw.sub
F:e5e33c24c5e55f592ca892b5aa8fa31f:208:subghz/security_pls_2_0.sub
F:2614f0aef367042f8623719d765bf2c0:62287:subghz/security_pls_2_0_raw.sub
F:8eb533544c4c02986800c90e935184ff:168:subghz/smc5326.sub
F:fc67a4fe7e0b3bc81a1c8da8caca7658:4750:subghz/smc5326_raw.sub
F:24196a4c4af1eb03404a2ee434c864bf:4096:subghz/somfy_keytis_raw.sub
F:6a5ece145a5694e543d99bf1b970baf0:9741:subghz/somfy_telis_raw.sub
F:0ad046bfa9ec872e92141a69bbf03d92:382605:subghz/test_random_raw.sub

View File

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,11.5,,
Version,+,11.6,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@ -2480,6 +2480,7 @@ Function,+,stream_read_line,_Bool,"Stream*, FuriString*"
Function,+,stream_rewind,_Bool,Stream*
Function,+,stream_save_to_file,size_t,"Stream*, Storage*, const char*, FS_OpenMode"
Function,+,stream_seek,_Bool,"Stream*, int32_t, StreamOffset"
Function,+,stream_seek_to_char,_Bool,"Stream*, char, StreamDirection"
Function,+,stream_size,size_t,Stream*
Function,+,stream_split,_Bool,"Stream*, Stream*, Stream*"
Function,+,stream_tell,size_t,Stream*

1 entry status name type params
2 Version + 11.5 11.6
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
2480 Function + stream_rewind _Bool Stream*
2481 Function + stream_save_to_file size_t Stream*, Storage*, const char*, FS_OpenMode
2482 Function + stream_seek _Bool Stream*, int32_t, StreamOffset
2483 Function + stream_seek_to_char _Bool Stream*, char, StreamDirection
2484 Function + stream_size size_t Stream*
2485 Function + stream_split _Bool Stream*, Stream*, Stream*
2486 Function + stream_tell size_t Stream*

View File

@ -8,6 +8,7 @@
#define SECTOR_SIZE 512
#define N_SECTORS 8
#define TAG "SDCache"
typedef struct {
uint32_t itr;
@ -19,14 +20,15 @@ static SectorCache* cache = NULL;
void sector_cache_init() {
if(cache == NULL) {
cache = furi_hal_memory_alloc(sizeof(SectorCache));
// TODO: tuneup allocation order, to place cache in mem pool (MEM2)
cache = memmgr_alloc_from_pool(sizeof(SectorCache));
}
if(cache != NULL) {
FURI_LOG_I("SectorCache", "Initializing sector cache");
FURI_LOG_I(TAG, "Init");
memset(cache, 0, sizeof(SectorCache));
} else {
FURI_LOG_E("SectorCache", "Cannot enable sector cache");
FURI_LOG_E(TAG, "Init failed");
}
}

View File

@ -4,6 +4,8 @@
#include <core/check.h>
#include <core/common_defines.h>
#define STREAM_BUFFER_SIZE (32U)
void stream_free(Stream* stream) {
furi_assert(stream);
stream->vtable->free(stream);
@ -24,6 +26,82 @@ bool stream_seek(Stream* stream, int32_t offset, StreamOffset offset_type) {
return stream->vtable->seek(stream, offset, offset_type);
}
static bool stream_seek_to_char_forward(Stream* stream, char c) {
// Search is starting from seconds character
if(!stream_seek(stream, 1, StreamOffsetFromCurrent)) {
return false;
}
// Search character in a stream
bool result = false;
while(!result) {
uint8_t buffer[STREAM_BUFFER_SIZE] = {0};
size_t ret = stream_read(stream, buffer, STREAM_BUFFER_SIZE);
for(size_t i = 0; i < ret; i++) {
if(buffer[i] == c) {
stream_seek(stream, (int32_t)i - ret, StreamOffsetFromCurrent);
result = true;
break;
}
}
if(ret != STREAM_BUFFER_SIZE) break;
}
return result;
}
static bool stream_seek_to_char_backward(Stream* stream, char c) {
size_t anchor = stream_tell(stream);
// Special case, no previous characters
if(anchor == 0) {
return false;
}
bool result = false;
while(!result) {
// Seek back
uint8_t buffer[STREAM_BUFFER_SIZE] = {0};
size_t to_read = STREAM_BUFFER_SIZE;
if(to_read > anchor) {
to_read = anchor;
}
anchor -= to_read;
furi_check(stream_seek(stream, anchor, StreamOffsetFromStart));
size_t ret = stream_read(stream, buffer, to_read);
for(size_t i = 0; i < ret; i++) {
size_t cursor = ret - i - 1;
if(buffer[cursor] == c) {
result = true;
furi_check(stream_seek(stream, anchor + cursor, StreamOffsetFromStart));
break;
} else {
}
}
if(ret != STREAM_BUFFER_SIZE) break;
}
return result;
}
bool stream_seek_to_char(Stream* stream, char c, StreamDirection direction) {
const size_t old_position = stream_tell(stream);
bool result = false;
if(direction == StreamDirectionForward) {
result = stream_seek_to_char_forward(stream, c);
} else if(direction == StreamDirectionBackward) {
result = stream_seek_to_char_backward(stream, c);
}
// Rollback
if(!result) {
stream_seek(stream, old_position, StreamOffsetFromStart);
}
return result;
}
size_t stream_tell(Stream* stream) {
furi_assert(stream);
return stream->vtable->tell(stream);
@ -69,11 +147,10 @@ static bool stream_write_struct(Stream* stream, const void* context) {
bool stream_read_line(Stream* stream, FuriString* str_result) {
furi_string_reset(str_result);
const uint8_t buffer_size = 32;
uint8_t buffer[buffer_size];
uint8_t buffer[STREAM_BUFFER_SIZE];
do {
uint16_t bytes_were_read = stream_read(stream, buffer, buffer_size);
uint16_t bytes_were_read = stream_read(stream, buffer, STREAM_BUFFER_SIZE);
if(bytes_were_read == 0) break;
bool result = false;

View File

@ -16,6 +16,11 @@ typedef enum {
StreamOffsetFromEnd,
} StreamOffset;
typedef enum {
StreamDirectionForward,
StreamDirectionBackward,
} StreamDirection;
typedef bool (*StreamWriteCB)(Stream* stream, const void* context);
/**
@ -31,15 +36,15 @@ void stream_free(Stream* stream);
void stream_clean(Stream* stream);
/**
* Indicates that the rw pointer is at the end of the stream
* Indicates that the RW pointer is at the end of the stream
* @param stream Stream instance
* @return true if rw pointer is at the end of the stream
* @return false if rw pointer is not at the end of the stream
* @return true if RW pointer is at the end of the stream
* @return false if RW pointer is not at the end of the stream
*/
bool stream_eof(Stream* stream);
/**
* Moves the rw pointer.
* Moves the RW pointer.
* @param stream Stream instance
* @param offset how much to move the pointer
* @param offset_type starting from what
@ -48,10 +53,20 @@ bool stream_eof(Stream* stream);
*/
bool stream_seek(Stream* stream, int32_t offset, StreamOffset offset_type);
/** Seek to next occurrence of the character
*
* @param stream Pointer to the stream instance
* @param[in] c The Character
* @param[in] direction The Direction
*
* @return true on success
*/
bool stream_seek_to_char(Stream* stream, char c, StreamDirection direction);
/**
* Gets the value of the rw pointer
* Gets the value of the RW pointer
* @param stream Stream instance
* @return size_t value of the rw pointer
* @return size_t value of the RW pointer
*/
size_t stream_tell(Stream* stream);
@ -101,13 +116,13 @@ bool stream_delete_and_insert(
* Read line from a stream (supports LF and CRLF line endings)
* @param stream
* @param str_result
* @return true if line lenght is not zero
* @return true if line length is not zero
* @return false otherwise
*/
bool stream_read_line(Stream* stream, FuriString* str_result);
/**
* Moves the rw pointer to the start
* Moves the RW pointer to the start
* @param stream Stream instance
*/
bool stream_rewind(Stream* stream);
@ -157,7 +172,7 @@ size_t stream_write_vaformat(Stream* stream, const char* format, va_list args);
/**
* Insert N chars to the stream, starting at the current pointer.
* Data will be inserted, not overwritteт, so the stream will be increased in size.
* Data will be inserted, not overwritten, so the stream will be increased in size.
* @param stream Stream instance
* @param data data to be inserted
* @param size size of data to be inserted
@ -273,7 +288,7 @@ bool stream_delete_and_insert_vaformat(
/**
* Remove N chars from the stream, starting at the current pointer.
* The size may be larger than stream size, the stream will be cleared from current rw pointer to the end.
* The size may be larger than stream size, the stream will be cleared from current RW pointer to the end.
* @param stream Stream instance
* @param size how many chars need to be deleted
* @return true if the operation was successful
@ -282,7 +297,7 @@ bool stream_delete_and_insert_vaformat(
bool stream_delete(Stream* stream, size_t size);
/**
* Copy data from one stream to another. Data will be copied from current rw pointer and to current rw pointer.
* Copy data from one stream to another. Data will be copied from current RW pointer and to current RW pointer.
* @param stream_from
* @param stream_to
* @param size
@ -328,7 +343,7 @@ size_t stream_load_from_file(Stream* stream, Storage* storage, const char* path)
size_t stream_save_to_file(Stream* stream, Storage* storage, const char* path, FS_OpenMode mode);
/**
* Dump stream inner data (size, RW positiot, content)
* Dump stream inner data (size, RW position, content)
* @param stream Stream instance
*/
void stream_dump_data(Stream* stream);

View File

@ -60,6 +60,12 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res
char type_code = furi_string_get_char(resource_manifest->linebuf, 0);
switch(type_code) {
case 'V':
resource_manifest->entry.type = ResourceManifestEntryTypeVersion;
break;
case 'T':
resource_manifest->entry.type = ResourceManifestEntryTypeTimestamp;
break;
case 'F':
resource_manifest->entry.type = ResourceManifestEntryTypeFile;
break;
@ -98,9 +104,9 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res
furi_string_right(resource_manifest->linebuf, offs + 1);
furi_string_set(resource_manifest->entry.name, resource_manifest->linebuf);
} else if(resource_manifest->entry.type == ResourceManifestEntryTypeDirectory) { //-V547
/* Parse directory entry
D:<name> */
} else { //-V547
/* Everything else is plain key value. Parse version, timestamp or directory entry
<Type>:<Value> */
/* Remove entry type code */
furi_string_right(resource_manifest->linebuf, 2);
@ -113,3 +119,45 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res
return NULL;
}
ResourceManifestEntry*
resource_manifest_reader_previous(ResourceManifestReader* resource_manifest) {
furi_assert(resource_manifest);
// Snapshot position for rollback
const size_t previous_position = stream_tell(resource_manifest->stream);
// We need to jump 2 lines back
size_t jumps = 2;
// Special case: end of the file.
const bool was_eof = stream_eof(resource_manifest->stream);
if(was_eof) {
jumps = 1;
}
while(jumps) {
if(!stream_seek_to_char(resource_manifest->stream, '\n', StreamDirectionBackward)) {
break;
}
if(stream_tell(resource_manifest->stream) < (previous_position - 1)) {
jumps--;
}
}
// Special case: first line. Force seek to zero
if(jumps == 1) {
jumps = 0;
stream_seek(resource_manifest->stream, 0, StreamOffsetFromStart);
}
if(jumps == 0) {
ResourceManifestEntry* entry = resource_manifest_reader_next(resource_manifest);
// Special case: was end of the file, prevent loop
if(was_eof) {
stream_seek(resource_manifest->stream, -1, StreamOffsetFromCurrent);
}
return entry;
} else {
stream_seek(resource_manifest->stream, previous_position, StreamOffsetFromStart);
return NULL;
}
}

View File

@ -11,6 +11,8 @@ extern "C" {
typedef enum {
ResourceManifestEntryTypeUnknown = 0,
ResourceManifestEntryTypeVersion,
ResourceManifestEntryTypeTimestamp,
ResourceManifestEntryTypeDirectory,
ResourceManifestEntryTypeFile,
} ResourceManifestEntryType;
@ -52,6 +54,18 @@ bool resource_manifest_reader_open(ResourceManifestReader* resource_manifest, co
*/
ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* resource_manifest);
/** Read previous file/dir entry from manifest
*
* You must be at the end of the manifest to use this function.
* Intended to be used after reaching end with resource_manifest_reader_next
*
* @param resource_manifest Pointer to the ResourceManifestReader instance
*
* @return entry or NULL if end of file
*/
ResourceManifestEntry*
resource_manifest_reader_previous(ResourceManifestReader* resource_manifest);
#ifdef __cplusplus
} // extern "C"
#endif