Explorar o código

[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
Albert Kharisov %!s(int64=4) %!d(string=hai) anos
pai
achega
769ab2aef2

+ 4 - 0
applications/gui/modules/button_panel.c

@@ -137,6 +137,10 @@ void button_panel_clean(ButtonPanel* button_panel) {
                     *button_item = NULL;
                 }
             }
+            model->reserve_x = 0;
+            model->reserve_y = 0;
+            LabelList_clean(model->labels);
+            ButtonMatrix_clean(model->button_matrix);
             return true;
         });
 }

+ 33 - 20
applications/irda/irda-app-brute-force.cpp

@@ -1,4 +1,8 @@
 #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) {
     records[name].index = index;
@@ -7,43 +11,51 @@ void IrdaAppBruteForce::add_record(int index, const char* name) {
 
 bool IrdaAppBruteForce::calculate_messages() {
     bool fs_res = false;
-    fs_res = file_parser.get_fs_api().file.open(
-        &file, universal_db_filename, FSAM_READ, FSOM_OPEN_EXISTING);
+    furi_assert(!file_parser);
+
+    file_parser = std::make_unique<IrdaAppFileParser>();
+    fs_res = file_parser->open_irda_file_read(universal_db_filename);
     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;
     }
 
-    file_parser.reset();
     while(1) {
-        auto message = file_parser.read_signal(&file);
-        if(!message) break;
-        auto element = records.find(message->name);
+        auto file_signal = file_parser->read_signal();
+        if(!file_signal) break;
+
+        auto element = records.find(file_signal->name);
         if(element != records.cend()) {
             ++element->second.amount;
         }
     }
 
-    file_parser.get_fs_api().file.close(&file);
+    file_parser->close();
+    file_parser.reset(nullptr);
 
     return true;
 }
 
 void IrdaAppBruteForce::stop_bruteforce() {
+    furi_assert((current_record.size()));
+
     if(current_record.size()) {
-        file_parser.get_fs_api().file.close(&file);
+        furi_assert(file_parser);
         current_record.clear();
+        file_parser->close();
+        file_parser.reset(nullptr);
     }
 }
 
 // TODO: [FL-1418] replace with timer-chained consequence of messages.
 bool IrdaAppBruteForce::send_next_bruteforce(void) {
     furi_assert(current_record.size());
+    furi_assert(file_parser);
 
     std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> file_signal;
 
     do {
-        file_signal = file_parser.read_signal(&file);
+        file_signal = file_parser->read_signal();
     } while(file_signal && current_record.compare(file_signal->name));
 
     if(file_signal) {
@@ -53,25 +65,26 @@ bool IrdaAppBruteForce::send_next_bruteforce(void) {
 }
 
 bool IrdaAppBruteForce::start_bruteforce(int index, int& record_amount) {
-    file_parser.reset();
+    bool result = false;
+    record_amount = 0;
+
     for(const auto& it : records) {
         if(it.second.index == index) {
             record_amount = it.second.amount;
-            current_record = it.first;
+            if(record_amount) {
+                current_record = it.first;
+            }
             break;
         }
     }
 
     if(record_amount) {
-        bool fs_res = file_parser.get_fs_api().file.open(
-            &file, universal_db_filename, FSAM_READ, FSOM_OPEN_EXISTING);
-        if(fs_res) {
-            return true;
-        } else {
-            file_parser.get_sd_api().show_error(
-                file_parser.get_sd_api().context, "Can't open file");
+        file_parser = std::make_unique<IrdaAppFileParser>();
+        result = file_parser->open_irda_file_read(universal_db_filename);
+        if(!result) {
+            (void)file_parser.reset(nullptr);
         }
     }
 
-    return false;
+    return result;
 }

+ 3 - 5
applications/irda/irda-app-brute-force.hpp

@@ -2,13 +2,13 @@
 #include "furi/check.h"
 #include <unordered_map>
 #include "irda-app-file-parser.hpp"
-
+#include <memory>
 
 class IrdaAppBruteForce {
     const char* universal_db_filename;
-    IrdaAppFileParser file_parser;
     File file;
     std::string current_record;
+    std::unique_ptr<IrdaAppFileParser> file_parser;
 
     typedef struct {
         int index;
@@ -30,8 +30,6 @@ public:
     void add_record(int index, const char* name);
 
     IrdaAppBruteForce(const char* filename) : universal_db_filename (filename) {}
-    ~IrdaAppBruteForce() {
-        stop_bruteforce();
-    }
+    ~IrdaAppBruteForce() {}
 };
 

+ 0 - 1
applications/irda/irda-app-event.hpp

@@ -15,7 +15,6 @@ public:
         TextEditDone,
         PopupTimer,
         ButtonPanelPressed,
-        ButtonPanelPopupBackPressed,
     };
 
     union {

+ 179 - 61
applications/irda/irda-app-file-parser.cpp

@@ -1,70 +1,139 @@
 #include "irda-app-file-parser.hpp"
+#include "furi/check.h"
 #include "irda-app-remote-manager.hpp"
 #include "irda-app-signal.h"
+#include "m-string.h"
+#include <text-store.h>
 #include <irda.h>
 #include <cstdio>
 #include <stdint.h>
+#include <string>
 #include <string_view>
 #include <furi.h>
+#include <file-worker-cpp.h>
 
 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) {
-    while(1) {
-        auto str = getline(file);
-        if(str.empty()) return nullptr;
+bool IrdaAppFileParser::open_irda_file_read(const char* name) {
+    std::string full_filename;
+    if(name[0] != '/')
+        full_filename = make_full_name(name);
+    else
+        full_filename = name;
 
-        auto message = parse_signal(str);
-        if(!message.get()) {
-            message = parse_signal_raw(str);
-        }
-        if(message) return message;
+    return file_worker.open(full_filename.c_str(), FSAM_READ, FSOM_OPEN_EXISTING);
+}
+
+bool IrdaAppFileParser::open_irda_file_write(const char* name) {
+    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;
+
+    written += sniprintf(
+        buf,
+        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);
+
+    furi_assert(written < buf_size);
+    if(written >= buf_size) {
+        written = 0;
     }
+
+    return written;
 }
 
-bool IrdaAppFileParser::store_signal(File* file, const IrdaAppSignal& signal, const char* name) {
-    char* content = new char[max_line_length];
+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);
 
-    if(!signal.is_raw()) {
-        auto message = signal.get_message();
-        auto protocol = message.protocol;
-
-        sniprintf(
-            content,
-            max_line_length,
-            "%.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);
+    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 {
-        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);
+        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);
         }
-        written += snprintf(&content[written], max_line_length - written, "\n");
     }
-    furi_assert(written < max_line_length);
+    string_clear(line);
 
-    size_t write_count = 0;
-    write_count = get_fs_api().file.write(file, content, written);
-    delete[] content;
-    return (file->error_id == FSE_OK) && (write_count == written);
+    return file_signal;
 }
 
 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>
     IrdaAppFileParser::parse_signal_raw(const std::string& string) const {
-    char protocol_name[32];
     uint32_t frequency;
     uint32_t duty_cycle;
     int str_len = string.size();
@@ -136,14 +204,10 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal>
     auto irda_file_signal = std::make_unique<IrdaFileSignal>();
 
     int parsed = std::sscanf(
-        str.data(),
-        "%31s %31s F:%ld DC:%ld",
-        irda_file_signal->name,
-        protocol_name,
-        &frequency,
-        &duty_cycle);
+        str.data(), "%31s RAW F:%ld DC:%ld", irda_file_signal->name, &frequency, &duty_cycle);
 
-    if(parsed != 4) {
+    if((parsed != 3) || (frequency > 42000) || (frequency < 32000) || (duty_cycle == 0) ||
+       (duty_cycle >= 100)) {
         return nullptr;
     }
 
@@ -152,9 +216,8 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal>
     header_len = sniprintf(
         dummy,
         sizeof(dummy),
-        "%.31s %.31s F:%ld DC:%ld",
+        "%.31s RAW F:%ld DC:%ld",
         irda_file_signal->name,
-        protocol_name,
         frequency,
         duty_cycle);
 
@@ -162,39 +225,94 @@ std::unique_ptr<IrdaAppFileParser::IrdaFileSignal>
     str.remove_prefix(header_len);
 
     /* 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;
 
     while(!str.empty()) {
         char buf[10];
         size_t index = str.find_first_not_of(' ', 1);
         if(index == std::string_view::npos) {
-            result = true;
             break;
         }
         str.remove_prefix(index);
         parsed = std::sscanf(str.data(), "%9s", buf);
         if(parsed != 1) {
+            result = false;
+            furi_assert(0);
             break;
         }
         str.remove_prefix(strlen(buf));
 
         int value = atoi(buf);
         if(value <= 0) {
+            result = false;
+            furi_assert(0);
             break;
         }
         raw_signal.timings[raw_signal.timings_cnt] = value;
         ++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;
         }
     }
 
     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 {
         (void)irda_file_signal.release();
-        delete[] raw_signal.timings;
     }
+    delete[] raw_signal.timings;
     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();
+}

+ 27 - 9
applications/irda/irda-app-file-parser.hpp

@@ -1,26 +1,44 @@
 #pragma once
 #include <file_reader/file_reader.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:
     typedef struct {
         char name[32];
         IrdaAppSignal signal;
     } IrdaFileSignal;
 
-    IrdaAppFileParser() {
-        /* Assume we can save max 512 samples */
-        set_max_line_length(max_line_length);
-    }
+    bool open_irda_file_read(const char* filename);
+    bool open_irda_file_write(const char* filename);
+    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);
-    bool store_signal(File* file, const IrdaAppSignal& signal, const char* name);
+    std::unique_ptr<IrdaAppFileParser::IrdaFileSignal> read_signal();
+    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:
-    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_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;
 };
 

+ 53 - 141
applications/irda/irda-app-remote-manager.cpp

@@ -5,32 +5,29 @@
 #include "gui/modules/button_menu.h"
 #include "irda.h"
 #include <cstdio>
+#include <stdint.h>
 #include <string>
 #include <utility>
 #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 bool find_string(const std::vector<std::string>& strings, const std::string& match_string) {
-    for(const auto& string : strings) {
-        if(!string.compare(match_string)) return true;
-    }
-    return false;
-}
+std::string IrdaAppRemoteManager::find_vacant_remote_name(const std::string& name) {
+    IrdaAppFileParser file_parser;
+    bool exist = true;
 
-static std::string
-    find_vacant_name(const std::vector<std::string>& strings, const std::string& name) {
-    // if suggested name is occupied, try another one (name2, name3, etc)
-    if(find_string(strings, name)) {
-        int i = 1;
-        while(find_string(strings, name + std::to_string(++i)))
-            ;
-        return name + std::to_string(i);
-    } else {
+    if(!file_parser.is_irda_file_exist(name.c_str(), &exist)) {
+        return std::string();
+    } else if(!exist) {
         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) {
@@ -43,11 +40,10 @@ bool IrdaAppRemoteManager::add_remote_with_button(
     const IrdaAppSignal& signal) {
     furi_check(button_name != nullptr);
 
-    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, default_remote_name);
+    auto new_name = find_vacant_remote_name(default_remote_name);
+    if(new_name.empty()) {
+        return false;
+    }
 
     remote = std::make_unique<IrdaAppRemote>(new_name);
     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;
 }
 
-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() {
-    FS_Error fs_res;
+    bool result;
     IrdaAppFileParser file_parser;
 
-    fs_res = file_parser.get_fs_api().common.remove(make_full_name(remote->name).c_str());
-    if(fs_res != FSE_OK) {
-        file_parser.get_sd_api().show_error(
-            file_parser.get_sd_api().context, "Error deleting file");
-        return false;
-    }
+    result = file_parser.remove_irda_file(remote->name.c_str());
+    reset_remote();
+    return result;
+}
+
+void IrdaAppRemoteManager::reset_remote() {
     remote.reset();
-    return true;
 }
 
 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);
     auto& buttons = remote->buttons;
     furi_check(index < buttons.size());
-    return buttons[index].name;
+    return buttons[index].name.c_str();
 }
 
 std::string IrdaAppRemoteManager::get_remote_name() {
-    furi_check(remote.get() != nullptr);
-    return remote->name;
+    return remote ? remote->name : std::string();
 }
 
 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(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_remote_name(str);
+    if(new_name.empty()) {
+        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());
+    bool result = file_parser.rename_irda_file(remote->name.c_str(), 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;
+
+    return result;
 }
 
 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) {
-    File file;
-    std::string dirname(std::string("/") + irda_directory);
-
     IrdaAppFileParser file_parser;
-    FS_Error fs_err = file_parser.get_fs_api().common.mkdir(dirname.c_str());
-    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(
-        &file, make_full_name(remote->name).c_str(), FSAM_WRITE, FSOM_CREATE_ALWAYS);
+    bool result = true;
 
-    if(!res) {
-        file_parser.get_sd_api().show_error(
-            file_parser.get_sd_api().context, "Cannot create\nnew remote file");
+    if(!file_parser.open_irda_file_write(remote->name.c_str())) {
         return false;
     }
 
     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) {
-            file_parser.get_sd_api().show_error(
-                file_parser.get_sd_api().context, "Cannot write\nto key file");
-            file_parser.get_fs_api().file.close(&file);
-            return false;
-        }
-    }
-
-    file_parser.get_fs_api().file.close(&file);
-    file_parser.get_sd_api().check_error(file_parser.get_sd_api().context);
-
-    return true;
-}
-
-bool IrdaAppRemoteManager::get_remote_list(std::vector<std::string>& remote_names) const {
-    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
+            result = false;
+            break;
         }
     }
 
