From 41c43f480512899b8ee0010f87669ced9a3088f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Fri, 6 Jan 2023 15:31:17 +0900 Subject: [PATCH] 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 --- .../debug/unit_tests/manifest/manifest.c | 75 +++++++++++++++++ .../debug/unit_tests/stream/stream_test.c | 24 ++++++ applications/debug/unit_tests/test_index.c | 2 + .../updater/util/update_task_worker_backup.c | 44 +++++++++- assets/unit_tests/Manifest | 76 +++++++++++++++++ firmware/targets/f7/api_symbols.csv | 3 +- firmware/targets/f7/fatfs/sector_cache.c | 8 +- lib/toolbox/stream/stream.c | 83 ++++++++++++++++++- lib/toolbox/stream/stream.h | 39 ++++++--- lib/update_util/resources/manifest.c | 54 +++++++++++- lib/update_util/resources/manifest.h | 14 ++++ 11 files changed, 398 insertions(+), 24 deletions(-) create mode 100644 applications/debug/unit_tests/manifest/manifest.c create mode 100644 assets/unit_tests/Manifest diff --git a/applications/debug/unit_tests/manifest/manifest.c b/applications/debug/unit_tests/manifest/manifest.c new file mode 100644 index 00000000..0b24ad1e --- /dev/null +++ b/applications/debug/unit_tests/manifest/manifest.c @@ -0,0 +1,75 @@ +#include +#include "../minunit.h" +#include + +#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; +} \ No newline at end of file diff --git a/applications/debug/unit_tests/stream/stream_test.c b/applications/debug/unit_tests/stream/stream_test.c index 802e3402..c2869657 100644 --- a/applications/debug/unit_tests/stream/stream_test.c +++ b/applications/debug/unit_tests/stream/stream_test.c @@ -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); diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index ccf47153..2bb9c423 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -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}, diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 1f88d4f4..78040106 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -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); } diff --git a/assets/unit_tests/Manifest b/assets/unit_tests/Manifest new file mode 100644 index 00000000..db2979ee --- /dev/null +++ b/assets/unit_tests/Manifest @@ -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 diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 9e1fa85d..ac7d1a0c 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -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* diff --git a/firmware/targets/f7/fatfs/sector_cache.c b/firmware/targets/f7/fatfs/sector_cache.c index 5a4f1b97..d23c1d5a 100644 --- a/firmware/targets/f7/fatfs/sector_cache.c +++ b/firmware/targets/f7/fatfs/sector_cache.c @@ -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"); } } diff --git a/lib/toolbox/stream/stream.c b/lib/toolbox/stream/stream.c index 055bab5b..407da0f2 100644 --- a/lib/toolbox/stream/stream.c +++ b/lib/toolbox/stream/stream.c @@ -4,6 +4,8 @@ #include #include +#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; diff --git a/lib/toolbox/stream/stream.h b/lib/toolbox/stream/stream.h index fc385510..84b4f0eb 100644 --- a/lib/toolbox/stream/stream.h +++ b/lib/toolbox/stream/stream.h @@ -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); diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index baa7aceb..5a818a0a 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -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: */ + } else { //-V547 + /* Everything else is plain key value. Parse version, timestamp or directory entry + : */ /* 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; + } +} diff --git a/lib/update_util/resources/manifest.h b/lib/update_util/resources/manifest.h index 8baa1613..ddceb5ff 100644 --- a/lib/update_util/resources/manifest.h +++ b/lib/update_util/resources/manifest.h @@ -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 \ No newline at end of file