Merge branch 'dev' into release-candidate
This commit is contained in:
		
						commit
						625eb0a4a5
					
				
							
								
								
									
										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
	 Aleksandr Kutuzov
						Aleksandr Kutuzov