diff --git a/RoadMap.md b/RoadMap.md new file mode 100644 index 00000000..e8b2bf09 --- /dev/null +++ b/RoadMap.md @@ -0,0 +1,52 @@ +# RoadMap + +# Where we are (0.x.x branch) + +Our goal for 0.x.x branch is to build stable usable apps and API. +First public release that we support in this branch is 0.43.1. Your device most likely came with this version. +You can develop applications but keep in mind that API is not final yet. + +## What's already implemented + +**Applications** + +- SubGhz: all most common protocols, reading RAW for everything else +- 125kHz RFID: all most common protocols +- NFC: reading/emulating Mifare Ultralight, reading MiFare Classic and DESFire, basic EMV, basic NFC-B,F,V +- Infrared: all most common RC protocols, RAW format for everything else +- GPIO: UART bridge, basic GPIO controls +- iButton: DS1990, Cyfral, Metacom +- Bad USB: Full USB Rubber Ducky support, some extras for windows alt codes +- U2F: Full U2F specification support + +**Extras** + +- BLE Keyboard +- Snake game + +**System and HAL** + +- Furi Core +- Furi HAL + +# Where we're going (Version 1) + +Main goal for 1.0.0 is to provide first stable version for both Users and Developers. + +## What we're planning to implement in 1.0.0 + +- Updating firmware from SD (work in progress, almost done) +- Loading applications from SD (tested as PoC, work scheduled for Q2) +- More protocols (gathering feedback) +- User documentation (work in progress) +- FuriCore: get rid of CMSIS API, replace hard real time timers, improve stability and performance (work in progress) +- FuriHal: deep sleep mode, stable API, examples, documentation (work in progress) +- Application improvements (a ton of things that we want to add and improve that are too numerous to list here) + +## When will it happen and where I can see the progress? + +Release 1.0.0 will most likely happen around the end of Q3 + +Development progress can be tracked in our public Miro board: + +https://miro.com/app/board/uXjVO_3D6xU=/?moveToWidget=3458764522498020058&cot=14 diff --git a/applications/archive/helpers/archive_apps.c b/applications/archive/helpers/archive_apps.c index 9293d225..d3cabd56 100644 --- a/applications/archive/helpers/archive_apps.c +++ b/applications/archive/helpers/archive_apps.c @@ -39,6 +39,8 @@ bool archive_app_read_dir(void* context, const char* path) { furi_assert(path); ArchiveBrowserView* browser = context; + archive_file_array_rm_all(browser); + ArchiveAppTypeEnum app = archive_get_app_type(path); if(app == ArchiveAppTypeU2f) { diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c index c1e9cacb..c8e78dca 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/archive/helpers/archive_browser.c @@ -3,25 +3,31 @@ #include "archive_browser.h" #include +bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) { + size_t array_size = files_array_size(model->files); + + if((idx >= model->array_offset + array_size) || (idx < model->array_offset)) { + return false; + } + + return true; +} + void archive_update_offset(ArchiveBrowserView* browser) { furi_assert(browser); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - size_t array_size = files_array_size(model->files); - uint16_t bounds = array_size > 3 ? 2 : array_size; + uint16_t bounds = model->item_cnt > 3 ? 2 : model->item_cnt; - if(array_size > 3 && model->idx >= array_size - 1) { - model->list_offset = model->idx - 3; - } else if( - model->last_offset && model->last_offset != model->list_offset && - model->tab_idx == model->last_tab) { - model->list_offset = model->last_offset; - model->last_offset = !model->last_offset; - } else if(model->list_offset < model->idx - bounds) { - model->list_offset = CLAMP(model->idx - 2, array_size - bounds, 0); - } else if(model->list_offset > model->idx - bounds) { - model->list_offset = CLAMP(model->idx - 1, array_size - bounds, 0); + if(model->item_cnt > 3 && model->item_idx >= model->item_cnt - 1) { + model->list_offset = model->item_idx - 3; + } else if(model->list_offset < model->item_idx - bounds) { + model->list_offset = CLAMP(model->item_idx - 2, model->item_cnt - bounds, 0); + } else if(model->list_offset > model->item_idx - bounds) { + model->list_offset = CLAMP(model->item_idx - 1, model->item_cnt - bounds, 0); } + return true; }); } @@ -32,8 +38,8 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { archive_get_filenames(browser, string_get_cstr(browser->path)); - if(!archive_file_array_size(browser) && !archive_get_depth(browser)) { - archive_switch_tab(browser, DEFAULT_TAB_DIR); + if(!archive_file_get_array_size(browser) && !archive_get_depth(browser)) { + archive_switch_tab(browser, TAB_RIGHT); } else { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -41,7 +47,7 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { while(idx < files_array_size(model->files)) { ArchiveFile_t* current = files_array_get(model->files, idx); if(!string_search(current->name, target)) { - model->idx = idx; + model->item_idx = idx + model->array_offset; break; } ++idx; @@ -53,7 +59,9 @@ void archive_update_focus(ArchiveBrowserView* browser, const char* target) { } } -size_t archive_file_array_size(ArchiveBrowserView* browser) { +size_t archive_file_get_array_size(ArchiveBrowserView* browser) { + furi_assert(browser); + uint16_t size = 0; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -63,40 +71,60 @@ size_t archive_file_array_size(ArchiveBrowserView* browser) { return size; } -void archive_file_array_rm_selected(ArchiveBrowserView* browser) { +void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count) { + furi_assert(browser); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - files_array_remove_v(model->files, model->idx, model->idx + 1); - model->idx = CLAMP(model->idx, files_array_size(model->files) - 1, 0); + model->item_cnt = count; + return false; + }); +} + +void archive_file_array_rm_selected(ArchiveBrowserView* browser) { + furi_assert(browser); + uint32_t items_cnt = 0; + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + files_array_remove_v( + model->files, + model->item_idx - model->array_offset, + model->item_idx - model->array_offset + 1); + model->item_cnt--; + model->item_idx = CLAMP(model->item_idx, model->item_cnt - 1, 0); + items_cnt = model->item_cnt; return false; }); - if(!archive_file_array_size(browser) && !archive_get_depth(browser)) { - archive_switch_tab(browser, DEFAULT_TAB_DIR); + if((items_cnt == 0) && (archive_get_depth(browser) == 0)) { + archive_switch_tab(browser, TAB_RIGHT); } archive_update_offset(browser); } -void archive_file_array_swap(ArchiveBrowserView* browser, int8_t d) { +void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir) { + furi_assert(browser); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { ArchiveFile_t temp; size_t array_size = files_array_size(model->files) - 1; - uint8_t swap_idx = CLAMP(model->idx + d, array_size, 0); + uint8_t swap_idx = CLAMP(model->item_idx + dir, array_size, 0); - if(model->idx == 0 && d < 0) { + if(model->item_idx == 0 && dir < 0) { ArchiveFile_t_init(&temp); files_array_pop_at(&temp, model->files, array_size); - files_array_push_at(model->files, model->idx, temp); + files_array_push_at(model->files, model->item_idx, temp); ArchiveFile_t_clear(&temp); - } else if(model->idx == array_size && d > 0) { + } else if(model->item_idx == array_size && dir > 0) { ArchiveFile_t_init(&temp); - files_array_pop_at(&temp, model->files, model->last_idx); + files_array_pop_at(&temp, model->files, model->item_idx); files_array_push_at(model->files, array_size, temp); ArchiveFile_t_clear(&temp); } else { - files_array_swap_at(model->files, model->idx, swap_idx); + files_array_swap_at(model->files, model->item_idx, swap_idx); } return false; @@ -104,6 +132,8 @@ void archive_file_array_swap(ArchiveBrowserView* browser, int8_t d) { } void archive_file_array_rm_all(ArchiveBrowserView* browser) { + furi_assert(browser); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_reset(model->files); @@ -111,23 +141,61 @@ void archive_file_array_rm_all(ArchiveBrowserView* browser) { }); } +bool archive_file_array_load(ArchiveBrowserView* browser, int8_t dir) { + furi_assert(browser); + + int32_t offset_new = 0; + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + if(model->item_cnt > FILE_LIST_BUF_LEN) { + if(dir < 0) { + offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 3; + } else if(dir == 0) { + offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 2; + } else { + offset_new = model->item_idx - FILE_LIST_BUF_LEN / 4 * 1; + } + offset_new = CLAMP(offset_new, model->item_cnt - FILE_LIST_BUF_LEN, 0); + } + return false; + }); + + bool res = archive_dir_read_items( + browser, string_get_cstr(browser->path), offset_new, FILE_LIST_BUF_LEN); + + with_view_model( + browser->view, (ArchiveBrowserViewModel * model) { + model->array_offset = offset_new; + model->list_loading = false; + return true; + }); + + return res; +} + ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser) { + furi_assert(browser); + ArchiveFile_t* selected; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - selected = files_array_size(model->files) ? files_array_get(model->files, model->idx) : - NULL; + selected = files_array_size(model->files) ? + files_array_get(model->files, model->item_idx - model->array_offset) : + NULL; return false; }); return selected; } ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) { + furi_assert(browser); + ArchiveFile_t* selected; - idx = CLAMP(idx, archive_file_array_size(browser), 0); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { + idx = CLAMP(idx - model->array_offset, files_array_size(model->files), 0); selected = files_array_size(model->files) ? files_array_get(model->files, idx) : NULL; return false; }); @@ -135,6 +203,8 @@ ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx) { } ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { + furi_assert(browser); + ArchiveTabEnum tab_id; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -145,10 +215,12 @@ ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser) { } uint8_t archive_get_depth(ArchiveBrowserView* browser) { + furi_assert(browser); + uint8_t depth; with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - depth = model->depth; + depth = idx_last_array_size(model->idx_last); return false; }); @@ -165,6 +237,8 @@ const char* archive_get_name(ArchiveBrowserView* browser) { } void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { + furi_assert(browser); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { model->tab_idx = tab; @@ -172,6 +246,8 @@ void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { }); } void archive_set_last_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab) { + furi_assert(browser); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { model->last_tab = model->tab_idx; @@ -198,11 +274,12 @@ void archive_add_app_item(ArchiveBrowserView* browser, const char* name) { ArchiveFile_t_init(&item); string_init_set_str(item.name, name); - set_file_type(&item, NULL, app_name + 1, true); + archive_set_file_type(&item, NULL, app_name + 1, true); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_push_back(model->files, item); + model->item_cnt = files_array_size(model->files); return false; }); ArchiveFile_t_clear(&item); @@ -216,10 +293,11 @@ void archive_add_file_item(ArchiveBrowserView* browser, FileInfo* file_info, con ArchiveFile_t item; - if(filter_by_extension(file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { + if(archive_filter_by_extension( + file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { ArchiveFile_t_init(&item); string_init_set_str(item.name, name); - set_file_type(&item, file_info, archive_get_path(browser), false); + archive_set_file_type(&item, file_info, archive_get_path(browser), false); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -234,12 +312,17 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { furi_assert(browser); with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - model->menu = show; - model->menu_idx = 0; - if(show) { - ArchiveFile_t* selected = files_array_get(model->files, model->idx); - selected->fav = archive_is_favorite("%s", string_get_cstr(selected->name)); + if(archive_is_item_in_array(model, model->item_idx)) { + model->menu = true; + model->menu_idx = 0; + ArchiveFile_t* selected = + files_array_get(model->files, model->item_idx - model->array_offset); + selected->fav = archive_is_favorite("%s", string_get_cstr(selected->name)); + } + } else { + model->menu = false; + model->menu_idx = 0; } return true; @@ -247,6 +330,8 @@ void archive_show_file_menu(ArchiveBrowserView* browser, bool show) { } void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active) { + furi_assert(browser); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { model->move_fav = active; @@ -282,7 +367,8 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { } else if(strncmp(path, "/app:", 5) == 0) { if(archive_app_is_available(browser, path)) tab_empty = false; } else { - if(archive_dir_not_empty(browser, archive_get_default_path(tab))) tab_empty = false; + uint32_t files_cnt = archive_dir_count_items(browser, archive_get_default_path(tab)); + if(files_cnt > 0) tab_empty = false; } if((tab_empty) && (tab != ArchiveTabBrowser)) { @@ -291,8 +377,9 @@ void archive_switch_tab(ArchiveBrowserView* browser, InputKey key) { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { if(model->last_tab != model->tab_idx) { - model->idx = 0; - model->depth = 0; + model->item_idx = 0; + model->array_offset = 0; + idx_last_array_reset(model->idx_last); } return false; }); @@ -305,11 +392,13 @@ void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { furi_assert(browser); furi_assert(name); + archive_dir_count_items(browser, string_get_cstr(name)); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - model->last_idx = model->idx; - model->idx = 0; - model->depth = CLAMP(model->depth + 1, MAX_DEPTH, 0); + idx_last_array_push_back(model->idx_last, model->item_idx); + model->array_offset = 0; + model->item_idx = 0; return false; }); @@ -329,10 +418,11 @@ void archive_leave_dir(ArchiveBrowserView* browser) { string_left(browser->path, pos); } + archive_dir_count_items(browser, path); + with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - model->depth = CLAMP(model->depth - 1, MAX_DEPTH, 0); - model->idx = model->last_idx; + idx_last_array_pop_back(&model->item_idx, model->idx_last); return false; }); diff --git a/applications/archive/helpers/archive_browser.h b/applications/archive/helpers/archive_browser.h index aa42a34a..968564cd 100644 --- a/applications/archive/helpers/archive_browser.h +++ b/applications/archive/helpers/archive_browser.h @@ -2,7 +2,8 @@ #include "../archive_i.h" -#define DEFAULT_TAB_DIR InputKeyRight //default tab swith direction +#define TAB_RIGHT InputKeyRight //default tab swith direction +#define FILE_LIST_BUF_LEN 100 static const char* tab_default_paths[] = { [ArchiveTabFavorites] = "/any/favorites", @@ -56,14 +57,18 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { return (type != ArchiveFileTypeFolder && type != ArchiveFileTypeUnknown); } +bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); void archive_update_offset(ArchiveBrowserView* browser); void archive_update_focus(ArchiveBrowserView* browser, const char* target); -size_t archive_file_array_size(ArchiveBrowserView* browser); +bool archive_file_array_load(ArchiveBrowserView* browser, int8_t dir); +size_t archive_file_get_array_size(ArchiveBrowserView* browser); void archive_file_array_rm_selected(ArchiveBrowserView* browser); -void archive_file_array_swap(ArchiveBrowserView* browser, int8_t d); +void archive_file_array_swap(ArchiveBrowserView* browser, int8_t dir); void archive_file_array_rm_all(ArchiveBrowserView* browser); +void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count); + ArchiveFile_t* archive_get_current_file(ArchiveBrowserView* browser); ArchiveFile_t* archive_get_file_at(ArchiveBrowserView* browser, size_t idx); ArchiveTabEnum archive_get_tab(ArchiveBrowserView* browser); diff --git a/applications/archive/helpers/archive_favorites.c b/applications/archive/helpers/archive_favorites.c index 97bf9149..e2777f5e 100644 --- a/applications/archive/helpers/archive_favorites.c +++ b/applications/archive/helpers/archive_favorites.c @@ -84,6 +84,9 @@ bool archive_favorites_read(void* context) { string_init(buffer); bool need_refresh = false; + uint16_t file_count = 0; + + archive_file_array_rm_all(browser); bool result = file_worker_open(file_worker, ARCHIVE_FAV_PATH, FSAM_READ, FSOM_OPEN_EXISTING); @@ -99,6 +102,7 @@ bool archive_favorites_read(void* context) { if(string_search(buffer, "/app:") == 0) { if(archive_app_is_available(browser, string_get_cstr(buffer))) { archive_add_app_item(browser, string_get_cstr(buffer)); + file_count++; } else { need_refresh = true; } @@ -106,10 +110,12 @@ bool archive_favorites_read(void* context) { bool file_exists = false; file_worker_is_file_exist(file_worker, string_get_cstr(buffer), &file_exists); - if(file_exists) + if(file_exists) { archive_add_file_item(browser, &file_info, string_get_cstr(buffer)); - else + file_count++; + } else { need_refresh = true; + } } string_reset(buffer); @@ -119,6 +125,8 @@ bool archive_favorites_read(void* context) { file_worker_close(file_worker); file_worker_free(file_worker); + archive_set_item_count(browser, file_count); + if(need_refresh) { archive_favourites_rescan(); } @@ -257,7 +265,7 @@ void archive_favorites_save(void* context) { ArchiveBrowserView* browser = context; FileWorker* file_worker = file_worker_alloc(true); - for(size_t i = 0; i < archive_file_array_size(browser); i++) { + for(size_t i = 0; i < archive_file_get_array_size(browser); i++) { ArchiveFile_t* item = archive_get_file_at(browser, i); archive_file_append(ARCHIVE_FAV_TEMP_PATH, "%s\n", string_get_cstr(item->name)); } diff --git a/applications/archive/helpers/archive_files.c b/applications/archive/helpers/archive_files.c index 08542bfe..96286519 100644 --- a/applications/archive/helpers/archive_files.c +++ b/applications/archive/helpers/archive_files.c @@ -6,7 +6,7 @@ #define ASSETS_DIR "assets" -bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { +bool archive_filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name) { furi_assert(file_info); furi_assert(tab_ext); furi_assert(name); @@ -45,7 +45,7 @@ void archive_get_file_extension(char* name, char* ext) { strncpy(ext, dot, MAX_EXT_LEN); } -void set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* path, bool is_app) { +void archive_set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* path, bool is_app) { furi_assert(file); file->is_app = is_app; @@ -83,21 +83,19 @@ bool archive_get_filenames(void* context, const char* path) { bool res; ArchiveBrowserView* browser = context; - archive_file_array_rm_all(browser); if(archive_get_tab(browser) == ArchiveTabFavorites) { res = archive_favorites_read(browser); } else if(strncmp(path, "/app:", 5) == 0) { res = archive_app_read_dir(browser, path); } else { - res = archive_read_dir(browser, path); + res = archive_file_array_load(browser, 0); } return res; } -bool archive_dir_not_empty(void* context, const char* path) { // can be simpler? +uint32_t archive_dir_count_items(void* context, const char* path) { furi_assert(context); - ArchiveBrowserView* browser = context; FileInfo file_info; @@ -108,23 +106,19 @@ bool archive_dir_not_empty(void* context, const char* path) { // can be simpler? if(!storage_dir_open(directory, path)) { storage_dir_close(directory); storage_file_free(directory); - return false; + return 0; } - bool files_found = false; + uint32_t files_found = 0; while(1) { if(!storage_dir_read(directory, &file_info, name, MAX_NAME_LEN)) { break; } - if(files_found) { - break; - } else if((storage_file_get_error(directory) == FSE_OK) && (name[0])) { - if(filter_by_extension( + if((storage_file_get_error(directory) == FSE_OK) && (name[0])) { + if(archive_filter_by_extension( &file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { - files_found = true; + files_found++; } - } else { - return false; } } storage_dir_close(directory); @@ -132,10 +126,12 @@ bool archive_dir_not_empty(void* context, const char* path) { // can be simpler? furi_record_close("storage"); + archive_set_item_count(browser, files_found); + return files_found; } -bool archive_read_dir(void* context, const char* path) { +uint32_t archive_dir_read_items(void* context, const char* path, uint32_t offset, uint32_t count) { furi_assert(context); ArchiveBrowserView* browser = context; @@ -145,7 +141,6 @@ bool archive_read_dir(void* context, const char* path) { char name[MAX_NAME_LEN]; snprintf(name, MAX_NAME_LEN, "%s/", path); size_t path_len = strlen(name); - size_t files_cnt = 0; if(!storage_dir_open(directory, path)) { storage_dir_close(directory); @@ -153,28 +148,48 @@ bool archive_read_dir(void* context, const char* path) { return false; } - while(1) { + // Skip items before offset + uint32_t items_cnt = 0; + while(items_cnt < offset) { + if(!storage_dir_read(directory, &file_info, &name[path_len], MAX_NAME_LEN)) { + break; + } + if(storage_file_get_error(directory) == FSE_OK) { + if(archive_filter_by_extension( + &file_info, archive_get_tab_ext(archive_get_tab(browser)), name)) { + items_cnt++; + } + } else { + break; + } + } + if(items_cnt != offset) { + storage_dir_close(directory); + storage_file_free(directory); + furi_record_close("storage"); + + return false; + } + + items_cnt = 0; + archive_file_array_rm_all(browser); + while(items_cnt < count) { if(!storage_dir_read(directory, &file_info, &name[path_len], MAX_NAME_LEN - path_len)) { break; } - if(files_cnt > MAX_FILES) { - break; - } else if(storage_file_get_error(directory) == FSE_OK) { + if(storage_file_get_error(directory) == FSE_OK) { archive_add_file_item(browser, &file_info, name); - ++files_cnt; + items_cnt++; } else { - storage_dir_close(directory); - storage_file_free(directory); - return false; + break; } } storage_dir_close(directory); storage_file_free(directory); - furi_record_close("storage"); - return true; + return (items_cnt == count); } void archive_file_append(const char* path, const char* format, ...) { @@ -210,10 +225,20 @@ void archive_delete_file(void* context, const char* format, ...) { va_end(args); ArchiveBrowserView* browser = context; - FileWorker* file_worker = file_worker_alloc(true); + Storage* fs_api = furi_record_open("storage"); - bool res = file_worker_remove(file_worker, string_get_cstr(filename)); - file_worker_free(file_worker); + FileInfo fileinfo; + storage_common_stat(fs_api, string_get_cstr(filename), &fileinfo); + + bool res = false; + + if(fileinfo.flags & FSF_DIRECTORY) { + res = storage_simply_remove_recursive(fs_api, string_get_cstr(filename)); + } else { + res = (storage_common_remove(fs_api, string_get_cstr(filename)) == FSE_OK); + } + + furi_record_close("storage"); if(archive_is_favorite("%s", string_get_cstr(filename))) { archive_favorites_delete("%s", string_get_cstr(filename)); @@ -224,4 +249,4 @@ void archive_delete_file(void* context, const char* format, ...) { } string_clear(filename); -} \ No newline at end of file +} diff --git a/applications/archive/helpers/archive_files.h b/applications/archive/helpers/archive_files.h index c8b16c53..e6f6eaa1 100644 --- a/applications/archive/helpers/archive_files.h +++ b/applications/archive/helpers/archive_files.h @@ -1,7 +1,6 @@ #pragma once #include "file_worker.h" - -#define MAX_FILES 100 //temp +#include typedef enum { ArchiveFileTypeIButton, @@ -13,7 +12,7 @@ typedef enum { ArchiveFileTypeU2f, ArchiveFileTypeFolder, ArchiveFileTypeUnknown, - ArchiveFileTypesTotal, + ArchiveFileTypeLoading, } ArchiveFileTypeEnum; typedef struct { @@ -56,12 +55,12 @@ ARRAY_DEF( INIT_SET(API_6(ArchiveFile_t_init_set)), CLEAR(API_2(ArchiveFile_t_clear)))) -bool filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); -void set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* path, bool is_app); +bool archive_filter_by_extension(FileInfo* file_info, const char* tab_ext, const char* name); +void archive_set_file_type(ArchiveFile_t* file, FileInfo* file_info, const char* path, bool is_app); void archive_trim_file_path(char* name, bool ext); void archive_get_file_extension(char* name, char* ext); bool archive_get_filenames(void* context, const char* path); -bool archive_dir_not_empty(void* context, const char* path); -bool archive_read_dir(void* context, const char* path); +uint32_t archive_dir_count_items(void* context, const char* path); +uint32_t archive_dir_read_items(void* context, const char* path, uint32_t offset, uint32_t count); void archive_file_append(const char* path, const char* format, ...); -void archive_delete_file(void* context, const char* format, ...); \ No newline at end of file +void archive_delete_file(void* context, const char* format, ...); diff --git a/applications/archive/scenes/archive_scene_browser.c b/applications/archive/scenes/archive_scene_browser.c index 462fecbd..9410062d 100644 --- a/applications/archive/scenes/archive_scene_browser.c +++ b/applications/archive/scenes/archive_scene_browser.c @@ -105,7 +105,9 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { consumed = true; break; case ArchiveBrowserEventFileMenuDelete: - scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneDelete); + if(archive_get_tab(browser) != ArchiveTabFavorites) { + scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneDelete); + } consumed = true; break; case ArchiveBrowserEventEnterDir: @@ -136,6 +138,14 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { archive_favorites_save(archive->browser); consumed = true; break; + case ArchiveBrowserEventLoadPrevItems: + archive_file_array_load(archive->browser, -1); + consumed = true; + break; + case ArchiveBrowserEventLoadNextItems: + archive_file_array_load(archive->browser, 1); + consumed = true; + break; case ArchiveBrowserEventExit: if(archive_get_depth(browser)) { diff --git a/applications/archive/scenes/archive_scene_rename.c b/applications/archive/scenes/archive_scene_rename.c index 9e573801..e0c4ba6c 100644 --- a/applications/archive/scenes/archive_scene_rename.c +++ b/applications/archive/scenes/archive_scene_rename.c @@ -31,8 +31,8 @@ void archive_scene_rename_on_enter(void* context) { MAX_TEXT_INPUT_LEN, false); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(archive_get_path(archive->browser), archive->file_extension); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + archive_get_path(archive->browser), archive->file_extension, NULL); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(archive->view_dispatcher, ArchiveViewTextInput); diff --git a/applications/archive/views/archive_browser_view.c b/applications/archive/views/archive_browser_view.c index fd81124e..cd63c4d7 100644 --- a/applications/archive/views/archive_browser_view.c +++ b/applications/archive/views/archive_browser_view.c @@ -24,6 +24,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeU2f] = &I_u2f_10px, [ArchiveFileTypeFolder] = &I_dir_10px, [ArchiveFileTypeUnknown] = &I_unknown_10px, + [ArchiveFileTypeLoading] = &I_unknown_10px, // TODO loading icon }; void archive_browser_set_callback( @@ -49,7 +50,7 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { string_init_set_str(menu[2], "Rename"); string_init_set_str(menu[3], "Delete"); - ArchiveFile_t* selected = files_array_get(model->files, model->idx); + ArchiveFile_t* selected = files_array_get(model->files, model->item_idx - model->array_offset); if(!archive_is_known_app(selected->type)) { string_set_str(menu[0], "---"); @@ -58,6 +59,7 @@ static void render_item_menu(Canvas* canvas, ArchiveBrowserViewModel* model) { } else { if(model->tab_idx == ArchiveTabFavorites) { string_set_str(menu[2], "Move"); + string_set_str(menu[3], "---"); } else if(selected->is_app) { string_set_str(menu[2], "---"); } @@ -100,36 +102,44 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) { furi_assert(model); size_t array_size = files_array_size(model->files); - bool scrollbar = array_size > 4; + bool scrollbar = model->item_cnt > 4; - for(size_t i = 0; i < MIN(array_size, MENU_ITEMS); ++i) { + for(size_t i = 0; i < MIN(model->item_cnt, MENU_ITEMS); ++i) { string_t str_buff; char cstr_buff[MAX_NAME_LEN]; - size_t idx = CLAMP(i + model->list_offset, array_size, 0); - uint8_t x_offset = (model->move_fav && model->idx == idx) ? MOVE_OFFSET : 0; + size_t idx = CLAMP(i + model->list_offset, model->item_cnt, 0); + uint8_t x_offset = (model->move_fav && model->item_idx == idx) ? MOVE_OFFSET : 0; - ArchiveFile_t* file = files_array_get(model->files, CLAMP(idx, array_size - 1, 0)); + ArchiveFileTypeEnum file_type = ArchiveFileTypeLoading; + + if(archive_is_item_in_array(model, idx)) { + ArchiveFile_t* file = + files_array_get(model->files, CLAMP(idx - model->array_offset, array_size - 1, 0)); + strlcpy(cstr_buff, string_get_cstr(file->name), string_size(file->name) + 1); + archive_trim_file_path(cstr_buff, archive_is_known_app(file->type)); + string_init_set_str(str_buff, cstr_buff); + file_type = file->type; + } else { + string_init_set_str(str_buff, "---"); + } - strlcpy(cstr_buff, string_get_cstr(file->name), string_size(file->name) + 1); - archive_trim_file_path(cstr_buff, archive_is_known_app(file->type)); - string_init_set_str(str_buff, cstr_buff); elements_string_fit_width( canvas, str_buff, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset); - if(model->idx == idx) { + if(model->item_idx == idx) { archive_draw_frame(canvas, i, scrollbar, model->move_fav); } else { canvas_set_color(canvas, ColorBlack); } - canvas_draw_icon( - canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file->type]); + canvas_draw_icon(canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]); canvas_draw_str(canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, string_get_cstr(str_buff)); + string_clear(str_buff); } if(scrollbar) { - elements_scrollbar_pos(canvas, 126, 15, 49, model->idx, array_size); + elements_scrollbar_pos(canvas, 126, 15, 49, model->item_idx, model->item_cnt); } if(model->menu) { @@ -173,13 +183,13 @@ static void archive_render_status_bar(Canvas* canvas, ArchiveBrowserViewModel* m canvas_set_color(canvas, ColorBlack); } -void archive_view_render(Canvas* canvas, void* model) { - ArchiveBrowserViewModel* m = model; +void archive_view_render(Canvas* canvas, void* mdl) { + ArchiveBrowserViewModel* model = mdl; - archive_render_status_bar(canvas, model); + archive_render_status_bar(canvas, mdl); - if(files_array_size(m->files)) { - draw_list(canvas, m); + if(model->item_cnt > 0) { + draw_list(canvas, model); } else { canvas_draw_str_aligned( canvas, GUI_DISPLAY_WIDTH / 2, 40, AlignCenter, AlignCenter, "Empty"); @@ -191,6 +201,26 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) { return browser->view; } +static bool is_file_list_load_required(ArchiveBrowserViewModel* model) { + size_t array_size = files_array_size(model->files); + + if((model->list_loading) || (array_size >= model->item_cnt)) { + return false; + } + + if((model->array_offset > 0) && + (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) { + return true; + } + + if(((model->array_offset + array_size) < model->item_cnt) && + (model->item_idx > (model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) { + return true; + } + + return false; +} + bool archive_view_input(InputEvent* event, void* context) { furi_assert(event); furi_assert(context); @@ -247,22 +277,28 @@ bool archive_view_input(InputEvent* event, void* context) { } } - if(event->key == InputKeyUp || event->key == InputKeyDown) { + if((event->key == InputKeyUp || event->key == InputKeyDown) && + (event->type == InputTypeShort || event->type == InputTypeRepeat)) { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { - uint16_t num_elements = (uint16_t)files_array_size(model->files); - if((event->type == InputTypeShort || event->type == InputTypeRepeat)) { - if(event->key == InputKeyUp) { - model->idx = ((model->idx - 1) + num_elements) % num_elements; - if(move_fav_mode) { - browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); - } - } else if(event->key == InputKeyDown) { - model->idx = (model->idx + 1) % num_elements; - if(move_fav_mode) { - browser->callback( - ArchiveBrowserEventFavMoveDown, browser->context); - } + if(event->key == InputKeyUp) { + model->item_idx = + ((model->item_idx - 1) + model->item_cnt) % model->item_cnt; + if(is_file_list_load_required(model)) { + model->list_loading = true; + browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context); + } + if(move_fav_mode) { + browser->callback(ArchiveBrowserEventFavMoveUp, browser->context); + } + } else if(event->key == InputKeyDown) { + model->item_idx = (model->item_idx + 1) % model->item_cnt; + if(is_file_list_load_required(model)) { + model->list_loading = true; + browser->callback(ArchiveBrowserEventLoadNextItems, browser->context); + } + if(move_fav_mode) { + browser->callback(ArchiveBrowserEventFavMoveDown, browser->context); } } @@ -317,6 +353,7 @@ ArchiveBrowserView* browser_alloc() { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_init(model->files); + idx_last_array_init(model->idx_last); return true; }); @@ -329,6 +366,7 @@ void browser_free(ArchiveBrowserView* browser) { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { files_array_clear(model->files); + idx_last_array_clear(model->idx_last); return false; }); diff --git a/applications/archive/views/archive_browser_view.h b/applications/archive/views/archive_browser_view.h index 9fc2cdf0..bec8fe2d 100644 --- a/applications/archive/views/archive_browser_view.h +++ b/applications/archive/views/archive_browser_view.h @@ -14,7 +14,6 @@ #define MAX_EXT_LEN 6 #define FRAME_HEIGHT 12 #define MENU_ITEMS 4 -#define MAX_DEPTH 32 #define MOVE_OFFSET 5 typedef enum { @@ -37,12 +36,18 @@ typedef enum { ArchiveBrowserEventFileMenuPin, ArchiveBrowserEventFileMenuAction, ArchiveBrowserEventFileMenuDelete, + ArchiveBrowserEventEnterDir, + ArchiveBrowserEventFavMoveUp, ArchiveBrowserEventFavMoveDown, ArchiveBrowserEventEnterFavMove, ArchiveBrowserEventExitFavMove, ArchiveBrowserEventSaveFavMove, + + ArchiveBrowserEventLoadPrevItems, + ArchiveBrowserEventLoadNextItems, + ArchiveBrowserEventExit, } ArchiveBrowserEvent; @@ -71,21 +76,23 @@ struct ArchiveBrowserView { string_t path; }; +ARRAY_DEF(idx_last_array, int32_t) + typedef struct { ArchiveTabEnum tab_idx; ArchiveTabEnum last_tab; files_array_t files; + idx_last_array_t idx_last; uint8_t menu_idx; bool move_fav; bool menu; + bool list_loading; - uint16_t idx; - uint16_t last_idx; - uint16_t list_offset; - uint16_t last_offset; - uint8_t depth; - + uint32_t item_cnt; + int32_t item_idx; + int32_t array_offset; + int32_t list_offset; } ArchiveBrowserViewModel; void archive_browser_set_callback( diff --git a/applications/desktop/animations/animation_manager.c b/applications/desktop/animations/animation_manager.c index afc9c42e..4c99724e 100644 --- a/applications/desktop/animations/animation_manager.c +++ b/applications/desktop/animations/animation_manager.c @@ -94,10 +94,21 @@ void animation_manager_set_interact_callback( } static void animation_manager_check_blocking_callback(const void* message, void* context) { - furi_assert(context); - AnimationManager* animation_manager = context; - if(animation_manager->check_blocking_callback) { - animation_manager->check_blocking_callback(animation_manager->context); + const StorageEvent* storage_event = message; + + switch(storage_event->type) { + case StorageEventTypeCardMount: + case StorageEventTypeCardUnmount: + case StorageEventTypeCardMountError: + furi_assert(context); + AnimationManager* animation_manager = context; + if(animation_manager->check_blocking_callback) { + animation_manager->check_blocking_callback(animation_manager->context); + } + break; + + default: + break; } } diff --git a/applications/gui/modules/validators.c b/applications/gui/modules/validators.c index df0be729..242dbe68 100644 --- a/applications/gui/modules/validators.c +++ b/applications/gui/modules/validators.c @@ -5,11 +5,19 @@ struct ValidatorIsFile { const char* app_path_folder; const char* app_extension; + char* current_name; }; bool validator_is_file_callback(const char* text, string_t error, void* context) { furi_assert(context); ValidatorIsFile* instance = context; + + if(instance->current_name != NULL) { + if(strcmp(instance->current_name, text) == 0) { + return true; + } + } + bool ret = true; string_t path; string_init_printf(path, "%s/%s%s", instance->app_path_folder, text, instance->app_extension); @@ -26,17 +34,21 @@ bool validator_is_file_callback(const char* text, string_t error, void* context) return ret; } -ValidatorIsFile* - validator_is_file_alloc_init(const char* app_path_folder, const char* app_extension) { +ValidatorIsFile* validator_is_file_alloc_init( + const char* app_path_folder, + const char* app_extension, + const char* current_name) { ValidatorIsFile* instance = malloc(sizeof(ValidatorIsFile)); instance->app_path_folder = app_path_folder; instance->app_extension = app_extension; + instance->current_name = strdup(current_name); return instance; } void validator_is_file_free(ValidatorIsFile* instance) { furi_assert(instance); + free(instance->current_name); free(instance); } diff --git a/applications/gui/modules/validators.h b/applications/gui/modules/validators.h index f62064b6..3a834a8a 100644 --- a/applications/gui/modules/validators.h +++ b/applications/gui/modules/validators.h @@ -8,8 +8,10 @@ extern "C" { #endif typedef struct ValidatorIsFile ValidatorIsFile; -ValidatorIsFile* - validator_is_file_alloc_init(const char* app_path_folder, const char* app_extension); +ValidatorIsFile* validator_is_file_alloc_init( + const char* app_path_folder, + const char* app_extension, + const char* current_name); void validator_is_file_free(ValidatorIsFile* instance); diff --git a/applications/ibutton/ibutton_cli.c b/applications/ibutton/ibutton_cli.c index 44d7025c..243ee36b 100644 --- a/applications/ibutton/ibutton_cli.c +++ b/applications/ibutton/ibutton_cli.c @@ -63,6 +63,7 @@ void ibutton_cli_print_key_data(iButtonKey* key) { #define EVENT_FLAG_IBUTTON_COMPLETE (1 << 0) static void ibutton_cli_worker_read_cb(void* context) { + furi_assert(context); osEventFlagsId_t event = context; osEventFlagsSet(event, EVENT_FLAG_IBUTTON_COMPLETE); } @@ -112,6 +113,7 @@ typedef struct { } iButtonWriteContext; static void ibutton_cli_worker_write_cb(void* context, iButtonWorkerWriteResult result) { + furi_assert(context); iButtonWriteContext* write_context = (iButtonWriteContext*)context; write_context->result = result; osEventFlagsSet(write_context->event, EVENT_FLAG_IBUTTON_COMPLETE); diff --git a/applications/ibutton/scene/ibutton_scene_add_type.cpp b/applications/ibutton/scene/ibutton_scene_add_type.cpp index aa76d3c0..c9537768 100644 --- a/applications/ibutton/scene/ibutton_scene_add_type.cpp +++ b/applications/ibutton/scene/ibutton_scene_add_type.cpp @@ -9,6 +9,7 @@ typedef enum { } SubmenuIndex; static void submenu_callback(void* context, uint32_t index) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_add_value.cpp b/applications/ibutton/scene/ibutton_scene_add_value.cpp index 95d5f82f..cb1e287c 100755 --- a/applications/ibutton/scene/ibutton_scene_add_value.cpp +++ b/applications/ibutton/scene/ibutton_scene_add_value.cpp @@ -3,6 +3,7 @@ #include static void byte_input_callback(void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_delete_confirm.cpp b/applications/ibutton/scene/ibutton_scene_delete_confirm.cpp index 91e471df..8b1aa9f5 100755 --- a/applications/ibutton/scene/ibutton_scene_delete_confirm.cpp +++ b/applications/ibutton/scene/ibutton_scene_delete_confirm.cpp @@ -2,6 +2,7 @@ #include "../ibutton_app.h" static void widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_delete_success.cpp b/applications/ibutton/scene/ibutton_scene_delete_success.cpp index e2b64999..b2241141 100644 --- a/applications/ibutton/scene/ibutton_scene_delete_success.cpp +++ b/applications/ibutton/scene/ibutton_scene_delete_success.cpp @@ -2,6 +2,7 @@ #include "../ibutton_app.h" static void popup_callback(void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; event.type = iButtonEvent::Type::EventTypeBack; diff --git a/applications/ibutton/scene/ibutton_scene_emulate.cpp b/applications/ibutton/scene/ibutton_scene_emulate.cpp index 8f517c78..e1b08e84 100644 --- a/applications/ibutton/scene/ibutton_scene_emulate.cpp +++ b/applications/ibutton/scene/ibutton_scene_emulate.cpp @@ -3,6 +3,7 @@ #include static void emulate_callback(void* context, bool emulated) { + furi_assert(context); if(emulated) { iButtonApp* app = static_cast(context); iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerEmulated}; diff --git a/applications/ibutton/scene/ibutton_scene_info.cpp b/applications/ibutton/scene/ibutton_scene_info.cpp index f4cbd809..afc8c050 100755 --- a/applications/ibutton/scene/ibutton_scene_info.cpp +++ b/applications/ibutton/scene/ibutton_scene_info.cpp @@ -2,6 +2,7 @@ #include "../ibutton_app.h" static void widget_callback(GuiButtonType result, InputType type, void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_read.cpp b/applications/ibutton/scene/ibutton_scene_read.cpp index b5c7ac8f..1353ef96 100644 --- a/applications/ibutton/scene/ibutton_scene_read.cpp +++ b/applications/ibutton/scene/ibutton_scene_read.cpp @@ -3,6 +3,7 @@ #include static void read_callback(void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerRead}; app->get_view_manager()->send_event(&event); diff --git a/applications/ibutton/scene/ibutton_scene_read_crc_error.cpp b/applications/ibutton/scene/ibutton_scene_read_crc_error.cpp index 9d67e442..1371334b 100644 --- a/applications/ibutton/scene/ibutton_scene_read_crc_error.cpp +++ b/applications/ibutton/scene/ibutton_scene_read_crc_error.cpp @@ -3,6 +3,7 @@ #include static void dialog_ex_callback(DialogExResult result, void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_read_not_key_error.cpp b/applications/ibutton/scene/ibutton_scene_read_not_key_error.cpp index fab3c3f2..b88f4655 100644 --- a/applications/ibutton/scene/ibutton_scene_read_not_key_error.cpp +++ b/applications/ibutton/scene/ibutton_scene_read_not_key_error.cpp @@ -3,6 +3,7 @@ #include static void dialog_ex_callback(DialogExResult result, void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_read_success.cpp b/applications/ibutton/scene/ibutton_scene_read_success.cpp index 818f7e38..6eb95721 100644 --- a/applications/ibutton/scene/ibutton_scene_read_success.cpp +++ b/applications/ibutton/scene/ibutton_scene_read_success.cpp @@ -3,6 +3,7 @@ #include static void dialog_ex_callback(DialogExResult result, void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_readed_key_menu.cpp b/applications/ibutton/scene/ibutton_scene_readed_key_menu.cpp index 63af10ad..02d540a2 100644 --- a/applications/ibutton/scene/ibutton_scene_readed_key_menu.cpp +++ b/applications/ibutton/scene/ibutton_scene_readed_key_menu.cpp @@ -4,11 +4,12 @@ typedef enum { SubmenuIndexWrite, SubmenuIndexEmulate, - SubmenuIndexNameAndSave, + SubmenuIndexSave, SubmenuIndexReadNewKey, } SubmenuIndex; static void submenu_callback(void* context, uint32_t index) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; @@ -25,7 +26,7 @@ void iButtonSceneReadedKeyMenu::on_enter(iButtonApp* app) { if(ibutton_key_get_type(app->get_key()) == iButtonKeyDS1990) { submenu_add_item(submenu, "Write", SubmenuIndexWrite, submenu_callback, app); } - submenu_add_item(submenu, "Name and save", SubmenuIndexNameAndSave, submenu_callback, app); + submenu_add_item(submenu, "Save", SubmenuIndexSave, submenu_callback, app); submenu_add_item(submenu, "Emulate", SubmenuIndexEmulate, submenu_callback, app); submenu_add_item(submenu, "Read new key", SubmenuIndexReadNewKey, submenu_callback, app); submenu_set_selected_item(submenu, submenu_item_selected); @@ -45,7 +46,7 @@ bool iButtonSceneReadedKeyMenu::on_event(iButtonApp* app, iButtonEvent* event) { case SubmenuIndexEmulate: app->switch_to_next_scene(iButtonApp::Scene::SceneEmulate); break; - case SubmenuIndexNameAndSave: + case SubmenuIndexSave: app->switch_to_next_scene(iButtonApp::Scene::SceneSaveName); break; case SubmenuIndexReadNewKey: diff --git a/applications/ibutton/scene/ibutton_scene_save_name.cpp b/applications/ibutton/scene/ibutton_scene_save_name.cpp index bde6133d..9a7ab846 100644 --- a/applications/ibutton/scene/ibutton_scene_save_name.cpp +++ b/applications/ibutton/scene/ibutton_scene_save_name.cpp @@ -3,6 +3,7 @@ #include static void text_input_callback(void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; @@ -34,7 +35,7 @@ void iButtonSceneSaveName::on_enter(iButtonApp* app) { key_name_empty); ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(app->app_folder, app->app_extension); + validator_is_file_alloc_init(app->app_folder, app->app_extension, key_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_manager->switch_to(iButtonAppViewManager::Type::iButtonAppViewTextInput); diff --git a/applications/ibutton/scene/ibutton_scene_save_success.cpp b/applications/ibutton/scene/ibutton_scene_save_success.cpp index 891b020a..0a1fc153 100644 --- a/applications/ibutton/scene/ibutton_scene_save_success.cpp +++ b/applications/ibutton/scene/ibutton_scene_save_success.cpp @@ -3,6 +3,7 @@ #include static void popup_callback(void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; event.type = iButtonEvent::Type::EventTypeBack; diff --git a/applications/ibutton/scene/ibutton_scene_saved_key_menu.cpp b/applications/ibutton/scene/ibutton_scene_saved_key_menu.cpp index 1d87e85f..d9d042d9 100644 --- a/applications/ibutton/scene/ibutton_scene_saved_key_menu.cpp +++ b/applications/ibutton/scene/ibutton_scene_saved_key_menu.cpp @@ -11,6 +11,7 @@ typedef enum { } SubmenuIndex; static void submenu_callback(void* context, uint32_t index) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_start.cpp b/applications/ibutton/scene/ibutton_scene_start.cpp index 2dbe9878..4b877dd2 100644 --- a/applications/ibutton/scene/ibutton_scene_start.cpp +++ b/applications/ibutton/scene/ibutton_scene_start.cpp @@ -8,6 +8,7 @@ typedef enum { } SubmenuIndex; static void submenu_callback(void* context, uint32_t index) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; diff --git a/applications/ibutton/scene/ibutton_scene_write.cpp b/applications/ibutton/scene/ibutton_scene_write.cpp index af86a148..847e7f01 100644 --- a/applications/ibutton/scene/ibutton_scene_write.cpp +++ b/applications/ibutton/scene/ibutton_scene_write.cpp @@ -2,6 +2,7 @@ #include "../ibutton_app.h" static void ibutton_worker_write_cb(void* context, iButtonWorkerWriteResult result) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; event.type = iButtonEvent::Type::EventTypeWorkerWrite; diff --git a/applications/ibutton/scene/ibutton_scene_write_success.cpp b/applications/ibutton/scene/ibutton_scene_write_success.cpp index 48acaaa4..dbeb595d 100644 --- a/applications/ibutton/scene/ibutton_scene_write_success.cpp +++ b/applications/ibutton/scene/ibutton_scene_write_success.cpp @@ -2,6 +2,7 @@ #include "../ibutton_app.h" static void popup_callback(void* context) { + furi_assert(context); iButtonApp* app = static_cast(context); iButtonEvent event; event.type = iButtonEvent::Type::EventTypeBack; diff --git a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp index 4b3578fa..0bdd90ac 100644 --- a/applications/infrared/scene/infrared_app_scene_edit_rename.cpp +++ b/applications/infrared/scene/infrared_app_scene_edit_rename.cpp @@ -21,8 +21,8 @@ void InfraredAppSceneEditRename::on_enter(InfraredApp* app) { enter_name_length = InfraredAppRemoteManager::max_remote_name_length; text_input_set_header_text(text_input, "Name the remote"); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(app->infrared_directory, app->infrared_extension); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + app->infrared_directory, app->infrared_extension, remote_name.c_str()); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); } diff --git a/applications/infrared/scene/infrared_app_scene_universal.cpp b/applications/infrared/scene/infrared_app_scene_universal.cpp index b97724fc..57b5653a 100644 --- a/applications/infrared/scene/infrared_app_scene_universal.cpp +++ b/applications/infrared/scene/infrared_app_scene_universal.cpp @@ -20,7 +20,7 @@ void InfraredAppSceneUniversal::on_enter(InfraredApp* app) { InfraredAppViewManager* view_manager = app->get_view_manager(); Submenu* submenu = view_manager->get_submenu(); - submenu_add_item(submenu, "TV's", SubmenuIndexUniversalTV, submenu_callback, app); + submenu_add_item(submenu, "TVs", SubmenuIndexUniversalTV, submenu_callback, app); submenu_set_selected_item(submenu, submenu_item_selected); submenu_item_selected = 0; diff --git a/applications/lfrfid/scene/lfrfid_app_scene_readed_menu.cpp b/applications/lfrfid/scene/lfrfid_app_scene_readed_menu.cpp index e5ffe562..9b0a6d8a 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_readed_menu.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_readed_menu.cpp @@ -2,7 +2,7 @@ typedef enum { SubmenuWrite, - SubmenuNameAndSave, + SubmenuSave, SubmenuEmulate, } SubmenuIndex; @@ -10,7 +10,7 @@ void LfRfidAppSceneReadedMenu::on_enter(LfRfidApp* app, bool need_restore) { auto submenu = app->view_controller.get(); submenu->add_item("Write", SubmenuWrite, submenu_callback, app); - submenu->add_item("Name and Save", SubmenuNameAndSave, submenu_callback, app); + submenu->add_item("Save", SubmenuSave, submenu_callback, app); submenu->add_item("Emulate", SubmenuEmulate, submenu_callback, app); if(need_restore) { @@ -29,7 +29,7 @@ bool LfRfidAppSceneReadedMenu::on_event(LfRfidApp* app, LfRfidApp::Event* event) case SubmenuWrite: app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::Write); break; - case SubmenuNameAndSave: + case SubmenuSave: app->scene_controller.switch_to_next_scene(LfRfidApp::SceneType::SaveName); break; case SubmenuEmulate: diff --git a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp index e5fee4fe..c2ad5dd1 100644 --- a/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp +++ b/applications/lfrfid/scene/lfrfid_app_scene_save_name.cpp @@ -22,7 +22,7 @@ void LfRfidAppSceneSaveName::on_enter(LfRfidApp* app, bool need_restore) { key_name_empty); ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(app->app_folder, app->app_extension); + validator_is_file_alloc_init(app->app_folder, app->app_extension, key_name); text_input->set_validator(validator_is_file_callback, validator_is_file); app->view_controller.switch_to(); diff --git a/applications/nfc/scenes/nfc_scene_save_name.c b/applications/nfc/scenes/nfc_scene_save_name.c index f239baa8..aa05c8a6 100755 --- a/applications/nfc/scenes/nfc_scene_save_name.c +++ b/applications/nfc/scenes/nfc_scene_save_name.c @@ -30,7 +30,7 @@ void nfc_scene_save_name_on_enter(void* context) { dev_name_empty); ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION); + validator_is_file_alloc_init(NFC_APP_FOLDER, NFC_APP_EXTENSION, nfc->dev->dev_name); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextInput); diff --git a/applications/notification/notification_messages.c b/applications/notification/notification_messages.c index 44b005d1..3934f5d0 100644 --- a/applications/notification/notification_messages.c +++ b/applications/notification/notification_messages.c @@ -436,4 +436,4 @@ const NotificationSequence sequence_audiovisual_alert = { &message_sound_off, &message_vibro_off, NULL, -}; +}; \ No newline at end of file diff --git a/applications/power/power_cli.c b/applications/power/power_cli.c index f65fb095..110318ac 100644 --- a/applications/power/power_cli.c +++ b/applications/power/power_cli.c @@ -20,6 +20,14 @@ void power_cli_reboot2dfu(Cli* cli, string_t args) { power_reboot(PowerBootModeDfu); } +static void power_cli_info_callback(const char* key, const char* value, bool last, void* context) { + printf("%-24s: %s\r\n", key, value); +} + +void power_cli_info(Cli* cli, string_t args) { + furi_hal_power_info_get(power_cli_info_callback, NULL); +} + void power_cli_debug(Cli* cli, string_t args) { furi_hal_power_dump_state(); } @@ -52,6 +60,7 @@ static void power_cli_command_print_usage() { printf("\toff\t - shutdown power\r\n"); printf("\treboot\t - reboot\r\n"); printf("\treboot2dfu\t - reboot to dfu bootloader\r\n"); + printf("\tinfo\t - show power info\r\n"); printf("\tdebug\t - show debug information\r\n"); printf("\t5v <0 or 1>\t - enable or disable 5v ext\r\n"); if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { @@ -84,6 +93,11 @@ void power_cli(Cli* cli, string_t args, void* context) { break; } + if(string_cmp_str(cmd, "info") == 0) { + power_cli_info(cli, args); + break; + } + if(string_cmp_str(cmd, "debug") == 0) { power_cli_debug(cli, args); break; diff --git a/applications/rpc/rpc_cli.c b/applications/rpc/rpc_cli.c index 849ef361..fd1f6e7f 100644 --- a/applications/rpc/rpc_cli.c +++ b/applications/rpc/rpc_cli.c @@ -4,6 +4,8 @@ #include #include +#define TAG "RpcCli" + typedef struct { Cli* cli; bool session_close_request; @@ -38,6 +40,9 @@ static void rpc_session_terminated_callback(void* context) { void rpc_cli_command_start_session(Cli* cli, string_t args, void* context) { Rpc* rpc = context; + uint32_t mem_before = memmgr_get_free_heap(); + FURI_LOG_D(TAG, "Free memory %d", mem_before); + furi_hal_usb_lock(); RpcSession* rpc_session = rpc_session_open(rpc); if(rpc_session == NULL) { diff --git a/applications/rpc/rpc_gui.c b/applications/rpc/rpc_gui.c index 3a4e21f0..84051bff 100644 --- a/applications/rpc/rpc_gui.c +++ b/applications/rpc/rpc_gui.c @@ -346,8 +346,19 @@ void rpc_system_gui_free(void* context) { } if(rpc_gui->is_streaming) { + rpc_gui->is_streaming = false; + // Remove GUI framebuffer callback gui_remove_framebuffer_callback( rpc_gui->gui, rpc_system_gui_screen_stream_frame_callback, context); + // Stop and release worker thread + osThreadFlagsSet( + furi_thread_get_thread_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagExit); + furi_thread_join(rpc_gui->transmit_thread); + furi_thread_free(rpc_gui->transmit_thread); + // Release frame + pb_release(&PB_Main_msg, rpc_gui->transmit_frame); + free(rpc_gui->transmit_frame); + rpc_gui->transmit_frame = NULL; } furi_record_close("gui"); free(rpc_gui); diff --git a/applications/rpc/rpc_system.c b/applications/rpc/rpc_system.c index 9884e30a..f3a8242a 100644 --- a/applications/rpc/rpc_system.c +++ b/applications/rpc/rpc_system.c @@ -6,6 +6,11 @@ #include "rpc_i.h" +typedef struct { + RpcSession* session; + PB_Main* response; +} RpcSystemContext; + static void rpc_system_system_ping_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(request->which_content == PB_Main_system_ping_request_tag); @@ -55,11 +60,6 @@ static void rpc_system_system_reboot_process(const PB_Main* request, void* conte } } -typedef struct { - RpcSession* session; - PB_Main* response; -} RpcSystemSystemDeviceInfoContext; - static void rpc_system_system_device_info_callback( const char* key, const char* value, @@ -67,7 +67,7 @@ static void rpc_system_system_device_info_callback( void* context) { furi_assert(key); furi_assert(value); - RpcSystemSystemDeviceInfoContext* ctx = context; + RpcSystemContext* ctx = context; char* str_key = strdup(key); char* str_value = strdup(value); @@ -91,7 +91,7 @@ static void rpc_system_system_device_info_process(const PB_Main* request, void* response->which_content = PB_Main_system_device_info_response_tag; response->command_status = PB_CommandStatus_OK; - RpcSystemSystemDeviceInfoContext device_info_context = { + RpcSystemContext device_info_context = { .session = session, .response = response, }; @@ -202,6 +202,46 @@ static void rpc_system_system_protobuf_version_process(const PB_Main* request, v free(response); } +static void rpc_system_system_power_info_callback( + const char* key, + const char* value, + bool last, + void* context) { + furi_assert(key); + furi_assert(value); + RpcSystemContext* ctx = context; + + char* str_key = strdup(key); + char* str_value = strdup(value); + + ctx->response->has_next = !last; + ctx->response->content.system_device_info_response.key = str_key; + ctx->response->content.system_device_info_response.value = str_value; + + rpc_send_and_release(ctx->session, ctx->response); +} + +static void rpc_system_system_get_power_info_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(request->which_content == PB_Main_system_power_info_request_tag); + + RpcSession* session = (RpcSession*)context; + furi_assert(session); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->which_content = PB_Main_system_power_info_response_tag; + response->command_status = PB_CommandStatus_OK; + + RpcSystemContext power_info_context = { + .session = session, + .response = response, + }; + furi_hal_power_info_get(rpc_system_system_power_info_callback, &power_info_context); + + free(response); +} + void* rpc_system_system_alloc(RpcSession* session) { RpcHandler rpc_handler = { .message_handler = NULL, @@ -233,5 +273,8 @@ void* rpc_system_system_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_system_protobuf_version_process; rpc_add_handler(session, PB_Main_system_protobuf_version_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_system_get_power_info_process; + rpc_add_handler(session, PB_Main_system_power_info_request_tag, &rpc_handler); + return NULL; } diff --git a/applications/storage/filesystem_api_defines.h b/applications/storage/filesystem_api_defines.h index e90e642f..cc33de26 100644 --- a/applications/storage/filesystem_api_defines.h +++ b/applications/storage/filesystem_api_defines.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #ifdef __cplusplus extern "C" { diff --git a/applications/storage/filesystem_api_internal.h b/applications/storage/filesystem_api_internal.h index c67e5395..29098e59 100644 --- a/applications/storage/filesystem_api_internal.h +++ b/applications/storage/filesystem_api_internal.h @@ -75,21 +75,21 @@ struct File { * @return end of file flag */ typedef struct { - bool (*open)( + bool (*const open)( void* context, File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode); - bool (*close)(void* context, File* file); + bool (*const close)(void* context, File* file); uint16_t (*read)(void* context, File* file, void* buff, uint16_t bytes_to_read); uint16_t (*write)(void* context, File* file, const void* buff, uint16_t bytes_to_write); - bool (*seek)(void* context, File* file, uint32_t offset, bool from_start); + bool (*const seek)(void* context, File* file, uint32_t offset, bool from_start); uint64_t (*tell)(void* context, File* file); - bool (*truncate)(void* context, File* file); + bool (*const truncate)(void* context, File* file); uint64_t (*size)(void* context, File* file); - bool (*sync)(void* context, File* file); - bool (*eof)(void* context, File* file); + bool (*const sync)(void* context, File* file); + bool (*const eof)(void* context, File* file); } FS_File_Api; /** Dir api structure @@ -118,10 +118,15 @@ typedef struct { * @return success flag */ typedef struct { - bool (*open)(void* context, File* file, const char* path); - bool (*close)(void* context, File* file); - bool (*read)(void* context, File* file, FileInfo* fileinfo, char* name, uint16_t name_length); - bool (*rewind)(void* context, File* file); + bool (*const open)(void* context, File* file, const char* path); + bool (*const close)(void* context, File* file); + bool (*const read)( + void* context, + File* file, + FileInfo* fileinfo, + char* name, + uint16_t name_length); + bool (*const rewind)(void* context, File* file); } FS_Dir_Api; /** Common api structure @@ -141,12 +146,6 @@ typedef struct { * @param path path to file/directory * @return FS_Error error info * - * @var FS_Common_Api::rename - * @brief Rename file/directory, - * file/directory must not be opened - * @param path path to file/directory - * @return FS_Error error info - * * @var FS_Common_Api::mkdir * @brief Create new directory * @param path path to new directory @@ -160,31 +159,21 @@ typedef struct { * @return FS_Error error info */ typedef struct { - FS_Error (*stat)(void* context, const char* path, FileInfo* fileinfo); - FS_Error (*remove)(void* context, const char* path); - FS_Error (*rename)(void* context, const char* old_path, const char* new_path); - FS_Error (*mkdir)(void* context, const char* path); - FS_Error ( - *fs_info)(void* context, const char* fs_path, uint64_t* total_space, uint64_t* free_space); + FS_Error (*const stat)(void* context, const char* path, FileInfo* fileinfo); + FS_Error (*const remove)(void* context, const char* path); + FS_Error (*const mkdir)(void* context, const char* path); + FS_Error (*const fs_info)( + void* context, + const char* fs_path, + uint64_t* total_space, + uint64_t* free_space); } FS_Common_Api; -/** Errors api structure - * @var FS_Error_Api::get_desc - * @brief Get error description text - * @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id) - * @return pointer to description text - */ -typedef struct { - const char* (*get_desc)(void* context, FS_Error error_id); -} FS_Error_Api; - /** Full filesystem api structure */ typedef struct { - FS_File_Api file; - FS_Dir_Api dir; - FS_Common_Api common; - FS_Error_Api error; - void* context; + const FS_File_Api file; + const FS_Dir_Api dir; + const FS_Common_Api common; } FS_Api; #ifdef __cplusplus diff --git a/applications/storage/storage.c b/applications/storage/storage.c index 9ce8d958..4aa5f105 100644 --- a/applications/storage/storage.c +++ b/applications/storage/storage.c @@ -11,6 +11,8 @@ #define ICON_SD_MOUNTED &I_SDcardMounted_11x8 #define ICON_SD_ERROR &I_SDcardFail_11x8 +#define TAG "Storage" + static void storage_app_sd_icon_draw_callback(Canvas* canvas, void* context) { furi_assert(canvas); furi_assert(context); @@ -63,15 +65,14 @@ void storage_tick(Storage* app) { } } - if(app->storage[ST_EXT].status != app->prev_ext_storage_status) { - app->prev_ext_storage_status = app->storage[ST_EXT].status; - furi_pubsub_publish(app->pubsub, &app->storage[ST_EXT].status); - } - // storage not enabled but was enabled (sd card unmount) if(app->storage[ST_EXT].status == StorageStatusNotReady && app->sd_gui.enabled == true) { app->sd_gui.enabled = false; view_port_enabled_set(app->sd_gui.view_port, false); + + FURI_LOG_I(TAG, "SD card unmount"); + StorageEvent event = {.type = StorageEventTypeCardUnmount}; + furi_pubsub_publish(app->pubsub, &event); } // storage enabled (or in error state) but was not enabled (sd card mount) @@ -83,6 +84,16 @@ void storage_tick(Storage* app) { app->sd_gui.enabled == false) { app->sd_gui.enabled = true; view_port_enabled_set(app->sd_gui.view_port, true); + + if(app->storage[ST_EXT].status == StorageStatusOK) { + FURI_LOG_I(TAG, "SD card mount"); + StorageEvent event = {.type = StorageEventTypeCardMount}; + furi_pubsub_publish(app->pubsub, &event); + } else { + FURI_LOG_I(TAG, "SD card mount error"); + StorageEvent event = {.type = StorageEventTypeCardMountError}; + furi_pubsub_publish(app->pubsub, &event); + } } } diff --git a/applications/storage/storage.h b/applications/storage/storage.h index f70827cb..d5eb87d3 100644 --- a/applications/storage/storage.h +++ b/applications/storage/storage.h @@ -1,5 +1,6 @@ #pragma once -#include +#include +#include #include "filesystem_api_defines.h" #include "storage_sd_api.h" @@ -18,6 +19,24 @@ File* storage_file_alloc(Storage* storage); */ void storage_file_free(File* file); +typedef enum { + StorageEventTypeCardMount, + StorageEventTypeCardUnmount, + StorageEventTypeCardMountError, + StorageEventTypeFileClose, + StorageEventTypeDirClose, +} StorageEventType; + +typedef struct { + StorageEventType type; +} StorageEvent; + +/** + * Get storage pubsub. + * Storage will send StorageEvent messages. + * @param storage + * @return FuriPubSub* + */ FuriPubSub* storage_get_pubsub(Storage* storage); /******************* File Functions *******************/ @@ -47,6 +66,12 @@ bool storage_file_close(File* file); */ bool storage_file_is_open(File* file); +/** Tells if the file is a directory + * @param file pointer to a file object + * @return bool true if file is a directory + */ +bool storage_file_is_dir(File* file); + /** Reads bytes from a file into a buffer * @param file pointer to file object. * @param buff pointer to a buffer, for reading @@ -272,13 +297,15 @@ bool storage_simply_mkdir(Storage* storage, const char* path); * @param filename * @param fileextension * @param nextfilename return name + * @param max_len max len name */ void storage_get_next_filename( Storage* storage, const char* dirname, const char* filename, const char* fileextension, - string_t nextfilename); + string_t nextfilename, + uint8_t max_len); #ifdef __cplusplus } diff --git a/applications/storage/storage_external_api.c b/applications/storage/storage_external_api.c index 9acc9ea4..8e39c08b 100644 --- a/applications/storage/storage_external_api.c +++ b/applications/storage/storage_external_api.c @@ -3,6 +3,7 @@ #include "storage.h" #include "storage_i.h" #include "storage_message.h" +#include #define MAX_NAME_LENGTH 256 @@ -46,12 +47,16 @@ #define S_RETURN_ERROR (return_data.error_value); #define S_RETURN_CSTRING (return_data.cstring_value); -#define FILE_OPENED 1 +#define FILE_OPENED_FILE 1 +#define FILE_OPENED_DIR 2 #define FILE_CLOSED 0 +typedef enum { + StorageEventFlagFileClose = (1 << 0), +} StorageEventFlag; /****************** FILE ******************/ -bool storage_file_open( +static bool storage_file_open_internal( File* file, const char* path, FS_AccessMode access_mode, @@ -67,7 +72,7 @@ bool storage_file_open( .open_mode = open_mode, }}; - file->file_id = FILE_OPENED; + file->file_id = FILE_OPENED_FILE; S_API_MESSAGE(StorageCommandFileOpen); S_API_EPILOGUE; @@ -75,6 +80,42 @@ bool storage_file_open( return S_RETURN_BOOL; } +static void storage_file_close_callback(const void* message, void* context) { + const StorageEvent* storage_event = message; + + if(storage_event->type == StorageEventTypeFileClose || + storage_event->type == StorageEventTypeDirClose) { + furi_assert(context); + osEventFlagsId_t event = context; + osEventFlagsSet(event, StorageEventFlagFileClose); + } +} + +bool storage_file_open( + File* file, + const char* path, + FS_AccessMode access_mode, + FS_OpenMode open_mode) { + bool result; + osEventFlagsId_t event = osEventFlagsNew(NULL); + FuriPubSubSubscription* subscription = furi_pubsub_subscribe( + storage_get_pubsub(file->storage), storage_file_close_callback, event); + + do { + result = storage_file_open_internal(file, path, access_mode, open_mode); + + if(!result && file->error_id == FSE_ALREADY_OPEN) { + osEventFlagsWait(event, StorageEventFlagFileClose, osFlagsWaitAny, osWaitForever); + } else { + break; + } + } while(true); + + furi_pubsub_unsubscribe(storage_get_pubsub(file->storage), subscription); + osEventFlagsDelete(event); + return result; +} + bool storage_file_close(File* file) { S_FILE_API_PROLOGUE; S_API_PROLOGUE; @@ -183,7 +224,7 @@ bool storage_file_eof(File* file) { /****************** DIR ******************/ -bool storage_dir_open(File* file, const char* path) { +static bool storage_dir_open_internal(File* file, const char* path) { S_FILE_API_PROLOGUE; S_API_PROLOGUE; @@ -193,13 +234,34 @@ bool storage_dir_open(File* file, const char* path) { .path = path, }}; - file->file_id = FILE_OPENED; + file->file_id = FILE_OPENED_DIR; S_API_MESSAGE(StorageCommandDirOpen); S_API_EPILOGUE; return S_RETURN_BOOL; } +bool storage_dir_open(File* file, const char* path) { + bool result; + osEventFlagsId_t event = osEventFlagsNew(NULL); + FuriPubSubSubscription* subscription = furi_pubsub_subscribe( + storage_get_pubsub(file->storage), storage_file_close_callback, event); + + do { + result = storage_dir_open_internal(file, path); + + if(!result && file->error_id == FSE_ALREADY_OPEN) { + osEventFlagsWait(event, StorageEventFlagFileClose, osFlagsWaitAny, osWaitForever); + } else { + break; + } + } while(true); + + furi_pubsub_unsubscribe(storage_get_pubsub(file->storage), subscription); + osEventFlagsDelete(event); + return result; +} + bool storage_dir_close(File* file) { S_FILE_API_PROLOGUE; S_API_PROLOGUE; @@ -259,31 +321,44 @@ 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) { - S_API_PROLOGUE; + FS_Error error = storage_common_copy(storage, old_path, new_path); + if(error == FSE_OK) { + error = storage_common_remove(storage, old_path); + } - SAData data = { - .cpaths = { - .old = old_path, - .new = new_path, - }}; - - S_API_MESSAGE(StorageCommandCommonRename); - S_API_EPILOGUE; - return S_RETURN_ERROR; + return error; } FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path) { - S_API_PROLOGUE; + FS_Error error; - SAData data = { - .cpaths = { - .old = old_path, - .new = new_path, - }}; + FileInfo fileinfo; + error = storage_common_stat(storage, old_path, &fileinfo); - S_API_MESSAGE(StorageCommandCommonCopy); - S_API_EPILOGUE; - return S_RETURN_ERROR; + if(error == FSE_OK) { + if(fileinfo.flags & FSF_DIRECTORY) { + error = storage_common_mkdir(storage, new_path); + } else { + Stream* stream_from = file_stream_alloc(storage); + Stream* stream_to = file_stream_alloc(storage); + + do { + if(!file_stream_open(stream_from, old_path, FSAM_READ, FSOM_OPEN_EXISTING)) break; + if(!file_stream_open(stream_to, new_path, FSAM_WRITE, FSOM_CREATE_NEW)) break; + stream_copy_full(stream_from, stream_to); + } while(false); + + error = file_stream_get_error(stream_from); + if(error == FSE_OK) { + error = file_stream_get_error(stream_to); + } + + stream_free(stream_from); + stream_free(stream_to); + } + } + + return error; } FS_Error storage_common_mkdir(Storage* storage, const char* path) { @@ -383,9 +458,17 @@ bool storage_file_is_open(File* file) { return (file->file_id != FILE_CLOSED); } +bool storage_file_is_dir(File* file) { + return (file->file_id == FILE_OPENED_DIR); +} + void storage_file_free(File* file) { if(storage_file_is_open(file)) { - storage_file_close(file); + if(storage_file_is_dir(file)) { + storage_dir_close(file); + } else { + storage_file_close(file); + } } free(file); @@ -473,7 +556,8 @@ void storage_get_next_filename( const char* dirname, const char* filename, const char* fileextension, - string_t nextfilename) { + string_t nextfilename, + uint8_t max_len) { string_t temp_str; uint16_t num = 0; @@ -483,8 +567,7 @@ void storage_get_next_filename( num++; string_printf(temp_str, "%s/%s%d%s", dirname, filename, num, fileextension); } - - if(num) { + if(num && (max_len > strlen(filename))) { string_printf(nextfilename, "%s%d", filename, num); } else { string_printf(nextfilename, "%s", filename); diff --git a/applications/storage/storage_glue.c b/applications/storage/storage_glue.c index 6271fbd8..7ce8ec2c 100644 --- a/applications/storage/storage_glue.c +++ b/applications/storage/storage_glue.c @@ -103,25 +103,7 @@ bool storage_has_file(const File* file, StorageData* storage_data) { return result; } -StorageType storage_get_type_by_path(const char* path) { - StorageType type = ST_ERROR; - - const char* ext_path = "/ext"; - const char* int_path = "/int"; - const char* any_path = "/any"; - - if(strlen(path) >= strlen(ext_path) && memcmp(path, ext_path, strlen(ext_path)) == 0) { - type = ST_EXT; - } else if(strlen(path) >= strlen(int_path) && memcmp(path, int_path, strlen(int_path)) == 0) { - type = ST_INT; - } else if(strlen(path) >= strlen(any_path) && memcmp(path, any_path, strlen(any_path)) == 0) { - type = ST_ANY; - } - - return type; -} - -bool storage_path_already_open(const char* path, StorageFileList_t array) { +bool storage_path_already_open(string_t path, StorageFileList_t array) { bool open = false; StorageFileList_it_t it; @@ -178,11 +160,7 @@ void* storage_get_storage_file_data(const File* file, StorageData* storage) { return founded_file->file_data; } -void storage_push_storage_file( - File* file, - const char* path, - StorageType type, - StorageData* storage) { +void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage) { StorageFile* storage_file = StorageFileList_push_new(storage->files); furi_check(storage_file != NULL); diff --git a/applications/storage/storage_glue.h b/applications/storage/storage_glue.h index bd8f317b..21147dce 100644 --- a/applications/storage/storage_glue.h +++ b/applications/storage/storage_glue.h @@ -3,7 +3,6 @@ #include #include "filesystem_api_internal.h" #include -#include #include #ifdef __cplusplus @@ -54,7 +53,7 @@ LIST_DEF( CLEAR(API_2(storage_file_clear)))) struct StorageData { - FS_Api fs_api; + const FS_Api* fs_api; StorageApi api; void* data; osMutexId_t mutex; @@ -63,17 +62,12 @@ struct StorageData { }; bool storage_has_file(const File* file, StorageData* storage_data); -StorageType storage_get_type_by_path(const char* path); -bool storage_path_already_open(const char* path, StorageFileList_t files); +bool storage_path_already_open(string_t path, StorageFileList_t files); void storage_set_storage_file_data(const File* file, void* file_data, StorageData* storage); void* storage_get_storage_file_data(const File* file, StorageData* storage); -void storage_push_storage_file( - File* file, - const char* path, - StorageType type, - StorageData* storage); +void storage_push_storage_file(File* file, string_t path, StorageType type, StorageData* storage); bool storage_pop_storage_file(File* file, StorageData* storage); #ifdef __cplusplus diff --git a/applications/storage/storage_i.h b/applications/storage/storage_i.h index d69a0ef8..0db5218d 100644 --- a/applications/storage/storage_i.h +++ b/applications/storage/storage_i.h @@ -19,7 +19,6 @@ typedef struct { struct Storage { osMessageQueueId_t message_queue; StorageData storage[STORAGE_COUNT]; - StorageStatus prev_ext_storage_status; StorageSDGui sd_gui; FuriPubSub* pubsub; }; diff --git a/applications/storage/storage_message.h b/applications/storage/storage_message.h index 0d409587..0e58fff2 100644 --- a/applications/storage/storage_message.h +++ b/applications/storage/storage_message.h @@ -47,11 +47,6 @@ typedef struct { FileInfo* fileinfo; } SADataCStat; -typedef struct { - const char* old; - const char* new; -} SADataCPaths; - typedef struct { const char* fs_path; uint64_t* total_space; @@ -84,7 +79,6 @@ typedef union { SADataDRead dread; SADataCStat cstat; - SADataCPaths cpaths; SADataCFSInfo cfsinfo; SADataError error; @@ -120,8 +114,6 @@ typedef enum { StorageCommandDirRewind, StorageCommandCommonStat, StorageCommandCommonRemove, - StorageCommandCommonRename, - StorageCommandCommonCopy, StorageCommandCommonMkDir, StorageCommandCommonFSInfo, StorageCommandSDFormat, diff --git a/applications/storage/storage_processing.c b/applications/storage/storage_processing.c index ccf7de9a..bb1ac9c3 100644 --- a/applications/storage/storage_processing.c +++ b/applications/storage/storage_processing.c @@ -1,8 +1,11 @@ #include "storage_processing.h" +#include +#include +#include #define FS_CALL(_storage, _fn) \ storage_data_lock(_storage); \ - ret = _storage->fs_api._fn; \ + ret = _storage->fs_api->_fn; \ storage_data_unlock(_storage); #define ST_CALL(_storage, _fn) \ @@ -11,18 +14,8 @@ storage_data_unlock(_storage); static StorageData* storage_get_storage_by_type(Storage* app, StorageType type) { - StorageData* storage; - - if(type == ST_ANY) { - type = ST_INT; - StorageData* ext_storage = &app->storage[ST_EXT]; - - if(storage_data_status(ext_storage) == StorageStatusOK) { - type = ST_EXT; - } - } - storage = &app->storage[type]; - + furi_check(type == ST_EXT || type == ST_INT); + StorageData* storage = &app->storage[type]; return storage; } @@ -42,10 +35,55 @@ static StorageData* get_storage_by_file(File* file, StorageData* storages) { return storage_data; } -const char* remove_vfs(const char* path) { +static const char* remove_vfs(const char* path) { return path + MIN(4, strlen(path)); } +static const char* ext_path = "/ext"; +static const char* int_path = "/int"; +static const char* any_path = "/any"; + +static StorageType storage_get_type_by_path(Storage* app, const char* path) { + StorageType type = ST_ERROR; + if(strlen(path) >= strlen(ext_path) && memcmp(path, ext_path, strlen(ext_path)) == 0) { + type = ST_EXT; + } else if(strlen(path) >= strlen(int_path) && memcmp(path, int_path, strlen(int_path)) == 0) { + type = ST_INT; + } else if(strlen(path) >= strlen(any_path) && memcmp(path, any_path, strlen(any_path)) == 0) { + type = ST_ANY; + } + + if(type == ST_ANY) { + type = ST_INT; + if(storage_data_status(&app->storage[ST_EXT]) == StorageStatusOK) { + type = ST_EXT; + } + } + + return type; +} + +static void storage_path_change_to_real_storage(string_t path, StorageType real_storage) { + if(memcmp(string_get_cstr(path), any_path, strlen(any_path)) == 0) { + switch(real_storage) { + case ST_EXT: + string_set_char(path, 0, ext_path[0]); + string_set_char(path, 1, ext_path[1]); + string_set_char(path, 2, ext_path[2]); + string_set_char(path, 3, ext_path[3]); + break; + case ST_INT: + string_set_char(path, 0, int_path[0]); + string_set_char(path, 1, int_path[1]); + string_set_char(path, 2, int_path[2]); + string_set_char(path, 3, int_path[3]); + break; + default: + break; + } + } +} + /******************* File Functions *******************/ bool storage_process_file_open( @@ -55,7 +93,7 @@ bool storage_process_file_open( FS_AccessMode access_mode, FS_OpenMode open_mode) { bool ret = false; - StorageType type = storage_get_type_by_path(path); + StorageType type = storage_get_type_by_path(app, path); StorageData* storage; file->error_id = FSE_OK; @@ -63,12 +101,18 @@ bool storage_process_file_open( file->error_id = FSE_INVALID_NAME; } else { storage = storage_get_storage_by_type(app, type); - if(storage_path_already_open(path, storage->files)) { + string_t real_path; + string_init_set(real_path, path); + storage_path_change_to_real_storage(real_path, type); + + if(storage_path_already_open(real_path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { - storage_push_storage_file(file, path, type, storage); + storage_push_storage_file(file, real_path, type, storage); FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); } + + string_clear(real_path); } return ret; @@ -83,6 +127,9 @@ bool storage_process_file_close(Storage* app, File* file) { } else { FS_CALL(storage, file.close(storage, file)); storage_pop_storage_file(file, storage); + + StorageEvent event = {.type = StorageEventTypeFileClose}; + furi_pubsub_publish(app->pubsub, &event); } return ret; @@ -205,7 +252,7 @@ static bool storage_process_file_eof(Storage* app, File* file) { bool storage_process_dir_open(Storage* app, File* file, const char* path) { bool ret = false; - StorageType type = storage_get_type_by_path(path); + StorageType type = storage_get_type_by_path(app, path); StorageData* storage; file->error_id = FSE_OK; @@ -213,12 +260,17 @@ bool storage_process_dir_open(Storage* app, File* file, const char* path) { file->error_id = FSE_INVALID_NAME; } else { storage = storage_get_storage_by_type(app, type); - if(storage_path_already_open(path, storage->files)) { + string_t real_path; + string_init_set(real_path, path); + storage_path_change_to_real_storage(real_path, type); + + if(storage_path_already_open(real_path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { - storage_push_storage_file(file, path, type, storage); + storage_push_storage_file(file, real_path, type, storage); FS_CALL(storage, dir.open(storage, file, remove_vfs(path))); } + string_clear(real_path); } return ret; @@ -233,6 +285,9 @@ bool storage_process_dir_close(Storage* app, File* file) { } else { FS_CALL(storage, dir.close(storage, file)); storage_pop_storage_file(file, storage); + + StorageEvent event = {.type = StorageEventTypeDirClose}; + furi_pubsub_publish(app->pubsub, &event); } return ret; @@ -273,7 +328,7 @@ bool storage_process_dir_rewind(Storage* app, File* file) { static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) { FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(path); + StorageType type = storage_get_type_by_path(app, path); if(storage_type_is_not_valid(type)) { ret = FSE_INVALID_NAME; @@ -287,7 +342,11 @@ static FS_Error storage_process_common_stat(Storage* app, const char* path, File static FS_Error storage_process_common_remove(Storage* app, const char* path) { FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(path); + StorageType type = storage_get_type_by_path(app, path); + + string_t real_path; + string_init_set(real_path, path); + storage_path_change_to_real_storage(real_path, type); do { if(storage_type_is_not_valid(type)) { @@ -296,7 +355,7 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) { } StorageData* storage = storage_get_storage_by_type(app, type); - if(storage_path_already_open(path, storage->files)) { + if(storage_path_already_open(real_path, storage->files)) { ret = FSE_ALREADY_OPEN; break; } @@ -304,12 +363,14 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) { FS_CALL(storage, common.remove(storage, remove_vfs(path))); } while(false); + string_clear(real_path); + return ret; } static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(path); + StorageType type = storage_get_type_by_path(app, path); if(storage_type_is_not_valid(type)) { ret = FSE_INVALID_NAME; @@ -321,86 +382,13 @@ static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { return ret; } -static FS_Error storage_process_common_copy(Storage* app, const char* old, const char* new) { - FS_Error ret = FSE_INTERNAL; - File file_old; - File file_new; - - FileInfo fileinfo; - ret = storage_process_common_stat(app, old, &fileinfo); - - if(ret == FSE_OK) { - if(fileinfo.flags & FSF_DIRECTORY) { - ret = storage_process_common_mkdir(app, new); - } else { - do { - if(!storage_process_file_open(app, &file_old, old, FSAM_READ, FSOM_OPEN_EXISTING)) { - ret = storage_file_get_error(&file_old); - storage_process_file_close(app, &file_old); - break; - } - - if(!storage_process_file_open(app, &file_new, new, FSAM_WRITE, FSOM_CREATE_NEW)) { - ret = storage_file_get_error(&file_new); - storage_process_file_close(app, &file_new); - storage_process_file_close(app, &file_old); - break; - } - - const uint16_t buffer_size = 64; - uint8_t* buffer = malloc(buffer_size); - uint16_t readed_size = 0; - uint16_t writed_size = 0; - - while(true) { - readed_size = storage_process_file_read(app, &file_old, buffer, buffer_size); - ret = storage_file_get_error(&file_old); - if(readed_size == 0) break; - - writed_size = storage_process_file_write(app, &file_new, buffer, readed_size); - ret = storage_file_get_error(&file_new); - if(writed_size < readed_size) break; - } - - free(buffer); - storage_process_file_close(app, &file_old); - storage_process_file_close(app, &file_new); - } while(false); - } - } - - return ret; -} - -static FS_Error storage_process_common_rename(Storage* app, const char* old, const char* new) { - FS_Error ret = FSE_INTERNAL; - StorageType type_old = storage_get_type_by_path(old); - StorageType type_new = storage_get_type_by_path(new); - - if(storage_type_is_not_valid(type_old) || storage_type_is_not_valid(type_new)) { - ret = FSE_INVALID_NAME; - } else { - if(type_old != type_new) { - ret = storage_process_common_copy(app, old, new); - if(ret == FSE_OK) { - ret = storage_process_common_remove(app, old); - } - } else { - StorageData* storage = storage_get_storage_by_type(app, type_old); - FS_CALL(storage, common.rename(storage, remove_vfs(old), remove_vfs(new))); - } - } - - return ret; -} - static FS_Error storage_process_common_fs_info( Storage* app, const char* fs_path, uint64_t* total_space, uint64_t* free_space) { FS_Error ret = FSE_OK; - StorageType type = storage_get_type_by_path(fs_path); + StorageType type = storage_get_type_by_path(app, fs_path); if(storage_type_is_not_valid(type)) { ret = FSE_INVALID_NAME; @@ -472,8 +460,7 @@ static FS_Error storage_process_sd_status(Storage* app) { } /****************** API calls processing ******************/ - -void storage_process_message(Storage* app, StorageMessage* message) { +void storage_process_message_internal(Storage* app, StorageMessage* message) { switch(message->command) { case StorageCommandFileOpen: message->return_data->bool_value = storage_process_file_open( @@ -556,14 +543,6 @@ void storage_process_message(Storage* app, StorageMessage* message) { message->return_data->error_value = storage_process_common_remove(app, message->data->path.path); break; - case StorageCommandCommonRename: - message->return_data->error_value = storage_process_common_rename( - app, message->data->cpaths.old, message->data->cpaths.new); - break; - case StorageCommandCommonCopy: - message->return_data->error_value = - storage_process_common_copy(app, message->data->cpaths.old, message->data->cpaths.new); - break; case StorageCommandCommonMkDir: message->return_data->error_value = storage_process_common_mkdir(app, message->data->path.path); @@ -592,3 +571,7 @@ void storage_process_message(Storage* app, StorageMessage* message) { osSemaphoreRelease(message->semaphore); } + +void storage_process_message(Storage* app, StorageMessage* message) { + storage_process_message_internal(app, message); +} diff --git a/applications/storage/storage_sd_api.h b/applications/storage/storage_sd_api.h index fe6f94af..2db35866 100644 --- a/applications/storage/storage_sd_api.h +++ b/applications/storage/storage_sd_api.h @@ -1,8 +1,6 @@ #pragma once #include #include "filesystem_api_defines.h" -#include -#include "storage_glue.h" #ifdef __cplusplus extern "C" { @@ -11,10 +9,11 @@ extern "C" { #define SD_LABEL_LENGTH 34 typedef enum { - FST_FAT12 = FS_FAT12, - FST_FAT16 = FS_FAT16, - FST_FAT32 = FS_FAT32, - FST_EXFAT = FS_EXFAT, + FST_UNKNOWN, + FST_FAT12, + FST_FAT16, + FST_FAT32, + FST_EXFAT, } SDFsType; typedef struct { diff --git a/applications/storage/storages/storage_ext.c b/applications/storage/storages/storage_ext.c index 1b76ef6c..8a3d21ca 100644 --- a/applications/storage/storages/storage_ext.c +++ b/applications/storage/storages/storage_ext.c @@ -164,7 +164,24 @@ FS_Error sd_card_info(StorageData* storage, SDInfo* sd_info) { sector_size = fs->ssize; #endif - sd_info->fs_type = fs->fs_type; + switch(fs->fs_type) { + case FS_FAT12: + sd_info->fs_type = FST_FAT12; + break; + case FS_FAT16: + sd_info->fs_type = FST_FAT16; + break; + case FS_FAT32: + sd_info->fs_type = FST_FAT32; + break; + case FS_EXFAT: + sd_info->fs_type = FST_EXFAT; + break; + + default: + sd_info->fs_type = FST_UNKNOWN; + break; + } sd_info->kb_total = total_sectors / 1024 * sector_size; sd_info->kb_free = free_sectors / 1024 * sector_size; @@ -466,11 +483,6 @@ static FS_Error storage_ext_common_remove(void* ctx, const char* path) { return storage_ext_parse_error(result); } -static FS_Error storage_ext_common_rename(void* ctx, const char* old_path, const char* new_path) { - SDError result = f_rename(old_path, new_path); - return storage_ext_parse_error(result); -} - static FS_Error storage_ext_common_mkdir(void* ctx, const char* path) { SDError result = f_mkdir(path); return storage_ext_parse_error(result); @@ -510,6 +522,35 @@ static FS_Error storage_ext_common_fs_info( } /******************* Init Storage *******************/ +static const FS_Api fs_api = { + .file = + { + .open = storage_ext_file_open, + .close = storage_ext_file_close, + .read = storage_ext_file_read, + .write = storage_ext_file_write, + .seek = storage_ext_file_seek, + .tell = storage_ext_file_tell, + .truncate = storage_ext_file_truncate, + .size = storage_ext_file_size, + .sync = storage_ext_file_sync, + .eof = storage_ext_file_eof, + }, + .dir = + { + .open = storage_ext_dir_open, + .close = storage_ext_dir_close, + .read = storage_ext_dir_read, + .rewind = storage_ext_dir_rewind, + }, + .common = + { + .stat = storage_ext_common_stat, + .mkdir = storage_ext_common_mkdir, + .remove = storage_ext_common_remove, + .fs_info = storage_ext_common_fs_info, + }, +}; void storage_ext_init(StorageData* storage) { SDData* sd_data = malloc(sizeof(SDData)); @@ -519,27 +560,7 @@ void storage_ext_init(StorageData* storage) { storage->data = sd_data; storage->api.tick = storage_ext_tick; - storage->fs_api.file.open = storage_ext_file_open; - storage->fs_api.file.close = storage_ext_file_close; - storage->fs_api.file.read = storage_ext_file_read; - storage->fs_api.file.write = storage_ext_file_write; - storage->fs_api.file.seek = storage_ext_file_seek; - storage->fs_api.file.tell = storage_ext_file_tell; - storage->fs_api.file.truncate = storage_ext_file_truncate; - storage->fs_api.file.size = storage_ext_file_size; - storage->fs_api.file.sync = storage_ext_file_sync; - storage->fs_api.file.eof = storage_ext_file_eof; - - storage->fs_api.dir.open = storage_ext_dir_open; - storage->fs_api.dir.close = storage_ext_dir_close; - storage->fs_api.dir.read = storage_ext_dir_read; - storage->fs_api.dir.rewind = storage_ext_dir_rewind; - - storage->fs_api.common.stat = storage_ext_common_stat; - storage->fs_api.common.mkdir = storage_ext_common_mkdir; - storage->fs_api.common.rename = storage_ext_common_rename; - storage->fs_api.common.remove = storage_ext_common_remove; - storage->fs_api.common.fs_info = storage_ext_common_fs_info; + storage->fs_api = &fs_api; hal_sd_detect_init(); diff --git a/applications/storage/storages/storage_int.c b/applications/storage/storages/storage_int.c index 811a1d6a..2f610929 100644 --- a/applications/storage/storages/storage_int.c +++ b/applications/storage/storages/storage_int.c @@ -636,13 +636,6 @@ static FS_Error storage_int_common_remove(void* ctx, const char* path) { return storage_int_parse_error(result); } -static FS_Error storage_int_common_rename(void* ctx, const char* old_path, const char* new_path) { - StorageData* storage = ctx; - lfs_t* lfs = lfs_get_from_storage(storage); - int result = lfs_rename(lfs, old_path, new_path); - return storage_int_parse_error(result); -} - static FS_Error storage_int_common_mkdir(void* ctx, const char* path) { StorageData* storage = ctx; lfs_t* lfs = lfs_get_from_storage(storage); @@ -671,6 +664,35 @@ static FS_Error storage_int_common_fs_info( } /******************* Init Storage *******************/ +static const FS_Api fs_api = { + .file = + { + .open = storage_int_file_open, + .close = storage_int_file_close, + .read = storage_int_file_read, + .write = storage_int_file_write, + .seek = storage_int_file_seek, + .tell = storage_int_file_tell, + .truncate = storage_int_file_truncate, + .size = storage_int_file_size, + .sync = storage_int_file_sync, + .eof = storage_int_file_eof, + }, + .dir = + { + .open = storage_int_dir_open, + .close = storage_int_dir_close, + .read = storage_int_dir_read, + .rewind = storage_int_dir_rewind, + }, + .common = + { + .stat = storage_int_common_stat, + .mkdir = storage_int_common_mkdir, + .remove = storage_int_common_remove, + .fs_info = storage_int_common_fs_info, + }, +}; void storage_int_init(StorageData* storage) { FURI_LOG_I(TAG, "Starting"); @@ -689,25 +711,5 @@ void storage_int_init(StorageData* storage) { storage->data = lfs_data; storage->api.tick = NULL; - storage->fs_api.file.open = storage_int_file_open; - storage->fs_api.file.close = storage_int_file_close; - storage->fs_api.file.read = storage_int_file_read; - storage->fs_api.file.write = storage_int_file_write; - storage->fs_api.file.seek = storage_int_file_seek; - storage->fs_api.file.tell = storage_int_file_tell; - storage->fs_api.file.truncate = storage_int_file_truncate; - storage->fs_api.file.size = storage_int_file_size; - storage->fs_api.file.sync = storage_int_file_sync; - storage->fs_api.file.eof = storage_int_file_eof; - - storage->fs_api.dir.open = storage_int_dir_open; - storage->fs_api.dir.close = storage_int_dir_close; - storage->fs_api.dir.read = storage_int_dir_read; - storage->fs_api.dir.rewind = storage_int_dir_rewind; - - storage->fs_api.common.stat = storage_int_common_stat; - storage->fs_api.common.mkdir = storage_int_common_mkdir; - storage->fs_api.common.rename = storage_int_common_rename; - storage->fs_api.common.remove = storage_int_common_remove; - storage->fs_api.common.fs_info = storage_int_common_fs_info; + storage->fs_api = &fs_api; } diff --git a/applications/subghz/helpers/subghz_custom_event.h b/applications/subghz/helpers/subghz_custom_event.h index e9890910..97920015 100644 --- a/applications/subghz/helpers/subghz_custom_event.h +++ b/applications/subghz/helpers/subghz_custom_event.h @@ -23,9 +23,9 @@ typedef enum { SubGhzCustomEventSceneExit, SubGhzCustomEventSceneStay, - SubGhzCustomEventViewReceverOK, - SubGhzCustomEventViewReceverConfig, - SubGhzCustomEventViewReceverBack, + SubGhzCustomEventViewReceiverOK, + SubGhzCustomEventViewReceiverConfig, + SubGhzCustomEventViewReceiverBack, SubGhzCustomEventViewReadRAWBack, SubGhzCustomEventViewReadRAWIDLE, diff --git a/applications/subghz/scenes/subghz_scene_delete.c b/applications/subghz/scenes/subghz_scene_delete.c index fa020648..dee16114 100644 --- a/applications/subghz/scenes/subghz_scene_delete.c +++ b/applications/subghz/scenes/subghz_scene_delete.c @@ -49,7 +49,7 @@ bool subghz_scene_delete_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDelete) { - strcpy(subghz->file_name_tmp, subghz->file_name); + strncpy(subghz->file_name_tmp, subghz->file_name, SUBGHZ_MAX_LEN_NAME); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_delete_raw.c b/applications/subghz/scenes/subghz_scene_delete_raw.c index 74ad75ce..df57926c 100644 --- a/applications/subghz/scenes/subghz_scene_delete_raw.c +++ b/applications/subghz/scenes/subghz_scene_delete_raw.c @@ -56,7 +56,7 @@ bool subghz_scene_delete_raw_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventSceneDeleteRAW) { - strcpy(subghz->file_name_tmp, subghz->file_name); + strncpy(subghz->file_name_tmp, subghz->file_name, SUBGHZ_MAX_LEN_NAME); if(subghz_delete_file(subghz)) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteSuccess); } else { diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 67e4df8f..d50d5626 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -24,7 +24,7 @@ bool subghz_scene_read_raw_update_filename(SubGhz* subghz) { } path_extract_filename_no_ext(string_get_cstr(temp_str), temp_str); - strcpy(subghz->file_name, string_get_cstr(temp_str)); + strncpy(subghz->file_name, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); ret = true; } while(false); diff --git a/applications/subghz/scenes/subghz_scene_receiver.c b/applications/subghz/scenes/subghz_scene_receiver.c index ac545e31..dc6b9968 100644 --- a/applications/subghz/scenes/subghz_scene_receiver.c +++ b/applications/subghz/scenes/subghz_scene_receiver.c @@ -111,7 +111,7 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case SubGhzCustomEventViewReceverBack: + case SubGhzCustomEventViewReceiverBack: // Stop CC1101 Rx subghz->state_notifications = SubGhzNotificationStateIDLE; @@ -134,13 +134,13 @@ bool subghz_scene_receiver_on_event(void* context, SceneManagerEvent event) { } return true; break; - case SubGhzCustomEventViewReceverOK: + case SubGhzCustomEventViewReceiverOK: subghz->txrx->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiverInfo); return true; break; - case SubGhzCustomEventViewReceverConfig: + case SubGhzCustomEventViewReceiverConfig: subghz->state_notifications = SubGhzNotificationStateIDLE; subghz->txrx->idx_menu_chosen = subghz_view_receiver_get_idx_menu(subghz->subghz_receiver); diff --git a/applications/subghz/scenes/subghz_scene_save_name.c b/applications/subghz/scenes/subghz_scene_save_name.c index 6f552ff9..539eb166 100644 --- a/applications/subghz/scenes/subghz_scene_save_name.c +++ b/applications/subghz/scenes/subghz_scene_save_name.c @@ -22,10 +22,10 @@ void subghz_scene_save_name_on_enter(void* context) { //highlighting the entire filename by default dev_name_empty = true; } else { - strcpy(subghz->file_name_tmp, subghz->file_name); + strncpy(subghz->file_name_tmp, subghz->file_name, SUBGHZ_MAX_LEN_NAME); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerNoSet) { - subghz_get_next_name_file(subghz); + subghz_get_next_name_file(subghz, SUBGHZ_MAX_LEN_NAME); if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) == SubGhzCustomEventManagerSetRAW) { dev_name_empty = true; @@ -39,11 +39,13 @@ void subghz_scene_save_name_on_enter(void* context) { subghz_scene_save_name_text_input_callback, subghz, subghz->file_name, - 22, //Max len name + SUBGHZ_MAX_LEN_NAME + 1, // buffer size dev_name_empty); - ValidatorIsFile* validator_is_file = - validator_is_file_alloc_init(SUBGHZ_APP_FOLDER, SUBGHZ_APP_EXTENSION); + ValidatorIsFile* validator_is_file = validator_is_file_alloc_init( + SUBGHZ_APP_FOLDER, + SUBGHZ_APP_EXTENSION, + (dev_name_empty) ? (NULL) : (subghz->file_name_tmp)); text_input_set_validator(text_input, validator_is_file_callback, validator_is_file); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTextInput); @@ -52,7 +54,7 @@ void subghz_scene_save_name_on_enter(void* context) { bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeBack) { - strcpy(subghz->file_name, subghz->file_name_tmp); + strncpy(subghz->file_name, subghz->file_name_tmp, SUBGHZ_MAX_LEN_NAME); scene_manager_previous_scene(subghz->scene_manager); return true; } else if(event.type == SceneManagerEventTypeCustom) { diff --git a/applications/subghz/subghz.c b/applications/subghz/subghz.c index 0fb2f6d9..3320db48 100644 --- a/applications/subghz/subghz.c +++ b/applications/subghz/subghz.c @@ -296,6 +296,10 @@ void subghz_free(SubGhz* subghz) { furi_record_close("notification"); subghz->notifications = NULL; + // About birds + furi_assert(subghz->file_name[SUBGHZ_MAX_LEN_NAME] == 0); + furi_assert(subghz->file_name_tmp[SUBGHZ_MAX_LEN_NAME] == 0); + // The rest free(subghz); } @@ -309,20 +313,26 @@ int32_t subghz_app(void* p) { subghz_environment_load_keystore( subghz->txrx->environment, "/ext/subghz/assets/keeloq_mfcodes_user"); // Check argument and run corresponding scene - if(p && subghz_key_load(subghz, p)) { - string_t filename; - string_init(filename); + if(p) { + if(subghz_key_load(subghz, p)) { + string_t filename; + string_init(filename); - path_extract_filename_no_ext(p, filename); - strcpy(subghz->file_name, string_get_cstr(filename)); - string_clear(filename); - if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { - //Load Raw TX - subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); + path_extract_filename_no_ext(p, filename); + strncpy(subghz->file_name, string_get_cstr(filename), SUBGHZ_MAX_LEN_NAME); + string_clear(filename); + if((!strcmp(subghz->txrx->decoder_result->protocol->name, "RAW"))) { + //Load Raw TX + subghz->txrx->rx_key_state = SubGhzRxKeyStateRAWLoad; + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW); + } else { + //Load transmitter TX + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter); + } } else { - //Load transmitter TX - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter); + //exit app + scene_manager_stop(subghz->scene_manager); + view_dispatcher_stop(subghz->view_dispatcher); } } else { if(load_database) { diff --git a/applications/subghz/subghz_i.c b/applications/subghz/subghz_i.c old mode 100755 new mode 100644 index 56ce7e97..80d7a52c --- a/applications/subghz/subghz_i.c +++ b/applications/subghz/subghz_i.c @@ -197,6 +197,22 @@ void subghz_tx_stop(SubGhz* subghz) { notification_message(subghz->notifications, &sequence_reset_red); } +void subghz_dialog_message_show_only_rx(SubGhz* subghz) { + DialogsApp* dialogs = subghz->dialogs; + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_text( + message, + "This frequency can\nonly be used for RX\nin your region", + 38, + 23, + AlignCenter, + AlignCenter); + dialog_message_set_icon(message, &I_DolphinFirstStart7_61x51, 67, 12); + dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_show(dialogs, message); + dialog_message_free(message); +} + bool subghz_key_load(SubGhz* subghz, const char* file_path) { furi_assert(subghz); furi_assert(file_path); @@ -205,10 +221,11 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); Stream* fff_data_stream = flipper_format_get_raw_stream(subghz->txrx->fff_data); + uint8_t err = 1; bool loaded = false; string_t temp_str; string_init(temp_str); - uint32_t version; + uint32_t temp_data32; do { stream_clean(fff_data_stream); @@ -217,25 +234,36 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { break; } - if(!flipper_format_read_header(fff_data_file, temp_str, &version)) { + if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) { FURI_LOG_E(TAG, "Missing or incorrect header"); break; } if(((!strcmp(string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) || (!strcmp(string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) && - version == SUBGHZ_KEY_FILE_VERSION) { + temp_data32 == SUBGHZ_KEY_FILE_VERSION) { } else { FURI_LOG_E(TAG, "Type or version mismatch"); break; } - if(!flipper_format_read_uint32( - fff_data_file, "Frequency", (uint32_t*)&subghz->txrx->frequency, 1)) { + if(!flipper_format_read_uint32(fff_data_file, "Frequency", (uint32_t*)&temp_data32, 1)) { FURI_LOG_E(TAG, "Missing Frequency"); break; } + if(!furi_hal_subghz_is_frequency_valid(temp_data32)) { + FURI_LOG_E(TAG, "Frequency not supported"); + break; + } + + if(!furi_hal_subghz_is_tx_allowed(temp_data32)) { + FURI_LOG_E(TAG, "This frequency can only be used for RX in your region"); + err = 2; + break; + } + subghz->txrx->frequency = temp_data32; + if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) { FURI_LOG_E(TAG, "Missing Preset"); break; @@ -267,24 +295,38 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path) { if(subghz->txrx->decoder_result) { subghz_protocol_decoder_base_deserialize( subghz->txrx->decoder_result, subghz->txrx->fff_data); + } else { + FURI_LOG_E(TAG, "Protocol not found"); + break; } loaded = true; } while(0); if(!loaded) { - dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); + switch(err) { + case 1: + dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile"); + break; + + case 2: + subghz_dialog_message_show_only_rx(subghz); + break; + + default: + furi_crash(NULL); + break; + } } string_clear(temp_str); - //string_clear(path); flipper_format_free(fff_data_file); furi_record_close("storage"); return loaded; } -bool subghz_get_next_name_file(SubGhz* subghz) { +bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len) { furi_assert(subghz); Storage* storage = furi_record_open("storage"); @@ -295,9 +337,9 @@ bool subghz_get_next_name_file(SubGhz* subghz) { if(strcmp(subghz->file_name, "")) { //get the name of the next free file storage_get_next_filename( - storage, SUBGHZ_RAW_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION, temp_str); + storage, SUBGHZ_RAW_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION, temp_str, max_len); - strcpy(subghz->file_name, string_get_cstr(temp_str)); + strncpy(subghz->file_name, string_get_cstr(temp_str), SUBGHZ_MAX_LEN_NAME); res = true; } @@ -393,12 +435,14 @@ bool subghz_rename_file(SubGhz* subghz) { string_init_printf( new_path, "%s/%s%s", SUBGHZ_APP_FOLDER, subghz->file_name, SUBGHZ_APP_EXTENSION); - FS_Error fs_result = - storage_common_rename(storage, string_get_cstr(old_path), string_get_cstr(new_path)); + if(string_cmp(old_path, new_path) != 0) { + FS_Error fs_result = + storage_common_rename(storage, string_get_cstr(old_path), string_get_cstr(new_path)); - if(fs_result != FSE_OK) { - dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); - ret = false; + if(fs_result != FSE_OK) { + dialog_message_show_storage_error(subghz->dialogs, "Cannot rename\n file/directory"); + ret = false; + } } string_clear(old_path); diff --git a/applications/subghz/subghz_i.h b/applications/subghz/subghz_i.h index efb3eafa..bfa80df3 100644 --- a/applications/subghz/subghz_i.h +++ b/applications/subghz/subghz_i.h @@ -33,7 +33,7 @@ #include -#define SUBGHZ_TEXT_STORE_SIZE 40 +#define SUBGHZ_MAX_LEN_NAME 21 extern const char* const subghz_frequencies_text[]; extern const uint32_t subghz_frequencies[]; @@ -115,8 +115,8 @@ struct SubGhz { TextInput* text_input; Widget* widget; DialogsApp* dialogs; - char file_name[SUBGHZ_TEXT_STORE_SIZE + 1]; - char file_name_tmp[SUBGHZ_TEXT_STORE_SIZE + 1]; + char file_name[SUBGHZ_MAX_LEN_NAME + 1]; + char file_name_tmp[SUBGHZ_MAX_LEN_NAME + 1]; SubGhzNotificationState state_notifications; SubGhzViewReceiver* subghz_receiver; @@ -155,8 +155,9 @@ void subghz_rx_end(SubGhz* subghz); void subghz_sleep(SubGhz* subghz); bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format); void subghz_tx_stop(SubGhz* subghz); +void subghz_dialog_message_show_only_rx(SubGhz* subghz); bool subghz_key_load(SubGhz* subghz, const char* file_path); -bool subghz_get_next_name_file(SubGhz* subghz); +bool subghz_get_next_name_file(SubGhz* subghz, uint8_t max_len); bool subghz_save_protocol_to_file( SubGhz* subghz, FlipperFormat* flipper_format, diff --git a/applications/subghz/views/receiver.c b/applications/subghz/views/receiver.c index 20ee37d3..03ba5bd2 100644 --- a/applications/subghz/views/receiver.c +++ b/applications/subghz/views/receiver.c @@ -181,7 +181,7 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { SubGhzViewReceiver* subghz_receiver = context; if(event->key == InputKeyBack && event->type == InputTypeShort) { - subghz_receiver->callback(SubGhzCustomEventViewReceverBack, subghz_receiver->context); + subghz_receiver->callback(SubGhzCustomEventViewReceiverBack, subghz_receiver->context); } else if( event->key == InputKeyUp && (event->type == InputTypeShort || event->type == InputTypeRepeat)) { @@ -199,13 +199,13 @@ bool subghz_view_receiver_input(InputEvent* event, void* context) { return true; }); } else if(event->key == InputKeyLeft && event->type == InputTypeShort) { - subghz_receiver->callback(SubGhzCustomEventViewReceverConfig, subghz_receiver->context); + subghz_receiver->callback(SubGhzCustomEventViewReceiverConfig, subghz_receiver->context); } else if(event->key == InputKeyOk && event->type == InputTypeShort) { with_view_model( subghz_receiver->view, (SubGhzViewReceiverModel * model) { if(model->history_item != 0) { subghz_receiver->callback( - SubGhzCustomEventViewReceverOK, subghz_receiver->context); + SubGhzCustomEventViewReceiverOK, subghz_receiver->context); } return false; }); diff --git a/applications/tests/storage/storage_test.c b/applications/tests/storage/storage_test.c new file mode 100644 index 00000000..3fccc68f --- /dev/null +++ b/applications/tests/storage/storage_test.c @@ -0,0 +1,171 @@ +#include "../minunit.h" +#include +#include +#include + +#define STORAGE_LOCKED_FILE "/ext/locked_file.test" +#define STORAGE_LOCKED_DIR "/int" + +static void storage_file_open_lock_setup() { + Storage* storage = furi_record_open("storage"); + File* file = storage_file_alloc(storage); + storage_simply_remove(storage, STORAGE_LOCKED_FILE); + mu_check(storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_WRITE, FSOM_CREATE_NEW)); + mu_check(storage_file_write(file, "0123", 4) == 4); + mu_check(storage_file_close(file)); + storage_file_free(file); + furi_record_close("storage"); +} + +static void storage_file_open_lock_teardown() { + Storage* storage = furi_record_open("storage"); + mu_check(storage_simply_remove(storage, STORAGE_LOCKED_FILE)); + furi_record_close("storage"); +} + +static int32_t storage_file_locker(void* ctx) { + Storage* storage = furi_record_open("storage"); + osSemaphoreId_t semaphore = ctx; + File* file = storage_file_alloc(storage); + furi_check(storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)); + osSemaphoreRelease(semaphore); + furi_hal_delay_ms(1000); + + furi_check(storage_file_close(file)); + furi_record_close("storage"); + storage_file_free(file); + return 0; +} + +MU_TEST(storage_file_open_lock) { + Storage* storage = furi_record_open("storage"); + bool result = false; + osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); + File* file = storage_file_alloc(storage); + + // file_locker thread start + FuriThread* locker_thread = furi_thread_alloc(); + furi_thread_set_name(locker_thread, "StorageFileLocker"); + furi_thread_set_stack_size(locker_thread, 2048); + furi_thread_set_context(locker_thread, semaphore); + furi_thread_set_callback(locker_thread, storage_file_locker); + mu_check(furi_thread_start(locker_thread)); + + // wait for file lock + osSemaphoreAcquire(semaphore, osWaitForever); + osSemaphoreDelete(semaphore); + + result = storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_READ_WRITE, FSOM_OPEN_EXISTING); + storage_file_close(file); + + // file_locker thread stop + mu_check(furi_thread_join(locker_thread) == osOK); + furi_thread_free(locker_thread); + + // clean data + storage_file_free(file); + furi_record_close("storage"); + + mu_assert(result, "cannot open locked file"); +} + +MU_TEST(storage_file_open_close) { + Storage* storage = furi_record_open("storage"); + File* file; + + file = storage_file_alloc(storage); + mu_check(storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)); + storage_file_close(file); + storage_file_free(file); + + for(size_t i = 0; i < 10; i++) { + file = storage_file_alloc(storage); + mu_check( + storage_file_open(file, STORAGE_LOCKED_FILE, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)); + storage_file_free(file); + } + + furi_record_close("storage"); +} + +MU_TEST_SUITE(storage_file) { + storage_file_open_lock_setup(); + MU_RUN_TEST(storage_file_open_close); + MU_RUN_TEST(storage_file_open_lock); + storage_file_open_lock_teardown(); +} + +MU_TEST(storage_dir_open_close) { + Storage* storage = furi_record_open("storage"); + File* file; + + file = storage_file_alloc(storage); + mu_check(storage_dir_open(file, STORAGE_LOCKED_DIR)); + storage_dir_close(file); + storage_file_free(file); + + for(size_t i = 0; i < 10; i++) { + file = storage_file_alloc(storage); + mu_check(storage_dir_open(file, STORAGE_LOCKED_DIR)); + storage_file_free(file); + } + + furi_record_close("storage"); +} + +static int32_t storage_dir_locker(void* ctx) { + Storage* storage = furi_record_open("storage"); + osSemaphoreId_t semaphore = ctx; + File* file = storage_file_alloc(storage); + furi_check(storage_dir_open(file, STORAGE_LOCKED_DIR)); + osSemaphoreRelease(semaphore); + furi_hal_delay_ms(1000); + + furi_check(storage_dir_close(file)); + furi_record_close("storage"); + storage_file_free(file); + return 0; +} + +MU_TEST(storage_dir_open_lock) { + Storage* storage = furi_record_open("storage"); + bool result = false; + osSemaphoreId_t semaphore = osSemaphoreNew(1, 0, NULL); + File* file = storage_file_alloc(storage); + + // file_locker thread start + FuriThread* locker_thread = furi_thread_alloc(); + furi_thread_set_name(locker_thread, "StorageDirLocker"); + furi_thread_set_stack_size(locker_thread, 2048); + furi_thread_set_context(locker_thread, semaphore); + furi_thread_set_callback(locker_thread, storage_dir_locker); + mu_check(furi_thread_start(locker_thread)); + + // wait for dir lock + osSemaphoreAcquire(semaphore, osWaitForever); + osSemaphoreDelete(semaphore); + + result = storage_dir_open(file, STORAGE_LOCKED_DIR); + storage_dir_close(file); + + // file_locker thread stop + mu_check(furi_thread_join(locker_thread) == osOK); + furi_thread_free(locker_thread); + + // clean data + storage_file_free(file); + furi_record_close("storage"); + + mu_assert(result, "cannot open locked dir"); +} + +MU_TEST_SUITE(storage_dir) { + MU_RUN_TEST(storage_dir_open_close); + MU_RUN_TEST(storage_dir_open_lock); +} + +int run_minunit_test_storage() { + MU_RUN_SUITE(storage_file); + MU_RUN_SUITE(storage_dir); + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/tests/test_index.c b/applications/tests/test_index.c index 391f759e..16e75414 100644 --- a/applications/tests/test_index.c +++ b/applications/tests/test_index.c @@ -16,6 +16,7 @@ int run_minunit_test_rpc(); int run_minunit_test_flipper_format(); int run_minunit_test_flipper_format_string(); int run_minunit_test_stream(); +int run_minunit_test_storage(); void minunit_print_progress(void) { static char progress[] = {'\\', '|', '/', '-'}; @@ -53,11 +54,12 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) { uint32_t cycle_counter = DWT->CYCCNT; test_result |= run_minunit(); - test_result |= run_minunit_test_infrared_decoder_encoder(); - test_result |= run_minunit_test_rpc(); + test_result |= run_minunit_test_storage(); test_result |= run_minunit_test_stream(); test_result |= run_minunit_test_flipper_format(); test_result |= run_minunit_test_flipper_format_string(); + test_result |= run_minunit_test_infrared_decoder_encoder(); + test_result |= run_minunit_test_rpc(); cycle_counter = (DWT->CYCCNT - cycle_counter); FURI_LOG_I(TAG, "Consumed: %0.2fs", (float)cycle_counter / (SystemCoreClock)); diff --git a/assets/compiled/flipper.pb.h b/assets/compiled/flipper.pb.h index 5f283889..88548bea 100644 --- a/assets/compiled/flipper.pb.h +++ b/assets/compiled/flipper.pb.h @@ -98,6 +98,11 @@ typedef struct _PB_Main { PB_System_PlayAudiovisualAlertRequest system_play_audiovisual_alert_request; PB_System_ProtobufVersionRequest system_protobuf_version_request; PB_System_ProtobufVersionResponse system_protobuf_version_response; + PB_System_UpdateRequest system_update_request; + PB_Storage_BackupCreateRequest storage_backup_create_request; + PB_Storage_BackupRestoreRequest storage_backup_restore_request; + PB_System_PowerInfoRequest system_power_info_request; + PB_System_PowerInfoResponse system_power_info_response; } content; } PB_Main; @@ -161,6 +166,11 @@ extern "C" { #define PB_Main_system_play_audiovisual_alert_request_tag 38 #define PB_Main_system_protobuf_version_request_tag 39 #define PB_Main_system_protobuf_version_response_tag 40 +#define PB_Main_system_update_request_tag 41 +#define PB_Main_storage_backup_create_request_tag 42 +#define PB_Main_storage_backup_restore_request_tag 43 +#define PB_Main_system_power_info_request_tag 44 +#define PB_Main_system_power_info_response_tag 45 /* Struct field encoding specification for nanopb */ #define PB_Empty_FIELDLIST(X, a) \ @@ -213,7 +223,12 @@ X(a, STATIC, ONEOF, MSG_W_CB, (content,system_get_datetime_response,content X(a, STATIC, ONEOF, MSG_W_CB, (content,system_set_datetime_request,content.system_set_datetime_request), 37) \ X(a, STATIC, ONEOF, MSG_W_CB, (content,system_play_audiovisual_alert_request,content.system_play_audiovisual_alert_request), 38) \ X(a, STATIC, ONEOF, MSG_W_CB, (content,system_protobuf_version_request,content.system_protobuf_version_request), 39) \ -X(a, STATIC, ONEOF, MSG_W_CB, (content,system_protobuf_version_response,content.system_protobuf_version_response), 40) +X(a, STATIC, ONEOF, MSG_W_CB, (content,system_protobuf_version_response,content.system_protobuf_version_response), 40) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,system_update_request,content.system_update_request), 41) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_backup_create_request,content.storage_backup_create_request), 42) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,storage_backup_restore_request,content.storage_backup_restore_request), 43) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,system_power_info_request,content.system_power_info_request), 44) \ +X(a, STATIC, ONEOF, MSG_W_CB, (content,system_power_info_response,content.system_power_info_response), 45) #define PB_Main_CALLBACK NULL #define PB_Main_DEFAULT NULL #define PB_Main_content_empty_MSGTYPE PB_Empty @@ -253,6 +268,11 @@ X(a, STATIC, ONEOF, MSG_W_CB, (content,system_protobuf_version_response,con #define PB_Main_content_system_play_audiovisual_alert_request_MSGTYPE PB_System_PlayAudiovisualAlertRequest #define PB_Main_content_system_protobuf_version_request_MSGTYPE PB_System_ProtobufVersionRequest #define PB_Main_content_system_protobuf_version_response_MSGTYPE PB_System_ProtobufVersionResponse +#define PB_Main_content_system_update_request_MSGTYPE PB_System_UpdateRequest +#define PB_Main_content_storage_backup_create_request_MSGTYPE PB_Storage_BackupCreateRequest +#define PB_Main_content_storage_backup_restore_request_MSGTYPE PB_Storage_BackupRestoreRequest +#define PB_Main_content_system_power_info_request_MSGTYPE PB_System_PowerInfoRequest +#define PB_Main_content_system_power_info_response_MSGTYPE PB_System_PowerInfoResponse extern const pb_msgdesc_t PB_Empty_msg; extern const pb_msgdesc_t PB_StopSession_msg; @@ -266,9 +286,9 @@ extern const pb_msgdesc_t PB_Main_msg; /* Maximum encoded size of messages (where known) */ #define PB_Empty_size 0 #define PB_StopSession_size 0 -#if defined(PB_System_PingRequest_size) && defined(PB_System_PingResponse_size) && defined(PB_Storage_ListRequest_size) && defined(PB_Storage_ListResponse_size) && defined(PB_Storage_ReadRequest_size) && defined(PB_Storage_ReadResponse_size) && defined(PB_Storage_WriteRequest_size) && defined(PB_Storage_DeleteRequest_size) && defined(PB_Storage_MkdirRequest_size) && defined(PB_Storage_Md5sumRequest_size) && defined(PB_App_StartRequest_size) && defined(PB_Gui_ScreenFrame_size) && defined(PB_Storage_StatRequest_size) && defined(PB_Storage_StatResponse_size) && defined(PB_Gui_StartVirtualDisplayRequest_size) && defined(PB_Storage_InfoRequest_size) && defined(PB_Storage_RenameRequest_size) && defined(PB_System_DeviceInfoResponse_size) +#if defined(PB_System_PingRequest_size) && defined(PB_System_PingResponse_size) && defined(PB_Storage_ListRequest_size) && defined(PB_Storage_ListResponse_size) && defined(PB_Storage_ReadRequest_size) && defined(PB_Storage_ReadResponse_size) && defined(PB_Storage_WriteRequest_size) && defined(PB_Storage_DeleteRequest_size) && defined(PB_Storage_MkdirRequest_size) && defined(PB_Storage_Md5sumRequest_size) && defined(PB_App_StartRequest_size) && defined(PB_Gui_ScreenFrame_size) && defined(PB_Storage_StatRequest_size) && defined(PB_Storage_StatResponse_size) && defined(PB_Gui_StartVirtualDisplayRequest_size) && defined(PB_Storage_InfoRequest_size) && defined(PB_Storage_RenameRequest_size) && defined(PB_System_DeviceInfoResponse_size) && defined(PB_System_UpdateRequest_size) && defined(PB_Storage_BackupCreateRequest_size) && defined(PB_Storage_BackupRestoreRequest_size) && defined(PB_System_PowerInfoResponse_size) #define PB_Main_size (10 + sizeof(union PB_Main_content_size_union)) -union PB_Main_content_size_union {char f5[(6 + PB_System_PingRequest_size)]; char f6[(6 + PB_System_PingResponse_size)]; char f7[(6 + PB_Storage_ListRequest_size)]; char f8[(6 + PB_Storage_ListResponse_size)]; char f9[(6 + PB_Storage_ReadRequest_size)]; char f10[(6 + PB_Storage_ReadResponse_size)]; char f11[(6 + PB_Storage_WriteRequest_size)]; char f12[(6 + PB_Storage_DeleteRequest_size)]; char f13[(6 + PB_Storage_MkdirRequest_size)]; char f14[(6 + PB_Storage_Md5sumRequest_size)]; char f16[(7 + PB_App_StartRequest_size)]; char f22[(7 + PB_Gui_ScreenFrame_size)]; char f24[(7 + PB_Storage_StatRequest_size)]; char f25[(7 + PB_Storage_StatResponse_size)]; char f26[(7 + PB_Gui_StartVirtualDisplayRequest_size)]; char f28[(7 + PB_Storage_InfoRequest_size)]; char f30[(7 + PB_Storage_RenameRequest_size)]; char f33[(7 + PB_System_DeviceInfoResponse_size)]; char f0[36];}; +union PB_Main_content_size_union {char f5[(6 + PB_System_PingRequest_size)]; char f6[(6 + PB_System_PingResponse_size)]; char f7[(6 + PB_Storage_ListRequest_size)]; char f8[(6 + PB_Storage_ListResponse_size)]; char f9[(6 + PB_Storage_ReadRequest_size)]; char f10[(6 + PB_Storage_ReadResponse_size)]; char f11[(6 + PB_Storage_WriteRequest_size)]; char f12[(6 + PB_Storage_DeleteRequest_size)]; char f13[(6 + PB_Storage_MkdirRequest_size)]; char f14[(6 + PB_Storage_Md5sumRequest_size)]; char f16[(7 + PB_App_StartRequest_size)]; char f22[(7 + PB_Gui_ScreenFrame_size)]; char f24[(7 + PB_Storage_StatRequest_size)]; char f25[(7 + PB_Storage_StatResponse_size)]; char f26[(7 + PB_Gui_StartVirtualDisplayRequest_size)]; char f28[(7 + PB_Storage_InfoRequest_size)]; char f30[(7 + PB_Storage_RenameRequest_size)]; char f33[(7 + PB_System_DeviceInfoResponse_size)]; char f41[(7 + PB_System_UpdateRequest_size)]; char f42[(7 + PB_Storage_BackupCreateRequest_size)]; char f43[(7 + PB_Storage_BackupRestoreRequest_size)]; char f45[(7 + PB_System_PowerInfoResponse_size)]; char f0[36];}; #endif #ifdef __cplusplus diff --git a/assets/compiled/protobuf_version.h b/assets/compiled/protobuf_version.h index 19d3f1ea..53e0b73b 100644 --- a/assets/compiled/protobuf_version.h +++ b/assets/compiled/protobuf_version.h @@ -1,3 +1,3 @@ #pragma once #define PROTOBUF_MAJOR_VERSION 0 -#define PROTOBUF_MINOR_VERSION 3 +#define PROTOBUF_MINOR_VERSION 5 diff --git a/assets/compiled/storage.pb.c b/assets/compiled/storage.pb.c index 9db544cb..244b273d 100644 --- a/assets/compiled/storage.pb.c +++ b/assets/compiled/storage.pb.c @@ -51,5 +51,11 @@ PB_BIND(PB_Storage_Md5sumResponse, PB_Storage_Md5sumResponse, AUTO) PB_BIND(PB_Storage_RenameRequest, PB_Storage_RenameRequest, AUTO) +PB_BIND(PB_Storage_BackupCreateRequest, PB_Storage_BackupCreateRequest, AUTO) + + +PB_BIND(PB_Storage_BackupRestoreRequest, PB_Storage_BackupRestoreRequest, AUTO) + + diff --git a/assets/compiled/storage.pb.h b/assets/compiled/storage.pb.h index 8a86c937..9b31dc31 100644 --- a/assets/compiled/storage.pb.h +++ b/assets/compiled/storage.pb.h @@ -16,6 +16,14 @@ typedef enum _PB_Storage_File_FileType { } PB_Storage_File_FileType; /* Struct definitions */ +typedef struct _PB_Storage_BackupCreateRequest { + char *archive_path; +} PB_Storage_BackupCreateRequest; + +typedef struct _PB_Storage_BackupRestoreRequest { + char *archive_path; +} PB_Storage_BackupRestoreRequest; + typedef struct _PB_Storage_InfoRequest { char *path; } PB_Storage_InfoRequest; @@ -114,6 +122,8 @@ extern "C" { #define PB_Storage_Md5sumRequest_init_default {NULL} #define PB_Storage_Md5sumResponse_init_default {""} #define PB_Storage_RenameRequest_init_default {NULL, NULL} +#define PB_Storage_BackupCreateRequest_init_default {NULL} +#define PB_Storage_BackupRestoreRequest_init_default {NULL} #define PB_Storage_File_init_zero {_PB_Storage_File_FileType_MIN, NULL, 0, NULL} #define PB_Storage_InfoRequest_init_zero {NULL} #define PB_Storage_InfoResponse_init_zero {0, 0} @@ -129,8 +139,12 @@ extern "C" { #define PB_Storage_Md5sumRequest_init_zero {NULL} #define PB_Storage_Md5sumResponse_init_zero {""} #define PB_Storage_RenameRequest_init_zero {NULL, NULL} +#define PB_Storage_BackupCreateRequest_init_zero {NULL} +#define PB_Storage_BackupRestoreRequest_init_zero {NULL} /* Field tags (for use in manual encoding/decoding) */ +#define PB_Storage_BackupCreateRequest_archive_path_tag 1 +#define PB_Storage_BackupRestoreRequest_archive_path_tag 1 #define PB_Storage_InfoRequest_path_tag 1 #define PB_Storage_ListRequest_path_tag 1 #define PB_Storage_Md5sumRequest_path_tag 1 @@ -241,6 +255,16 @@ X(a, POINTER, SINGULAR, STRING, new_path, 2) #define PB_Storage_RenameRequest_CALLBACK NULL #define PB_Storage_RenameRequest_DEFAULT NULL +#define PB_Storage_BackupCreateRequest_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, STRING, archive_path, 1) +#define PB_Storage_BackupCreateRequest_CALLBACK NULL +#define PB_Storage_BackupCreateRequest_DEFAULT NULL + +#define PB_Storage_BackupRestoreRequest_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, STRING, archive_path, 1) +#define PB_Storage_BackupRestoreRequest_CALLBACK NULL +#define PB_Storage_BackupRestoreRequest_DEFAULT NULL + extern const pb_msgdesc_t PB_Storage_File_msg; extern const pb_msgdesc_t PB_Storage_InfoRequest_msg; extern const pb_msgdesc_t PB_Storage_InfoResponse_msg; @@ -256,6 +280,8 @@ extern const pb_msgdesc_t PB_Storage_MkdirRequest_msg; extern const pb_msgdesc_t PB_Storage_Md5sumRequest_msg; extern const pb_msgdesc_t PB_Storage_Md5sumResponse_msg; extern const pb_msgdesc_t PB_Storage_RenameRequest_msg; +extern const pb_msgdesc_t PB_Storage_BackupCreateRequest_msg; +extern const pb_msgdesc_t PB_Storage_BackupRestoreRequest_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define PB_Storage_File_fields &PB_Storage_File_msg @@ -273,6 +299,8 @@ extern const pb_msgdesc_t PB_Storage_RenameRequest_msg; #define PB_Storage_Md5sumRequest_fields &PB_Storage_Md5sumRequest_msg #define PB_Storage_Md5sumResponse_fields &PB_Storage_Md5sumResponse_msg #define PB_Storage_RenameRequest_fields &PB_Storage_RenameRequest_msg +#define PB_Storage_BackupCreateRequest_fields &PB_Storage_BackupCreateRequest_msg +#define PB_Storage_BackupRestoreRequest_fields &PB_Storage_BackupRestoreRequest_msg /* Maximum encoded size of messages (where known) */ /* PB_Storage_File_size depends on runtime parameters */ @@ -288,6 +316,8 @@ extern const pb_msgdesc_t PB_Storage_RenameRequest_msg; /* PB_Storage_MkdirRequest_size depends on runtime parameters */ /* PB_Storage_Md5sumRequest_size depends on runtime parameters */ /* PB_Storage_RenameRequest_size depends on runtime parameters */ +/* PB_Storage_BackupCreateRequest_size depends on runtime parameters */ +/* PB_Storage_BackupRestoreRequest_size depends on runtime parameters */ #define PB_Storage_InfoResponse_size 22 #define PB_Storage_Md5sumResponse_size 34 diff --git a/assets/compiled/system.pb.c b/assets/compiled/system.pb.c index 8f2a37ae..45ab3a01 100644 --- a/assets/compiled/system.pb.c +++ b/assets/compiled/system.pb.c @@ -45,5 +45,14 @@ PB_BIND(PB_System_ProtobufVersionRequest, PB_System_ProtobufVersionRequest, AUTO PB_BIND(PB_System_ProtobufVersionResponse, PB_System_ProtobufVersionResponse, AUTO) +PB_BIND(PB_System_UpdateRequest, PB_System_UpdateRequest, AUTO) + + +PB_BIND(PB_System_PowerInfoRequest, PB_System_PowerInfoRequest, AUTO) + + +PB_BIND(PB_System_PowerInfoResponse, PB_System_PowerInfoResponse, AUTO) + + diff --git a/assets/compiled/system.pb.h b/assets/compiled/system.pb.h index 93c32b22..0932c6fb 100644 --- a/assets/compiled/system.pb.h +++ b/assets/compiled/system.pb.h @@ -45,10 +45,23 @@ typedef struct _PB_System_PlayAudiovisualAlertRequest { char dummy_field; } PB_System_PlayAudiovisualAlertRequest; +typedef struct _PB_System_PowerInfoRequest { + char dummy_field; +} PB_System_PowerInfoRequest; + +typedef struct _PB_System_PowerInfoResponse { + char *key; + char *value; +} PB_System_PowerInfoResponse; + typedef struct _PB_System_ProtobufVersionRequest { char dummy_field; } PB_System_ProtobufVersionRequest; +typedef struct _PB_System_UpdateRequest { + char *update_folder; +} PB_System_UpdateRequest; + typedef struct _PB_System_DateTime { /* Time */ uint8_t hour; /* *< Hour in 24H format: 0-23 */ @@ -105,6 +118,9 @@ extern "C" { #define PB_System_PlayAudiovisualAlertRequest_init_default {0} #define PB_System_ProtobufVersionRequest_init_default {0} #define PB_System_ProtobufVersionResponse_init_default {0, 0} +#define PB_System_UpdateRequest_init_default {NULL} +#define PB_System_PowerInfoRequest_init_default {0} +#define PB_System_PowerInfoResponse_init_default {NULL, NULL} #define PB_System_PingRequest_init_zero {NULL} #define PB_System_PingResponse_init_zero {NULL} #define PB_System_RebootRequest_init_zero {_PB_System_RebootRequest_RebootMode_MIN} @@ -118,12 +134,18 @@ extern "C" { #define PB_System_PlayAudiovisualAlertRequest_init_zero {0} #define PB_System_ProtobufVersionRequest_init_zero {0} #define PB_System_ProtobufVersionResponse_init_zero {0, 0} +#define PB_System_UpdateRequest_init_zero {NULL} +#define PB_System_PowerInfoRequest_init_zero {0} +#define PB_System_PowerInfoResponse_init_zero {NULL, NULL} /* Field tags (for use in manual encoding/decoding) */ #define PB_System_DeviceInfoResponse_key_tag 1 #define PB_System_DeviceInfoResponse_value_tag 2 #define PB_System_PingRequest_data_tag 1 #define PB_System_PingResponse_data_tag 1 +#define PB_System_PowerInfoResponse_key_tag 1 +#define PB_System_PowerInfoResponse_value_tag 2 +#define PB_System_UpdateRequest_update_folder_tag 1 #define PB_System_DateTime_hour_tag 1 #define PB_System_DateTime_minute_tag 2 #define PB_System_DateTime_second_tag 3 @@ -213,6 +235,22 @@ X(a, STATIC, SINGULAR, UINT32, minor, 2) #define PB_System_ProtobufVersionResponse_CALLBACK NULL #define PB_System_ProtobufVersionResponse_DEFAULT NULL +#define PB_System_UpdateRequest_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, STRING, update_folder, 1) +#define PB_System_UpdateRequest_CALLBACK NULL +#define PB_System_UpdateRequest_DEFAULT NULL + +#define PB_System_PowerInfoRequest_FIELDLIST(X, a) \ + +#define PB_System_PowerInfoRequest_CALLBACK NULL +#define PB_System_PowerInfoRequest_DEFAULT NULL + +#define PB_System_PowerInfoResponse_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, STRING, key, 1) \ +X(a, POINTER, SINGULAR, STRING, value, 2) +#define PB_System_PowerInfoResponse_CALLBACK NULL +#define PB_System_PowerInfoResponse_DEFAULT NULL + extern const pb_msgdesc_t PB_System_PingRequest_msg; extern const pb_msgdesc_t PB_System_PingResponse_msg; extern const pb_msgdesc_t PB_System_RebootRequest_msg; @@ -226,6 +264,9 @@ extern const pb_msgdesc_t PB_System_DateTime_msg; extern const pb_msgdesc_t PB_System_PlayAudiovisualAlertRequest_msg; extern const pb_msgdesc_t PB_System_ProtobufVersionRequest_msg; extern const pb_msgdesc_t PB_System_ProtobufVersionResponse_msg; +extern const pb_msgdesc_t PB_System_UpdateRequest_msg; +extern const pb_msgdesc_t PB_System_PowerInfoRequest_msg; +extern const pb_msgdesc_t PB_System_PowerInfoResponse_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define PB_System_PingRequest_fields &PB_System_PingRequest_msg @@ -241,17 +282,23 @@ extern const pb_msgdesc_t PB_System_ProtobufVersionResponse_msg; #define PB_System_PlayAudiovisualAlertRequest_fields &PB_System_PlayAudiovisualAlertRequest_msg #define PB_System_ProtobufVersionRequest_fields &PB_System_ProtobufVersionRequest_msg #define PB_System_ProtobufVersionResponse_fields &PB_System_ProtobufVersionResponse_msg +#define PB_System_UpdateRequest_fields &PB_System_UpdateRequest_msg +#define PB_System_PowerInfoRequest_fields &PB_System_PowerInfoRequest_msg +#define PB_System_PowerInfoResponse_fields &PB_System_PowerInfoResponse_msg /* Maximum encoded size of messages (where known) */ /* PB_System_PingRequest_size depends on runtime parameters */ /* PB_System_PingResponse_size depends on runtime parameters */ /* PB_System_DeviceInfoResponse_size depends on runtime parameters */ +/* PB_System_UpdateRequest_size depends on runtime parameters */ +/* PB_System_PowerInfoResponse_size depends on runtime parameters */ #define PB_System_DateTime_size 22 #define PB_System_DeviceInfoRequest_size 0 #define PB_System_FactoryResetRequest_size 0 #define PB_System_GetDateTimeRequest_size 0 #define PB_System_GetDateTimeResponse_size 24 #define PB_System_PlayAudiovisualAlertRequest_size 0 +#define PB_System_PowerInfoRequest_size 0 #define PB_System_ProtobufVersionRequest_size 0 #define PB_System_ProtobufVersionResponse_size 12 #define PB_System_RebootRequest_size 2 diff --git a/assets/protobuf b/assets/protobuf index cd11b029..0403ae1b 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit cd11b029ac21462ea8a7615126d0a29e087c2908 +Subproject commit 0403ae1ba7a4501274da54b3aa6274f76fdd090c diff --git a/firmware/targets/f7/furi_hal/furi_hal_crypto.c b/firmware/targets/f7/furi_hal/furi_hal_crypto.c index c34eb735..2164ebc3 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_crypto.c +++ b/firmware/targets/f7/furi_hal/furi_hal_crypto.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -14,13 +15,16 @@ #define CRYPTO_TIMEOUT (1000) #define CRYPTO_MODE_ENCRYPT 0U +#define CRYPTO_MODE_INIT (AES_CR_MODE_0) #define CRYPTO_MODE_DECRYPT (AES_CR_MODE_1) #define CRYPTO_MODE_DECRYPT_INIT (AES_CR_MODE_0 | AES_CR_MODE_1) + #define CRYPTO_DATATYPE_32B 0U #define CRYPTO_KEYSIZE_256B (AES_CR_KEYSIZE) #define CRYPTO_AES_CBC (AES_CR_CHMOD_0) static osMutexId_t furi_hal_crypto_mutex = NULL; +static bool furi_hal_crypto_mode_init_done = false; static const uint8_t enclave_signature_iv[ENCLAVE_FACTORY_KEY_SLOTS][16] = { {0xac, 0x5d, 0x68, 0xb8, 0x79, 0x74, 0xfc, 0x7f, 0x45, 0x02, 0x82, 0xf1, 0x48, 0x7e, 0x75, 0x8a}, @@ -176,16 +180,8 @@ bool furi_hal_crypto_store_add_key(FuriHalCryptoKey* key, uint8_t* slot) { return (shci_state == SHCI_Success); } -static void crypto_enable() { - SET_BIT(AES1->CR, AES_CR_EN); -} - -static void crypto_disable() { - CLEAR_BIT(AES1->CR, AES_CR_EN); -} - static void crypto_key_init(uint32_t* key, uint32_t* iv) { - crypto_disable(); + CLEAR_BIT(AES1->CR, AES_CR_EN); MODIFY_REG( AES1->CR, AES_CR_DATATYPE | AES_CR_KEYSIZE | AES_CR_CHMOD, @@ -249,12 +245,13 @@ bool furi_hal_crypto_store_load_key(uint8_t slot, const uint8_t* iv) { return false; } + furi_hal_crypto_mode_init_done = false; crypto_key_init(NULL, (uint32_t*)iv); if(SHCI_C2_FUS_LoadUsrKey(slot) == SHCI_Success) { return true; } else { - crypto_disable(); + CLEAR_BIT(AES1->CR, AES_CR_EN); furi_check(osMutexRelease(furi_hal_crypto_mutex) == osOK); return false; } @@ -265,9 +262,16 @@ bool furi_hal_crypto_store_unload_key(uint8_t slot) { return false; } - crypto_disable(); + CLEAR_BIT(AES1->CR, AES_CR_EN); SHCI_CmdStatus_t shci_state = SHCI_C2_FUS_UnloadUsrKey(slot); + furi_assert(shci_state == SHCI_Success); + + FURI_CRITICAL_ENTER(); + LL_AHB2_GRP1_ForceReset(LL_AHB2_GRP1_PERIPH_AES1); + LL_AHB2_GRP1_ReleaseReset(LL_AHB2_GRP1_PERIPH_AES1); + FURI_CRITICAL_EXIT(); + furi_check(osMutexRelease(furi_hal_crypto_mutex) == osOK); return (shci_state == SHCI_Success); } @@ -275,7 +279,7 @@ bool furi_hal_crypto_store_unload_key(uint8_t slot) { bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) { bool state = false; - crypto_enable(); + SET_BIT(AES1->CR, AES_CR_EN); MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_ENCRYPT); @@ -290,7 +294,7 @@ bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) } } - crypto_disable(); + CLEAR_BIT(AES1->CR, AES_CR_EN); return state; } @@ -298,9 +302,28 @@ bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size) { bool state = false; - MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_DECRYPT_INIT); + if(!furi_hal_crypto_mode_init_done) { + MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_INIT); - crypto_enable(); + SET_BIT(AES1->CR, AES_CR_EN); + + uint32_t countdown = CRYPTO_TIMEOUT; + while(!READ_BIT(AES1->SR, AES_SR_CCF)) { + if(LL_SYSTICK_IsActiveCounterFlag()) { + countdown--; + } + if(countdown == 0) { + return false; + } + } + + SET_BIT(AES1->CR, AES_CR_CCFC); + + furi_hal_crypto_mode_init_done = true; + } + + MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_DECRYPT); + SET_BIT(AES1->CR, AES_CR_EN); for(size_t i = 0; i < size; i += CRYPTO_BLK_LEN) { size_t blk_len = size - i; @@ -313,7 +336,7 @@ bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size) } } - crypto_disable(); + CLEAR_BIT(AES1->CR, AES_CR_EN); return state; } diff --git a/firmware/targets/f7/furi_hal/furi_hal_power.c b/firmware/targets/f7/furi_hal/furi_hal_power.c index ec30656e..e5060536 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_power.c +++ b/firmware/targets/f7/furi_hal/furi_hal_power.c @@ -239,6 +239,13 @@ uint32_t furi_hal_power_get_battery_full_capacity() { return ret; } +uint32_t furi_hal_power_get_battery_design_capacity() { + furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); + uint32_t ret = bq27220_get_design_capacity(&furi_hal_i2c_handle_power); + furi_hal_i2c_release(&furi_hal_i2c_handle_power); + return ret; +} + float furi_hal_power_get_battery_voltage(FuriHalPowerIC ic) { float ret = 0.0f; @@ -399,3 +406,58 @@ void furi_hal_power_suppress_charge_exit() { furi_hal_i2c_release(&furi_hal_i2c_handle_power); } } + +void furi_hal_power_info_get(FuriHalPowerInfoCallback out, void* context) { + furi_assert(out); + + string_t value; + string_init(value); + + // Power Info version + out("power_info_major", "1", false, context); + out("power_info_minor", "0", false, context); + + uint8_t charge = furi_hal_power_get_pct(); + + string_printf(value, "%u", charge); + out("charge_level", string_get_cstr(value), false, context); + + if(furi_hal_power_is_charging()) { + if(charge < 100) { + string_printf(value, "charging"); + } else { + string_printf(value, "charged"); + } + } else { + string_printf(value, "discharging"); + } + out("charge_state", string_get_cstr(value), false, context); + + uint16_t voltage = + (uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f); + string_printf(value, "%u", voltage); + out("battery_voltage", string_get_cstr(value), false, context); + + int16_t current = + (int16_t)(furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge) * 1000.f); + string_printf(value, "%d", current); + out("battery_current", string_get_cstr(value), false, context); + + int16_t temperature = (int16_t)furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge); + string_printf(value, "%d", temperature); + out("gauge_temp", string_get_cstr(value), false, context); + + string_printf(value, "%u", furi_hal_power_get_bat_health_pct()); + out("battery_health", string_get_cstr(value), false, context); + + string_printf(value, "%u", furi_hal_power_get_battery_remaining_capacity()); + out("capacity_remain", string_get_cstr(value), false, context); + + string_printf(value, "%u", furi_hal_power_get_battery_full_capacity()); + out("capacity_full", string_get_cstr(value), false, context); + + string_printf(value, "%u", furi_hal_power_get_battery_design_capacity()); + out("capacity_design", string_get_cstr(value), true, context); + + string_clear(value); +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/firmware/targets/f7/furi_hal/furi_hal_speaker.c index 80c69a08..298574d8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_speaker.c +++ b/firmware/targets/f7/furi_hal/furi_hal_speaker.c @@ -25,21 +25,32 @@ void furi_hal_speaker_start(float frequency, float volume) { if(volume > 1) volume = 1; volume = volume * volume * volume; + uint32_t autoreload = (SystemCoreClock / FURI_HAL_SPEAKER_PRESCALER / frequency) - 1; + if(autoreload < 2) { + autoreload = 2; + } else if(autoreload > UINT16_MAX) { + autoreload = UINT16_MAX; + } + LL_TIM_InitTypeDef TIM_InitStruct = {0}; TIM_InitStruct.Prescaler = FURI_HAL_SPEAKER_PRESCALER - 1; - TIM_InitStruct.Autoreload = ((SystemCoreClock / FURI_HAL_SPEAKER_PRESCALER) / frequency) - 1; + TIM_InitStruct.Autoreload = autoreload; LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct); #ifdef FURI_HAL_SPEAKER_NEW_VOLUME - uint16_t compare_value = volume * FURI_HAL_SPEAKER_MAX_VOLUME; - uint16_t clip_value = volume * TIM_InitStruct.Autoreload / 2; + uint32_t compare_value = volume * FURI_HAL_SPEAKER_MAX_VOLUME; + uint32_t clip_value = volume * TIM_InitStruct.Autoreload / 2; if(compare_value > clip_value) { compare_value = clip_value; } #else - uint16_t compare_value = volume * TIM_InitStruct.Autoreload / 2; + uint32_t compare_value = volume * autoreload / 2; #endif + if(compare_value == 0) { + compare_value = 1; + } + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; @@ -51,6 +62,6 @@ void furi_hal_speaker_start(float frequency, float volume) { } void furi_hal_speaker_stop() { - LL_TIM_CC_DisableChannel(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL); + LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); } diff --git a/firmware/targets/furi_hal_include/furi_hal_power.h b/firmware/targets/furi_hal_include/furi_hal_power.h index 3f5a0020..c55e3804 100644 --- a/firmware/targets/furi_hal_include/furi_hal_power.h +++ b/firmware/targets/furi_hal_include/furi_hal_power.h @@ -113,6 +113,12 @@ uint32_t furi_hal_power_get_battery_remaining_capacity(); */ uint32_t furi_hal_power_get_battery_full_capacity(); +/** Get battery capacity in mAh from battery profile + * + * @return capacity in mAh + */ +uint32_t furi_hal_power_get_battery_design_capacity(); + /** Get battery voltage in V * * @param ic FuriHalPowerIc to get measurment @@ -171,6 +177,23 @@ void furi_hal_power_suppress_charge_enter(); */ void furi_hal_power_suppress_charge_exit(); +/** Callback type called by furi_hal_power_info_get every time another key-value pair of information is ready + * + * @param key[in] power information type identifier + * @param value[in] power information value + * @param last[in] whether the passed key-value pair is the last one + * @param context[in] to pass to callback + */ +typedef void ( + *FuriHalPowerInfoCallback)(const char* key, const char* value, bool last, void* context); + +/** Get power information + * + * @param[in] callback callback to provide with new data + * @param[in] context context to pass to callback + */ +void furi_hal_power_info_get(FuriHalPowerInfoCallback callback, void* context); + #ifdef __cplusplus } #endif diff --git a/lib/subghz/subghz_keystore.c b/lib/subghz/subghz_keystore.c index d0efc6a0..af953f95 100644 --- a/lib/subghz/subghz_keystore.c +++ b/lib/subghz/subghz_keystore.c @@ -187,6 +187,8 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { string_t filetype; string_init(filetype); + FURI_LOG_I(TAG, "Loading keystore %s", file_name); + Storage* storage = furi_record_open("storage"); FlipperFormat* flipper_format = flipper_format_file_alloc(storage); @@ -224,7 +226,6 @@ bool subghz_keystore_load(SubGhzKeystore* instance, const char* file_name) { FURI_LOG_E(TAG, "Unknown encryption"); break; } - FURI_LOG_I(TAG, "Loading keystore %s", file_name); } while(0); flipper_format_free(flipper_format); diff --git a/lib/toolbox/stream/file_stream.c b/lib/toolbox/stream/file_stream.c index 56dc1de8..340cb61a 100644 --- a/lib/toolbox/stream/file_stream.c +++ b/lib/toolbox/stream/file_stream.c @@ -61,6 +61,13 @@ bool file_stream_close(Stream* _stream) { return storage_file_close(stream->file); } +FS_Error file_stream_get_error(Stream* _stream) { + furi_assert(_stream); + FileStream* stream = (FileStream*)_stream; + furi_check(stream->stream_base.vtable == &file_stream_vtable); + return storage_file_get_error(stream->file); +} + static void file_stream_free(FileStream* stream) { storage_file_free(stream->file); free(stream); @@ -169,7 +176,7 @@ static bool file_stream_delete_and_insert( string_t scratch_name; string_t tmp_name; string_init(tmp_name); - storage_get_next_filename(_stream->storage, "/any", ".scratch", ".pad", tmp_name); + storage_get_next_filename(_stream->storage, "/any", ".scratch", ".pad", tmp_name, 255); string_init_printf(scratch_name, "/any/%s.pad", string_get_cstr(tmp_name)); string_clear(tmp_name); diff --git a/lib/toolbox/stream/file_stream.h b/lib/toolbox/stream/file_stream.h index 3f19d07b..ec371a53 100644 --- a/lib/toolbox/stream/file_stream.h +++ b/lib/toolbox/stream/file_stream.h @@ -35,6 +35,13 @@ bool file_stream_open( */ bool file_stream_close(Stream* stream); +/** + * Retrieves the error id from the file object + * @param stream pointer to stream object. + * @return FS_Error error id + */ +FS_Error file_stream_get_error(Stream* stream); + #ifdef __cplusplus } #endif \ No newline at end of file