-    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);
+    file_parser.close();
 
-    return true;
+    return result;
 }
 
-bool IrdaAppRemoteManager::load(const std::string& name_arg, bool fullpath) {
+bool IrdaAppRemoteManager::load(const std::string& name) {
     bool fs_res = false;
     IrdaAppFileParser file_parser;
-    File file;
-    std::string full_filename;
-    std::string remote_name;
-
-    if(fullpath) {
-        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);
+    fs_res = file_parser.open_irda_file_read(name.c_str());
     if(!fs_res) {
-        file_parser.get_sd_api().show_error(
-            file_parser.get_sd_api().context, "Error opening file");
         return false;
     }
 
-    remote = std::make_unique<IrdaAppRemote>(remote_name);
+    remote = std::make_unique<IrdaAppRemote>(name);
 
     while(1) {
-        auto file_signal = file_parser.read_signal(&file);
-        if(!file_signal.get()) break;
+        auto file_signal = file_parser.read_signal();
+        if(!file_signal) {
+            break;
+        }
         remote->buttons.emplace_back(file_signal->name, file_signal->signal);
     }
-    file_parser.get_fs_api().file.close(&file);
+    file_parser.close();
 
     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;
-}

+ 3 - 4
applications/irda/irda-app-remote-manager.hpp

@@ -9,7 +9,6 @@
 #include <filesystem-api.h>
 #include "irda-app-signal.h"
 
-
 class IrdaAppRemoteButton {
     friend class IrdaAppRemoteManager;
     std::string name;
@@ -49,8 +48,8 @@ public:
     int find_remote_name(const std::vector<std::string>& strings);
     bool rename_button(uint32_t index, 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::string get_button_name(uint32_t index);
     std::string get_remote_name();
@@ -58,9 +57,9 @@ public:
     const IrdaAppSignal& get_button_data(size_t index) const;
     bool delete_button(uint32_t index);
     bool delete_remote();
+    void reset_remote();
 
     bool store();
-    bool load(const std::string& name, bool fullpath = false);
-    bool check_fs() const;
+    bool load(const std::string& name);
 };
 

+ 7 - 0
applications/irda/irda-app-view-manager.cpp

@@ -1,6 +1,7 @@
 #include "furi.h"
 #include "gui/modules/button_panel.h"
 #include "irda-app.hpp"
+#include "irda/irda-app-event.hpp"
 #include <callback-connector.h>
 
 IrdaAppViewManager::IrdaAppViewManager() {
@@ -98,6 +99,12 @@ osMessageQueueId_t IrdaAppViewManager::get_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) {
     if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) {
         event->type = IrdaAppEvent::Type::Tick;

+ 1 - 0
applications/irda/irda-app-view-manager.hpp

@@ -28,6 +28,7 @@ public:
 
     void receive_event(IrdaAppEvent* event);
     void send_event(IrdaAppEvent* event);
+    void clear_events();
 
     DialogEx* get_dialog_ex();
     Submenu* get_submenu();

+ 11 - 3
applications/irda/irda-app.cpp

@@ -1,4 +1,5 @@
 #include "irda-app.hpp"
+#include "irda/irda-app-file-parser.hpp"
 #include <irda_worker.h>
 #include <furi.h>
 #include <gui/gui.h>
@@ -12,12 +13,16 @@ int32_t IrdaApp::run(void* args) {
     bool exit = false;
 
     if(args) {
-        const char* remote_name = static_cast<const char*>(args);
-        bool result = remote_manager.load(std::string(remote_name), true);
+        std::string remote_name;
+        {
+            IrdaAppFileParser file_parser;
+            remote_name = file_parser.make_name(static_cast<const char*>(args));
+        }
+        bool result = remote_manager.load(remote_name);
         if(result) {
             current_scene = IrdaApp::Scene::Remote;
         } 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;
         }
     }
@@ -65,6 +70,7 @@ void IrdaApp::switch_to_next_scene_without_saving(Scene next_scene) {
         scenes[current_scene]->on_exit(this);
         current_scene = next_scene;
         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);
         current_scene = previous_scene;
         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);
     current_scene = previous_scene;
     scenes[current_scene]->on_enter(this);
+    view_manager.clear_events();
     return false;
 }
 

+ 1 - 3
applications/irda/irda-app.hpp

@@ -2,7 +2,7 @@
 #include <map>
 #include <irda.h>
 #include <furi.h>
-#include "irda/scene/irda-app-scene.hpp"
+#include "scene/irda-app-scene.hpp"
 #include "irda-app-event.hpp"
 #include "scene/irda-app-scene.hpp"
 #include "irda-app-view-manager.hpp"
@@ -34,7 +34,6 @@ public:
         LearnSuccess,
         LearnEnterName,
         LearnDone,
-        LearnDoneAfter,
         Remote,
         RemoteList,
         Edit,
@@ -126,7 +125,6 @@ private:
         {Scene::LearnSuccess, new IrdaAppSceneLearnSuccess()},
         {Scene::LearnEnterName, new IrdaAppSceneLearnEnterName()},
         {Scene::LearnDone, new IrdaAppSceneLearnDone()},
-        {Scene::LearnDoneAfter, new IrdaAppSceneLearnDoneAfter()},
         {Scene::Remote, new IrdaAppSceneRemote()},
         {Scene::RemoteList, new IrdaAppSceneRemoteList()},
         {Scene::Edit, new IrdaAppSceneEdit()},

+ 0 - 32
applications/irda/scene/irda-app-scene-learn-done-after.cpp

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

+ 1 - 5
applications/irda/scene/irda-app-scene-learn-done.cpp

@@ -24,11 +24,7 @@ bool IrdaAppSceneLearnDone::on_event(IrdaApp* app, IrdaAppEvent* event) {
     bool consumed = false;
 
     if(event->type == IrdaAppEvent::Type::PopupTimer) {
-        if(app->get_learn_new_remote()) {
-            app->switch_to_next_scene(IrdaApp::Scene::LearnDoneAfter);
-        } else {
-            app->switch_to_next_scene(IrdaApp::Scene::Remote);
-        }
+        app->switch_to_next_scene(IrdaApp::Scene::Remote);
         consumed = true;
     }
 

+ 8 - 3
applications/irda/scene/irda-app-scene-learn-success.cpp

@@ -1,5 +1,7 @@
 #include "../irda-app.hpp"
 #include "irda.h"
+#include "../irda-app-file-parser.hpp"
+#include <memory>
 
 static void dialog_result_callback(DialogExResult result, void* context) {
     auto app = static_cast<IrdaApp*>(context);
@@ -61,15 +63,18 @@ bool IrdaAppSceneLearnSuccess::on_event(IrdaApp* app, IrdaAppEvent* event) {
             signal.transmit();
             break;
         }
-        case DialogExResultRight:
-            auto remote_manager = app->get_remote_manager();
-            if(remote_manager->check_fs()) {
+        case DialogExResultRight: {
+            IrdaAppFileParser file_parser;
+            if(file_parser.check_errors()) {
                 app->switch_to_next_scene(IrdaApp::Scene::LearnEnterName);
             } else {
                 app->switch_to_previous_scene();
             }
             break;
         }
+        default:
+            break;
+        }
     }
 
     return consumed;

+ 13 - 58
applications/irda/scene/irda-app-scene-remote-list.cpp

@@ -1,75 +1,30 @@
 #include "../irda-app.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);
-}
+#include "irda/irda-app-event.hpp"
 
 void IrdaAppSceneRemoteList::on_enter(IrdaApp* app) {
-    IrdaAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
+    IrdaAppFileParser file_parser;
+    bool success = false;
     auto remote_manager = app->get_remote_manager();
-    int i = 0;
-
-    bool result = remote_manager->get_remote_list(remote_names);
-    if(!result) {
-        app->switch_to_previous_scene();
-        return;
-    }
-
-    for(auto& name : remote_names) {
-        submenu_add_item(submenu, name.c_str(), i++, submenu_callback, app);
+    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;
+        }
     }
-    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);
+    if(!success) {
+        app->switch_to_previous_scene();
     }
