[FL-1791] Flipper file format (#740)
* Lib: new flipper file format library * Lib: flipper file format cpp wrapper * Storage: simple function for remove file and check error * iButton app: remove file worker, use new flipper file format instead * Dialogs: storage error message * Storage: simple function for mkdir and check error * iButton app: error messages * Libs: update makefile * RFID app: remove file worker, use new flipper file format instead * Flipper File: library documentation Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									e0c1928fde
								
							
						
					
					
						commit
						c8b36dd406
					
				| @ -60,3 +60,14 @@ DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage | ||||
| 
 | ||||
|     return return_data.dialog_value; | ||||
| } | ||||
| 
 | ||||
| /****************** Storage error ******************/ | ||||
| 
 | ||||
| void dialog_message_show_storage_error(DialogsApp* context, const char* error_text) { | ||||
|     DialogMessage* message = dialog_message_alloc(); | ||||
|     dialog_message_set_text(message, error_text, 88, 32, AlignCenter, AlignCenter); | ||||
|     dialog_message_set_icon(message, &I_SDQuestion_35x43, 5, 6); | ||||
|     dialog_message_set_buttons(message, "Back", NULL, NULL); | ||||
|     dialog_message_show(context, message); | ||||
|     dialog_message_free(message); | ||||
| } | ||||
| @ -123,6 +123,13 @@ void dialog_message_set_buttons( | ||||
|  */ | ||||
| DialogMessageButton dialog_message_show(DialogsApp* context, const DialogMessage* message); | ||||
| 
 | ||||
| /**
 | ||||
|  * Show SD error message (with question sign) | ||||
|  * @param context  | ||||
|  * @param error_text  | ||||
|  */ | ||||
| void dialog_message_show_storage_error(DialogsApp* context, const char* error_text); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -2,11 +2,12 @@ | ||||
| #include <stdarg.h> | ||||
| #include <callback-connector.h> | ||||
| #include <m-string.h> | ||||
| #include <file-worker-cpp.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| #include <toolbox/path.h> | ||||
| #include <toolbox/flipper-file-cpp.h> | ||||
| 
 | ||||
| const char* iButtonApp::app_folder = "/any/ibutton"; | ||||
| const char* iButtonApp::app_extension = ".ibtn"; | ||||
| const char* iButtonApp::app_filetype = "Flipper iButton key"; | ||||
| 
 | ||||
| void iButtonApp::run(void* args) { | ||||
|     iButtonEvent event; | ||||
| @ -37,7 +38,9 @@ void iButtonApp::run(void* args) { | ||||
| } | ||||
| 
 | ||||
| iButtonApp::iButtonApp() | ||||
|     : notification{"notification"} { | ||||
|     : notification{"notification"} | ||||
|     , storage{"storage"} | ||||
|     , dialogs{"dialogs"} { | ||||
|     furi_hal_power_insomnia_enter(); | ||||
|     key_worker = new KeyWorker(&ibutton_gpio); | ||||
| } | ||||
| @ -188,102 +191,90 @@ bool iButtonApp::save_key(const char* key_name) { | ||||
|     // Create ibutton directory if necessary
 | ||||
|     make_app_folder(); | ||||
| 
 | ||||
|     FileWorkerCpp file_worker; | ||||
|     FlipperFileCpp file(storage); | ||||
|     string_t key_file_name; | ||||
|     bool result = false; | ||||
|     string_init(key_file_name); | ||||
| 
 | ||||
|     // First remove key if it was saved
 | ||||
|     string_init_printf(key_file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension); | ||||
|     if(!file_worker.remove(string_get_cstr(key_file_name))) { | ||||
|         string_clear(key_file_name); | ||||
|         return false; | ||||
|     }; | ||||
|     do { | ||||
|         // First remove key if it was saved (we rename the key)
 | ||||
|         if(!delete_key()) break; | ||||
| 
 | ||||
|     // Save the key
 | ||||
|     get_key()->set_name(key_name); | ||||
|     string_printf(key_file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension); | ||||
|         // Save the key
 | ||||
|         key.set_name(key_name); | ||||
| 
 | ||||
|     bool res = file_worker.open(string_get_cstr(key_file_name), FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||
|         // Set full file name, for new key
 | ||||
|         string_printf(key_file_name, "%s/%s%s", app_folder, key.get_name(), app_extension); | ||||
| 
 | ||||
|         // Open file for write
 | ||||
|         if(!file.new_write(string_get_cstr(key_file_name))) break; | ||||
| 
 | ||||
|         // Write header
 | ||||
|         if(!file.write_header_cstr(iButtonApp::app_filetype, 1)) break; | ||||
| 
 | ||||
|         // Write key type
 | ||||
|         if(!file.write_comment_cstr("Key type can be Cyfral, Dallas or Metakom")) break; | ||||
|         const char* key_type = key.get_key_type_string_by_type(key.get_key_type()); | ||||
|         if(!file.write_string_cstr("Key type", key_type)) break; | ||||
| 
 | ||||
|         // Write data
 | ||||
|         if(!file.write_comment_cstr( | ||||
|                "Data size for Cyfral is 2, for Metakom is 4, for Dallas is 8")) | ||||
|             break; | ||||
| 
 | ||||
|         if(!file.write_hex_array("Data", key.get_data(), key.get_type_data_size())) break; | ||||
|         result = true; | ||||
| 
 | ||||
|     } while(false); | ||||
| 
 | ||||
|     file.close(); | ||||
|     string_clear(key_file_name); | ||||
| 
 | ||||
|     if(res) { | ||||
|         // type header
 | ||||
|         const char* key_type = "E "; | ||||
| 
 | ||||
|         switch(get_key()->get_key_type()) { | ||||
|         case iButtonKeyType::KeyCyfral: | ||||
|             key_type = "C "; | ||||
|             break; | ||||
|         case iButtonKeyType::KeyDallas: | ||||
|             key_type = "D "; | ||||
|             break; | ||||
|         case iButtonKeyType::KeyMetakom: | ||||
|             key_type = "M "; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(!file_worker.write(key_type, 2)) { | ||||
|             file_worker.close(); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if(!file_worker.write_hex(get_key()->get_data(), get_key()->get_type_data_size())) { | ||||
|             file_worker.close(); | ||||
|             return false; | ||||
|         } | ||||
|         result = true; | ||||
|     if(!result) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); | ||||
|     } | ||||
| 
 | ||||
|     file_worker.close(); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool iButtonApp::load_key_data(string_t key_path) { | ||||
|     FileWorkerCpp file_worker; | ||||
|     FlipperFileCpp file(storage); | ||||
|     bool result = false; | ||||
|     string_t data; | ||||
|     string_init(data); | ||||
| 
 | ||||
|     // Open key file
 | ||||
|     if(!file_worker.open(string_get_cstr(key_path), FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||
|         file_worker.close(); | ||||
|         return false; | ||||
|     do { | ||||
|         if(!file.open_read(string_get_cstr(key_path))) break; | ||||
| 
 | ||||
|         // header
 | ||||
|         uint32_t version; | ||||
|         if(!file.read_header(data, &version)) break; | ||||
|         if(string_cmp_str(data, iButtonApp::app_filetype) != 0) break; | ||||
|         if(version != 1) break; | ||||
| 
 | ||||
|         // key type
 | ||||
|         iButtonKeyType type; | ||||
|         if(!file.read_string("Key type", data)) break; | ||||
|         if(!key.get_key_type_by_type_string(string_get_cstr(data), &type)) break; | ||||
| 
 | ||||
|         // key data
 | ||||
|         uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0}; | ||||
|         if(!file.read_hex_array("Data", key_data, key.get_type_data_size_by_type(type))) break; | ||||
| 
 | ||||
|         key.set_type(type); | ||||
|         key.set_data(key_data, IBUTTON_KEY_DATA_SIZE); | ||||
| 
 | ||||
|         result = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     file.close(); | ||||
|     string_clear(data); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); | ||||
|     } | ||||
| 
 | ||||
|     const uint8_t byte_text_size = 4; | ||||
|     char byte_text[byte_text_size] = {0, 0, 0, 0}; | ||||
| 
 | ||||
|     // Load type header
 | ||||
|     if(!file_worker.read(byte_text, 2)) { | ||||
|         file_worker.close(); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     iButtonKeyType key_type = iButtonKeyType::KeyCyfral; | ||||
|     if(strcmp(byte_text, "C ") == 0) { | ||||
|         key_type = iButtonKeyType::KeyCyfral; | ||||
|     } else if(strcmp(byte_text, "M ") == 0) { | ||||
|         key_type = iButtonKeyType::KeyMetakom; | ||||
|     } else if(strcmp(byte_text, "D ") == 0) { | ||||
|         key_type = iButtonKeyType::KeyDallas; | ||||
|     } else { | ||||
|         file_worker.show_error("Cannot parse\nkey file"); | ||||
|         file_worker.close(); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     iButtonKeyType old_type = get_key()->get_key_type(); | ||||
|     get_key()->set_type(key_type); | ||||
| 
 | ||||
|     uint8_t key_data[IBUTTON_KEY_DATA_SIZE] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||||
|     if(!file_worker.read_hex(key_data, get_key()->get_type_data_size())) { | ||||
|         get_key()->set_type(old_type); | ||||
|         file_worker.close(); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     file_worker.close(); | ||||
|     get_key()->set_data(key_data, IBUTTON_KEY_DATA_SIZE); | ||||
| 
 | ||||
|     return true; | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool iButtonApp::load_key(const char* key_name) { | ||||
| @ -303,11 +294,15 @@ bool iButtonApp::load_key(const char* key_name) { | ||||
| 
 | ||||
| bool iButtonApp::load_key() { | ||||
|     bool result = false; | ||||
|     FileWorkerCpp file_worker; | ||||
| 
 | ||||
|     // Input events and views are managed by file_select
 | ||||
|     bool res = file_worker.file_select( | ||||
|         app_folder, app_extension, get_file_name(), get_file_name_size(), get_key()->get_name()); | ||||
|     bool res = dialog_file_select_show( | ||||
|         dialogs, | ||||
|         app_folder, | ||||
|         app_extension, | ||||
|         get_file_name(), | ||||
|         get_file_name_size(), | ||||
|         get_key()->get_name()); | ||||
| 
 | ||||
|     if(res) { | ||||
|         string_t key_str; | ||||
| @ -328,16 +323,16 @@ bool iButtonApp::load_key() { | ||||
| bool iButtonApp::delete_key() { | ||||
|     string_t file_name; | ||||
|     bool result = false; | ||||
|     FileWorkerCpp file_worker; | ||||
| 
 | ||||
|     string_init_printf(file_name, "%s/%s%s", app_folder, get_key()->get_name(), app_extension); | ||||
|     result = file_worker.remove(string_get_cstr(file_name)); | ||||
|     result = storage_simply_remove(storage, string_get_cstr(file_name)); | ||||
|     string_clear(file_name); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void iButtonApp::make_app_folder() { | ||||
|     FileWorkerCpp file_worker; | ||||
|     file_worker.mkdir(app_folder); | ||||
|     if(!storage_simply_mkdir(storage, app_folder)) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot create\napp folder"); | ||||
|     } | ||||
| } | ||||
| @ -30,6 +30,8 @@ | ||||
| #include "ibutton-key.h" | ||||
| 
 | ||||
| #include <notification/notification-messages.h> | ||||
| #include <storage/storage.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| 
 | ||||
| #include <record-controller.hpp> | ||||
| 
 | ||||
| @ -126,6 +128,8 @@ private: | ||||
|     iButtonKey key; | ||||
| 
 | ||||
|     RecordController<NotificationApp> notification; | ||||
|     RecordController<Storage> storage; | ||||
|     RecordController<DialogsApp> dialogs; | ||||
| 
 | ||||
|     static const uint8_t file_name_size = 100; | ||||
|     char file_name[file_name_size]; | ||||
| @ -135,6 +139,7 @@ private: | ||||
| 
 | ||||
|     static const char* app_folder; | ||||
|     static const char* app_extension; | ||||
|     static const char* app_filetype; | ||||
| 
 | ||||
|     bool load_key_data(string_t key_path); | ||||
|     void make_app_folder(); | ||||
|  | ||||
| @ -22,21 +22,7 @@ uint8_t* iButtonKey::get_data() { | ||||
| } | ||||
| 
 | ||||
| uint8_t iButtonKey::get_type_data_size() { | ||||
|     uint8_t size = 0; | ||||
| 
 | ||||
|     switch(type) { | ||||
|     case iButtonKeyType::KeyCyfral: | ||||
|         size = 2; | ||||
|         break; | ||||
|     case iButtonKeyType::KeyMetakom: | ||||
|         size = 4; | ||||
|         break; | ||||
|     case iButtonKeyType::KeyDallas: | ||||
|         size = 8; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return size; | ||||
|     return get_type_data_size_by_type(type); | ||||
| } | ||||
| 
 | ||||
| void iButtonKey::set_name(const char* _name) { | ||||
| @ -55,5 +41,55 @@ iButtonKeyType iButtonKey::get_key_type() { | ||||
|     return type; | ||||
| } | ||||
| 
 | ||||
| const char* iButtonKey::get_key_type_string_by_type(iButtonKeyType key_type) { | ||||
|     switch(key_type) { | ||||
|     case iButtonKeyType::KeyCyfral: | ||||
|         return "Cyfral"; | ||||
|         break; | ||||
|     case iButtonKeyType::KeyMetakom: | ||||
|         return "Metakom"; | ||||
|         break; | ||||
|     case iButtonKeyType::KeyDallas: | ||||
|         return "Dallas"; | ||||
|         break; | ||||
|     default: | ||||
|         furi_crash("Invalid iButton type"); | ||||
|         return ""; | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool iButtonKey::get_key_type_by_type_string(const char* type_string, iButtonKeyType* key_type) { | ||||
|     if(strcmp(type_string, get_key_type_string_by_type(iButtonKeyType::KeyCyfral)) == 0) { | ||||
|         *key_type = iButtonKeyType::KeyCyfral; | ||||
|     } else if(strcmp(type_string, get_key_type_string_by_type(iButtonKeyType::KeyMetakom)) == 0) { | ||||
|         *key_type = iButtonKeyType::KeyMetakom; | ||||
|     } else if(strcmp(type_string, get_key_type_string_by_type(iButtonKeyType::KeyDallas)) == 0) { | ||||
|         *key_type = iButtonKeyType::KeyDallas; | ||||
|     } else { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| uint8_t iButtonKey::get_type_data_size_by_type(iButtonKeyType key_type) { | ||||
|     uint8_t size = 0; | ||||
| 
 | ||||
|     switch(key_type) { | ||||
|     case iButtonKeyType::KeyCyfral: | ||||
|         size = 2; | ||||
|         break; | ||||
|     case iButtonKeyType::KeyMetakom: | ||||
|         size = 4; | ||||
|         break; | ||||
|     case iButtonKeyType::KeyDallas: | ||||
|         size = 8; | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     return size; | ||||
| } | ||||
| 
 | ||||
| iButtonKey::iButtonKey() { | ||||
| } | ||||
|  | ||||
| @ -17,6 +17,10 @@ public: | ||||
|     void set_type(iButtonKeyType key_type); | ||||
|     iButtonKeyType get_key_type(); | ||||
| 
 | ||||
|     const char* get_key_type_string_by_type(iButtonKeyType key_type); | ||||
|     bool get_key_type_by_type_string(const char* type_string, iButtonKeyType* key_type); | ||||
|     uint8_t get_type_data_size_by_type(iButtonKeyType key_type); | ||||
| 
 | ||||
|     iButtonKey(); | ||||
| 
 | ||||
| private: | ||||
|  | ||||
| @ -16,15 +16,18 @@ | ||||
| #include "scene/lfrfid-app-scene-delete-confirm.h" | ||||
| #include "scene/lfrfid-app-scene-delete-success.h" | ||||
| 
 | ||||
| #include <file-worker-cpp.h> | ||||
| #include <lib/toolbox/path.h> | ||||
| #include <lib/toolbox/flipper-file-cpp.h> | ||||
| 
 | ||||
| const char* LfRfidApp::app_folder = "/any/lfrfid"; | ||||
| const char* LfRfidApp::app_extension = ".rfid"; | ||||
| const char* LfRfidApp::app_filetype = "Flipper RFID key"; | ||||
| 
 | ||||
| LfRfidApp::LfRfidApp() | ||||
|     : scene_controller{this} | ||||
|     , notification{"notification"} | ||||
|     , storage{"storage"} | ||||
|     , dialogs{"dialogs"} | ||||
|     , text_store(40) { | ||||
|     furi_hal_power_insomnia_enter(); | ||||
| } | ||||
| @ -77,20 +80,20 @@ bool LfRfidApp::save_key(RfidKey* key) { | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::load_key_from_file_select(bool need_restore) { | ||||
|     FileWorkerCpp file_worker; | ||||
|     TextStore* filename_ts = new TextStore(64); | ||||
|     bool result; | ||||
|     bool result = false; | ||||
| 
 | ||||
|     if(need_restore) { | ||||
|         result = file_worker.file_select( | ||||
|         result = dialog_file_select_show( | ||||
|             dialogs, | ||||
|             app_folder, | ||||
|             app_extension, | ||||
|             filename_ts->text, | ||||
|             filename_ts->text_size, | ||||
|             worker.key.get_name()); | ||||
|     } else { | ||||
|         result = file_worker.file_select( | ||||
|             app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL); | ||||
|         result = dialog_file_select_show( | ||||
|             dialogs, app_folder, app_extension, filename_ts->text, filename_ts->text_size, NULL); | ||||
|     } | ||||
| 
 | ||||
|     if(result) { | ||||
| @ -105,86 +108,87 @@ bool LfRfidApp::load_key_from_file_select(bool need_restore) { | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::delete_key(RfidKey* key) { | ||||
|     FileWorkerCpp file_worker; | ||||
|     string_t file_name; | ||||
|     bool result = false; | ||||
| 
 | ||||
|     string_init_printf(file_name, "%s/%s%s", app_folder, key->get_name(), app_extension); | ||||
|     result = file_worker.remove(string_get_cstr(file_name)); | ||||
|     result = storage_simply_remove(storage, string_get_cstr(file_name)); | ||||
|     string_clear(file_name); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::load_key_data(const char* path, RfidKey* key) { | ||||
|     FileWorkerCpp file_worker; | ||||
|     FlipperFileCpp file(storage); | ||||
|     bool result = false; | ||||
|     string_t str_result; | ||||
|     string_init(str_result); | ||||
| 
 | ||||
|     bool res = file_worker.open(path, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     do { | ||||
|         if(!file.open_read(path)) break; | ||||
| 
 | ||||
|     if(res) { | ||||
|         string_t str_result; | ||||
|         string_init(str_result); | ||||
|         // header
 | ||||
|         uint32_t version; | ||||
|         if(!file.read_header(str_result, &version)) break; | ||||
|         if(string_cmp_str(str_result, app_filetype) != 0) break; | ||||
|         if(version != 1) break; | ||||
| 
 | ||||
|         do { | ||||
|             RfidKey loaded_key; | ||||
|             LfrfidKeyType loaded_type; | ||||
|         // key type
 | ||||
|         LfrfidKeyType type; | ||||
|         RfidKey loaded_key; | ||||
| 
 | ||||
|             // load type
 | ||||
|             if(!file_worker.read_until(str_result, ' ')) break; | ||||
|             if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &loaded_type)) { | ||||
|                 file_worker.show_error("Cannot parse\nfile"); | ||||
|                 break; | ||||
|             } | ||||
|             loaded_key.set_type(loaded_type); | ||||
|         if(!file.read_string("Key type", str_result)) break; | ||||
|         if(!lfrfid_key_get_string_type(string_get_cstr(str_result), &type)) break; | ||||
|         loaded_key.set_type(type); | ||||
| 
 | ||||
|             // load data
 | ||||
|             uint8_t tmp_data[loaded_key.get_type_data_count()]; | ||||
|             if(!file_worker.read_hex(tmp_data, loaded_key.get_type_data_count())) break; | ||||
|             loaded_key.set_data(tmp_data, loaded_key.get_type_data_count()); | ||||
|         // key data
 | ||||
|         uint8_t key_data[loaded_key.get_type_data_count()] = {}; | ||||
|         if(!file.read_hex_array("Data", key_data, loaded_key.get_type_data_count())) break; | ||||
|         loaded_key.set_data(key_data, loaded_key.get_type_data_count()); | ||||
| 
 | ||||
|             *key = loaded_key; | ||||
|             result = true; | ||||
|         } while(0); | ||||
| 
 | ||||
|         // load name
 | ||||
|         path_extract_filename_no_ext(path, str_result); | ||||
|         key->set_name(string_get_cstr(str_result)); | ||||
|         loaded_key.set_name(string_get_cstr(str_result)); | ||||
| 
 | ||||
|         string_clear(str_result); | ||||
|         *key = loaded_key; | ||||
|         result = true; | ||||
|     } while(0); | ||||
| 
 | ||||
|     file.close(); | ||||
|     string_clear(str_result); | ||||
| 
 | ||||
|     if(!result) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot load\nkey file"); | ||||
|     } | ||||
| 
 | ||||
|     file_worker.close(); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool LfRfidApp::save_key_data(const char* path, RfidKey* key) { | ||||
|     FileWorkerCpp file_worker; | ||||
|     FlipperFileCpp file(storage); | ||||
|     bool result = false; | ||||
| 
 | ||||
|     bool res = file_worker.open(path, FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||
|     do { | ||||
|         if(!file.new_write(path)) break; | ||||
|         if(!file.write_header_cstr(app_filetype, 1)) break; | ||||
|         if(!file.write_comment_cstr("Key type can be EM4100, H10301 or I40134")) break; | ||||
|         if(!file.write_string_cstr("Key type", lfrfid_key_get_type_string(key->get_type()))) break; | ||||
|         if(!file.write_comment_cstr("Data size for EM4100 is 5, for H10301 is 3, for I40134 is 3")) | ||||
|             break; | ||||
|         if(!file.write_hex_array("Data", key->get_data(), key->get_type_data_count())) break; | ||||
|         result = true; | ||||
|     } while(0); | ||||
| 
 | ||||
|     if(res) { | ||||
|         do { | ||||
|             // type header
 | ||||
|             const char* key_type = lfrfid_key_get_type_string(key->get_type()); | ||||
|             char delimeter = ' '; | ||||
|     file.close(); | ||||
| 
 | ||||
|             if(!file_worker.write(key_type, strlen(key_type))) break; | ||||
|             if(!file_worker.write(&delimeter)) break; | ||||
|             if(!file_worker.write_hex(key->get_data(), key->get_type_data_count())) break; | ||||
| 
 | ||||
|             result = true; | ||||
|         } while(0); | ||||
|     if(!result) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot save\nkey file"); | ||||
|     } | ||||
| 
 | ||||
|     file_worker.close(); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void LfRfidApp::make_app_folder() { | ||||
|     FileWorkerCpp file_worker; | ||||
|     file_worker.mkdir(app_folder); | ||||
|     if(!storage_simply_mkdir(storage, app_folder)) { | ||||
|         dialog_message_show_storage_error(dialogs, "Cannot create\napp folder"); | ||||
|     } | ||||
| } | ||||
| @ -16,6 +16,8 @@ | ||||
| #include "view/container-vm.h" | ||||
| 
 | ||||
| #include <notification/notification-messages.h> | ||||
| #include <storage/storage.h> | ||||
| #include <dialogs/dialogs.h> | ||||
| 
 | ||||
| #include "helpers/rfid-worker.h" | ||||
| 
 | ||||
| @ -63,6 +65,8 @@ public: | ||||
|     LfRfidApp(); | ||||
| 
 | ||||
|     RecordController<NotificationApp> notification; | ||||
|     RecordController<Storage> storage; | ||||
|     RecordController<DialogsApp> dialogs; | ||||
| 
 | ||||
|     RfidWorker worker; | ||||
| 
 | ||||
| @ -72,6 +76,7 @@ public: | ||||
| 
 | ||||
|     static const char* app_folder; | ||||
|     static const char* app_extension; | ||||
|     static const char* app_filetype; | ||||
| 
 | ||||
|     bool save_key(RfidKey* key); | ||||
|     bool load_key_from_file_select(bool need_restore); | ||||
|  | ||||
| @ -381,3 +381,15 @@ void storage_file_free(File* file) { | ||||
| 
 | ||||
|     free(file); | ||||
| } | ||||
| 
 | ||||
| bool storage_simply_remove(Storage* storage, const char* path) { | ||||
|     FS_Error result; | ||||
|     result = storage_common_remove(storage, path); | ||||
|     return result == FSE_OK || result == FSE_NOT_EXIST; | ||||
| } | ||||
| 
 | ||||
| bool storage_simply_mkdir(Storage* storage, const char* path) { | ||||
|     FS_Error result; | ||||
|     result = storage_common_mkdir(storage, path); | ||||
|     return result == FSE_OK || result == FSE_EXIST; | ||||
| } | ||||
| @ -230,6 +230,24 @@ FS_Error storage_sd_info(Storage* api, SDInfo* info); | ||||
|  */ | ||||
| FS_Error storage_sd_status(Storage* api); | ||||
| 
 | ||||
| /***************** Simplified Functions ******************/ | ||||
| 
 | ||||
| /**
 | ||||
|  * Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open | ||||
|  * @param storage pointer to the api | ||||
|  * @param path  | ||||
|  * @return true on success or if file/dir is not exist | ||||
|  */ | ||||
| bool storage_simply_remove(Storage* storage, const char* path); | ||||
| 
 | ||||
| /**
 | ||||
|  * Creates a directory | ||||
|  * @param storage  | ||||
|  * @param path  | ||||
|  * @return true on success or if directory is already exist | ||||
|  */ | ||||
| bool storage_simply_mkdir(Storage* storage, const char* path); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| @ -108,6 +108,7 @@ CPP_SOURCES		+= $(wildcard $(LIB_DIR)/app-scened-template/*/*.cpp) | ||||
| 
 | ||||
| # Toolbox
 | ||||
| C_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*.c) | ||||
| CPP_SOURCES		+= $(wildcard $(LIB_DIR)/toolbox/*.cpp) | ||||
| 
 | ||||
| # USB Stack
 | ||||
| CFLAGS			+= -I$(LIB_DIR)/libusb_stm32/inc | ||||
|  | ||||
							
								
								
									
										72
									
								
								lib/toolbox/flipper-file-cpp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								lib/toolbox/flipper-file-cpp.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| #include "flipper-file-cpp.h" | ||||
| 
 | ||||
| FlipperFileCpp::FlipperFileCpp(Storage* storage) { | ||||
|     file = flipper_file_alloc(storage); | ||||
| } | ||||
| 
 | ||||
| FlipperFileCpp::~FlipperFileCpp() { | ||||
|     flipper_file_free(file); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::open_read(const char* filename) { | ||||
|     return flipper_file_open_read(file, filename); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::new_write(const char* filename) { | ||||
|     return flipper_file_new_write(file, filename); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::close() { | ||||
|     return flipper_file_close(file); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::read_header(string_t filetype, uint32_t* version) { | ||||
|     return flipper_file_read_header(file, filetype, version); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_header(string_t filetype, const uint32_t version) { | ||||
|     return flipper_file_write_header(file, filetype, version); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_header_cstr(const char* filetype, const uint32_t version) { | ||||
|     return flipper_file_write_header_cstr(file, filetype, version); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::read_string(const char* key, string_t data) { | ||||
|     return flipper_file_read_string(file, key, data); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_string(const char* key, string_t data) { | ||||
|     return flipper_file_write_string(file, key, data); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_string_cstr(const char* key, const char* data) { | ||||
|     return flipper_file_write_string_cstr(file, key, data); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::read_uint32(const char* key, uint32_t* data) { | ||||
|     return flipper_file_read_uint32(file, key, data); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_uint32(const char* key, const uint32_t data) { | ||||
|     return flipper_file_write_uint32(file, key, data); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_comment(string_t data) { | ||||
|     return flipper_file_write_comment(file, data); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_comment_cstr(const char* data) { | ||||
|     return flipper_file_write_comment_cstr(file, data); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::write_hex_array( | ||||
|     const char* key, | ||||
|     const uint8_t* data, | ||||
|     const uint16_t data_size) { | ||||
|     return flipper_file_write_hex_array(file, key, data, data_size); | ||||
| } | ||||
| 
 | ||||
| bool FlipperFileCpp::read_hex_array(const char* key, uint8_t* data, const uint16_t data_size) { | ||||
|     return flipper_file_read_hex_array(file, key, data, data_size); | ||||
| } | ||||
							
								
								
									
										41
									
								
								lib/toolbox/flipper-file-cpp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/toolbox/flipper-file-cpp.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| #pragma once | ||||
| #include "flipper-file.h" | ||||
| 
 | ||||
| class FlipperFileCpp { | ||||
| private: | ||||
|     FlipperFile* file; | ||||
| 
 | ||||
| public: | ||||
|     FlipperFileCpp(Storage* storage); | ||||
|     ~FlipperFileCpp(); | ||||
| 
 | ||||
|     bool open_read(const char* filename); | ||||
| 
 | ||||
|     bool new_write(const char* filename); | ||||
| 
 | ||||
|     bool close(); | ||||
| 
 | ||||
|     bool read_header(string_t filetype, uint32_t* version); | ||||
| 
 | ||||
|     bool write_header(string_t filetype, const uint32_t version); | ||||
| 
 | ||||
|     bool write_header_cstr(const char* filetype, const uint32_t version); | ||||
| 
 | ||||
|     bool read_string(const char* key, string_t data); | ||||
| 
 | ||||
|     bool write_string(const char* key, string_t data); | ||||
| 
 | ||||
|     bool write_string_cstr(const char* key, const char* data); | ||||
| 
 | ||||
|     bool read_uint32(const char* key, uint32_t* data); | ||||
| 
 | ||||
|     bool write_uint32(const char* key, const uint32_t data); | ||||
| 
 | ||||
|     bool write_comment(string_t data); | ||||
| 
 | ||||
|     bool write_comment_cstr(const char* data); | ||||
| 
 | ||||
|     bool write_hex_array(const char* key, const uint8_t* data, const uint16_t data_size); | ||||
| 
 | ||||
|     bool read_hex_array(const char* key, uint8_t* data, const uint16_t data_size); | ||||
| }; | ||||
							
								
								
									
										464
									
								
								lib/toolbox/flipper-file.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										464
									
								
								lib/toolbox/flipper-file.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,464 @@ | ||||
| #include <furi.h> | ||||
| #include "flipper-file.h" | ||||
| #include <toolbox/hex.h> | ||||
| #include <inttypes.h> | ||||
| 
 | ||||
| struct FlipperFile { | ||||
|     File* file; | ||||
| }; | ||||
| 
 | ||||
| const char* flipper_file_filetype_key = "Filetype"; | ||||
| const char* flipper_file_version_key = "Version"; | ||||
| const char flipper_file_eoln = '\n'; | ||||
| const char flipper_file_eolr = '\r'; | ||||
| const char flipper_file_delimiter = ':'; | ||||
| const char flipper_file_comment = '#'; | ||||
| 
 | ||||
| /**
 | ||||
|  * Writes data to a file as a hexadecimal array. | ||||
|  * @param file  | ||||
|  * @param data  | ||||
|  * @param data_size  | ||||
|  * @return true on success write  | ||||
|  */ | ||||
| bool flipper_file_write_hex_internal(File* file, const uint8_t* data, const uint16_t data_size) { | ||||
|     const uint8_t byte_text_size = 3; | ||||
|     char byte_text[byte_text_size]; | ||||
| 
 | ||||
|     bool result = true; | ||||
|     uint16_t bytes_written; | ||||
|     for(uint8_t i = 0; i < data_size; i++) { | ||||
|         snprintf(byte_text, byte_text_size, "%02X", data[i]); | ||||
| 
 | ||||
|         if(i != 0) { | ||||
|             // space
 | ||||
|             const char space = ' '; | ||||
|             bytes_written = storage_file_write(file, &space, sizeof(space)); | ||||
|             if(bytes_written != sizeof(space)) { | ||||
|                 result = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         bytes_written = storage_file_write(file, &byte_text, strlen(byte_text)); | ||||
|         if(bytes_written != strlen(byte_text)) { | ||||
|             result = false; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads a valid key from a file as a string. | ||||
|  * After reading, the rw pointer will be on the flipper_file_delimiter symbol. | ||||
|  * Optimized not to read comments and values into RAM. | ||||
|  * @param file  | ||||
|  * @param key  | ||||
|  * @return true on success read  | ||||
|  */ | ||||
| bool flipper_file_read_valid_key(File* file, string_t key) { | ||||
|     string_clean(key); | ||||
|     bool found = false; | ||||
|     bool error = false; | ||||
|     const uint8_t buffer_size = 32; | ||||
|     uint8_t buffer[buffer_size]; | ||||
|     bool accumulate = true; | ||||
|     bool new_line = true; | ||||
| 
 | ||||
|     while(true) { | ||||
|         uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); | ||||
|         if(bytes_were_read == 0) break; | ||||
| 
 | ||||
|         for(uint16_t i = 0; i < bytes_were_read; i++) { | ||||
|             if(buffer[i] == flipper_file_eoln) { | ||||
|                 // EOL found, clean data, start accumulating data and set the new_line flag
 | ||||
|                 string_clean(key); | ||||
|                 accumulate = true; | ||||
|                 new_line = true; | ||||
|             } else if(buffer[i] == flipper_file_eolr) { | ||||
|                 // Ignore
 | ||||
|             } else if(buffer[i] == flipper_file_comment && new_line) { | ||||
|                 // if there is a comment character and we are at the beginning of a new line
 | ||||
|                 // do not accumulate comment data and reset the new_line flag
 | ||||
|                 accumulate = false; | ||||
|                 new_line = false; | ||||
|             } else if(buffer[i] == flipper_file_delimiter) { | ||||
|                 if(new_line) { | ||||
|                     // we are on a "new line" and found the delimiter
 | ||||
|                     // this can only be if we have previously found some kind of key, so
 | ||||
|                     // clear the data, set the flag that we no longer want to accumulate data
 | ||||
|                     // and reset the new_line flag
 | ||||
|                     string_clean(key); | ||||
|                     accumulate = false; | ||||
|                     new_line = false; | ||||
|                 } else { | ||||
|                     // parse the delimiter only if we are accumulating data
 | ||||
|                     if(accumulate) { | ||||
|                         // we found the delimiter, move the rw pointer to the correct location
 | ||||
|                         // and signal that we have found something
 | ||||
|                         // TODO negative seek
 | ||||
|                         uint64_t position = storage_file_tell(file); | ||||
|                         position = position - bytes_were_read + i; | ||||
|                         if(!storage_file_seek(file, position, true)) { | ||||
|                             error = true; | ||||
|                             break; | ||||
|                         } | ||||
| 
 | ||||
|                         found = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 // just new symbol, reset the new_line flag
 | ||||
|                 new_line = false; | ||||
|                 if(accumulate) { | ||||
|                     // and accumulate data if we want
 | ||||
|                     string_push_back(key, buffer[i]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(found || error) break; | ||||
|     } | ||||
| 
 | ||||
|     return found; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Sets rw pointer to the data after the key | ||||
|  * @param file  | ||||
|  * @param key  | ||||
|  * @return true if key was found  | ||||
|  */ | ||||
| bool flipper_file_seek_to_key(File* file, const char* key) { | ||||
|     bool found = false; | ||||
|     string_t readed_key; | ||||
| 
 | ||||
|     string_init(readed_key); | ||||
| 
 | ||||
|     // TODO optimize this to search from a stored rw pointer
 | ||||
|     if(storage_file_seek(file, 0, true)) { | ||||
|         while(!storage_file_eof(file)) { | ||||
|             if(flipper_file_read_valid_key(file, readed_key)) { | ||||
|                 if(string_cmp_str(readed_key, key) == 0) { | ||||
|                     uint64_t position = storage_file_tell(file); | ||||
|                     if(!storage_file_seek(file, position + 2, true)) break; | ||||
| 
 | ||||
|                     found = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     string_clear(readed_key); | ||||
| 
 | ||||
|     return found; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads data as a string from the stored rw pointer to the \r or \n symbol position | ||||
|  * @param file  | ||||
|  * @param str_result  | ||||
|  * @return true on success read | ||||
|  */ | ||||
| bool flipper_file_read_until(File* file, string_t str_result) { | ||||
|     string_clean(str_result); | ||||
|     const uint8_t buffer_size = 32; | ||||
|     uint8_t buffer[buffer_size]; | ||||
| 
 | ||||
|     do { | ||||
|         uint16_t bytes_were_read = storage_file_read(file, buffer, buffer_size); | ||||
|         if(bytes_were_read == 0) break; | ||||
| 
 | ||||
|         bool result = false; | ||||
|         bool error = false; | ||||
|         for(uint16_t i = 0; i < bytes_were_read; i++) { | ||||
|             if(buffer[i] == flipper_file_eoln) { | ||||
|                 // TODO negative seek
 | ||||
|                 uint64_t position = storage_file_tell(file); | ||||
|                 position = position - bytes_were_read + i; | ||||
|                 if(!storage_file_seek(file, position, true)) { | ||||
|                     error = true; | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 result = true; | ||||
|                 break; | ||||
|             } else if(buffer[i] == flipper_file_eolr) { | ||||
|                 // Ignore
 | ||||
|             } else { | ||||
|                 string_push_back(str_result, buffer[i]); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(result || error) { | ||||
|             break; | ||||
|         } | ||||
|     } while(true); | ||||
| 
 | ||||
|     return string_size(str_result) != 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads single hexadecimal data from a file to byte | ||||
|  * @param file  | ||||
|  * @param byte  | ||||
|  * @return bool  | ||||
|  */ | ||||
| bool flipper_file_read_hex_byte(File* file, uint8_t* byte) { | ||||
|     uint8_t hi_nibble_value, low_nibble_value; | ||||
|     uint8_t text[3]; | ||||
|     bool result = false; | ||||
| 
 | ||||
|     uint16_t bytes_were_read = storage_file_read(file, text, 3); | ||||
|     if(bytes_were_read >= 2) { | ||||
|         if(text[0] != ' ') { | ||||
|             if(hex_char_to_hex_nibble(text[0], &hi_nibble_value) && | ||||
|                hex_char_to_hex_nibble(text[1], &low_nibble_value)) { | ||||
|                 *byte = (hi_nibble_value << 4) | low_nibble_value; | ||||
|                 result = true; | ||||
|             } | ||||
|         } else { | ||||
|             if(hex_char_to_hex_nibble(text[1], &hi_nibble_value) && | ||||
|                hex_char_to_hex_nibble(text[2], &low_nibble_value)) { | ||||
|                 *byte = (hi_nibble_value << 4) | low_nibble_value; | ||||
|                 result = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| FlipperFile* flipper_file_alloc(Storage* storage) { | ||||
|     FlipperFile* flipper_file = malloc(sizeof(FlipperFile)); | ||||
|     flipper_file->file = storage_file_alloc(storage); | ||||
|     return flipper_file; | ||||
| } | ||||
| 
 | ||||
| void flipper_file_free(FlipperFile* flipper_file) { | ||||
|     furi_assert(flipper_file); | ||||
|     if(storage_file_is_open(flipper_file->file)) { | ||||
|         storage_file_close(flipper_file->file); | ||||
|     } | ||||
|     storage_file_free(flipper_file->file); | ||||
|     free(flipper_file); | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_open_read(FlipperFile* flipper_file, const char* filename) { | ||||
|     furi_assert(flipper_file); | ||||
|     bool result = storage_file_open(flipper_file->file, filename, FSAM_READ, FSOM_OPEN_EXISTING); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename) { | ||||
|     furi_assert(flipper_file); | ||||
|     bool result = storage_file_open(flipper_file->file, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_close(FlipperFile* flipper_file) { | ||||
|     furi_assert(flipper_file); | ||||
|     if(storage_file_is_open(flipper_file->file)) { | ||||
|         return storage_file_close(flipper_file->file); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_read_header(FlipperFile* flipper_file, string_t filetype, uint32_t* version) { | ||||
|     bool result = false; | ||||
|     do { | ||||
|         result = flipper_file_read_string(flipper_file, flipper_file_filetype_key, filetype); | ||||
|         if(!result) break; | ||||
|         result = flipper_file_read_uint32(flipper_file, flipper_file_version_key, version); | ||||
|         if(!result) break; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_header( | ||||
|     FlipperFile* flipper_file, | ||||
|     string_t filetype, | ||||
|     const uint32_t version) { | ||||
|     bool result = false; | ||||
|     do { | ||||
|         result = flipper_file_write_string(flipper_file, flipper_file_filetype_key, filetype); | ||||
|         if(!result) break; | ||||
|         result = flipper_file_write_uint32(flipper_file, flipper_file_version_key, version); | ||||
|         if(!result) break; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_header_cstr( | ||||
|     FlipperFile* flipper_file, | ||||
|     const char* filetype, | ||||
|     const uint32_t version) { | ||||
|     bool result = false; | ||||
|     string_t value; | ||||
|     string_init_set(value, filetype); | ||||
|     result = flipper_file_write_header(flipper_file, value, version); | ||||
|     string_clear(value); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_read_string(FlipperFile* flipper_file, const char* key, string_t data) { | ||||
|     furi_assert(flipper_file); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     if(flipper_file_seek_to_key(flipper_file->file, key)) { | ||||
|         if(flipper_file_read_until(flipper_file->file, data)) { | ||||
|             result = true; | ||||
|         } | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, string_t data) { | ||||
|     furi_assert(flipper_file); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     do { | ||||
|         uint16_t bytes_written; | ||||
|         bytes_written = storage_file_write(flipper_file->file, key, strlen(key)); | ||||
|         if(bytes_written != strlen(key)) break; | ||||
| 
 | ||||
|         const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer)); | ||||
|         if(bytes_written != sizeof(delimiter_buffer)) break; | ||||
| 
 | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data)); | ||||
|         if(bytes_written != string_size(data)) break; | ||||
| 
 | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); | ||||
|         if(bytes_written != sizeof(flipper_file_eoln)) break; | ||||
| 
 | ||||
|         result = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data) { | ||||
|     bool result = false; | ||||
|     string_t value; | ||||
|     string_init_set(value, data); | ||||
|     result = flipper_file_write_string(flipper_file, key, value); | ||||
|     string_clear(value); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_read_uint32(FlipperFile* flipper_file, const char* key, uint32_t* data) { | ||||
|     bool result = false; | ||||
|     string_t value; | ||||
|     string_init(value); | ||||
| 
 | ||||
|     result = flipper_file_read_string(flipper_file, key, value); | ||||
|     if(result) { | ||||
|         int ret = sscanf(string_get_cstr(value), "%" PRIu32, data); | ||||
|         if(ret != 1) result = false; | ||||
|     } | ||||
| 
 | ||||
|     string_clear(value); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_uint32(FlipperFile* flipper_file, const char* key, const uint32_t data) { | ||||
|     bool result = false; | ||||
|     string_t value; | ||||
|     string_init_printf(value, "%" PRIu32, data); | ||||
|     result = flipper_file_write_string(flipper_file, key, value); | ||||
|     string_clear(value); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data) { | ||||
|     furi_assert(flipper_file); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     do { | ||||
|         uint16_t bytes_written; | ||||
|         const char comment_buffer[2] = {flipper_file_comment, ' '}; | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, comment_buffer, sizeof(comment_buffer)); | ||||
|         if(bytes_written != sizeof(comment_buffer)) break; | ||||
| 
 | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, string_get_cstr(data), string_size(data)); | ||||
|         if(bytes_written != string_size(data)) break; | ||||
| 
 | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); | ||||
|         if(bytes_written != sizeof(flipper_file_eoln)) break; | ||||
| 
 | ||||
|         result = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data) { | ||||
|     bool result = false; | ||||
|     string_t value; | ||||
|     string_init_set(value, data); | ||||
|     result = flipper_file_write_comment(flipper_file, value); | ||||
|     string_clear(value); | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_write_hex_array( | ||||
|     FlipperFile* flipper_file, | ||||
|     const char* key, | ||||
|     const uint8_t* data, | ||||
|     const uint16_t data_size) { | ||||
|     furi_assert(flipper_file); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     do { | ||||
|         uint16_t bytes_written; | ||||
|         bytes_written = storage_file_write(flipper_file->file, key, strlen(key)); | ||||
|         if(bytes_written != strlen(key)) break; | ||||
| 
 | ||||
|         const char delimiter_buffer[2] = {flipper_file_delimiter, ' '}; | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, delimiter_buffer, sizeof(delimiter_buffer)); | ||||
|         if(bytes_written != sizeof(delimiter_buffer)) break; | ||||
| 
 | ||||
|         if(!flipper_file_write_hex_internal(flipper_file->file, data, data_size)) break; | ||||
| 
 | ||||
|         bytes_written = | ||||
|             storage_file_write(flipper_file->file, &flipper_file_eoln, sizeof(flipper_file_eoln)); | ||||
|         if(bytes_written != sizeof(flipper_file_eoln)) break; | ||||
| 
 | ||||
|         result = true; | ||||
|     } while(false); | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| bool flipper_file_read_hex_array( | ||||
|     FlipperFile* flipper_file, | ||||
|     const char* key, | ||||
|     uint8_t* data, | ||||
|     const uint16_t data_size) { | ||||
|     furi_assert(flipper_file); | ||||
| 
 | ||||
|     bool result = false; | ||||
|     if(flipper_file_seek_to_key(flipper_file->file, key)) { | ||||
|         result = true; | ||||
|         for(uint16_t i = 0; i < data_size; i++) { | ||||
|             if(!flipper_file_read_hex_byte(flipper_file->file, &data[i])) { | ||||
|                 result = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
							
								
								
									
										267
									
								
								lib/toolbox/flipper-file.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								lib/toolbox/flipper-file.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,267 @@ | ||||
| /**
 | ||||
|  * @file flipper-file.h | ||||
|  * Flipper File Format helper library. | ||||
|  *  | ||||
|  * Flipper File Format is a fairly simple format for storing data in a file. | ||||
|  *  | ||||
|  * Flipper file structure: | ||||
|  *  | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  * # Commentary | ||||
|  * Field name: field value | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  *  | ||||
|  * Lines starting with the # character are ignored (considered as comments). The separator between the name of the value and the value itself is the string ": ". | ||||
|  * | ||||
|  * Currently supported types: | ||||
|  *  | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  * String: text | ||||
|  * Uint32: 1 | ||||
|  * Hex Array: A4 B3 C2 D1 12 FF | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  *  | ||||
|  * End of line is LF when writing, but CR is supported when reading. | ||||
|  *  | ||||
|  * The library is designed in such a way that comments and field values are completely ignored when searching for keys, that is, they do not consume memory. | ||||
|  *  | ||||
|  * File example:  | ||||
|  *  | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  * Filetype: Flipper Test File | ||||
|  * Version: 1 | ||||
|  * # Just test file | ||||
|  * String: String value | ||||
|  * UINT: 1234 | ||||
|  * Hex Array: 00 01 FF A3 | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  *  | ||||
|  * Writing: | ||||
|  *  | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  * FlipperFile file = flipper_file_alloc(storage); | ||||
|  *  | ||||
|  * do { | ||||
|  *     const uint32_t version = 1; | ||||
|  *     const char* string_value = "String value"; | ||||
|  *     const uint32_t uint32_value = 1234; | ||||
|  *     const uint16_t array_size = 4; | ||||
|  *     const uint8_t* array[array_size] = {0x00, 0x01, 0xFF, 0xA3}; | ||||
|  *      | ||||
|  *     if(!flipper_file_new_write(file, "/ext/flipper_file_test")) break; | ||||
|  *     if(!flipper_file_write_header_cstr(file, "Flipper Test File", version)) break; | ||||
|  *     if(!flipper_file_write_comment_cstr(file, "Just test file")) break; | ||||
|  *     if(!flipper_file_write_string_cstr(file, "String", string_value)) break; | ||||
|  *     if(!flipper_file_flipper_file_write_uint32(file, "UINT", uint32_value)) break; | ||||
|  *     if(!flipper_file_write_hex_array(file, "Hex Array", array, array_size)) break; | ||||
|  *      | ||||
|  *     // signal that the file was written successfully
 | ||||
|  * } while(0); | ||||
|  *  | ||||
|  * flipper_file_close(file); | ||||
|  * flipper_file_free(file); | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  *  | ||||
|  * Reading: | ||||
|  *  | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  * FlipperFile file = flipper_file_alloc(storage); | ||||
|  *  | ||||
|  * do { | ||||
|  *     uint32_t version = 1; | ||||
|  *     string_t file_type; | ||||
|  *     string_t string_value; | ||||
|  *     uint32_t uint32_value = 1; | ||||
|  *     uint16_t array_size = 4; | ||||
|  *     uint8_t* array[array_size] = {0}; | ||||
|  *     string_init(file_type); | ||||
|  *     string_init(string_value); | ||||
|  *      | ||||
|  *     if(!flipper_file_open_read(file, "/ext/flipper_file_test")) break; | ||||
|  *     if(!flipper_file_read_header(file, file_type, &version)) break; | ||||
|  *     if(!flipper_file_read_string(file, "String", string_value)) break; | ||||
|  *     if(!flipper_file_read_uint32(file, "UINT", &uint32_value)) break; | ||||
|  *     if(!flipper_file_read_hex_array(file, "Hex Array", array, array_size)) break; | ||||
|  *      | ||||
|  *     // signal that the file was read successfully
 | ||||
|  * } while(0); | ||||
|  *  | ||||
|  * flipper_file_close(file); | ||||
|  * flipper_file_free(file); | ||||
|  * ~~~~~~~~~~~~~~~~~~~~~ | ||||
|  *  | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| #include <stdint.h> | ||||
| #include <mlib/m-string.h> | ||||
| #include <storage/storage.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| /** FlipperFile type anonymous structure. */ | ||||
| typedef struct FlipperFile FlipperFile; | ||||
| 
 | ||||
| /**
 | ||||
|  * Allocate FlipperFile. | ||||
|  * @param storage storage api | ||||
|  * @return FlipperFile* Pointer to a FlipperFile instance | ||||
|  */ | ||||
| FlipperFile* flipper_file_alloc(Storage* storage); | ||||
| 
 | ||||
| /**
 | ||||
|  * Free FlipperFile. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  */ | ||||
| void flipper_file_free(FlipperFile* flipper_file); | ||||
| 
 | ||||
| /**
 | ||||
|  * Open file for reading. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param filename File name and path | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_open_read(FlipperFile* flipper_file, const char* filename); | ||||
| 
 | ||||
| /**
 | ||||
|  * Open file for writing. Creates a new file, or deletes the contents of the file if it already exists. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param filename File name and path | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_new_write(FlipperFile* flipper_file, const char* filename); | ||||
| 
 | ||||
| /**
 | ||||
|  * Close the file. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_close(FlipperFile* flipper_file); | ||||
| 
 | ||||
| /**
 | ||||
|  * Read the header (file type and version) from the file. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param filetype File type string | ||||
|  * @param version Version Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_read_header(FlipperFile* flipper_file, string_t filetype, uint32_t* version); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write the header (file type and version) to the file. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param filetype File type string | ||||
|  * @param version Version Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_header( | ||||
|     FlipperFile* flipper_file, | ||||
|     string_t filetype, | ||||
|     const uint32_t version); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write the header (file type and version) to the file. Plain C string version. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param filetype File type string | ||||
|  * @param version Version Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_header_cstr( | ||||
|     FlipperFile* flipper_file, | ||||
|     const char* filetype, | ||||
|     const uint32_t version); | ||||
| 
 | ||||
| /**
 | ||||
|  * Read a string from a file by Key | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param key Key | ||||
|  * @param data Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_read_string(FlipperFile* flipper_file, const char* key, string_t data); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write key and string to file. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param key Key | ||||
|  * @param data Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_string(FlipperFile* flipper_file, const char* key, string_t data); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write key and string to file. Plain C string version. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param key Key | ||||
|  * @param data Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_string_cstr(FlipperFile* flipper_file, const char* key, const char* data); | ||||
| 
 | ||||
| /**
 | ||||
|  * Read uint32 from a file by Key | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param key Key | ||||
|  * @param data Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_read_uint32(FlipperFile* flipper_file, const char* key, uint32_t* data); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write key and uint32 to file. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param key Key | ||||
|  * @param data Value | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_uint32(FlipperFile* flipper_file, const char* key, const uint32_t data); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write comment to file. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param data Comment text | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_comment(FlipperFile* flipper_file, string_t data); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write comment to file. Plain C string version. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param data Comment text | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_comment_cstr(FlipperFile* flipper_file, const char* data); | ||||
| 
 | ||||
| /**
 | ||||
|  * Read hex array from a file by Key | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param key Key | ||||
|  * @param data Value | ||||
|  * @param data_size Value size | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_read_hex_array( | ||||
|     FlipperFile* flipper_file, | ||||
|     const char* key, | ||||
|     uint8_t* data, | ||||
|     const uint16_t data_size); | ||||
| 
 | ||||
| /**
 | ||||
|  * Write key and hex array to file. | ||||
|  * @param flipper_file Pointer to a FlipperFile instance | ||||
|  * @param key Key | ||||
|  * @param data Value | ||||
|  * @param data_size Value size | ||||
|  * @return True on success | ||||
|  */ | ||||
| bool flipper_file_write_hex_array( | ||||
|     FlipperFile* flipper_file, | ||||
|     const char* key, | ||||
|     const uint8_t* data, | ||||
|     const uint16_t data_size); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 SG
						SG