Merge branch 'dev' into release-candidate

This commit is contained in:
Aleksandr Kutuzov 2022-04-07 18:48:44 +03:00
commit 625eb0a4a5
No known key found for this signature in database
GPG Key ID: 0D0011717914BBCD
81 changed files with 1434 additions and 527 deletions

52
RoadMap.md Normal file
View File

@ -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

View File

@ -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) {

View File

@ -3,25 +3,31 @@
#include "archive_browser.h"
#include <math.h>
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;
});

View File

@ -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);

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -1,7 +1,6 @@
#pragma once
#include "file_worker.h"
#define MAX_FILES 100 //temp
#include <m-array.h>
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, ...);
void archive_delete_file(void* context, const char* format, ...);

View File

@ -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)) {

View File

@ -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);

View File

@ -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;
});

View File

@ -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(

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -9,6 +9,7 @@ typedef enum {
} SubmenuIndex;
static void submenu_callback(void* context, uint32_t index) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;

View File

@ -3,6 +3,7 @@
#include <dolphin/dolphin.h>
static void byte_input_callback(void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;

View File

@ -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<iButtonApp*>(context);
iButtonEvent event;

View File

@ -2,6 +2,7 @@
#include "../ibutton_app.h"
static void popup_callback(void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;
event.type = iButtonEvent::Type::EventTypeBack;

View File

@ -3,6 +3,7 @@
#include <dolphin/dolphin.h>
static void emulate_callback(void* context, bool emulated) {
furi_assert(context);
if(emulated) {
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerEmulated};

View File

@ -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<iButtonApp*>(context);
iButtonEvent event;

View File

@ -3,6 +3,7 @@
#include <dolphin/dolphin.h>
static void read_callback(void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event = {.type = iButtonEvent::Type::EventTypeWorkerRead};
app->get_view_manager()->send_event(&event);

View File

@ -3,6 +3,7 @@
#include <one_wire/maxim_crc.h>
static void dialog_ex_callback(DialogExResult result, void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;

View File

@ -3,6 +3,7 @@
#include <one_wire/maxim_crc.h>
static void dialog_ex_callback(DialogExResult result, void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;

View File

@ -3,6 +3,7 @@
#include <dolphin/dolphin.h>
static void dialog_ex_callback(DialogExResult result, void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;

View File

@ -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<iButtonApp*>(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:

View File

@ -3,6 +3,7 @@
#include <lib/toolbox/random_name.h>
static void text_input_callback(void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(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);

View File

@ -3,6 +3,7 @@
#include <dolphin/dolphin.h>
static void popup_callback(void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;
event.type = iButtonEvent::Type::EventTypeBack;

View File

@ -11,6 +11,7 @@ typedef enum {
} SubmenuIndex;
static void submenu_callback(void* context, uint32_t index) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;

View File

@ -8,6 +8,7 @@ typedef enum {
} SubmenuIndex;
static void submenu_callback(void* context, uint32_t index) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;

View File

@ -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<iButtonApp*>(context);
iButtonEvent event;
event.type = iButtonEvent::Type::EventTypeWorkerWrite;

View File

@ -2,6 +2,7 @@
#include "../ibutton_app.h"
static void popup_callback(void* context) {
furi_assert(context);
iButtonApp* app = static_cast<iButtonApp*>(context);
iButtonEvent event;
event.type = iButtonEvent::Type::EventTypeBack;

View File

@ -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);
}

View File

@ -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;

View File

@ -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<SubmenuVM>();
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:

View File

@ -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<TextInputVM>();

View File

@ -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);

View File

@ -436,4 +436,4 @@ const NotificationSequence sequence_audiovisual_alert = {
&message_sound_off,
&message_vibro_off,
NULL,
};
};

View File

@ -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;

View File

@ -4,6 +4,8 @@
#include <furi_hal.h>
#include <semphr.h>
#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) {

View File

@ -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);

View File

@ -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;
}

View File

@ -1,5 +1,5 @@
#pragma once
#include <furi.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <furi.h>
#include <stdint.h>
#include <m-string.h>
#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
}

View File

@ -3,6 +3,7 @@
#include "storage.h"
#include "storage_i.h"
#include "storage_message.h"
#include <toolbox/stream/file_stream.h>
#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);

View File

@ -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);

View File

@ -3,7 +3,6 @@
#include <furi.h>
#include "filesystem_api_internal.h"
#include <m-string.h>
#include <m-array.h>
#include <m-list.h>
#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

View File

@ -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;
};

View File

@ -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,

View File

@ -1,8 +1,11 @@
#include "storage_processing.h"
#include <m-list.h>
#include <m-dict.h>
#include <m-string.h>
#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);
}

View File

@ -1,8 +1,6 @@
#pragma once
#include <furi.h>
#include "filesystem_api_defines.h"
#include <fatfs.h>
#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 {

View File

@ -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();

View File

@ -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;
}

View File

@ -23,9 +23,9 @@ typedef enum {
SubGhzCustomEventSceneExit,
SubGhzCustomEventSceneStay,
SubGhzCustomEventViewReceverOK,
SubGhzCustomEventViewReceverConfig,
SubGhzCustomEventViewReceverBack,
SubGhzCustomEventViewReceiverOK,
SubGhzCustomEventViewReceiverConfig,
SubGhzCustomEventViewReceiverBack,
SubGhzCustomEventViewReadRAWBack,
SubGhzCustomEventViewReadRAWIDLE,

View File

@ -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 {

View File

@ -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 {

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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) {

74
applications/subghz/subghz_i.c Executable file → Normal file
View File

@ -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);

View File

@ -33,7 +33,7 @@
#include <gui/modules/variable_item_list.h>
#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,

View File

@ -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;
});

View File

@ -0,0 +1,171 @@
#include "../minunit.h"
#include <furi.h>
#include <furi_hal_delay.h>
#include <storage/storage.h>
#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;
}

View File

@ -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));

View File

@ -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

View File

@ -1,3 +1,3 @@
#pragma once
#define PROTOBUF_MAJOR_VERSION 0
#define PROTOBUF_MINOR_VERSION 3
#define PROTOBUF_MINOR_VERSION 5

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

@ -1 +1 @@
Subproject commit cd11b029ac21462ea8a7615126d0a29e087c2908
Subproject commit 0403ae1ba7a4501274da54b3aa6274f76fdd090c

View File

@ -2,6 +2,7 @@
#include <furi_hal_bt.h>
#include <furi_hal_random.h>
#include <stm32wbxx_ll_cortex.h>
#include <stm32wbxx_ll_bus.h>
#include <furi.h>
#include <shci.h>
@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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