-
-    submenu_item_selected = 0;
-    view_manager->switch_to(IrdaAppViewManager::ViewType::Submenu);
 }
 
 bool IrdaAppSceneRemoteList::on_event(IrdaApp* app, IrdaAppEvent* event) {
     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;
 }
 
 void IrdaAppSceneRemoteList::on_exit(IrdaApp* app) {
-    IrdaAppViewManager* view_manager = app->get_view_manager();
-    Submenu* submenu = view_manager->get_submenu();
-
-    submenu_clean(submenu);
 }

+ 1 - 0
applications/irda/scene/irda-app-scene-start.cpp

@@ -61,5 +61,6 @@ void IrdaAppSceneStart::on_exit(IrdaApp* app) {
     IrdaAppViewManager* view_manager = app->get_view_manager();
     Submenu* submenu = view_manager->get_submenu();
 
+    app->get_remote_manager()->reset_remote();
     submenu_clean(submenu);
 }

+ 18 - 17
applications/irda/scene/irda-app-scene-universal-common.cpp

@@ -28,7 +28,7 @@ static bool irda_popup_brut_input_callback(InputEvent* event, void* context) {
         consumed = true;
         IrdaAppEvent irda_event;
 
-        irda_event.type = IrdaAppEvent::Type::ButtonPanelPopupBackPressed;
+        irda_event.type = IrdaAppEvent::Type::Back;
         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 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();
             IrdaAppEvent tick_event = {.type = IrdaAppEvent::Type::Tick};
             view_manager->send_event(&tick_event);
@@ -70,26 +70,27 @@ bool IrdaAppSceneUniversalCommon::on_event(IrdaApp* app, IrdaAppEvent* event) {
                 brute_force_started = false;
                 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;
-    }
-
-    if(event->type == IrdaAppEvent::Type::ButtonPanelPopupBackPressed) {
-        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) {
+    } else {
+        if(event->type == IrdaAppEvent::Type::ButtonPanelPressed) {
+            int record_amount = 0;
+            if(brute_force.start_bruteforce(event->payload.menu_index, record_amount)) {
                 brute_force_started = true;
                 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();
+            consumed = true;
         }
-        consumed = true;
     }
 
     return consumed;

+ 2 - 9
applications/irda/scene/irda-app-scene.hpp

@@ -65,13 +65,6 @@ public:
     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 {
 public:
     void on_enter(IrdaApp* app) final;
@@ -155,14 +148,14 @@ protected:
 class IrdaAppSceneUniversalTV : public IrdaAppSceneUniversalCommon {
 public:
     void on_enter(IrdaApp* app) final;
-    IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/irda/universal/tv.ir") {}
+    IrdaAppSceneUniversalTV() : IrdaAppSceneUniversalCommon("/assets/ext/irda/tv.ir") {}
     ~IrdaAppSceneUniversalTV() {}
 };
 
 class IrdaAppSceneUniversalAudio : public IrdaAppSceneUniversalCommon {
 public:
     void on_enter(IrdaApp* app) final;
-    IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/irda/universal/audio.ir") {}
+    IrdaAppSceneUniversalAudio() : IrdaAppSceneUniversalCommon("/assets/ext/irda/audio.ir") {}
     ~IrdaAppSceneUniversalAudio() {}
 };
 

+ 9 - 4
applications/sd-filesystem/sd-filesystem.c

@@ -42,7 +42,7 @@ typedef struct {
     const char* extension;
     char* result;
     uint8_t result_size;
-    char* selected_filename;
+    const char* selected_filename;
 } SdAppFileSelectData;
 
 typedef struct {
@@ -64,7 +64,7 @@ bool sd_api_file_select(
     const char* extension,
     char* result,
     uint8_t result_size,
-    char* selected_filename);
+    const char* selected_filename);
 void sd_api_check_error(SdApp* sd_app);
 void sd_api_show_error(SdApp* sd_app, const char* error_text);
 
@@ -435,7 +435,7 @@ bool sd_api_file_select(
     const char* extension,
     char* result,
     uint8_t result_size,
-    char* selected_filename) {
+    const char* selected_filename) {
     bool retval = false;
 
     SdAppEvent message = {
@@ -629,7 +629,12 @@ void free_view_holder(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);
     string_set_str(sd_app->text_holder, "");
     sd_app->sd_app_state = SdAppStateBackground;

+ 19 - 2
lib/app-scened-template/file-worker-cpp.cpp

@@ -62,7 +62,24 @@ bool FileWorkerCpp::file_select(
     const char* extension,
     char* result,
     uint8_t result_size,
-    char* selected_filename) {
+    const char* selected_filename) {
     return file_worker_file_select(
         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);
+}
+

+ 44 - 1
lib/app-scened-template/file-worker-cpp.h

@@ -128,7 +128,50 @@ public:
         const char* extension,
         char* result,
         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:
     FileWorker* file_worker;

+ 98 - 4
lib/app-scened-template/file-worker.c

@@ -1,4 +1,5 @@
 #include "file-worker.h"
+#include "m-string.h"
 #include <hex.h>
 #include <sd-card-api.h>
 #include <furi.h>
@@ -8,6 +9,8 @@ struct FileWorker {
     SdCard_Api* sd_ex_api;
     bool silent;
     File file;
+    char file_buf[48];
+    size_t file_buf_cnt;
 };
 
 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->fs_api = furi_record_open("sdcard");
     file_worker->sd_ex_api = furi_record_open("sdcard-ex");
+    file_worker->file_buf_cnt = 0;
 
     return file_worker;
 }
@@ -215,14 +219,17 @@ bool file_worker_file_select(
     const char* extension,
     char* result,
     uint8_t result_size,
-    char* selected_filename) {
+    const char* selected_filename) {
     return file_worker->sd_ex_api->file_select(
         file_worker->sd_ex_api->context, path, extension, result, result_size, selected_filename);
 }
 
 bool file_worker_check_common_errors(FileWorker* file_worker) {
-    file_worker->sd_ex_api->check_error(file_worker->sd_ex_api->context);
-    return true;
+    /* TODO: [FL-1431] Add return value to file_parser.get_sd_api().check_error() and replace get_fs_info(). */
+    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) {
@@ -277,4 +284,91 @@ bool file_worker_seek_internal(FileWorker* file_worker, uint64_t position, bool
     }
 
     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);
+}
+

+ 49 - 2
lib/app-scened-template/file-worker.h

@@ -163,8 +163,55 @@ bool file_worker_file_select(
     const char* extension,
     char* result,
     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
 }
-#endif
+#endif

+ 2 - 2
lib/common-api/sd-card-api.h

@@ -15,11 +15,11 @@ typedef struct {
         const char* extension,
         char* result,
         uint8_t result_size,
-        char* selected_filename);
+        const char* selected_filename);
     void (*check_error)(SdApp* context);
     void (*show_error)(SdApp* context, const char* error_text);
 } SdCard_Api;
 
 #ifdef __cplusplus
 }
-#endif
+#endif