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:
		
							
								
								
									
										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
 | 
			
		||||
		Reference in New Issue
	
	Block a user