mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-11-22 20:43:07 +00:00
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:
parent
b8dd75884c
commit
41c43f4805
11 changed files with 398 additions and 24 deletions
75
applications/debug/unit_tests/manifest/manifest.c
Normal file
75
applications/debug/unit_tests/manifest/manifest.c
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
76
assets/unit_tests/Manifest
Normal file
76
assets/unit_tests/Manifest
Normal 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
|
|
@ -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*
|
||||
|
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue