[FL-1489] IRDA: move to FileWorker (#594)
* [FL-1489] IRDA: move to FileWorker, fixes * Use FileWorker * Use file_select to select remotes * Fix some crashes * Add RAW parsing restrictions * Remove excess scene (LearnDoneAfter) * Move all file system logic to standalone object
This commit is contained in:
		
							parent
							
								
									b886ae17b6
								
							
						
					
					
						commit
						769ab2aef2
					
				| @ -137,6 +137,10 @@ void button_panel_clean(ButtonPanel* button_panel) { | |||||||
|                     *button_item = NULL; |                     *button_item = NULL; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             model->reserve_x = 0; | ||||||
|  |             model->reserve_y = 0; | ||||||
|  |             LabelList_clean(model->labels); | ||||||
|  |             ButtonMatrix_clean(model->button_matrix); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,4 +1,8 @@ | |||||||
| #include "irda-app-brute-force.hpp" | #include "irda-app-brute-force.hpp" | ||||||
|  | #include "irda/irda-app-file-parser.hpp" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <file-worker-cpp.h> | ||||||
|  | #include <memory> | ||||||
| 
 | 
 | ||||||
| void IrdaAppBruteForce::add_record(int index, const char* name) { | void IrdaAppBruteForce::add_record(int index, const char* name) { | ||||||
|     records[name].index = index; |     records[name].index = index; | ||||||
| @ -7,43 +11,51 @@ void IrdaAppBruteForce::add_record(int index, const char* name) { | |||||||
| 
 | 
 | ||||||
| bool IrdaAppBruteForce::calculate_messages() { | bool IrdaAppBruteForce::calculate_messages() { | ||||||
|     bool fs_res = false; |     bool fs_res = false; | ||||||
|     fs_res = file_parser.get_fs_api().file.open( |     furi_assert(!file_parser); | ||||||
|         &file, universal_db_filename, FSAM_READ, FSOM_OPEN_EXISTING); | 
 | ||||||
|  |     file_parser = std::make_unique<IrdaAppFileParser>(); | ||||||
|  |     fs_res = file_parser->open_irda_file_read(universal_db_filename); | ||||||
|     if(!fs_res) { |     if(!fs_res) { | ||||||
|         file_parser.get_sd_api().show_error(file_parser.get_sd_api().context, "Can't open file"); |         file_parser.reset(nullptr); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     file_parser.reset(); |  | ||||||
|     while(1) { |     while(1) { | ||||||
|         auto message = file_parser.read_signal(&file); |         auto file_signal = file_parser->read_signal(); | ||||||
|         if(!message) break; |         if(!file_signal) break; | ||||||
|         auto element = records.find(message->name); | 
 | ||||||
|  |         auto element = records.find(file_signal->name); | ||||||
|         if(element != records.cend()) { |         if(element != records.cend()) { | ||||||
|             ++element->second.amount; |             ++element->second.amount; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     file_parser.get_fs_api().file.close(&file); |     file_parser->close(); | ||||||
|  |     file_parser.reset(nullptr); | ||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void IrdaAppBruteForce::stop_bruteforce() { | void IrdaAppBruteForce::stop_bruteforce() { | ||||||
|  |     furi_assert((current_record.size())); | ||||||
|  | 
 | ||||||
|     if(current_record.size()) { |     if(current_record.size()) { | ||||||
|         file_parser.get_fs_api().file.close(&file); |         furi_assert(file_parser); | ||||||
|         current_record.clear(); |         current_record.clear(); | ||||||
|  |         file_parser->close(); | ||||||
|  |         file_parser.reset(nullptr); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: [FL-1418] replace with timer-chained consequence of messages.
 | // TODO: [FL-1418] replace with timer-chained consequence of messages.
 | ||||||
| bool IrdaAppBruteForce::send_next_bruteforce(void) { | bool IrdaAppBruteForce::send_next_bruteforce(void) { | ||||||
|     furi_assert(current_record.size()); |     furi_assert(current_record.size()); | ||||||
|  |     furi_assert(file_parser); | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> file_signal; |     std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> file_signal; | ||||||
| 
 | 
 | ||||||
|     do { |     do { | ||||||
|         file_signal = file_parser.read_signal(&file); |         file_signal = file_parser->read_signal(); | ||||||
|     } while(file_signal && current_record.compare(file_signal->name)); |     } while(file_signal && current_record.compare(file_signal->name)); | ||||||
| 
 | 
 | ||||||
|     if(file_signal) { |     if(file_signal) { | ||||||
| @ -53,25 +65,26 @@ bool IrdaAppBruteForce::send_next_bruteforce(void) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppBruteForce::start_bruteforce(int index, int& record_amount) { | bool IrdaAppBruteForce::start_bruteforce(int index, int& record_amount) { | ||||||
|     file_parser.reset(); |     bool result = false; | ||||||
|  |     record_amount = 0; | ||||||
|  | 
 | ||||||
|     for(const auto& it : records) { |     for(const auto& it : records) { | ||||||
|         if(it.second.index == index) { |         if(it.second.index == index) { | ||||||
|             record_amount = it.second.amount; |             record_amount = it.second.amount; | ||||||
|             current_record = it.first; |             if(record_amount) { | ||||||
|  |                 current_record = it.first; | ||||||
|  |             } | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(record_amount) { |     if(record_amount) { | ||||||
|         bool fs_res = file_parser.get_fs_api().file.open( |         file_parser = std::make_unique<IrdaAppFileParser>(); | ||||||
|             &file, universal_db_filename, FSAM_READ, FSOM_OPEN_EXISTING); |         result = file_parser->open_irda_file_read(universal_db_filename); | ||||||
|         if(fs_res) { |         if(!result) { | ||||||
|             return true; |             (void)file_parser.reset(nullptr); | ||||||
|         } else { |  | ||||||
|             file_parser.get_sd_api().show_error( |  | ||||||
|                 file_parser.get_sd_api().context, "Can't open file"); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return false; |     return result; | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,13 +2,13 @@ | |||||||
| #include "furi/check.h" | #include "furi/check.h" | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
| #include "irda-app-file-parser.hpp" | #include "irda-app-file-parser.hpp" | ||||||
| 
 | #include <memory> | ||||||
| 
 | 
 | ||||||
| class IrdaAppBruteForce { | class IrdaAppBruteForce { | ||||||
|     const char* universal_db_filename; |     const char* universal_db_filename; | ||||||
|     IrdaAppFileParser file_parser; |  | ||||||
|     File file; |     File file; | ||||||
|     std::string current_record; |     std::string current_record; | ||||||
|  |     std::unique_ptr<IrdaAppFileParser> file_parser; | ||||||
| 
 | 
 | ||||||
|     typedef struct { |     typedef struct { | ||||||
|         int index; |         int index; | ||||||
| @ -30,8 +30,6 @@ public: | |||||||
|     void add_record(int index, const char* name); |     void add_record(int index, const char* name); | ||||||
| 
 | 
 | ||||||
|     IrdaAppBruteForce(const char* filename) : universal_db_filename (filename) {} |     IrdaAppBruteForce(const char* filename) : universal_db_filename (filename) {} | ||||||
|     ~IrdaAppBruteForce() { |     ~IrdaAppBruteForce() {} | ||||||
|         stop_bruteforce(); |  | ||||||
|     } |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -15,7 +15,6 @@ public: | |||||||
|         TextEditDone, |         TextEditDone, | ||||||
|         PopupTimer, |         PopupTimer, | ||||||
|         ButtonPanelPressed, |         ButtonPanelPressed, | ||||||
|         ButtonPanelPopupBackPressed, |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     union { |     union { | ||||||
|  | |||||||
| @ -1,70 +1,139 @@ | |||||||
| #include "irda-app-file-parser.hpp" | #include "irda-app-file-parser.hpp" | ||||||
|  | #include "furi/check.h" | ||||||
| #include "irda-app-remote-manager.hpp" | #include "irda-app-remote-manager.hpp" | ||||||
| #include "irda-app-signal.h" | #include "irda-app-signal.h" | ||||||
|  | #include "m-string.h" | ||||||
|  | #include <text-store.h> | ||||||
| #include <irda.h> | #include <irda.h> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  | #include <string> | ||||||
| #include <string_view> | #include <string_view> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
|  | #include <file-worker-cpp.h> | ||||||
| 
 | 
 | ||||||
| uint32_t const IrdaAppFileParser::max_line_length = ((9 + 1) * 512 + 100); | uint32_t const IrdaAppFileParser::max_line_length = ((9 + 1) * 512 + 100); | ||||||
|  | const char* IrdaAppFileParser::irda_directory = "/irda"; | ||||||
|  | const char* IrdaAppFileParser::irda_extension = ".ir"; | ||||||
|  | uint32_t const IrdaAppFileParser::max_raw_timings_in_signal = 512; | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> IrdaAppFileParser::read_signal(File* file) { | bool IrdaAppFileParser::open_irda_file_read(const char* name) { | ||||||
|     while(1) { |     std::string full_filename; | ||||||
|         auto str = getline(file); |     if(name[0] != '/') | ||||||
|         if(str.empty()) return nullptr; |         full_filename = make_full_name(name); | ||||||
|  |     else | ||||||
|  |         full_filename = name; | ||||||
| 
 | 
 | ||||||
|         auto message = parse_signal(str); |     return file_worker.open(full_filename.c_str(), FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|         if(!message.get()) { |  | ||||||
|             message = parse_signal_raw(str); |  | ||||||
|         } |  | ||||||
|         if(message) return message; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppFileParser::store_signal(File* file, const IrdaAppSignal& signal, const char* name) { | bool IrdaAppFileParser::open_irda_file_write(const char* name) { | ||||||
|     char* content = new char[max_line_length]; |     std::string dirname(irda_directory); | ||||||
|  |     auto full_filename = make_full_name(name); | ||||||
|  | 
 | ||||||
|  |     if(!file_worker.mkdir(dirname.c_str())) return false; | ||||||
|  | 
 | ||||||
|  |     return file_worker.open(full_filename.c_str(), FSAM_WRITE, FSOM_CREATE_ALWAYS); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | size_t IrdaAppFileParser::stringify_message( | ||||||
|  |     const IrdaAppSignal& signal, | ||||||
|  |     const char* name, | ||||||
|  |     char* buf, | ||||||
|  |     size_t buf_size) { | ||||||
|  |     auto message = signal.get_message(); | ||||||
|  |     auto protocol = message.protocol; | ||||||
|     size_t written = 0; |     size_t written = 0; | ||||||
| 
 | 
 | ||||||
|     if(!signal.is_raw()) { |     written += sniprintf( | ||||||
|         auto message = signal.get_message(); |         buf, | ||||||
|         auto protocol = message.protocol; |         buf_size, | ||||||
|  |         "%.31s %.31s A:%0*lX C:%0*lX\n", | ||||||
|  |         name, | ||||||
|  |         irda_get_protocol_name(protocol), | ||||||
|  |         irda_get_protocol_address_length(protocol), | ||||||
|  |         message.address, | ||||||
|  |         irda_get_protocol_command_length(protocol), | ||||||
|  |         message.command); | ||||||
| 
 | 
 | ||||||
|         sniprintf( |     furi_assert(written < buf_size); | ||||||
|             content, |     if(written >= buf_size) { | ||||||
|             max_line_length, |         written = 0; | ||||||
|             "%.31s %.31s A:%0*lX C:%0*lX\n", |  | ||||||
|             name, |  | ||||||
|             irda_get_protocol_name(protocol), |  | ||||||
|             irda_get_protocol_address_length(protocol), |  | ||||||
|             message.address, |  | ||||||
|             irda_get_protocol_command_length(protocol), |  | ||||||
|             message.command); |  | ||||||
|         written = strlen(content); |  | ||||||
|     } else { |  | ||||||
|         int duty_cycle = 100 * IRDA_COMMON_DUTY_CYCLE; |  | ||||||
|         written += sniprintf( |  | ||||||
|             &content[written], |  | ||||||
|             max_line_length - written, |  | ||||||
|             "%.31s RAW F:%d DC:%d", |  | ||||||
|             name, |  | ||||||
|             IRDA_COMMON_CARRIER_FREQUENCY, |  | ||||||
|             duty_cycle); |  | ||||||
| 
 |  | ||||||
|         auto& raw_signal = signal.get_raw_signal(); |  | ||||||
|         for(size_t i = 0; i < raw_signal.timings_cnt; ++i) { |  | ||||||
|             written += sniprintf( |  | ||||||
|                 &content[written], max_line_length - written, " %ld", raw_signal.timings[i]); |  | ||||||
|             furi_assert(written <= max_line_length); |  | ||||||
|         } |  | ||||||
|         written += snprintf(&content[written], max_line_length - written, "\n"); |  | ||||||
|     } |     } | ||||||
|     furi_assert(written < max_line_length); |  | ||||||
| 
 | 
 | ||||||
|     size_t write_count = 0; |     return written; | ||||||
|     write_count = get_fs_api().file.write(file, content, written); | } | ||||||
|     delete[] content; | 
 | ||||||
|     return (file->error_id == FSE_OK) && (write_count == written); | size_t IrdaAppFileParser::stringify_raw_signal( | ||||||
|  |     const IrdaAppSignal& signal, | ||||||
|  |     const char* name, | ||||||
|  |     char* buf, | ||||||
|  |     size_t buf_size) { | ||||||
|  |     size_t written = 0; | ||||||
|  |     int duty_cycle = 100 * IRDA_COMMON_DUTY_CYCLE; | ||||||
|  |     written += sniprintf( | ||||||
|  |         &buf[written], | ||||||
|  |         max_line_length - written, | ||||||
|  |         "%.31s RAW F:%d DC:%d", | ||||||
|  |         name, | ||||||
|  |         IRDA_COMMON_CARRIER_FREQUENCY, | ||||||
|  |         duty_cycle); | ||||||
|  | 
 | ||||||
|  |     auto& raw_signal = signal.get_raw_signal(); | ||||||
|  |     for(size_t i = 0; i < raw_signal.timings_cnt; ++i) { | ||||||
|  |         written += sniprintf(&buf[written], buf_size - written, " %ld", raw_signal.timings[i]); | ||||||
|  |         if(written > buf_size) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     written += snprintf(&buf[written], buf_size - written, "\n"); | ||||||
|  | 
 | ||||||
|  |     furi_assert(written < buf_size); | ||||||
|  |     if(written >= buf_size) { | ||||||
|  |         written = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return written; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool IrdaAppFileParser::save_signal(const IrdaAppSignal& signal, const char* name) { | ||||||
|  |     char* buf = new char[max_line_length]; | ||||||
|  |     size_t buf_cnt = 0; | ||||||
|  |     bool write_result = false; | ||||||
|  | 
 | ||||||
|  |     if(signal.is_raw()) { | ||||||
|  |         buf_cnt = stringify_raw_signal(signal, name, buf, max_line_length); | ||||||
|  |     } else { | ||||||
|  |         buf_cnt = stringify_message(signal, name, buf, max_line_length); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(buf_cnt) { | ||||||
|  |         write_result = file_worker.write(buf, buf_cnt); | ||||||
|  |     } | ||||||
|  |     delete[] buf; | ||||||
|  |     return write_result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> IrdaAppFileParser::read_signal(void) { | ||||||
|  |     string_t line; | ||||||
|  |     string_init(line); | ||||||
|  |     string_reserve(line, max_line_length); | ||||||
|  |     std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> file_signal; | ||||||
|  | 
 | ||||||
|  |     while(!file_signal && | ||||||
|  |           file_worker.read_until_buffered(line, file_buf, &file_buf_cnt, sizeof(file_buf))) { | ||||||
|  |         if(string_empty_p(line)) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         auto c_str = string_get_cstr(line); | ||||||
|  |         file_signal = parse_signal(c_str); | ||||||
|  |         if(!file_signal) { | ||||||
|  |             file_signal = parse_signal_raw(c_str); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     string_clear(line); | ||||||
|  | 
 | ||||||
|  |     return file_signal; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | ||||||
| @ -128,7 +197,6 @@ const char* find_first_not_of(const char* str, char symbol) { | |||||||
| 
 | 
 | ||||||
| std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | ||||||
|     IrdaAppFileParser::parse_signal_raw(const std::string& string) const { |     IrdaAppFileParser::parse_signal_raw(const std::string& string) const { | ||||||
|     char protocol_name[32]; |  | ||||||
|     uint32_t frequency; |     uint32_t frequency; | ||||||
|     uint32_t duty_cycle; |     uint32_t duty_cycle; | ||||||
|     int str_len = string.size(); |     int str_len = string.size(); | ||||||
| @ -136,14 +204,10 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     auto irda_file_signal = std::make_unique<IrdaFileSignal>(); |     auto irda_file_signal = std::make_unique<IrdaFileSignal>(); | ||||||
| 
 | 
 | ||||||
|     int parsed = std::sscanf( |     int parsed = std::sscanf( | ||||||
|         str.data(), |         str.data(), "%31s RAW F:%ld DC:%ld", irda_file_signal->name, &frequency, &duty_cycle); | ||||||
|         "%31s %31s F:%ld DC:%ld", |  | ||||||
|         irda_file_signal->name, |  | ||||||
|         protocol_name, |  | ||||||
|         &frequency, |  | ||||||
|         &duty_cycle); |  | ||||||
| 
 | 
 | ||||||
|     if(parsed != 4) { |     if((parsed != 3) || (frequency > 42000) || (frequency < 32000) || (duty_cycle == 0) || | ||||||
|  |        (duty_cycle >= 100)) { | ||||||
|         return nullptr; |         return nullptr; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -152,9 +216,8 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     header_len = sniprintf( |     header_len = sniprintf( | ||||||
|         dummy, |         dummy, | ||||||
|         sizeof(dummy), |         sizeof(dummy), | ||||||
|         "%.31s %.31s F:%ld DC:%ld", |         "%.31s RAW F:%ld DC:%ld", | ||||||
|         irda_file_signal->name, |         irda_file_signal->name, | ||||||
|         protocol_name, |  | ||||||
|         frequency, |         frequency, | ||||||
|         duty_cycle); |         duty_cycle); | ||||||
| 
 | 
 | ||||||
| @ -162,39 +225,94 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> | |||||||
|     str.remove_prefix(header_len); |     str.remove_prefix(header_len); | ||||||
| 
 | 
 | ||||||
|     /* move allocated timings into raw signal object */ |     /* move allocated timings into raw signal object */ | ||||||
|     IrdaAppSignal::RawSignal raw_signal = {.timings_cnt = 0, .timings = new uint32_t[500]}; |     IrdaAppSignal::RawSignal raw_signal = { | ||||||
|  |         .timings_cnt = 0, .timings = new uint32_t[max_raw_timings_in_signal]}; | ||||||
|     bool result = false; |     bool result = false; | ||||||
| 
 | 
 | ||||||
|     while(!str.empty()) { |     while(!str.empty()) { | ||||||
|         char buf[10]; |         char buf[10]; | ||||||
|         size_t index = str.find_first_not_of(' ', 1); |         size_t index = str.find_first_not_of(' ', 1); | ||||||
|         if(index == std::string_view::npos) { |         if(index == std::string_view::npos) { | ||||||
|             result = true; |  | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         str.remove_prefix(index); |         str.remove_prefix(index); | ||||||
|         parsed = std::sscanf(str.data(), "%9s", buf); |         parsed = std::sscanf(str.data(), "%9s", buf); | ||||||
|         if(parsed != 1) { |         if(parsed != 1) { | ||||||
|  |             result = false; | ||||||
|  |             furi_assert(0); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         str.remove_prefix(strlen(buf)); |         str.remove_prefix(strlen(buf)); | ||||||
| 
 | 
 | ||||||
|         int value = atoi(buf); |         int value = atoi(buf); | ||||||
|         if(value <= 0) { |         if(value <= 0) { | ||||||
|  |             result = false; | ||||||
|  |             furi_assert(0); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         raw_signal.timings[raw_signal.timings_cnt] = value; |         raw_signal.timings[raw_signal.timings_cnt] = value; | ||||||
|         ++raw_signal.timings_cnt; |         ++raw_signal.timings_cnt; | ||||||
|         if(raw_signal.timings_cnt >= 500) { |         result = true; | ||||||
|  |         if(raw_signal.timings_cnt >= max_raw_timings_in_signal) { | ||||||
|  |             result = false; | ||||||
|  |             furi_assert(0); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(result) { |     if(result) { | ||||||
|         irda_file_signal->signal.set_raw_signal(raw_signal.timings, raw_signal.timings_cnt); |         /* copy timings instead of moving them to occupy less than max_raw_timings_in_signal */ | ||||||
|  |         irda_file_signal->signal.copy_raw_signal(raw_signal.timings, raw_signal.timings_cnt); | ||||||
|     } else { |     } else { | ||||||
|         (void)irda_file_signal.release(); |         (void)irda_file_signal.release(); | ||||||
|         delete[] raw_signal.timings; |  | ||||||
|     } |     } | ||||||
|  |     delete[] raw_signal.timings; | ||||||
|     return irda_file_signal; |     return irda_file_signal; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool IrdaAppFileParser::is_irda_file_exist(const char* name, bool* exist) { | ||||||
|  |     std::string full_path = make_full_name(name); | ||||||
|  |     return file_worker.is_file_exist(full_path.c_str(), exist); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string IrdaAppFileParser::make_full_name(const std::string& remote_name) const { | ||||||
|  |     return std::string("") + irda_directory + "/" + remote_name + irda_extension; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string IrdaAppFileParser::make_name(const std::string& full_name) const { | ||||||
|  |     std::string str(full_name, full_name.find_last_of('/') + 1, full_name.size()); | ||||||
|  |     str.erase(str.find_last_of('.')); | ||||||
|  | 
 | ||||||
|  |     return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool IrdaAppFileParser::remove_irda_file(const char* name) { | ||||||
|  |     std::string full_filename = make_full_name(name); | ||||||
|  |     return file_worker.remove(full_filename.c_str()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool IrdaAppFileParser::rename_irda_file(const char* old_name, const char* new_name) { | ||||||
|  |     std::string old_filename = make_full_name(old_name); | ||||||
|  |     std::string new_filename = make_full_name(new_name); | ||||||
|  |     return file_worker.rename(old_filename.c_str(), new_filename.c_str()); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool IrdaAppFileParser::close() { | ||||||
|  |     return file_worker.close(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool IrdaAppFileParser::check_errors() { | ||||||
|  |     return file_worker.check_errors(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::string IrdaAppFileParser::file_select(const char* selected) { | ||||||
|  |     TextStore* filename_ts = new TextStore(128); | ||||||
|  |     bool result; | ||||||
|  | 
 | ||||||
|  |     result = file_worker.file_select( | ||||||
|  |         irda_directory, irda_extension, filename_ts->text, filename_ts->text_size, selected); | ||||||
|  | 
 | ||||||
|  |     delete filename_ts; | ||||||
|  | 
 | ||||||
|  |     return result ? std::string(filename_ts->text) : std::string(); | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,26 +1,44 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include <file_reader/file_reader.h> | #include <file_reader/file_reader.h> | ||||||
| #include <irda.h> | #include <irda.h> | ||||||
| #include "irda-app-remote-manager.hpp" | #include <file-worker-cpp.h> | ||||||
|  | #include "irda-app-signal.h" | ||||||
| 
 | 
 | ||||||
| class IrdaAppFileParser : public FileReader { | class IrdaAppFileParser { | ||||||
| public: | public: | ||||||
|     typedef struct { |     typedef struct { | ||||||
|         char name[32]; |         char name[32]; | ||||||
|         IrdaAppSignal signal; |         IrdaAppSignal signal; | ||||||
|     } IrdaFileSignal; |     } IrdaFileSignal; | ||||||
| 
 | 
 | ||||||
|     IrdaAppFileParser() { |     bool open_irda_file_read(const char* filename); | ||||||
|         /* Assume we can save max 512 samples */ |     bool open_irda_file_write(const char* filename); | ||||||
|         set_max_line_length(max_line_length); |     bool is_irda_file_exist(const char* filename, bool* exist); | ||||||
|     } |     bool rename_irda_file(const char* filename, const char* newname); | ||||||
|  |     bool remove_irda_file(const char* name); | ||||||
|  |     bool close(); | ||||||
|  |     bool check_errors(); | ||||||
| 
 | 
 | ||||||
|     std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> read_signal(File* file); |     std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> read_signal(); | ||||||
|     bool store_signal(File* file, const IrdaAppSignal& signal, const char* name); |     bool save_signal(const IrdaAppSignal& signal, const char* name); | ||||||
|  |     std::string file_select(const char* selected); | ||||||
|  | 
 | ||||||
|  |     std::string make_name(const std::string& full_name) const; | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     static const uint32_t max_line_length; |     size_t stringify_message(const IrdaAppSignal& signal, const char* name, char* content, size_t content_len); | ||||||
|  |     size_t stringify_raw_signal(const IrdaAppSignal& signal, const char* name, char* content, size_t content_len); | ||||||
|     std::unique_ptr<IrdaFileSignal> parse_signal(const std::string& str) const; |     std::unique_ptr<IrdaFileSignal> parse_signal(const std::string& str) const; | ||||||
|     std::unique_ptr<IrdaFileSignal> parse_signal_raw(const std::string& str) const; |     std::unique_ptr<IrdaFileSignal> parse_signal_raw(const std::string& str) const; | ||||||
|  |     std::string make_full_name(const std::string& name) const; | ||||||
|  | 
 | ||||||
|  |     static const char* irda_directory; | ||||||
|  |     static const char* irda_extension; | ||||||
|  |     static const uint32_t max_line_length; | ||||||
|  |     static uint32_t const max_raw_timings_in_signal; | ||||||
|  | 
 | ||||||
|  |     FileWorkerCpp file_worker; | ||||||
|  |     char file_buf[128]; | ||||||
|  |     size_t file_buf_cnt = 0; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,32 +5,29 @@ | |||||||
| #include "gui/modules/button_menu.h" | #include "gui/modules/button_menu.h" | ||||||
| #include "irda.h" | #include "irda.h" | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  | #include <stdint.h> | ||||||
| #include <string> | #include <string> | ||||||
| #include <utility> | #include <utility> | ||||||
| #include "irda-app-file-parser.hpp" | #include "irda-app-file-parser.hpp" | ||||||
| 
 | 
 | ||||||
| const char* IrdaAppRemoteManager::irda_directory = "irda"; |  | ||||||
| const char* IrdaAppRemoteManager::irda_extension = ".ir"; |  | ||||||
| static const std::string default_remote_name = "remote"; | static const std::string default_remote_name = "remote"; | ||||||
| 
 | 
 | ||||||
| static bool find_string(const std::vector<std::string>& strings, const std::string& match_string) { | std::string IrdaAppRemoteManager::find_vacant_remote_name(const std::string& name) { | ||||||
|     for(const auto& string : strings) { |     IrdaAppFileParser file_parser; | ||||||
|         if(!string.compare(match_string)) return true; |     bool exist = true; | ||||||
|     } |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| static std::string |     if(!file_parser.is_irda_file_exist(name.c_str(), &exist)) { | ||||||
|     find_vacant_name(const std::vector<std::string>& strings, const std::string& name) { |         return std::string(); | ||||||
|     // if suggested name is occupied, try another one (name2, name3, etc)
 |     } else if(!exist) { | ||||||
|     if(find_string(strings, name)) { |  | ||||||
|         int i = 1; |  | ||||||
|         while(find_string(strings, name + std::to_string(++i))) |  | ||||||
|             ; |  | ||||||
|         return name + std::to_string(i); |  | ||||||
|     } else { |  | ||||||
|         return name; |         return name; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     uint32_t i = 1; | ||||||
|  |     /* if suggested name is occupied, try another one (name2, name3, etc) */ | ||||||
|  |     while(file_parser.is_irda_file_exist((name + std::to_string(++i)).c_str(), &exist) && exist) | ||||||
|  |         ; | ||||||
|  | 
 | ||||||
|  |     return !exist ? name + std::to_string(i) : std::string(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppRemoteManager::add_button(const char* button_name, const IrdaAppSignal& signal) { | bool IrdaAppRemoteManager::add_button(const char* button_name, const IrdaAppSignal& signal) { | ||||||
| @ -43,11 +40,10 @@ bool IrdaAppRemoteManager::add_remote_with_button( | |||||||
|     const IrdaAppSignal& signal) { |     const IrdaAppSignal& signal) { | ||||||
|     furi_check(button_name != nullptr); |     furi_check(button_name != nullptr); | ||||||
| 
 | 
 | ||||||
|     std::vector<std::string> remote_list; |     auto new_name = find_vacant_remote_name(default_remote_name); | ||||||
|     bool result = get_remote_list(remote_list); |     if(new_name.empty()) { | ||||||
|     if(!result) return false; |         return false; | ||||||
| 
 |     } | ||||||
|     auto new_name = find_vacant_name(remote_list, default_remote_name); |  | ||||||
| 
 | 
 | ||||||
|     remote = std::make_unique<IrdaAppRemote>(new_name); |     remote = std::make_unique<IrdaAppRemote>(new_name); | ||||||
|     return add_button(button_name, signal); |     return add_button(button_name, signal); | ||||||
| @ -73,29 +69,17 @@ const IrdaAppSignal& IrdaAppRemoteManager::get_button_data(size_t index) const { | |||||||
|     return buttons.at(index).signal; |     return buttons.at(index).signal; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string IrdaAppRemoteManager::make_full_name(const std::string& remote_name) const { |  | ||||||
|     return std::string("/") + irda_directory + "/" + remote_name + irda_extension; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::string IrdaAppRemoteManager::make_remote_name(const std::string& full_name) const { |  | ||||||
|     std::string str(full_name, full_name.find_last_of('/') + 1, full_name.size()); |  | ||||||
|     str.erase(str.find_last_of('.')); |  | ||||||
| 
 |  | ||||||
|     return str; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool IrdaAppRemoteManager::delete_remote() { | bool IrdaAppRemoteManager::delete_remote() { | ||||||
|     FS_Error fs_res; |     bool result; | ||||||
|     IrdaAppFileParser file_parser; |     IrdaAppFileParser file_parser; | ||||||
| 
 | 
 | ||||||
|     fs_res = file_parser.get_fs_api().common.remove(make_full_name(remote->name).c_str()); |     result = file_parser.remove_irda_file(remote->name.c_str()); | ||||||
|     if(fs_res != FSE_OK) { |     reset_remote(); | ||||||
|         file_parser.get_sd_api().show_error( |     return result; | ||||||
|             file_parser.get_sd_api().context, "Error deleting file"); | } | ||||||
|         return false; | 
 | ||||||
|     } | void IrdaAppRemoteManager::reset_remote() { | ||||||
|     remote.reset(); |     remote.reset(); | ||||||
|     return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppRemoteManager::delete_button(uint32_t index) { | bool IrdaAppRemoteManager::delete_button(uint32_t index) { | ||||||
| @ -111,12 +95,11 @@ std::string IrdaAppRemoteManager::get_button_name(uint32_t index) { | |||||||
|     furi_check(remote.get() != nullptr); |     furi_check(remote.get() != nullptr); | ||||||
|     auto& buttons = remote->buttons; |     auto& buttons = remote->buttons; | ||||||
|     furi_check(index < buttons.size()); |     furi_check(index < buttons.size()); | ||||||
|     return buttons[index].name; |     return buttons[index].name.c_str(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::string IrdaAppRemoteManager::get_remote_name() { | std::string IrdaAppRemoteManager::get_remote_name() { | ||||||
|     furi_check(remote.get() != nullptr); |     return remote ? remote->name : std::string(); | ||||||
|     return remote->name; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| int IrdaAppRemoteManager::find_remote_name(const std::vector<std::string>& strings) { | int IrdaAppRemoteManager::find_remote_name(const std::vector<std::string>& strings) { | ||||||
| @ -134,22 +117,21 @@ bool IrdaAppRemoteManager::rename_remote(const char* str) { | |||||||
|     furi_check(str != nullptr); |     furi_check(str != nullptr); | ||||||
|     furi_check(remote.get() != nullptr); |     furi_check(remote.get() != nullptr); | ||||||
| 
 | 
 | ||||||
|     if(!remote->name.compare(str)) return true; |     if(!remote->name.compare(str)) { | ||||||
| 
 |         return true; | ||||||
|     std::vector<std::string> remote_list; |  | ||||||
|     bool result = get_remote_list(remote_list); |  | ||||||
|     if(!result) return false; |  | ||||||
| 
 |  | ||||||
|     auto new_name = find_vacant_name(remote_list, str); |  | ||||||
|     IrdaAppFileParser file_parser; |  | ||||||
|     FS_Error fs_err = file_parser.get_fs_api().common.rename( |  | ||||||
|         make_full_name(remote->name).c_str(), make_full_name(new_name).c_str()); |  | ||||||
|     remote->name = new_name; |  | ||||||
|     if(fs_err != FSE_OK) { |  | ||||||
|         file_parser.get_sd_api().show_error( |  | ||||||
|             file_parser.get_sd_api().context, "Error renaming\nremote file"); |  | ||||||
|     } |     } | ||||||
|     return fs_err == FSE_OK; | 
 | ||||||
|  |     auto new_name = find_vacant_remote_name(str); | ||||||
|  |     if(new_name.empty()) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     IrdaAppFileParser file_parser; | ||||||
|  |     bool result = file_parser.rename_irda_file(remote->name.c_str(), new_name.c_str()); | ||||||
|  | 
 | ||||||
|  |     remote->name = new_name; | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppRemoteManager::rename_button(uint32_t index, const char* str) { | bool IrdaAppRemoteManager::rename_button(uint32_t index, const char* str) { | ||||||
| @ -167,115 +149,45 @@ size_t IrdaAppRemoteManager::get_number_of_buttons() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppRemoteManager::store(void) { | bool IrdaAppRemoteManager::store(void) { | ||||||
|     File file; |  | ||||||
|     std::string dirname(std::string("/") + irda_directory); |  | ||||||
| 
 |  | ||||||
|     IrdaAppFileParser file_parser; |     IrdaAppFileParser file_parser; | ||||||
|     FS_Error fs_err = file_parser.get_fs_api().common.mkdir(dirname.c_str()); |     bool result = true; | ||||||
|     if((fs_err != FSE_OK) && (fs_err != FSE_EXIST)) { |  | ||||||
|         file_parser.get_sd_api().show_error( |  | ||||||
|             file_parser.get_sd_api().context, "Can't create directory"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     bool res = file_parser.get_fs_api().file.open( |     if(!file_parser.open_irda_file_write(remote->name.c_str())) { | ||||||
|         &file, make_full_name(remote->name).c_str(), FSAM_WRITE, FSOM_CREATE_ALWAYS); |  | ||||||
| 
 |  | ||||||
|     if(!res) { |  | ||||||
|         file_parser.get_sd_api().show_error( |  | ||||||
|             file_parser.get_sd_api().context, "Cannot create\nnew remote file"); |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for(const auto& button : remote->buttons) { |     for(const auto& button : remote->buttons) { | ||||||
|         bool result = file_parser.store_signal(&file, button.signal, button.name.c_str()); |         bool result = file_parser.save_signal(button.signal, button.name.c_str()); | ||||||
|         if(!result) { |         if(!result) { | ||||||
|             file_parser.get_sd_api().show_error( |             result = false; | ||||||
|                 file_parser.get_sd_api().context, "Cannot write\nto key file"); |             break; | ||||||
|             file_parser.get_fs_api().file.close(&file); |  | ||||||
|             return false; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     file_parser.get_fs_api().file.close(&file); |     file_parser.close(); | ||||||
|     file_parser.get_sd_api().check_error(file_parser.get_sd_api().context); |  | ||||||
| 
 | 
 | ||||||
|     return true; |     return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppRemoteManager::get_remote_list(std::vector<std::string>& remote_names) const { | bool IrdaAppRemoteManager::load(const std::string& name) { | ||||||
|     bool fs_res = false; |  | ||||||
|     char name[128]; |  | ||||||
|     File dir; |  | ||||||
|     std::string dirname(std::string("/") + irda_directory); |  | ||||||
|     remote_names.clear(); |  | ||||||
| 
 |  | ||||||
|     IrdaAppFileParser file_parser; |  | ||||||
|     fs_res = file_parser.get_fs_api().dir.open(&dir, dirname.c_str()); |  | ||||||
|     if(!fs_res) { |  | ||||||
|         if(!check_fs()) { |  | ||||||
|             file_parser.get_sd_api().show_error( |  | ||||||
|                 file_parser.get_sd_api().context, "Cannot open\napplication directory"); |  | ||||||
|             return false; |  | ||||||
|         } else { |  | ||||||
|             return true; // SD ok, but no files written yet
 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     while(file_parser.get_fs_api().dir.read(&dir, nullptr, name, sizeof(name)) && strlen(name)) { |  | ||||||
|         std::string filename(name); |  | ||||||
|         auto extension_index = filename.rfind(irda_extension); |  | ||||||
|         if((extension_index == std::string::npos) || |  | ||||||
|            (extension_index + strlen(irda_extension) != filename.size())) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|         remote_names.push_back(filename.erase(extension_index)); |  | ||||||
|     } |  | ||||||
|     file_parser.get_fs_api().dir.close(&dir); |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool IrdaAppRemoteManager::load(const std::string& name_arg, bool fullpath) { |  | ||||||
|     bool fs_res = false; |     bool fs_res = false; | ||||||
|     IrdaAppFileParser file_parser; |     IrdaAppFileParser file_parser; | ||||||
|     File file; |  | ||||||
|     std::string full_filename; |  | ||||||
|     std::string remote_name; |  | ||||||
| 
 | 
 | ||||||
|     if(fullpath) { |     fs_res = file_parser.open_irda_file_read(name.c_str()); | ||||||
|         full_filename = name_arg; |  | ||||||
|         remote_name = make_remote_name(name_arg); |  | ||||||
|     } else { |  | ||||||
|         full_filename = make_full_name(name_arg); |  | ||||||
|         remote_name = name_arg; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fs_res = file_parser.get_fs_api().file.open( |  | ||||||
|         &file, full_filename.c_str(), FSAM_READ, FSOM_OPEN_EXISTING); |  | ||||||
|     if(!fs_res) { |     if(!fs_res) { | ||||||
|         file_parser.get_sd_api().show_error( |  | ||||||
|             file_parser.get_sd_api().context, "Error opening file"); |  | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     remote = std::make_unique<IrdaAppRemote>(remote_name); |     remote = std::make_unique<IrdaAppRemote>(name); | ||||||
| 
 | 
 | ||||||
|     while(1) { |     while(1) { | ||||||
|         auto file_signal = file_parser.read_signal(&file); |         auto file_signal = file_parser.read_signal(); | ||||||
|         if(!file_signal.get()) break; |         if(!file_signal) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|         remote->buttons.emplace_back(file_signal->name, file_signal->signal); |         remote->buttons.emplace_back(file_signal->name, file_signal->signal); | ||||||
|     } |     } | ||||||
|     file_parser.get_fs_api().file.close(&file); |     file_parser.close(); | ||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| bool IrdaAppRemoteManager::check_fs() const { |  | ||||||
|     // TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info().
 |  | ||||||
|     IrdaAppFileParser file_parser; |  | ||||||
|     auto fs_err = file_parser.get_fs_api().common.get_fs_info(nullptr, nullptr); |  | ||||||
|     if(fs_err != FSE_OK) |  | ||||||
|         file_parser.get_sd_api().show_error(file_parser.get_sd_api().context, "SD card not found"); |  | ||||||
|     return fs_err == FSE_OK; |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -9,7 +9,6 @@ | |||||||
| #include <filesystem-api.h> | #include <filesystem-api.h> | ||||||
| #include "irda-app-signal.h" | #include "irda-app-signal.h" | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| class IrdaAppRemoteButton { | class IrdaAppRemoteButton { | ||||||
|     friend class IrdaAppRemoteManager; |     friend class IrdaAppRemoteManager; | ||||||
|     std::string name; |     std::string name; | ||||||
| @ -49,8 +48,8 @@ public: | |||||||
|     int find_remote_name(const std::vector<std::string>& strings); |     int find_remote_name(const std::vector<std::string>& strings); | ||||||
|     bool rename_button(uint32_t index, const char* str); |     bool rename_button(uint32_t index, const char* str); | ||||||
|     bool rename_remote(const char* str); |     bool rename_remote(const char* str); | ||||||
|  |     std::string find_vacant_remote_name(const std::string& name); | ||||||
| 
 | 
 | ||||||
|     bool get_remote_list(std::vector<std::string>& remote_names) const; |  | ||||||
|     std::vector<std::string> get_button_list() const; |     std::vector<std::string> get_button_list() const; | ||||||
|     std::string get_button_name(uint32_t index); |     std::string get_button_name(uint32_t index); | ||||||
|     std::string get_remote_name(); |     std::string get_remote_name(); | ||||||
| @ -58,9 +57,9 @@ public: | |||||||
|     const IrdaAppSignal& get_button_data(size_t index) const; |     const IrdaAppSignal& get_button_data(size_t index) const; | ||||||
|     bool delete_button(uint32_t index); |     bool delete_button(uint32_t index); | ||||||
|     bool delete_remote(); |     bool delete_remote(); | ||||||
|  |     void reset_remote(); | ||||||
| 
 | 
 | ||||||
|     bool store(); |     bool store(); | ||||||
|     bool load(const std::string& name, bool fullpath = false); |     bool load(const std::string& name); | ||||||
|     bool check_fs() const; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| #include "furi.h" | #include "furi.h" | ||||||
| #include "gui/modules/button_panel.h" | #include "gui/modules/button_panel.h" | ||||||
| #include "irda-app.hpp" | #include "irda-app.hpp" | ||||||
|  | #include "irda/irda-app-event.hpp" | ||||||
| #include <callback-connector.h> | #include <callback-connector.h> | ||||||
| 
 | 
 | ||||||
| IrdaAppViewManager::IrdaAppViewManager() { | IrdaAppViewManager::IrdaAppViewManager() { | ||||||
| @ -98,6 +99,12 @@ osMessageQueueId_t IrdaAppViewManager::get_event_queue() { | |||||||
|     return event_queue; |     return event_queue; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void IrdaAppViewManager::clear_events() { | ||||||
|  |     IrdaAppEvent event; | ||||||
|  |     while(osMessageQueueGet(event_queue, &event, NULL, 0) == osOK) | ||||||
|  |         ; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void IrdaAppViewManager::receive_event(IrdaAppEvent* event) { | void IrdaAppViewManager::receive_event(IrdaAppEvent* event) { | ||||||
|     if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) { |     if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) { | ||||||
|         event->type = IrdaAppEvent::Type::Tick; |         event->type = IrdaAppEvent::Type::Tick; | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ public: | |||||||
| 
 | 
 | ||||||
|     void receive_event(IrdaAppEvent* event); |     void receive_event(IrdaAppEvent* event); | ||||||
|     void send_event(IrdaAppEvent* event); |     void send_event(IrdaAppEvent* event); | ||||||
|  |     void clear_events(); | ||||||
| 
 | 
 | ||||||
|     DialogEx* get_dialog_ex(); |     DialogEx* get_dialog_ex(); | ||||||
|     Submenu* get_submenu(); |     Submenu* get_submenu(); | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "irda-app.hpp" | #include "irda-app.hpp" | ||||||
|  | #include "irda/irda-app-file-parser.hpp" | ||||||
| #include <irda_worker.h> | #include <irda_worker.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include <gui/gui.h> | #include <gui/gui.h> | ||||||
| @ -12,12 +13,16 @@ int32_t IrdaApp::run(void* args) { | |||||||
|     bool exit = false; |     bool exit = false; | ||||||
| 
 | 
 | ||||||
|     if(args) { |     if(args) { | ||||||
|         const char* remote_name = static_cast<const char*>(args); |         std::string remote_name; | ||||||
|         bool result = remote_manager.load(std::string(remote_name), true); |         { | ||||||
|  |             IrdaAppFileParser file_parser; | ||||||
|  |             remote_name = file_parser.make_name(static_cast<const char*>(args)); | ||||||
|  |         } | ||||||
|  |         bool result = remote_manager.load(remote_name); | ||||||
|         if(result) { |         if(result) { | ||||||
|             current_scene = IrdaApp::Scene::Remote; |             current_scene = IrdaApp::Scene::Remote; | ||||||
|         } else { |         } else { | ||||||
|             printf("Failed to load remote \'%s\'\r\n", remote_name); |             printf("Failed to load remote \'%s\'\r\n", remote_name.c_str()); | ||||||
|             return -1; |             return -1; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @ -65,6 +70,7 @@ void IrdaApp::switch_to_next_scene_without_saving(Scene next_scene) { | |||||||
|         scenes[current_scene]->on_exit(this); |         scenes[current_scene]->on_exit(this); | ||||||
|         current_scene = next_scene; |         current_scene = next_scene; | ||||||
|         scenes[current_scene]->on_enter(this); |         scenes[current_scene]->on_enter(this); | ||||||
|  |         view_manager.clear_events(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -93,6 +99,7 @@ void IrdaApp::search_and_switch_to_previous_scene(const std::initializer_list<Sc | |||||||
|         scenes[current_scene]->on_exit(this); |         scenes[current_scene]->on_exit(this); | ||||||
|         current_scene = previous_scene; |         current_scene = previous_scene; | ||||||
|         scenes[current_scene]->on_enter(this); |         scenes[current_scene]->on_enter(this); | ||||||
|  |         view_manager.clear_events(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -106,6 +113,7 @@ bool IrdaApp::switch_to_previous_scene(uint8_t count) { | |||||||
|     scenes[current_scene]->on_exit(this); |     scenes[current_scene]->on_exit(this); | ||||||
|     current_scene = previous_scene; |     current_scene = previous_scene; | ||||||
|     scenes[current_scene]->on_enter(this); |     scenes[current_scene]->on_enter(this); | ||||||
|  |     view_manager.clear_events(); | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| #include <map> | #include <map> | ||||||
| #include <irda.h> | #include <irda.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| #include "irda/scene/irda-app-scene.hpp" | #include "scene/irda-app-scene.hpp" | ||||||
| #include "irda-app-event.hpp" | #include "irda-app-event.hpp" | ||||||
| #include "scene/irda-app-scene.hpp" | #include "scene/irda-app-scene.hpp" | ||||||
| #include "irda-app-view-manager.hpp" | #include "irda-app-view-manager.hpp" | ||||||
| @ -34,7 +34,6 @@ public: | |||||||
|         LearnSuccess, |         LearnSuccess, | ||||||
|         LearnEnterName, |         LearnEnterName, | ||||||
|         LearnDone, |         LearnDone, | ||||||
|         LearnDoneAfter, |  | ||||||
|         Remote, |         Remote, | ||||||
|         RemoteList, |         RemoteList, | ||||||
|         Edit, |         Edit, | ||||||
| @ -126,7 +125,6 @@ private: | |||||||
|         {Scene::LearnSuccess, new IrdaAppSceneLearnSuccess()}, |         {Scene::LearnSuccess, new IrdaAppSceneLearnSuccess()}, | ||||||
|         {Scene::LearnEnterName, new IrdaAppSceneLearnEnterName()}, |         {Scene::LearnEnterName, new IrdaAppSceneLearnEnterName()}, | ||||||
|         {Scene::LearnDone, new IrdaAppSceneLearnDone()}, |         {Scene::LearnDone, new IrdaAppSceneLearnDone()}, | ||||||
|         {Scene::LearnDoneAfter, new IrdaAppSceneLearnDoneAfter()}, |  | ||||||
|         {Scene::Remote, new IrdaAppSceneRemote()}, |         {Scene::Remote, new IrdaAppSceneRemote()}, | ||||||
|         {Scene::RemoteList, new IrdaAppSceneRemoteList()}, |         {Scene::RemoteList, new IrdaAppSceneRemoteList()}, | ||||||
|         {Scene::Edit, new IrdaAppSceneEdit()}, |         {Scene::Edit, new IrdaAppSceneEdit()}, | ||||||
|  | |||||||
| @ -1,32 +0,0 @@ | |||||||
| #include "../irda-app.hpp" |  | ||||||
| #include <gui/modules/popup.h> |  | ||||||
| 
 |  | ||||||
| void IrdaAppSceneLearnDoneAfter::on_enter(IrdaApp* app) { |  | ||||||
|     auto view_manager = app->get_view_manager(); |  | ||||||
|     auto popup = view_manager->get_popup(); |  | ||||||
| 
 |  | ||||||
|     popup_set_icon(popup, 0, 30, &I_IrdaSendShort_128x34); |  | ||||||
|     popup_set_text( |  | ||||||
|         popup, "Get ready!\nPoint flipper at target.", 64, 16, AlignCenter, AlignCenter); |  | ||||||
| 
 |  | ||||||
|     popup_set_callback(popup, IrdaApp::popup_callback); |  | ||||||
|     popup_set_context(popup, app); |  | ||||||
|     popup_set_timeout(popup, 1500); |  | ||||||
|     popup_enable_timeout(popup); |  | ||||||
| 
 |  | ||||||
|     view_manager->switch_to(IrdaAppViewManager::ViewType::Popup); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool IrdaAppSceneLearnDoneAfter::on_event(IrdaApp* app, IrdaAppEvent* event) { |  | ||||||
|     bool consumed = false; |  | ||||||
| 
 |  | ||||||
|     if(event->type == IrdaAppEvent::Type::PopupTimer) { |  | ||||||
|         app->switch_to_next_scene(IrdaApp::Scene::Remote); |  | ||||||
|         consumed = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return consumed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void IrdaAppSceneLearnDoneAfter::on_exit(IrdaApp* app) { |  | ||||||
| } |  | ||||||
| @ -24,11 +24,7 @@ bool IrdaAppSceneLearnDone::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == IrdaAppEvent::Type::PopupTimer) { |     if(event->type == IrdaAppEvent::Type::PopupTimer) { | ||||||
|         if(app->get_learn_new_remote()) { |         app->switch_to_next_scene(IrdaApp::Scene::Remote); | ||||||
|             app->switch_to_next_scene(IrdaApp::Scene::LearnDoneAfter); |  | ||||||
|         } else { |  | ||||||
|             app->switch_to_next_scene(IrdaApp::Scene::Remote); |  | ||||||
|         } |  | ||||||
|         consumed = true; |         consumed = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,5 +1,7 @@ | |||||||
| #include "../irda-app.hpp" | #include "../irda-app.hpp" | ||||||
| #include "irda.h" | #include "irda.h" | ||||||
|  | #include "../irda-app-file-parser.hpp" | ||||||
|  | #include <memory> | ||||||
| 
 | 
 | ||||||
| static void dialog_result_callback(DialogExResult result, void* context) { | static void dialog_result_callback(DialogExResult result, void* context) { | ||||||
|     auto app = static_cast<IrdaApp*>(context); |     auto app = static_cast<IrdaApp*>(context); | ||||||
| @ -61,15 +63,18 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|             signal.transmit(); |             signal.transmit(); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|         case DialogExResultRight: |         case DialogExResultRight: { | ||||||
|             auto remote_manager = app->get_remote_manager(); |             IrdaAppFileParser file_parser; | ||||||
|             if(remote_manager->check_fs()) { |             if(file_parser.check_errors()) { | ||||||
|                 app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); |                 app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName); | ||||||
|             } else { |             } else { | ||||||
|                 app->switch_to_previous_scene(); |                 app->switch_to_previous_scene(); | ||||||
|             } |             } | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
|  | |||||||
| @ -1,75 +1,30 @@ | |||||||
| #include "../irda-app.hpp" | #include "../irda-app.hpp" | ||||||
| 
 | #include "irda/irda-app-event.hpp" | ||||||
| typedef enum { |  | ||||||
|     SubmenuIndexPlus = -1, |  | ||||||
| } SubmenuIndex; |  | ||||||
| 
 |  | ||||||
| static void submenu_callback(void* context, uint32_t index) { |  | ||||||
|     IrdaApp* app = static_cast<IrdaApp*>(context); |  | ||||||
|     IrdaAppEvent event; |  | ||||||
| 
 |  | ||||||
|     event.type = IrdaAppEvent::Type::MenuSelected; |  | ||||||
|     event.payload.menu_index = index; |  | ||||||
| 
 |  | ||||||
|     app->get_view_manager()->send_event(&event); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) { | void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) { | ||||||
|     IrdaAppViewManager* view_manager = app->get_view_manager(); |     IrdaAppFileParser file_parser; | ||||||
|     Submenu* submenu = view_manager->get_submenu(); |     bool success = false; | ||||||
|     auto remote_manager = app->get_remote_manager(); |     auto remote_manager = app->get_remote_manager(); | ||||||
|     int i = 0; |     auto last_selected_remote = remote_manager->get_remote_name(); | ||||||
|  |     auto selected_file = file_parser.file_select( | ||||||
|  |         last_selected_remote.size() ? last_selected_remote.c_str() : nullptr); | ||||||
|  |     if(!selected_file.empty()) { | ||||||
|  |         if(remote_manager->load(selected_file)) { | ||||||
|  |             app->switch_to_next_scene(IrdaApp::Scene::Remote); | ||||||
|  |             success = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     bool result = remote_manager->get_remote_list(remote_names); |     if(!success) { | ||||||
|     if(!result) { |  | ||||||
|         app->switch_to_previous_scene(); |         app->switch_to_previous_scene(); | ||||||
|         return; |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     for(auto& name : remote_names) { |  | ||||||
|         submenu_add_item(submenu, name.c_str(), i++, submenu_callback, app); |  | ||||||
|     } |  | ||||||
|     submenu_add_item( |  | ||||||
|         submenu, "                           +", SubmenuIndexPlus, submenu_callback, app); |  | ||||||
| 
 |  | ||||||
|     if((SubmenuIndex)submenu_item_selected == SubmenuIndexPlus) { |  | ||||||
|         submenu_set_selected_item(submenu, submenu_item_selected); |  | ||||||
|     } else { |  | ||||||
|         int remote_index = remote_manager->find_remote_name(remote_names); |  | ||||||
|         submenu_set_selected_item(submenu, (remote_index >= 0) ? remote_index : 0); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     submenu_item_selected = 0; |  | ||||||
|     view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IrdaAppSceneRemoteList::on_event(IrdaApp* app, IrdaAppEvent* event) { | bool IrdaAppSceneRemoteList::on_event(IrdaApp* app, IrdaAppEvent* event) { | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == IrdaAppEvent::Type::MenuSelected) { |  | ||||||
|         switch(event->payload.menu_index) { |  | ||||||
|         case SubmenuIndexPlus: |  | ||||||
|             app->set_learn_new_remote(true); |  | ||||||
|             app->switch_to_next_scene(IrdaApp::Scene::Learn); |  | ||||||
|             submenu_item_selected = event->payload.menu_index; |  | ||||||
|             break; |  | ||||||
|         default: |  | ||||||
|             auto remote_manager = app->get_remote_manager(); |  | ||||||
|             bool result = remote_manager->load(remote_names.at(event->payload.menu_index)); |  | ||||||
|             if(result) { |  | ||||||
|                 app->switch_to_next_scene(IrdaApp::Scene::Remote); |  | ||||||
|             } |  | ||||||
|             consumed = true; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return consumed; |     return consumed; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void IrdaAppSceneRemoteList::on_exit(IrdaApp* app) { | void IrdaAppSceneRemoteList::on_exit(IrdaApp* app) { | ||||||
|     IrdaAppViewManager* view_manager = app->get_view_manager(); |  | ||||||
|     Submenu* submenu = view_manager->get_submenu(); |  | ||||||
| 
 |  | ||||||
|     submenu_clean(submenu); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -61,5 +61,6 @@ void IrdaAppSceneStart::on_exit(IrdaApp* app) { | |||||||
|     IrdaAppViewManager* view_manager = app->get_view_manager(); |     IrdaAppViewManager* view_manager = app->get_view_manager(); | ||||||
|     Submenu* submenu = view_manager->get_submenu(); |     Submenu* submenu = view_manager->get_submenu(); | ||||||
| 
 | 
 | ||||||
|  |     app->get_remote_manager()->reset_remote(); | ||||||
|     submenu_clean(submenu); |     submenu_clean(submenu); | ||||||
| } | } | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ static bool irda_popup_brut_input_callback(InputEvent* event, void* context) { | |||||||
|         consumed = true; |         consumed = true; | ||||||
|         IrdaAppEvent irda_event; |         IrdaAppEvent irda_event; | ||||||
| 
 | 
 | ||||||
|         irda_event.type = IrdaAppEvent::Type::ButtonPanelPopupBackPressed; |         irda_event.type = IrdaAppEvent::Type::Back; | ||||||
|         app->get_view_manager()->send_event(&irda_event); |         app->get_view_manager()->send_event(&irda_event); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -58,8 +58,8 @@ void IrdaAppSceneUniversalCommon::progress_popup(IrdaApp* app) { | |||||||
| bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) { | bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) { | ||||||
|     bool consumed = false; |     bool consumed = false; | ||||||
| 
 | 
 | ||||||
|     if(event->type == IrdaAppEvent::Type::Tick) { |     if(brute_force_started) { | ||||||
|         if(brute_force_started) { |         if(event->type == IrdaAppEvent::Type::Tick) { | ||||||
|             auto view_manager = app->get_view_manager(); |             auto view_manager = app->get_view_manager(); | ||||||
|             IrdaAppEvent tick_event = {.type = IrdaAppEvent::Type::Tick}; |             IrdaAppEvent tick_event = {.type = IrdaAppEvent::Type::Tick}; | ||||||
|             view_manager->send_event(&tick_event); |             view_manager->send_event(&tick_event); | ||||||
| @ -70,26 +70,27 @@ bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) { | |||||||
|                 brute_force_started = false; |                 brute_force_started = false; | ||||||
|                 remove_popup(app); |                 remove_popup(app); | ||||||
|             } |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event->type == IrdaAppEvent::Type::Back) { | ||||||
|  |             brute_force_started = false; | ||||||
|  |             brute_force.stop_bruteforce(); | ||||||
|  |             remove_popup(app); | ||||||
|  |             consumed = true; | ||||||
|         } |         } | ||||||
|         consumed = true; |     } else { | ||||||
|     } |         if(event->type == IrdaAppEvent::Type::ButtonPanelPressed) { | ||||||
| 
 |             int record_amount = 0; | ||||||
|     if(event->type == IrdaAppEvent::Type::ButtonPanelPopupBackPressed) { |             if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) { | ||||||
|         consumed = true; |  | ||||||
|         brute_force_started = false; |  | ||||||
|         brute_force.stop_bruteforce(); |  | ||||||
|         remove_popup(app); |  | ||||||
|     } else if(event->type == IrdaAppEvent::Type::ButtonPanelPressed) { |  | ||||||
|         int record_amount = 0; |  | ||||||
|         if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) { |  | ||||||
|             if(record_amount > 0) { |  | ||||||
|                 brute_force_started = true; |                 brute_force_started = true; | ||||||
|                 show_popup(app, record_amount); |                 show_popup(app, record_amount); | ||||||
|  |             } else { | ||||||
|  |                 app->switch_to_previous_scene(); | ||||||
|             } |             } | ||||||
|         } else { |             consumed = true; | ||||||
|  |         } else if(event->type == IrdaAppEvent::Type::Back) { | ||||||
|             app->switch_to_previous_scene(); |             app->switch_to_previous_scene(); | ||||||
|  |             consumed = true; | ||||||
|         } |         } | ||||||
|         consumed = true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return consumed; |     return consumed; | ||||||
|  | |||||||
| @ -65,13 +65,6 @@ public: | |||||||
|     void on_exit(IrdaApp* app) final; |     void on_exit(IrdaApp* app) final; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class IrdaAppSceneLearnDoneAfter : public IrdaAppScene { |  | ||||||
| public: |  | ||||||
|     void on_enter(IrdaApp* app) final; |  | ||||||
|     bool on_event(IrdaApp* app, IrdaAppEvent* event) final; |  | ||||||
|     void on_exit(IrdaApp* app) final; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class IrdaAppSceneRemote : public IrdaAppScene { | class IrdaAppSceneRemote : public IrdaAppScene { | ||||||
| public: | public: | ||||||
|     void on_enter(IrdaApp* app) final; |     void on_enter(IrdaApp* app) final; | ||||||
| @ -155,14 +148,14 @@ protected: | |||||||
| class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon { | class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon { | ||||||
| public: | public: | ||||||
|     void on_enter(IrdaApp* app) final; |     void on_enter(IrdaApp* app) final; | ||||||
|     IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/irda/universal/tv.ir") {} |     IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/assets/ext/irda/tv.ir") {} | ||||||
|     ~IrdaAppSceneUniversalTV() {} |     ~IrdaAppSceneUniversalTV() {} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon { | class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon { | ||||||
| public: | public: | ||||||
|     void on_enter(IrdaApp* app) final; |     void on_enter(IrdaApp* app) final; | ||||||
|     IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/irda/universal/audio.ir") {} |     IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/assets/ext/irda/audio.ir") {} | ||||||
|     ~IrdaAppSceneUniversalAudio() {} |     ~IrdaAppSceneUniversalAudio() {} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ typedef struct { | |||||||
|     const char* extension; |     const char* extension; | ||||||
|     char* result; |     char* result; | ||||||
|     uint8_t result_size; |     uint8_t result_size; | ||||||
|     char* selected_filename; |     const char* selected_filename; | ||||||
| } SdAppFileSelectData; | } SdAppFileSelectData; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| @ -64,7 +64,7 @@ bool sd_api_file_select( | |||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     char* result, | ||||||
|     uint8_t result_size, |     uint8_t result_size, | ||||||
|     char* selected_filename); |     const char* selected_filename); | ||||||
| void sd_api_check_error(SdApp* sd_app); | void sd_api_check_error(SdApp* sd_app); | ||||||
| void sd_api_show_error(SdApp* sd_app, const char* error_text); | void sd_api_show_error(SdApp* sd_app, const char* error_text); | ||||||
| 
 | 
 | ||||||
| @ -435,7 +435,7 @@ bool sd_api_file_select( | |||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     char* result, | ||||||
|     uint8_t result_size, |     uint8_t result_size, | ||||||
|     char* selected_filename) { |     const char* selected_filename) { | ||||||
|     bool retval = false; |     bool retval = false; | ||||||
| 
 | 
 | ||||||
|     SdAppEvent message = { |     SdAppEvent message = { | ||||||
| @ -629,7 +629,12 @@ void free_view_holder(SdApp* sd_app) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void app_reset_state(SdApp* sd_app) { | void app_reset_state(SdApp* sd_app) { | ||||||
|     view_holder_stop(sd_app->view_holder); |     _fs_lock(&sd_app->info); | ||||||
|  |     if(sd_app->view_holder) { | ||||||
|  |         view_holder_stop(sd_app->view_holder); | ||||||
|  |     } | ||||||
|  |     _fs_unlock(&sd_app->info); | ||||||
|  | 
 | ||||||
|     free_view_holder(sd_app); |     free_view_holder(sd_app); | ||||||
|     string_set_str(sd_app->text_holder, ""); |     string_set_str(sd_app->text_holder, ""); | ||||||
|     sd_app->sd_app_state = SdAppStateBackground; |     sd_app->sd_app_state = SdAppStateBackground; | ||||||
|  | |||||||
| @ -62,7 +62,24 @@ bool FileWorkerCpp::file_select( | |||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     char* result, | ||||||
|     uint8_t result_size, |     uint8_t result_size, | ||||||
|     char* selected_filename) { |     const char* selected_filename) { | ||||||
|     return file_worker_file_select( |     return file_worker_file_select( | ||||||
|         file_worker, path, extension, result, result_size, selected_filename); |         file_worker, path, extension, result, result_size, selected_filename); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool FileWorkerCpp::read_until_buffered(string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t max_length, char separator) { | ||||||
|  |     return file_worker_read_until_buffered(file_worker, str_result, file_buf, file_buf_cnt, max_length, separator); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FileWorkerCpp::is_file_exist(const char* filename, bool* exist) { | ||||||
|  |     return file_worker_is_file_exist(file_worker, filename, exist); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FileWorkerCpp::rename(const char* old_path, const char* new_path) { | ||||||
|  |     return file_worker_rename(file_worker, old_path, new_path); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FileWorkerCpp::check_errors() { | ||||||
|  |     return file_worker_check_errors(file_worker); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -128,7 +128,50 @@ public: | |||||||
|         const char* extension, |         const char* extension, | ||||||
|         char* result, |         char* result, | ||||||
|         uint8_t result_size, |         uint8_t result_size, | ||||||
|         char* selected_filename); |         const char* selected_filename); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * @brief Reads data from a file until separator or EOF is found. | ||||||
|  |      * Moves seek pointer to the next symbol after the separator. The separator is included in the result. | ||||||
|  |      * | ||||||
|  |      * @param result | ||||||
|  |      * @param file_buf | ||||||
|  |      * @param file_buf_cnt | ||||||
|  |      * @param max_length | ||||||
|  |      * @param separator | ||||||
|  |      * @return true on success | ||||||
|  |      */ | ||||||
|  |     bool read_until_buffered(string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t max_length, char separator = '\n'); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * @brief Check whether file exist or not | ||||||
|  |      * | ||||||
|  |      * @param file_worker FileWorker instance | ||||||
|  |      * @param filename | ||||||
|  |      * @param exist - flag to show file exist | ||||||
|  |      * @return true on success | ||||||
|  |      */ | ||||||
|  |     bool is_file_exist( | ||||||
|  |         const char* filename, | ||||||
|  |         bool* exist); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * @brief Rename file or directory | ||||||
|  |      * | ||||||
|  |      * @param old_filename | ||||||
|  |      * @param new_filename | ||||||
|  |      * @return true on success | ||||||
|  |      */ | ||||||
|  |     bool rename( | ||||||
|  |         const char* old_path, | ||||||
|  |         const char* new_path); | ||||||
|  | 
 | ||||||
|  |     /**
 | ||||||
|  |      * @brief Check errors | ||||||
|  |      * | ||||||
|  |      * @return true if no errors | ||||||
|  |      */ | ||||||
|  |     bool check_errors(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     FileWorker* file_worker; |     FileWorker* file_worker; | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| #include "file-worker.h" | #include "file-worker.h" | ||||||
|  | #include "m-string.h" | ||||||
| #include <hex.h> | #include <hex.h> | ||||||
| #include <sd-card-api.h> | #include <sd-card-api.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| @ -8,6 +9,8 @@ struct FileWorker { | |||||||
|     SdCard_Api* sd_ex_api; |     SdCard_Api* sd_ex_api; | ||||||
|     bool silent; |     bool silent; | ||||||
|     File file; |     File file; | ||||||
|  |     char file_buf[48]; | ||||||
|  |     size_t file_buf_cnt; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| bool file_worker_check_common_errors(FileWorker* file_worker); | bool file_worker_check_common_errors(FileWorker* file_worker); | ||||||
| @ -25,6 +28,7 @@ FileWorker* file_worker_alloc(bool _silent) { | |||||||
|     file_worker->silent = _silent; |     file_worker->silent = _silent; | ||||||
|     file_worker->fs_api = furi_record_open("sdcard"); |     file_worker->fs_api = furi_record_open("sdcard"); | ||||||
|     file_worker->sd_ex_api = furi_record_open("sdcard-ex"); |     file_worker->sd_ex_api = furi_record_open("sdcard-ex"); | ||||||
|  |     file_worker->file_buf_cnt = 0; | ||||||
| 
 | 
 | ||||||
|     return file_worker; |     return file_worker; | ||||||
| } | } | ||||||
| @ -215,14 +219,17 @@ bool file_worker_file_select( | |||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     char* result, | ||||||
|     uint8_t result_size, |     uint8_t result_size, | ||||||
|     char* selected_filename) { |     const char* selected_filename) { | ||||||
|     return file_worker->sd_ex_api->file_select( |     return file_worker->sd_ex_api->file_select( | ||||||
|         file_worker->sd_ex_api->context, path, extension, result, result_size, selected_filename); |         file_worker->sd_ex_api->context, path, extension, result, result_size, selected_filename); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool file_worker_check_common_errors(FileWorker* file_worker) { | bool file_worker_check_common_errors(FileWorker* file_worker) { | ||||||
|     file_worker->sd_ex_api->check_error(file_worker->sd_ex_api->context); |     /* TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info(). */ | ||||||
|     return true; |     FS_Error fs_err = file_worker->fs_api->common.get_fs_info(NULL, NULL); | ||||||
|  |     if(fs_err != FSE_OK) | ||||||
|  |         file_worker->sd_ex_api->show_error(file_worker->sd_ex_api->context, "SD card not found"); | ||||||
|  |     return fs_err == FSE_OK; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text) { | void file_worker_show_error_internal(FileWorker* file_worker, const char* error_text) { | ||||||
| @ -278,3 +285,90 @@ bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool | |||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t file_buf_size, char separator) { | ||||||
|  |     furi_assert(string_capacity(str_result) > 0); | ||||||
|  |     furi_assert(file_buf_size <= 512);  /* fs_api->file.read now supports up to 512 bytes reading at a time */ | ||||||
|  | 
 | ||||||
|  |     string_clean(str_result); | ||||||
|  |     size_t newline_index = 0; | ||||||
|  |     bool found_eol = false; | ||||||
|  |     bool max_length_exceeded = false; | ||||||
|  |     size_t max_length = string_capacity(str_result) - 1; | ||||||
|  | 
 | ||||||
|  |     while(1) { | ||||||
|  |         if(*file_buf_cnt > 0) { | ||||||
|  |             size_t end_index = 0; | ||||||
|  |             char* endline_ptr = (char*)memchr(file_buf, separator, *file_buf_cnt); | ||||||
|  |             newline_index = endline_ptr - file_buf; | ||||||
|  | 
 | ||||||
|  |             if(endline_ptr == 0) { | ||||||
|  |                 end_index = *file_buf_cnt; | ||||||
|  |             } else if(newline_index < *file_buf_cnt) { | ||||||
|  |                 end_index = newline_index + 1; | ||||||
|  |                 found_eol = true; | ||||||
|  |             } else { | ||||||
|  |                 furi_assert(0); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (max_length && (string_size(str_result) + end_index > max_length)) | ||||||
|  |                 max_length_exceeded = true; | ||||||
|  | 
 | ||||||
|  |             if (!max_length_exceeded) { | ||||||
|  |                 for (size_t i = 0; i < end_index; ++i) { | ||||||
|  |                     string_push_back(str_result, file_buf[i]); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             memmove(file_buf, &file_buf[end_index], *file_buf_cnt - end_index); | ||||||
|  |             *file_buf_cnt = *file_buf_cnt - end_index; | ||||||
|  |             if(found_eol) break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         *file_buf_cnt += | ||||||
|  |             file_worker->fs_api->file.read(&file_worker->file, &file_buf[*file_buf_cnt], file_buf_size - *file_buf_cnt); | ||||||
|  |         if(file_worker->file.error_id != FSE_OK) { | ||||||
|  |             file_worker_show_error_internal(file_worker, "Cannot read\nfile"); | ||||||
|  |             string_clear(str_result); | ||||||
|  |             *file_buf_cnt = 0; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         if(*file_buf_cnt == 0) { | ||||||
|  |             break; // end of reading
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (max_length_exceeded) | ||||||
|  |         string_clear(str_result); | ||||||
|  | 
 | ||||||
|  |     return string_size(str_result) || *file_buf_cnt; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool file_worker_rename(FileWorker* file_worker, const char* old_path, const char* new_path) { | ||||||
|  |     FS_Error fs_result = file_worker->fs_api->common.rename(old_path, new_path); | ||||||
|  | 
 | ||||||
|  |     if(fs_result != FSE_OK && fs_result != FSE_EXIST) { | ||||||
|  |         file_worker_show_error_internal(file_worker, "Cannot rename\n file/directory"); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return file_worker_check_common_errors(file_worker); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool file_worker_check_errors(FileWorker* file_worker) { | ||||||
|  |     return file_worker_check_common_errors(file_worker); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool file_worker_is_file_exist( | ||||||
|  |     FileWorker* file_worker, | ||||||
|  |     const char* filename, | ||||||
|  |     bool* exist) { | ||||||
|  | 
 | ||||||
|  |     File file; | ||||||
|  |     *exist = file_worker->fs_api->file.open(&file, filename, FSAM_READ, FSOM_OPEN_EXISTING); | ||||||
|  |     if (*exist) | ||||||
|  |         file_worker->fs_api->file.close(&file); | ||||||
|  | 
 | ||||||
|  |     return file_worker_check_common_errors(file_worker); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -163,7 +163,54 @@ bool file_worker_file_select( | |||||||
|     const char* extension, |     const char* extension, | ||||||
|     char* result, |     char* result, | ||||||
|     uint8_t result_size, |     uint8_t result_size, | ||||||
|     char* selected_filename); |     const char* selected_filename); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Reads data from a file until separator or EOF is found. | ||||||
|  |  * The separator is included in the result. | ||||||
|  |  * | ||||||
|  |  * @param file_worker FileWorker instance | ||||||
|  |  * @param str_result | ||||||
|  |  * @param file_buf | ||||||
|  |  * @param file_buf_cnt | ||||||
|  |  * @param max_length | ||||||
|  |  * @param separator | ||||||
|  |  * @return true on success | ||||||
|  |  */ | ||||||
|  | bool file_worker_read_until_buffered(FileWorker* file_worker, string_t str_result, char* file_buf, size_t* file_buf_cnt, size_t max_length, char separator); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Check whether file exist or not | ||||||
|  |  * | ||||||
|  |  * @param file_worker FileWorker instance | ||||||
|  |  * @param filename | ||||||
|  |  * @param exist - flag to show file exist | ||||||
|  |  * @return true on success | ||||||
|  |  */ | ||||||
|  | bool file_worker_is_file_exist( | ||||||
|  |     FileWorker* file_worker, | ||||||
|  |     const char* filename, | ||||||
|  |     bool* exist); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Rename file or directory | ||||||
|  |  * | ||||||
|  |  * @param file_worker FileWorker instance | ||||||
|  |  * @param old_filename | ||||||
|  |  * @param new_filename | ||||||
|  |  * @return true on success | ||||||
|  |  */ | ||||||
|  | bool file_worker_rename(FileWorker* file_worker, | ||||||
|  |     const char* old_path, | ||||||
|  |     const char* new_path); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * @brief Check errors | ||||||
|  |  * | ||||||
|  |  * @param file_worker FileWorker instance | ||||||
|  |  * @return true on success | ||||||
|  |  */ | ||||||
|  | bool file_worker_check_errors(FileWorker* file_worker); | ||||||
| 
 | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ typedef struct { | |||||||
|         const char* extension, |         const char* extension, | ||||||
|         char* result, |         char* result, | ||||||
|         uint8_t result_size, |         uint8_t result_size, | ||||||
|         char* selected_filename); |         const char* selected_filename); | ||||||
|     void (*check_error)(SdApp* context); |     void (*check_error)(SdApp* context); | ||||||
|     void (*show_error)(SdApp* context, const char* error_text); |     void (*show_error)(SdApp* context, const char* error_text); | ||||||
| } SdCard_Api; | } SdCard_Api; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Albert Kharisov
						Albert Kharisov