Merge branch 'release-candidate' into release
This commit is contained in:
commit
215466e2d6
52
RoadMap.md
Normal file
52
RoadMap.md
Normal 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
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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, ...);
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -436,4 +436,4 @@ const NotificationSequence sequence_audiovisual_alert = {
|
||||
&message_sound_off,
|
||||
&message_vibro_off,
|
||||
NULL,
|
||||
};
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -23,9 +23,9 @@ typedef enum {
|
||||
SubGhzCustomEventSceneExit,
|
||||
SubGhzCustomEventSceneStay,
|
||||
|
||||
SubGhzCustomEventViewReceverOK,
|
||||
SubGhzCustomEventViewReceverConfig,
|
||||
SubGhzCustomEventViewReceverBack,
|
||||
SubGhzCustomEventViewReceiverOK,
|
||||
SubGhzCustomEventViewReceiverConfig,
|
||||
SubGhzCustomEventViewReceiverBack,
|
||||
|
||||
SubGhzCustomEventViewReadRAWBack,
|
||||
SubGhzCustomEventViewReadRAWIDLE,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
74
applications/subghz/subghz_i.c
Executable file → Normal 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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
171
applications/tests/storage/storage_test.c
Normal file
171
applications/tests/storage/storage_test.c
Normal 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;
|
||||
}
|
||||
@ -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));
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
#pragma once
|
||||
#define PROTOBUF_MAJOR_VERSION 0
|
||||
#define PROTOBUF_MINOR_VERSION 3
|
||||
#define PROTOBUF_MINOR_VERSION 5
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user