Просмотр исходного кода

[FL-2499] Folders rename fix (#1190)

* Toolbox: dir_walk concept (like os.walk)
* Storage CLI: tree command
* Storage: fix folders copying, stage 1
* UnitTest: proper delays in subghz tests
* Toolbox: dir_walk, recursive and filter options
* dir_walk: unit tests
* Merge: Fix unused param
* SubGhz: cleaned up data parsing routine
* SubGhz unit test: cleaned up logs, yield data load
* SubGhz unit test: naming

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
SG 3 лет назад
Родитель
Сommit
fac4391af7

+ 2 - 2
applications/storage/storage.h

@@ -282,7 +282,7 @@ FS_Error storage_int_restore(Storage* api, const char* dstname);
 /***************** Simplified Functions ******************/
 
 /**
- * Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open
+ * Removes a file/directory, the directory must be empty and the file/directory must not be open
  * @param storage pointer to the api
  * @param path 
  * @return true on success or if file/dir is not exist
@@ -290,7 +290,7 @@ FS_Error storage_int_restore(Storage* api, const char* dstname);
 bool storage_simply_remove(Storage* storage, const char* path);
 
 /**
- * Removes a file/directory from the repository, the directory can be not empty
+ * Recursively removes a file/directory, the directory can be not empty
  * @param storage pointer to the api
  * @param path
  * @return true on success or if file/dir is not exist

+ 45 - 0
applications/storage/storage_cli.c

@@ -4,6 +4,7 @@
 #include <cli/cli.h>
 #include <lib/toolbox/args.h>
 #include <lib/toolbox/md5.h>
+#include <lib/toolbox/dir_walk.h>
 #include <storage/storage.h>
 #include <storage/storage_sd_api.h>
 #include <power/power_service/power.h>
@@ -18,6 +19,7 @@ static void storage_cli_print_usage() {
     printf("\tinfo\t - get FS info\r\n");
     printf("\tformat\t - format filesystem\r\n");
     printf("\tlist\t - list files and dirs\r\n");
+    printf("\ttree\t - list files and dirs, recursive\r\n");
     printf("\tremove\t - delete the file or directory\r\n");
     printf("\tread\t - read text from file and print file size and content to cli\r\n");
     printf(
@@ -138,6 +140,44 @@ static void storage_cli_list(Cli* cli, string_t path) {
     }
 }
 
+static void storage_cli_tree(Cli* cli, string_t path) {
+    if(string_cmp_str(path, "/") == 0) {
+        string_set(path, "/int");
+        storage_cli_tree(cli, path);
+        string_set(path, "/ext");
+        storage_cli_tree(cli, path);
+    } else {
+        Storage* api = furi_record_open("storage");
+        DirWalk* dir_walk = dir_walk_alloc(api);
+        string_t name;
+        string_init(name);
+
+        if(dir_walk_open(dir_walk, string_get_cstr(path))) {
+            FileInfo fileinfo;
+            bool read_done = false;
+
+            while(dir_walk_read(dir_walk, name, &fileinfo) == DirWalkOK) {
+                read_done = true;
+                if(fileinfo.flags & FSF_DIRECTORY) {
+                    printf("\t[D] %s\r\n", string_get_cstr(name));
+                } else {
+                    printf("\t[F] %s %lub\r\n", string_get_cstr(name), (uint32_t)(fileinfo.size));
+                }
+            }
+
+            if(!read_done) {
+                printf("\tEmpty\r\n");
+            }
+        } else {
+            storage_cli_print_error(dir_walk_get_error(dir_walk));
+        }
+
+        string_clear(name);
+        dir_walk_free(dir_walk);
+        furi_record_close("storage");
+    }
+}
+
 static void storage_cli_read(Cli* cli, string_t path) {
     UNUSED(cli);
     Storage* api = furi_record_open("storage");
@@ -474,6 +514,11 @@ void storage_cli(Cli* cli, string_t args, void* context) {
             break;
         }
 
+        if(string_cmp_str(cmd, "tree") == 0) {
+            storage_cli_tree(cli, path);
+            break;
+        }
+
         if(string_cmp_str(cmd, "read") == 0) {
             storage_cli_read(cli, path);
             break;

+ 60 - 2
applications/storage/storage_external_api.c

@@ -4,6 +4,7 @@
 #include "storage_i.h"
 #include "storage_message.h"
 #include <toolbox/stream/file_stream.h>
+#include <toolbox/dir_walk.h>
 
 #define MAX_NAME_LENGTH 256
 
@@ -332,12 +333,69 @@ FS_Error storage_common_remove(Storage* storage, const char* path) {
 FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path) {
     FS_Error error = storage_common_copy(storage, old_path, new_path);
     if(error == FSE_OK) {
-        error = storage_common_remove(storage, old_path);
+        if(storage_simply_remove_recursive(storage, old_path)) {
+            error = FSE_OK;
+        } else {
+            error = FSE_INTERNAL;
+        }
     }
 
     return error;
 }
 
+static FS_Error
+    storage_copy_recursive(Storage* storage, const char* old_path, const char* new_path) {
+    FS_Error error = storage_common_mkdir(storage, new_path);
+    DirWalk* dir_walk = dir_walk_alloc(storage);
+    string_t path;
+    string_t tmp_new_path;
+    string_t tmp_old_path;
+    FileInfo fileinfo;
+    string_init(path);
+    string_init(tmp_new_path);
+    string_init(tmp_old_path);
+
+    do {
+        if(error != FSE_OK) break;
+
+        if(!dir_walk_open(dir_walk, old_path)) {
+            error = dir_walk_get_error(dir_walk);
+            break;
+        }
+
+        while(1) {
+            DirWalkResult res = dir_walk_read(dir_walk, path, &fileinfo);
+
+            if(res == DirWalkError) {
+                error = dir_walk_get_error(dir_walk);
+                break;
+            } else if(res == DirWalkLast) {
+                break;
+            } else {
+                string_set(tmp_old_path, path);
+                string_right(path, strlen(old_path));
+                string_printf(tmp_new_path, "%s%s", new_path, string_get_cstr(path));
+
+                if(fileinfo.flags & FSF_DIRECTORY) {
+                    error = storage_common_mkdir(storage, string_get_cstr(tmp_new_path));
+                } else {
+                    error = storage_common_copy(
+                        storage, string_get_cstr(tmp_old_path), string_get_cstr(tmp_new_path));
+                }
+
+                if(error != FSE_OK) break;
+            }
+        }
+
+    } while(false);
+
+    string_clear(tmp_new_path);
+    string_clear(tmp_old_path);
+    string_clear(path);
+    dir_walk_free(dir_walk);
+    return error;
+}
+
 FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) {
     FS_Error error;
 
@@ -346,7 +404,7 @@ FS_Error storage_common_copy(Storage* storage, const char* old_path, const char*
 
     if(error == FSE_OK) {
         if(fileinfo.flags & FSF_DIRECTORY) {
-            error = storage_common_mkdir(storage, new_path);
+            error = storage_copy_recursive(storage, old_path, new_path);
         } else {
             Stream* stream_from = file_stream_alloc(storage);
             Stream* stream_to = file_stream_alloc(storage);

+ 272 - 0
applications/unit_tests/storage/dirwalk_test.c

@@ -0,0 +1,272 @@
+#include "../minunit.h"
+#include <furi.h>
+#include <m-dict.h>
+#include <toolbox/dir_walk.h>
+
+static const char* const storage_test_dirwalk_paths[] = {
+    "1",
+    "11",
+    "111",
+    "1/2",
+    "1/22",
+    "1/222",
+    "11/2",
+    "111/2",
+    "111/22",
+    "111/22/33",
+};
+
+static const char* const storage_test_dirwalk_files[] = {
+    "file1.test",
+    "file2.test",
+    "file3.ext_test",
+    "1/file1.test",
+    "111/22/33/file1.test",
+    "111/22/33/file2.test",
+    "111/22/33/file3.ext_test",
+    "111/22/33/file4.ext_test",
+};
+
+typedef struct {
+    const char* const path;
+    bool is_dir;
+} StorageTestPathDesc;
+
+const StorageTestPathDesc storage_test_dirwalk_full[] = {
+    {.path = "1", .is_dir = true},
+    {.path = "11", .is_dir = true},
+    {.path = "111", .is_dir = true},
+    {.path = "1/2", .is_dir = true},
+    {.path = "1/22", .is_dir = true},
+    {.path = "1/222", .is_dir = true},
+    {.path = "11/2", .is_dir = true},
+    {.path = "111/2", .is_dir = true},
+    {.path = "111/22", .is_dir = true},
+    {.path = "111/22/33", .is_dir = true},
+    {.path = "file1.test", .is_dir = false},
+    {.path = "file2.test", .is_dir = false},
+    {.path = "file3.ext_test", .is_dir = false},
+    {.path = "1/file1.test", .is_dir = false},
+    {.path = "111/22/33/file1.test", .is_dir = false},
+    {.path = "111/22/33/file2.test", .is_dir = false},
+    {.path = "111/22/33/file3.ext_test", .is_dir = false},
+    {.path = "111/22/33/file4.ext_test", .is_dir = false},
+};
+
+const StorageTestPathDesc storage_test_dirwalk_no_recursive[] = {
+    {.path = "1", .is_dir = true},
+    {.path = "11", .is_dir = true},
+    {.path = "111", .is_dir = true},
+    {.path = "file1.test", .is_dir = false},
+    {.path = "file2.test", .is_dir = false},
+    {.path = "file3.ext_test", .is_dir = false},
+};
+
+const StorageTestPathDesc storage_test_dirwalk_filter[] = {
+    {.path = "file1.test", .is_dir = false},
+    {.path = "file2.test", .is_dir = false},
+    {.path = "1/file1.test", .is_dir = false},
+    {.path = "111/22/33/file1.test", .is_dir = false},
+    {.path = "111/22/33/file2.test", .is_dir = false},
+};
+
+typedef struct {
+    bool is_dir;
+    bool visited;
+} StorageTestPath;
+
+DICT_DEF2(StorageTestPathDict, string_t, STRING_OPLIST, StorageTestPath, M_POD_OPLIST)
+
+static StorageTestPathDict_t*
+    storage_test_paths_alloc(const StorageTestPathDesc paths[], size_t paths_count) {
+    StorageTestPathDict_t* data = malloc(sizeof(StorageTestPathDict_t));
+    StorageTestPathDict_init(*data);
+
+    for(size_t i = 0; i < paths_count; i++) {
+        string_t key;
+        string_init_set(key, paths[i].path);
+        StorageTestPath value = {
+            .is_dir = paths[i].is_dir,
+            .visited = false,
+        };
+
+        StorageTestPathDict_set_at(*data, key, value);
+        string_clear(key);
+    }
+
+    return data;
+}
+
+static void storage_test_paths_free(StorageTestPathDict_t* data) {
+    StorageTestPathDict_clear(*data);
+    free(data);
+}
+
+static bool storage_test_paths_mark(StorageTestPathDict_t* data, string_t path, bool is_dir) {
+    bool found = false;
+
+    StorageTestPath* record = StorageTestPathDict_get(*data, path);
+    if(record) {
+        if(is_dir == record->is_dir) {
+            if(record->visited == false) {
+                record->visited = true;
+                found = true;
+            }
+        }
+    }
+
+    return found;
+}
+
+static bool storage_test_paths_check(StorageTestPathDict_t* data) {
+    bool error = false;
+
+    StorageTestPathDict_it_t it;
+    for(StorageTestPathDict_it(it, *data); !StorageTestPathDict_end_p(it);
+        StorageTestPathDict_next(it)) {
+        const StorageTestPathDict_itref_t* itref = StorageTestPathDict_cref(it);
+
+        if(itref->value.visited == false) {
+            error = true;
+            break;
+        }
+    }
+
+    return error;
+}
+
+static bool write_file_13DA(Storage* storage, const char* path) {
+    File* file = storage_file_alloc(storage);
+    bool result = false;
+    if(storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        result = storage_file_write(file, "13DA", 4) == 4;
+    }
+    storage_file_close(file);
+    storage_file_free(file);
+
+    return result;
+}
+
+static void storage_dirs_create(Storage* storage, const char* base) {
+    string_t path;
+    string_init(path);
+
+    storage_common_mkdir(storage, base);
+
+    for(size_t i = 0; i < COUNT_OF(storage_test_dirwalk_paths); i++) {
+        string_printf(path, "%s/%s", base, storage_test_dirwalk_paths[i]);
+        storage_common_mkdir(storage, string_get_cstr(path));
+    }
+
+    for(size_t i = 0; i < COUNT_OF(storage_test_dirwalk_files); i++) {
+        string_printf(path, "%s/%s", base, storage_test_dirwalk_files[i]);
+        write_file_13DA(storage, string_get_cstr(path));
+    }
+
+    string_clear(path);
+}
+
+MU_TEST_1(test_dirwalk_full, Storage* storage) {
+    string_t path;
+    string_init(path);
+    FileInfo fileinfo;
+
+    StorageTestPathDict_t* paths =
+        storage_test_paths_alloc(storage_test_dirwalk_full, COUNT_OF(storage_test_dirwalk_full));
+
+    DirWalk* dir_walk = dir_walk_alloc(storage);
+    mu_check(dir_walk_open(dir_walk, "/ext/dirwalk"));
+
+    while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
+        string_right(path, strlen("/ext/dirwalk/"));
+        mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
+    }
+
+    dir_walk_free(dir_walk);
+    string_clear(path);
+
+    mu_check(storage_test_paths_check(paths) == false);
+
+    storage_test_paths_free(paths);
+}
+
+MU_TEST_1(test_dirwalk_no_recursive, Storage* storage) {
+    string_t path;
+    string_init(path);
+    FileInfo fileinfo;
+
+    StorageTestPathDict_t* paths = storage_test_paths_alloc(
+        storage_test_dirwalk_no_recursive, COUNT_OF(storage_test_dirwalk_no_recursive));
+
+    DirWalk* dir_walk = dir_walk_alloc(storage);
+    dir_walk_set_recursive(dir_walk, false);
+    mu_check(dir_walk_open(dir_walk, "/ext/dirwalk"));
+
+    while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
+        string_right(path, strlen("/ext/dirwalk/"));
+        mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
+    }
+
+    dir_walk_free(dir_walk);
+    string_clear(path);
+
+    mu_check(storage_test_paths_check(paths) == false);
+
+    storage_test_paths_free(paths);
+}
+
+static bool test_dirwalk_filter_no_folder_ext(const char* name, FileInfo* fileinfo, void* ctx) {
+    UNUSED(ctx);
+
+    // only files
+    if(!(fileinfo->flags & FSF_DIRECTORY)) {
+        // with ".test" in name
+        if(strstr(name, ".test") != NULL) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+MU_TEST_1(test_dirwalk_filter, Storage* storage) {
+    string_t path;
+    string_init(path);
+    FileInfo fileinfo;
+
+    StorageTestPathDict_t* paths = storage_test_paths_alloc(
+        storage_test_dirwalk_filter, COUNT_OF(storage_test_dirwalk_filter));
+
+    DirWalk* dir_walk = dir_walk_alloc(storage);
+    dir_walk_set_filter_cb(dir_walk, test_dirwalk_filter_no_folder_ext, NULL);
+    mu_check(dir_walk_open(dir_walk, "/ext/dirwalk"));
+
+    while(dir_walk_read(dir_walk, path, &fileinfo) == DirWalkOK) {
+        string_right(path, strlen("/ext/dirwalk/"));
+        mu_check(storage_test_paths_mark(paths, path, (fileinfo.flags & FSF_DIRECTORY)));
+    }
+
+    dir_walk_free(dir_walk);
+    string_clear(path);
+
+    mu_check(storage_test_paths_check(paths) == false);
+
+    storage_test_paths_free(paths);
+}
+
+MU_TEST_SUITE(test_dirwalk_suite) {
+    Storage* storage = furi_record_open("storage");
+    storage_dirs_create(storage, "/ext/dirwalk");
+
+    MU_RUN_TEST_1(test_dirwalk_full, storage);
+    MU_RUN_TEST_1(test_dirwalk_no_recursive, storage);
+    MU_RUN_TEST_1(test_dirwalk_filter, storage);
+
+    storage_simply_remove_recursive(storage, "/ext/dirwalk");
+    furi_record_close("storage");
+}
+
+int run_minunit_test_dirwalk() {
+    MU_RUN_SUITE(test_dirwalk_suite);
+    return MU_EXIT_CODE;
+}

+ 145 - 0
applications/unit_tests/storage/storage_test.c

@@ -164,8 +164,153 @@ MU_TEST_SUITE(storage_dir) {
     MU_RUN_TEST(storage_dir_open_lock);
 }
 
+static const char* const storage_copy_test_paths[] = {
+    "1",
+    "11",
+    "111",
+    "1/2",
+    "1/22",
+    "1/222",
+    "11/1",
+    "111/2",
+    "111/22",
+    "111/22/33",
+};
+
+static const char* const storage_copy_test_files[] = {
+    "file.test",
+    "1/file.test",
+    "111/22/33/file.test",
+};
+
+static bool write_file_13DA(Storage* storage, const char* path) {
+    File* file = storage_file_alloc(storage);
+    bool result = false;
+    if(storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        result = storage_file_write(file, "13DA", 4) == 4;
+    }
+    storage_file_close(file);
+    storage_file_free(file);
+
+    return result;
+}
+
+static bool check_file_13DA(Storage* storage, const char* path) {
+    File* file = storage_file_alloc(storage);
+    bool result = false;
+    if(storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        char data[10] = {0};
+        result = storage_file_read(file, data, 4) == 4;
+        if(result) {
+            result = memcmp(data, "13DA", 4) == 0;
+        }
+    }
+    storage_file_close(file);
+    storage_file_free(file);
+
+    return result;
+}
+
+static void storage_dir_create(Storage* storage, const char* base) {
+    string_t path;
+    string_init(path);
+
+    storage_common_mkdir(storage, base);
+
+    for(size_t i = 0; i < COUNT_OF(storage_copy_test_paths); i++) {
+        string_printf(path, "%s/%s", base, storage_copy_test_paths[i]);
+        storage_common_mkdir(storage, string_get_cstr(path));
+    }
+
+    for(size_t i = 0; i < COUNT_OF(storage_copy_test_files); i++) {
+        string_printf(path, "%s/%s", base, storage_copy_test_files[i]);
+        write_file_13DA(storage, string_get_cstr(path));
+    }
+
+    string_clear(path);
+}
+
+static void storage_dir_remove(Storage* storage, const char* base) {
+    storage_simply_remove_recursive(storage, base);
+}
+
+static bool storage_dir_rename_check(Storage* storage, const char* base) {
+    bool result = false;
+    string_t path;
+    string_init(path);
+
+    result = (storage_common_stat(storage, base, NULL) == FSE_OK);
+
+    if(result) {
+        for(size_t i = 0; i < COUNT_OF(storage_copy_test_paths); i++) {
+            string_printf(path, "%s/%s", base, storage_copy_test_paths[i]);
+            result = (storage_common_stat(storage, string_get_cstr(path), NULL) == FSE_OK);
+            if(!result) {
+                break;
+            }
+        }
+    }
+
+    if(result) {
+        for(size_t i = 0; i < COUNT_OF(storage_copy_test_files); i++) {
+            string_printf(path, "%s/%s", base, storage_copy_test_files[i]);
+            result = check_file_13DA(storage, string_get_cstr(path));
+            if(!result) {
+                break;
+            }
+        }
+    }
+
+    string_clear(path);
+    return result;
+}
+
+MU_TEST(storage_file_rename) {
+    Storage* storage = furi_record_open("storage");
+    File* file = storage_file_alloc(storage);
+
+    mu_check(write_file_13DA(storage, "/ext/file.old"));
+    mu_check(check_file_13DA(storage, "/ext/file.old"));
+    mu_assert_int_eq(FSE_OK, storage_common_rename(storage, "/ext/file.old", "/ext/file.new"));
+    mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, "/ext/file.old", NULL));
+    mu_assert_int_eq(FSE_OK, storage_common_stat(storage, "/ext/file.new", NULL));
+    mu_check(check_file_13DA(storage, "/ext/file.new"));
+    mu_assert_int_eq(FSE_OK, storage_common_remove(storage, "/ext/file.new"));
+
+    storage_file_free(file);
+    furi_record_close("storage");
+}
+
+MU_TEST(storage_dir_rename) {
+    Storage* storage = furi_record_open("storage");
+
+    storage_dir_create(storage, "/ext/dir.old");
+
+    mu_check(storage_dir_rename_check(storage, "/ext/dir.old"));
+
+    mu_assert_int_eq(FSE_OK, storage_common_rename(storage, "/ext/dir.old", "/ext/dir.new"));
+    mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, "/ext/dir.old", NULL));
+    mu_check(storage_dir_rename_check(storage, "/ext/dir.new"));
+
+    storage_dir_remove(storage, "/ext/dir.new");
+    mu_assert_int_eq(FSE_NOT_EXIST, storage_common_stat(storage, "/ext/dir.new", NULL));
+
+    furi_record_close("storage");
+}
+
+MU_TEST_SUITE(storage_rename) {
+    MU_RUN_TEST(storage_file_rename);
+    MU_RUN_TEST(storage_dir_rename);
+
+    Storage* storage = furi_record_open("storage");
+    storage_dir_remove(storage, "/ext/dir.old");
+    storage_dir_remove(storage, "/ext/dir.new");
+    furi_record_close("storage");
+}
+
 int run_minunit_test_storage() {
     MU_RUN_SUITE(storage_file);
     MU_RUN_SUITE(storage_dir);
+    MU_RUN_SUITE(storage_rename);
     return MU_EXIT_CODE;
 }

+ 51 - 51
applications/unit_tests/subghz/subghz_test.c

@@ -32,7 +32,7 @@ static void subghz_test_rx_callback(
     string_init(text);
     subghz_protocol_decoder_base_get_string(decoder_base, text);
     subghz_receiver_reset(receiver_handler);
-    FURI_LOG_I(TAG, "\r\n%s", string_get_cstr(text));
+    FURI_LOG_T(TAG, "\r\n%s", string_get_cstr(text));
     string_clear(text);
     subghz_test_decoder_count++;
 }
@@ -54,7 +54,7 @@ static void subghz_test_deinit(void) {
     subghz_environment_free(environment_handler);
 }
 
-static bool subghz_decode_test(const char* path, const char* name_decoder) {
+static bool subghz_decoder_test(const char* path, const char* name_decoder) {
     subghz_test_decoder_count = 0;
     uint32_t test_start = furi_hal_get_tick();
 
@@ -64,18 +64,18 @@ static bool subghz_decode_test(const char* path, const char* name_decoder) {
     if(decoder) {
         file_worker_encoder_handler = subghz_file_encoder_worker_alloc();
         if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) {
-            //the worker needs a file in order to open and read part of the file
+            // the worker needs a file in order to open and read part of the file
             osDelay(100);
 
             LevelDuration level_duration;
             while(furi_hal_get_tick() - test_start < TEST_TIMEOUT) {
-                furi_hal_delay_us(
-                    500); //you need to have time to read from the file from the SD card
                 level_duration =
                     subghz_file_encoder_worker_get_level_duration(file_worker_encoder_handler);
                 if(!level_duration_is_reset(level_duration)) {
                     bool level = level_duration_get_level(level_duration);
                     uint32_t duration = level_duration_get_duration(level_duration);
+                    // Yield, to load data inside the worker
+                    osThreadYield();
                     decoder->protocol->decoder->feed(decoder, level, duration);
                 } else {
                     break;
@@ -88,7 +88,7 @@ static bool subghz_decode_test(const char* path, const char* name_decoder) {
         }
         subghz_file_encoder_worker_free(file_worker_encoder_handler);
     }
-    FURI_LOG_I(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
+    FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
     if(furi_hal_get_tick() - test_start > TEST_TIMEOUT) {
         printf("\033[0;31mTest decoder %s ERROR TimeOut\033[0m\r\n", name_decoder);
         return false;
@@ -97,24 +97,25 @@ static bool subghz_decode_test(const char* path, const char* name_decoder) {
     }
 }
 
-static bool subghz_decode_ramdom_test(const char* path) {
+static bool subghz_decode_random_test(const char* path) {
     subghz_test_decoder_count = 0;
     subghz_receiver_reset(receiver_handler);
     uint32_t test_start = furi_hal_get_tick();
 
     file_worker_encoder_handler = subghz_file_encoder_worker_alloc();
     if(subghz_file_encoder_worker_start(file_worker_encoder_handler, path)) {
-        //the worker needs a file in order to open and read part of the file
+        // the worker needs a file in order to open and read part of the file
         osDelay(100);
 
         LevelDuration level_duration;
         while(furi_hal_get_tick() - test_start < TEST_TIMEOUT * 10) {
-            furi_hal_delay_us(500); //you need to have time to read from the file from the SD card
             level_duration =
                 subghz_file_encoder_worker_get_level_duration(file_worker_encoder_handler);
             if(!level_duration_is_reset(level_duration)) {
                 bool level = level_duration_get_level(level_duration);
                 uint32_t duration = level_duration_get_duration(level_duration);
+                // Yield, to load data inside the worker
+                osThreadYield();
                 subghz_receiver_decode(receiver_handler, level, duration);
             } else {
                 break;
@@ -126,7 +127,7 @@ static bool subghz_decode_ramdom_test(const char* path) {
         }
         subghz_file_encoder_worker_free(file_worker_encoder_handler);
     }
-    FURI_LOG_I(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
+    FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
     if(furi_hal_get_tick() - test_start > TEST_TIMEOUT * 10) {
         printf("\033[0;31mRandom test ERROR TimeOut\033[0m\r\n");
         return false;
@@ -137,7 +138,7 @@ static bool subghz_decode_ramdom_test(const char* path) {
     }
 }
 
-static bool subghz_ecode_test(const char* path) {
+static bool subghz_encoder_test(const char* path) {
     subghz_test_decoder_count = 0;
     uint32_t test_start = furi_hal_get_tick();
     string_t temp_str;
@@ -189,7 +190,7 @@ static bool subghz_ecode_test(const char* path) {
         subghz_transmitter_free(transmitter);
     }
     flipper_format_free(fff_data_file);
-    FURI_LOG_I(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
+    FURI_LOG_T(TAG, "\r\n Decoder count parse \033[0;33m%d\033[0m ", subghz_test_decoder_count);
     if(furi_hal_get_tick() - test_start > TEST_TIMEOUT) {
         printf("\033[0;31mTest encoder %s ERROR TimeOut\033[0m\r\n", string_get_cstr(temp_str));
         subghz_test_decoder_count = 0;
@@ -207,167 +208,166 @@ MU_TEST(subghz_keystore_test) {
 
 MU_TEST(subghz_decoder_came_atomo_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/came_atomo_raw.sub", SUBGHZ_PROTOCOL_CAME_ATOMO_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_CAME_ATOMO_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_came_test) {
     mu_assert(
-        subghz_decode_test("/ext/unit_tests/subghz/came_raw.sub", SUBGHZ_PROTOCOL_CAME_NAME),
+        subghz_decoder_test("/ext/unit_tests/subghz/came_raw.sub", SUBGHZ_PROTOCOL_CAME_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_CAME_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_came_twee_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/came_twee_raw.sub", SUBGHZ_PROTOCOL_CAME_TWEE_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_CAME_TWEE_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_faac_slh_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/faac_slh_raw.sub", SUBGHZ_PROTOCOL_FAAC_SLH_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_FAAC_SLH_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_gate_tx_test) {
     mu_assert(
-        subghz_decode_test("/ext/unit_tests/subghz/gate_tx_raw.sub", SUBGHZ_PROTOCOL_GATE_TX_NAME),
+        subghz_decoder_test("/ext/unit_tests/subghz/gate_tx_raw.sub", SUBGHZ_PROTOCOL_GATE_TX_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_GATE_TX_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_hormann_hsm_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/hormann_hsm_raw.sub", SUBGHZ_PROTOCOL_HORMANN_HSM_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_HORMANN_HSM_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_ido_test) {
     mu_assert(
-        subghz_decode_test("/ext/unit_tests/subghz/ido_117_111_raw.sub", SUBGHZ_PROTOCOL_IDO_NAME),
+        subghz_decoder_test("/ext/unit_tests/subghz/ido_117_111_raw.sub", SUBGHZ_PROTOCOL_IDO_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_IDO_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_keelog_test) {
     mu_assert(
-        subghz_decode_test("/ext/unit_tests/subghz/doorhan_raw.sub", SUBGHZ_PROTOCOL_KEELOQ_NAME),
+        subghz_decoder_test("/ext/unit_tests/subghz/doorhan_raw.sub", SUBGHZ_PROTOCOL_KEELOQ_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_KEELOQ_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_kia_seed_test) {
     mu_assert(
-        subghz_decode_test("/ext/unit_tests/subghz/kia_seed_raw.sub", SUBGHZ_PROTOCOL_KIA_NAME),
+        subghz_decoder_test("/ext/unit_tests/subghz/kia_seed_raw.sub", SUBGHZ_PROTOCOL_KIA_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_KIA_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_nero_radio_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/nero_radio_raw.sub", SUBGHZ_PROTOCOL_NERO_RADIO_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_NERO_RADIO_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_nero_sketch_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/nero_sketch_raw.sub", SUBGHZ_PROTOCOL_NERO_SKETCH_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_NERO_SKETCH_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_nice_flo_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/nice_flo_raw.sub", SUBGHZ_PROTOCOL_NICE_FLO_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_NICE_FLO_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_nice_flor_s_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/nice_flor_s_raw.sub", SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_NICE_FLOR_S_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_princeton_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/Princeton_raw.sub", SUBGHZ_PROTOCOL_PRINCETON_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_PRINCETON_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_scher_khan_magic_code_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/scher_khan_magic_code.sub", SUBGHZ_PROTOCOL_SCHER_KHAN_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_SCHER_KHAN_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_somfy_keytis_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/Somfy_keytis_raw.sub", SUBGHZ_PROTOCOL_SOMFY_KEYTIS_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_SOMFY_KEYTIS_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_somfy_telis_test) {
     mu_assert(
-        subghz_decode_test(
+        subghz_decoder_test(
             "/ext/unit_tests/subghz/somfy_telis_raw.sub", SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_SOMFY_TELIS_NAME " error\r\n");
 }
 
 MU_TEST(subghz_decoder_star_line_test) {
     mu_assert(
-        subghz_decode_test("/ext/unit_tests/subghz/cenmax_raw.sub", SUBGHZ_PROTOCOL_STAR_LINE_NAME),
+        subghz_decoder_test(
+            "/ext/unit_tests/subghz/cenmax_raw.sub", SUBGHZ_PROTOCOL_STAR_LINE_NAME),
         "Test decoder " SUBGHZ_PROTOCOL_STAR_LINE_NAME " error\r\n");
 }
 
-MU_TEST(subghz_ecoder_princeton_test) {
+MU_TEST(subghz_encoder_princeton_test) {
     mu_assert(
-        subghz_ecode_test("/ext/unit_tests/subghz/princeton.sub"),
+        subghz_encoder_test("/ext/unit_tests/subghz/princeton.sub"),
         "Test encoder " SUBGHZ_PROTOCOL_PRINCETON_NAME " error\r\n");
 }
 
-MU_TEST(subghz_ecoder_came_test) {
+MU_TEST(subghz_encoder_came_test) {
     mu_assert(
-        subghz_ecode_test("/ext/unit_tests/subghz/came.sub"),
+        subghz_encoder_test("/ext/unit_tests/subghz/came.sub"),
         "Test encoder " SUBGHZ_PROTOCOL_CAME_NAME " error\r\n");
 }
 
-MU_TEST(subghz_ecoder_came_twee_test) {
+MU_TEST(subghz_encoder_came_twee_test) {
     mu_assert(
-        subghz_ecode_test("/ext/unit_tests/subghz/came_twee.sub"),
+        subghz_encoder_test("/ext/unit_tests/subghz/came_twee.sub"),
         "Test encoder " SUBGHZ_PROTOCOL_CAME_TWEE_NAME " error\r\n");
 }
 
-MU_TEST(subghz_ecoder_gate_tx_test) {
+MU_TEST(subghz_encoder_gate_tx_test) {
     mu_assert(
-        subghz_ecode_test("/ext/unit_tests/subghz/gate_tx.sub"),
+        subghz_encoder_test("/ext/unit_tests/subghz/gate_tx.sub"),
         "Test encoder " SUBGHZ_PROTOCOL_GATE_TX_NAME " error\r\n");
 }
 
-MU_TEST(subghz_ecoder_nice_flo_test) {
+MU_TEST(subghz_encoder_nice_flo_test) {
     mu_assert(
-        subghz_ecode_test("/ext/unit_tests/subghz/nice_flo.sub"),
+        subghz_encoder_test("/ext/unit_tests/subghz/nice_flo.sub"),
         "Test encoder " SUBGHZ_PROTOCOL_NICE_FLO_NAME " error\r\n");
 }
 
-MU_TEST(subghz_ecoder_keelog_test) {
+MU_TEST(subghz_encoder_keelog_test) {
     mu_assert(
-        subghz_ecode_test("/ext/unit_tests/subghz/doorhan.sub"),
+        subghz_encoder_test("/ext/unit_tests/subghz/doorhan.sub"),
         "Test encoder " SUBGHZ_PROTOCOL_KEELOQ_NAME " error\r\n");
 }
 
 MU_TEST(subghz_random_test) {
-    mu_assert(subghz_decode_ramdom_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
+    mu_assert(subghz_decode_random_test(TEST_RANDOM_DIR_NAME), "Random test error\r\n");
 }
 
 MU_TEST_SUITE(subghz) {
-    //MU_SUITE_CONFIGURE(&subghz_test_init, &subghz_test_deinit);
-
     subghz_test_init();
     MU_RUN_TEST(subghz_keystore_test);
 
@@ -390,12 +390,12 @@ MU_TEST_SUITE(subghz) {
     MU_RUN_TEST(subghz_decoder_somfy_telis_test);
     MU_RUN_TEST(subghz_decoder_star_line_test);
 
-    MU_RUN_TEST(subghz_ecoder_princeton_test);
-    MU_RUN_TEST(subghz_ecoder_came_test);
-    MU_RUN_TEST(subghz_ecoder_came_twee_test);
-    MU_RUN_TEST(subghz_ecoder_gate_tx_test);
-    MU_RUN_TEST(subghz_ecoder_nice_flo_test);
-    MU_RUN_TEST(subghz_ecoder_keelog_test);
+    MU_RUN_TEST(subghz_encoder_princeton_test);
+    MU_RUN_TEST(subghz_encoder_came_test);
+    MU_RUN_TEST(subghz_encoder_came_twee_test);
+    MU_RUN_TEST(subghz_encoder_gate_tx_test);
+    MU_RUN_TEST(subghz_encoder_nice_flo_test);
+    MU_RUN_TEST(subghz_encoder_keelog_test);
 
     MU_RUN_TEST(subghz_random_test);
     subghz_test_deinit();

+ 2 - 0
applications/unit_tests/test_index.c

@@ -18,6 +18,7 @@ int run_minunit_test_flipper_format_string();
 int run_minunit_test_stream();
 int run_minunit_test_storage();
 int run_minunit_test_subghz();
+int run_minunit_test_dirwalk();
 
 void minunit_print_progress(void) {
     static char progress[] = {'\\', '|', '/', '-'};
@@ -60,6 +61,7 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) {
         test_result |= run_minunit();
         test_result |= run_minunit_test_storage();
         test_result |= run_minunit_test_stream();
+        test_result |= run_minunit_test_dirwalk();
         test_result |= run_minunit_test_flipper_format();
         test_result |= run_minunit_test_flipper_format_string();
         test_result |= run_minunit_test_infrared_decoder_encoder();

+ 14 - 19
lib/subghz/subghz_file_encoder_worker.c

@@ -54,27 +54,24 @@ void subghz_file_encoder_worker_add_level_duration(
     }
 }
 
-bool subghz_file_encoder_worker_data_parse(
-    SubGhzFileEncoderWorker* instance,
-    const char* strStart,
-    size_t len) {
+bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, const char* strStart) {
     char* str1;
-    size_t ind_start = (size_t)strStart; //store the start address of the beginning of the line
     bool res = false;
+    // Line sample: "RAW_Data: -1, 2, -2..."
+
+    // Look for a key in the line
+    str1 = strstr(strStart, "RAW_Data: ");
 
-    str1 = strstr(
-        strStart, "RAW_Data: "); //looking for the beginning of the desired title in the line
     if(str1 != NULL) {
-        str1 = strchr(
-            str1,
-            ' '); //if found, shift the pointer by 1 element per line "RAW_Data: -1, 2, -2..."
-        while(
-            strchr(str1, ' ') != NULL &&
-            ((size_t)str1 <
-             (len +
-              ind_start))) { //check that there is still an element in the line and that it has not gone beyond the line
+        // Skip key
+        str1 = strchr(str1, ' ');
+
+        // Check that there is still an element in the line
+        while(strchr(str1, ' ') != NULL) {
             str1 = strchr(str1, ' ');
-            str1 += 1; //if found, shift the pointer by next element per line
+
+            // Skip space
+            str1 += 1;
             subghz_file_encoder_worker_add_level_duration(instance, atoi(str1));
         }
         res = true;
@@ -143,9 +140,7 @@ static int32_t subghz_file_encoder_worker_thread(void* context) {
             if(stream_read_line(stream, instance->str_data)) {
                 string_strim(instance->str_data);
                 if(!subghz_file_encoder_worker_data_parse(
-                       instance,
-                       string_get_cstr(instance->str_data),
-                       strlen(string_get_cstr(instance->str_data)))) {
+                       instance, string_get_cstr(instance->str_data))) {
                     //to stop DMA correctly
                     subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
                     subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);

+ 152 - 0
lib/toolbox/dir_walk.c

@@ -0,0 +1,152 @@
+#include "dir_walk.h"
+#include <m-list.h>
+
+LIST_DEF(DirIndexList, uint32_t);
+
+struct DirWalk {
+    File* file;
+    string_t path;
+    DirIndexList_t index_list;
+    uint32_t current_index;
+    bool recursive;
+    DirWalkFilterCb filter_cb;
+    void* filter_context;
+};
+
+DirWalk* dir_walk_alloc(Storage* storage) {
+    DirWalk* dir_walk = malloc(sizeof(DirWalk));
+    string_init(dir_walk->path);
+    dir_walk->file = storage_file_alloc(storage);
+    DirIndexList_init(dir_walk->index_list);
+    dir_walk->recursive = true;
+    dir_walk->filter_cb = NULL;
+    return dir_walk;
+}
+
+void dir_walk_free(DirWalk* dir_walk) {
+    storage_file_free(dir_walk->file);
+    string_clear(dir_walk->path);
+    DirIndexList_clear(dir_walk->index_list);
+    free(dir_walk);
+}
+
+void dir_walk_set_recursive(DirWalk* dir_walk, bool recursive) {
+    dir_walk->recursive = recursive;
+}
+
+void dir_walk_set_filter_cb(DirWalk* dir_walk, DirWalkFilterCb cb, void* context) {
+    dir_walk->filter_cb = cb;
+    dir_walk->filter_context = context;
+}
+
+bool dir_walk_open(DirWalk* dir_walk, const char* path) {
+    string_set(dir_walk->path, path);
+    dir_walk->current_index = 0;
+    return storage_dir_open(dir_walk->file, path);
+}
+
+static bool dir_walk_filter(DirWalk* dir_walk, const char* name, FileInfo* fileinfo) {
+    if(dir_walk->filter_cb) {
+        return dir_walk->filter_cb(name, fileinfo, dir_walk->filter_context);
+    } else {
+        return true;
+    }
+}
+
+static DirWalkResult dir_walk_iter(DirWalk* dir_walk, string_t return_path, FileInfo* fileinfo) {
+    DirWalkResult result = DirWalkError;
+    char* name = malloc(256);
+    FileInfo info;
+    bool end = false;
+
+    while(!end) {
+        storage_dir_read(dir_walk->file, &info, name, 255);
+
+        if(storage_file_get_error(dir_walk->file) == FSE_OK) {
+            result = DirWalkOK;
+            dir_walk->current_index++;
+
+            if(dir_walk_filter(dir_walk, name, &info)) {
+                if(return_path != NULL) {
+                    string_printf(return_path, "%s/%s", string_get_cstr(dir_walk->path), name);
+                }
+
+                if(fileinfo != NULL) {
+                    memcpy(fileinfo, &info, sizeof(FileInfo));
+                }
+
+                end = true;
+            }
+
+            if((info.flags & FSF_DIRECTORY) && dir_walk->recursive) {
+                // step into
+                DirIndexList_push_back(dir_walk->index_list, dir_walk->current_index);
+                dir_walk->current_index = 0;
+                storage_dir_close(dir_walk->file);
+
+                string_cat_printf(dir_walk->path, "/%s", name);
+                storage_dir_open(dir_walk->file, string_get_cstr(dir_walk->path));
+            }
+        } else if(storage_file_get_error(dir_walk->file) == FSE_NOT_EXIST) {
+            if(DirIndexList_size(dir_walk->index_list) == 0) {
+                // last
+                result = DirWalkLast;
+                end = true;
+            } else {
+                // step out
+                uint32_t index;
+                DirIndexList_pop_back(&index, dir_walk->index_list);
+                dir_walk->current_index = 0;
+
+                storage_dir_close(dir_walk->file);
+
+                size_t last_char = string_search_rchar(dir_walk->path, '/');
+                if(last_char != STRING_FAILURE) {
+                    string_left(dir_walk->path, last_char);
+                }
+
+                storage_dir_open(dir_walk->file, string_get_cstr(dir_walk->path));
+
+                // rewind
+                while(true) {
+                    if(index == dir_walk->current_index) {
+                        result = DirWalkOK;
+                        break;
+                    }
+
+                    if(!storage_dir_read(dir_walk->file, &info, name, 255)) {
+                        result = DirWalkError;
+                        end = true;
+                        break;
+                    }
+
+                    dir_walk->current_index++;
+                }
+            }
+        } else {
+            result = DirWalkError;
+            end = true;
+        }
+    }
+
+    free(name);
+    return result;
+}
+
+FS_Error dir_walk_get_error(DirWalk* dir_walk) {
+    return storage_file_get_error(dir_walk->file);
+}
+
+DirWalkResult dir_walk_read(DirWalk* dir_walk, string_t return_path, FileInfo* fileinfo) {
+    return dir_walk_iter(dir_walk, return_path, fileinfo);
+}
+
+void dir_walk_close(DirWalk* dir_walk) {
+    if(storage_file_is_open(dir_walk->file)) {
+        storage_dir_close(dir_walk->file);
+    }
+
+    DirIndexList_reset(dir_walk->index_list);
+    string_reset(dir_walk->path);
+    dir_walk->current_index = 0;
+}

+ 79 - 0
lib/toolbox/dir_walk.h

@@ -0,0 +1,79 @@
+#pragma once
+#include <storage/storage.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DirWalk DirWalk;
+
+typedef enum {
+    DirWalkOK, /**< OK */
+    DirWalkError, /**< Error */
+    DirWalkLast, /**< Last element */
+} DirWalkResult;
+
+typedef bool (*DirWalkFilterCb)(const char* name, FileInfo* fileinfo, void* ctx);
+
+/**
+ * Allocate DirWalk
+ * @param storage 
+ * @return DirWalk* 
+ */
+DirWalk* dir_walk_alloc(Storage* storage);
+
+/**
+ * Free DirWalk
+ * @param dir_walk 
+ */
+void dir_walk_free(DirWalk* dir_walk);
+
+/**
+ * Set recursive mode (true by default)
+ * @param dir_walk 
+ * @param recursive 
+ */
+void dir_walk_set_recursive(DirWalk* dir_walk, bool recursive);
+
+/**
+ * Set filter callback (Should return true if the data is valid)
+ * @param dir_walk 
+ * @param cb 
+ * @param context 
+ */
+void dir_walk_set_filter_cb(DirWalk* dir_walk, DirWalkFilterCb cb, void* context);
+
+/**
+ * Open directory 
+ * @param dir_walk 
+ * @param path 
+ * @return true 
+ * @return false 
+ */
+bool dir_walk_open(DirWalk* dir_walk, const char* path);
+
+/**
+ * Get error id
+ * @param dir_walk 
+ * @return FS_Error 
+ */
+FS_Error dir_walk_get_error(DirWalk* dir_walk);
+
+/**
+ * Read next element from directory
+ * @param dir_walk 
+ * @param return_path 
+ * @param fileinfo 
+ * @return DirWalkResult 
+ */
+DirWalkResult dir_walk_read(DirWalk* dir_walk, string_t return_path, FileInfo* fileinfo);
+
+/**
+ * Close directory
+ * @param dir_walk 
+ */
+void dir_walk_close(DirWalk* dir_walk);
+
+#ifdef __cplusplus
+}
+#endif