mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-10 05:59:08 -07:00
Merge branch 'dev' of https://github.com/flipperdevices/flipperzero-firmware into xfw-dev
This commit is contained in:
@@ -4,7 +4,6 @@ App(
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
targets=["f7"],
|
||||
entry_point="accessor_app",
|
||||
cdefines=["APP_ACCESSOR"],
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
order=40,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Battery Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="battery_test_app",
|
||||
cdefines=["APP_BATTERY_TEST"],
|
||||
requires=[
|
||||
"gui",
|
||||
"power",
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Blink Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="blink_test_app",
|
||||
cdefines=["APP_BLINK"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=10,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="CCID Debug",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="ccid_test_app",
|
||||
cdefines=["CCID_TEST"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Crash Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="crash_test_app",
|
||||
cdefines=["APP_CRASH_TEST"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_category="Debug",
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Display Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="display_test_app",
|
||||
cdefines=["APP_DISPLAY_TEST"],
|
||||
requires=["gui"],
|
||||
fap_libs=["misc"],
|
||||
stack_size=1 * 1024,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="File Browser Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="file_browser_app",
|
||||
cdefines=["APP_FILE_BROWSER_TEST"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=150,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Keypad Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="keypad_test_app",
|
||||
cdefines=["APP_KEYPAD_TEST"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=30,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Locale Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="locale_test_app",
|
||||
cdefines=["APP_LOCALE"],
|
||||
requires=["gui", "locale"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Text Box Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="text_box_test_app",
|
||||
cdefines=["APP_TEXT_BOX_TEST"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=140,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="UART Echo",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="uart_echo_app",
|
||||
cdefines=["APP_UART_ECHO"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
|
||||
@@ -3,18 +3,29 @@
|
||||
#include <furi.h>
|
||||
#include "../minunit.h"
|
||||
|
||||
void test_furi_create_open() {
|
||||
// 1. Create record
|
||||
uint8_t test_data = 0;
|
||||
furi_record_create("test/holding", (void*)&test_data);
|
||||
#define TEST_RECORD_NAME "test/holding"
|
||||
|
||||
// 2. Open it
|
||||
void* record = furi_record_open("test/holding");
|
||||
void test_furi_create_open() {
|
||||
// Test that record does not exist
|
||||
mu_check(furi_record_exists(TEST_RECORD_NAME) == false);
|
||||
|
||||
// Create record
|
||||
uint8_t test_data = 0;
|
||||
furi_record_create(TEST_RECORD_NAME, (void*)&test_data);
|
||||
|
||||
// Test that record exists
|
||||
mu_check(furi_record_exists(TEST_RECORD_NAME) == true);
|
||||
|
||||
// Open it
|
||||
void* record = furi_record_open(TEST_RECORD_NAME);
|
||||
mu_assert_pointers_eq(record, &test_data);
|
||||
|
||||
// 3. Close it
|
||||
furi_record_close("test/holding");
|
||||
// Close it
|
||||
furi_record_close(TEST_RECORD_NAME);
|
||||
|
||||
// 4. Clean up
|
||||
furi_record_destroy("test/holding");
|
||||
// Clean up
|
||||
furi_record_destroy(TEST_RECORD_NAME);
|
||||
|
||||
// Test that record does not exist
|
||||
mu_check(furi_record_exists(TEST_RECORD_NAME) == false);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ __attribute__((unused)) static void (*minunit_teardown)(void) = NULL;
|
||||
|
||||
void minunit_print_progress(void);
|
||||
void minunit_print_fail(const char* error);
|
||||
void minunit_printf_warning(const char* format, ...);
|
||||
|
||||
/* Definitions */
|
||||
#define MU_TEST(method_name) static void method_name(void)
|
||||
@@ -150,6 +151,10 @@ void minunit_print_fail(const char* error);
|
||||
minunit_end_proc_timer - minunit_proc_timer);)
|
||||
#define MU_EXIT_CODE minunit_fail
|
||||
|
||||
/* Warnings */
|
||||
#define mu_warn(message) \
|
||||
MU__SAFE_BLOCK(minunit_printf_warning("%s:%d: %s", __FILE__, __LINE__, message);)
|
||||
|
||||
/* Assertions */
|
||||
#define mu_check(test) \
|
||||
MU__SAFE_BLOCK( \
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <nfc/nfc_poller.h>
|
||||
#include <nfc/nfc_listener.h>
|
||||
#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
|
||||
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller_sync_api.h>
|
||||
#include <nfc/protocols/iso14443_3a/iso14443_3a_poller_sync.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#include <nfc/helpers/nfc_dict.h>
|
||||
#include <nfc/nfc.h>
|
||||
@@ -182,8 +182,8 @@ MU_TEST(iso14443_3a_reader) {
|
||||
|
||||
Iso14443_3aData iso14443_3a_poller_data = {};
|
||||
mu_assert(
|
||||
iso14443_3a_poller_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone,
|
||||
"iso14443_3a_poller_read() failed");
|
||||
iso14443_3a_poller_sync_read(poller, &iso14443_3a_poller_data) == Iso14443_3aErrorNone,
|
||||
"iso14443_3a_poller_sync_read() failed");
|
||||
|
||||
nfc_listener_stop(iso3_listener);
|
||||
mu_assert(
|
||||
@@ -203,15 +203,26 @@ static void mf_ultralight_reader_test(const char* path) {
|
||||
NfcDevice* nfc_device = nfc_device_alloc();
|
||||
mu_assert(nfc_device_load(nfc_device, path), "nfc_device_load() failed\r\n");
|
||||
|
||||
NfcListener* mfu_listener = nfc_listener_alloc(
|
||||
listener,
|
||||
NfcProtocolMfUltralight,
|
||||
nfc_device_get_data(nfc_device, NfcProtocolMfUltralight));
|
||||
MfUltralightData* data =
|
||||
(MfUltralightData*)nfc_device_get_data(nfc_device, NfcProtocolMfUltralight);
|
||||
|
||||
uint32_t features = mf_ultralight_get_feature_support_set(data->type);
|
||||
bool pwd_supported =
|
||||
mf_ultralight_support_feature(features, MfUltralightFeatureSupportPasswordAuth);
|
||||
uint8_t pwd_num = mf_ultralight_get_pwd_page_num(data->type);
|
||||
const uint8_t zero_pwd[4] = {0, 0, 0, 0};
|
||||
|
||||
if(pwd_supported && !memcmp(data->page[pwd_num].data, zero_pwd, sizeof(zero_pwd))) {
|
||||
data->pages_read -= 2;
|
||||
}
|
||||
|
||||
NfcListener* mfu_listener = nfc_listener_alloc(listener, NfcProtocolMfUltralight, data);
|
||||
|
||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
nfc_listener_stop(mfu_listener);
|
||||
nfc_listener_free(mfu_listener);
|
||||
@@ -259,8 +270,8 @@ MU_TEST(ntag_213_locked_reader) {
|
||||
nfc_listener_start(mfu_listener, NULL, NULL);
|
||||
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
nfc_listener_stop(mfu_listener);
|
||||
nfc_listener_free(mfu_listener);
|
||||
@@ -297,8 +308,8 @@ static void mf_ultralight_write() {
|
||||
MfUltralightData* mfu_data = mf_ultralight_alloc();
|
||||
|
||||
// Initial read
|
||||
MfUltralightError error = mf_ultralight_poller_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
||||
MfUltralightError error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
mu_assert(
|
||||
mf_ultralight_is_equal(mfu_data, nfc_device_get_data(nfc_device, NfcProtocolMfUltralight)),
|
||||
@@ -310,13 +321,13 @@ static void mf_ultralight_write() {
|
||||
FURI_LOG_D(TAG, "Writing page %d", i);
|
||||
furi_hal_random_fill_buf(page.data, sizeof(MfUltralightPage));
|
||||
mfu_data->page[i] = page;
|
||||
error = mf_ultralight_poller_write_page(poller, i, &page);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_write_page() failed");
|
||||
error = mf_ultralight_poller_sync_write_page(poller, i, &page);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_write_page() failed");
|
||||
}
|
||||
|
||||
// Verification read
|
||||
error = mf_ultralight_poller_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_read_card() failed");
|
||||
error = mf_ultralight_poller_sync_read_card(poller, mfu_data);
|
||||
mu_assert(error == MfUltralightErrorNone, "mf_ultralight_poller_sync_read_card() failed");
|
||||
|
||||
nfc_listener_stop(mfu_listener);
|
||||
const MfUltralightData* mfu_listener_data =
|
||||
@@ -344,7 +355,7 @@ static void mf_classic_reader() {
|
||||
MfClassicBlock block = {};
|
||||
MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
|
||||
|
||||
mf_classic_poller_read_block(poller, 0, &key, MfClassicKeyTypeA, &block);
|
||||
mf_classic_poller_sync_read_block(poller, 0, &key, MfClassicKeyTypeA, &block);
|
||||
|
||||
nfc_listener_stop(mfc_listener);
|
||||
nfc_listener_free(mfc_listener);
|
||||
@@ -372,8 +383,8 @@ static void mf_classic_write() {
|
||||
furi_hal_random_fill_buf(block_write.data, sizeof(MfClassicBlock));
|
||||
MfClassicKey key = {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
|
||||
|
||||
mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
||||
mf_classic_poller_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read);
|
||||
mf_classic_poller_sync_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
||||
mf_classic_poller_sync_read_block(poller, 1, &key, MfClassicKeyTypeA, &block_read);
|
||||
|
||||
nfc_listener_stop(mfc_listener);
|
||||
nfc_listener_free(mfc_listener);
|
||||
@@ -402,16 +413,18 @@ static void mf_classic_value_block() {
|
||||
mf_classic_value_to_block(value, 1, &block_write);
|
||||
|
||||
MfClassicError error = MfClassicErrorNone;
|
||||
error = mf_classic_poller_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
||||
error = mf_classic_poller_sync_write_block(poller, 1, &key, MfClassicKeyTypeA, &block_write);
|
||||
mu_assert(error == MfClassicErrorNone, "Write failed");
|
||||
|
||||
int32_t data = 200;
|
||||
int32_t new_value = 0;
|
||||
error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, data, &new_value);
|
||||
error =
|
||||
mf_classic_poller_sync_change_value(poller, 1, &key, MfClassicKeyTypeA, data, &new_value);
|
||||
mu_assert(error == MfClassicErrorNone, "Value increment failed");
|
||||
mu_assert(new_value == value + data, "Value not match");
|
||||
|
||||
error = mf_classic_poller_change_value(poller, 1, &key, MfClassicKeyTypeA, -data, &new_value);
|
||||
error =
|
||||
mf_classic_poller_sync_change_value(poller, 1, &key, MfClassicKeyTypeA, -data, &new_value);
|
||||
mu_assert(error == MfClassicErrorNone, "Value decrement failed");
|
||||
mu_assert(new_value == value, "Value not match");
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Filetype: Flipper SubGhz Key File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok270Async
|
||||
Protocol: Mastercode
|
||||
Bit: 36
|
||||
Key: 00 00 00 0B 7E 00 3C 08
|
||||
@@ -0,0 +1,6 @@
|
||||
Filetype: Flipper SubGhz RAW File
|
||||
Version: 1
|
||||
Frequency: 433920000
|
||||
Preset: FuriHalSubGhzPresetOok270Async
|
||||
Protocol: RAW
|
||||
RAW_Data: 10389 -66 405095 -102 207 -106 1165 -130 963739 -1232 899 -2250 2003 -1190 2017 -1202 911 -2256 2021 -1162 2045 -1134 2047 -1164 2047 -1138 2031 -1180 2039 -1182 949 -2190 995 -2214 961 -2228 963 -2198 963 -2214 977 -2212 975 -2210 975 -2208 971 -2200 963 -2210 993 -2184 2075 -1130 2051 -1142 2055 -1136 2047 -1178 965 -2236 933 -2220 975 -2184 999 -2222 967 -2208 969 -2214 979 -2202 2027 -1156 975 -2242 943 -16080 2023 -1162 967 -2220 2057 -1114 2061 -1124 1007 -2242 2025 -1134 2055 -1168 2017 -1138 2075 -1134 2053 -1136 2075 -1130 979 -2214 979 -2174 999 -2182 1001 -2204 977 -2206 1003 -2188 979 -2176 999 -2182 1009 -2176 1009 -2176 1001 -2212 2029 -1116 2091 -1102 2109 -1092 2095 -1126 1001 -2150 1011 -2180 1011 -2180 1009 -2178 1009 -2172 1009 -2166 1001 -2198 2065 -1136 975 -2220 971 -16018 2097 -1166 951 -2240 2009 -1186 2011 -1160 979 -2208 2035 -1134 2053 -1138 2061 -1158 2045 -1152 2029 -1152 2051 -1166 963 -2188 993 -2222 951 -2214 963 -2220 965 -2212 979 -2212 977 -2180 1003 -2202 965 -2218 975 -2216 967 -2188 2061 -1124 2083 -1126 2071 -1130 2059 -1134 993 -2188 979 -2240 947 -2204 979 -2214 971 -2214 973 -2210 971 -2206 2053 -1130 979 -2216 969 -16056 2053 -1134 1001 -2224 2021 -1150 2051 -1154 953 -2240 2045 -1146 2023 -1168 2033 -1144 2065 -1146 2055 -1130 2071 -1160 961 -2192 973 -2190 1005 -2214 975 -2206 967 -2206 975 -2206 967 -2208 975 -2212 967 -2212 979 -2218 977 -2178 2063 -1156 2035 -1160 2061 -1126 2065 -1130 981 -2186 1003 -2210 977 -2208 973 -2202 977 -2200 965 -2248 943 -2206 2039 -1190 941 -48536 65 -7254 263 -68 363 -102 131 -232 263 -264 751 -230 225 -822 397 -634 231 -268 263 -134 267 -64 867 -132 305 -138 67 -100 331 -98 891 -66 455 -66 531 -100 299 -134 897 -98 693 -132 291 -132 333 -98 337 -68 331
|
||||
@@ -139,7 +139,7 @@ static bool write_file_13DA(Storage* storage, const char* path) {
|
||||
File* file = storage_file_alloc(storage);
|
||||
bool result = false;
|
||||
if(storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
result = storage_file_write(file, "13DA", 4) == 4;
|
||||
result = (storage_file_write(file, "13DA", 4) == 4);
|
||||
}
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
|
||||
@@ -115,6 +115,66 @@ MU_TEST(storage_file_open_close) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static bool storage_file_read_write_test(File* file, uint8_t* data, size_t test_size) {
|
||||
const char* filename = UNIT_TESTS_PATH("storage_chunk.test");
|
||||
|
||||
// fill with pattern
|
||||
for(size_t i = 0; i < test_size; i++) {
|
||||
data[i] = (i % 113);
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
do {
|
||||
if(!storage_file_open(file, filename, FSAM_WRITE, FSOM_CREATE_ALWAYS)) break;
|
||||
if(test_size != storage_file_write(file, data, test_size)) break;
|
||||
storage_file_close(file);
|
||||
|
||||
// reset data
|
||||
memset(data, 0, test_size);
|
||||
|
||||
if(!storage_file_open(file, filename, FSAM_READ, FSOM_OPEN_EXISTING)) break;
|
||||
if(test_size != storage_file_read(file, data, test_size)) break;
|
||||
storage_file_close(file);
|
||||
|
||||
// check that data is correct
|
||||
for(size_t i = 0; i < test_size; i++) {
|
||||
if(data[i] != (i % 113)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MU_TEST(storage_file_read_write_64k) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
size_t size_1k = 1024;
|
||||
size_t size_64k = size_1k + size_1k * 63;
|
||||
size_t size_65k = size_64k + size_1k;
|
||||
size_t size_max = size_65k + 8;
|
||||
|
||||
size_t max_ram_block = memmgr_heap_get_max_free_block();
|
||||
|
||||
if(max_ram_block < size_max) {
|
||||
mu_warn("Not enough RAM for >64k block test");
|
||||
} else {
|
||||
uint8_t* data = malloc(size_max);
|
||||
mu_check(storage_file_read_write_test(file, data, size_1k));
|
||||
mu_check(storage_file_read_write_test(file, data, size_64k));
|
||||
mu_check(storage_file_read_write_test(file, data, size_65k));
|
||||
mu_check(storage_file_read_write_test(file, data, size_max));
|
||||
free(data);
|
||||
}
|
||||
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(storage_file) {
|
||||
storage_file_open_lock_setup();
|
||||
MU_RUN_TEST(storage_file_open_close);
|
||||
@@ -122,6 +182,10 @@ MU_TEST_SUITE(storage_file) {
|
||||
storage_file_open_lock_teardown();
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(storage_file_64k) {
|
||||
MU_RUN_TEST(storage_file_read_write_64k);
|
||||
}
|
||||
|
||||
MU_TEST(storage_dir_open_close) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file;
|
||||
@@ -640,6 +704,7 @@ MU_TEST_SUITE(test_md5_calc_suite) {
|
||||
|
||||
int run_minunit_test_storage() {
|
||||
MU_RUN_SUITE(storage_file);
|
||||
MU_RUN_SUITE(storage_file_64k);
|
||||
MU_RUN_SUITE(storage_dir);
|
||||
MU_RUN_SUITE(storage_rename);
|
||||
MU_RUN_SUITE(test_data_path);
|
||||
|
||||
@@ -324,6 +324,7 @@ bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) {
|
||||
furi_hal_subghz_set_frequency_and_path(433920000);
|
||||
|
||||
if(!furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test)) {
|
||||
mu_warn("SubGHZ transmission is prohibited");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -652,6 +653,13 @@ MU_TEST(subghz_decoder_kinggates_stylo4k_test) {
|
||||
"Test decoder " SUBGHZ_PROTOCOL_KINGGATES_STYLO_4K_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_mastercode_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
EXT_PATH("unit_tests/subghz/mastercode_raw.sub"), SUBGHZ_PROTOCOL_MASTERCODE_NAME),
|
||||
"Test decoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||
}
|
||||
|
||||
//test encoders
|
||||
MU_TEST(subghz_encoder_princeton_test) {
|
||||
mu_assert(
|
||||
@@ -803,6 +811,12 @@ MU_TEST(subghz_encoder_dooya_test) {
|
||||
"Test encoder " SUBGHZ_PROTOCOL_DOOYA_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_encoder_mastercode_test) {
|
||||
mu_assert(
|
||||
subghz_encoder_test(EXT_PATH("unit_tests/subghz/mastercode.sub")),
|
||||
"Test encoder " SUBGHZ_PROTOCOL_MASTERCODE_NAME " error\r\n");
|
||||
}
|
||||
|
||||
MU_TEST(subghz_decoder_acurite_592txr_test) {
|
||||
mu_assert(
|
||||
subghz_decoder_test(
|
||||
@@ -860,6 +874,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_decoder_alutech_at_4n_test);
|
||||
MU_RUN_TEST(subghz_decoder_nice_one_test);
|
||||
MU_RUN_TEST(subghz_decoder_kinggates_stylo4k_test);
|
||||
MU_RUN_TEST(subghz_decoder_mastercode_test);
|
||||
|
||||
MU_RUN_TEST(subghz_encoder_princeton_test);
|
||||
MU_RUN_TEST(subghz_encoder_came_test);
|
||||
@@ -886,6 +901,7 @@ MU_TEST_SUITE(subghz) {
|
||||
MU_RUN_TEST(subghz_encoder_smc5326_test);
|
||||
MU_RUN_TEST(subghz_encoder_holtek_ht12x_test);
|
||||
MU_RUN_TEST(subghz_encoder_dooya_test);
|
||||
MU_RUN_TEST(subghz_encoder_mastercode_test);
|
||||
MU_RUN_TEST(subghz_decoder_acurite_592txr_test);
|
||||
|
||||
MU_RUN_TEST(subghz_random_test);
|
||||
|
||||
@@ -78,6 +78,16 @@ void minunit_print_fail(const char* str) {
|
||||
printf(_FURI_LOG_CLR_E "%s\r\n" _FURI_LOG_CLR_RESET, str);
|
||||
}
|
||||
|
||||
void minunit_printf_warning(const char* format, ...) {
|
||||
FuriString* str = furi_string_alloc();
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
furi_string_vprintf(str, format, args);
|
||||
va_end(args);
|
||||
printf(_FURI_LOG_CLR_W "%s\r\n" _FURI_LOG_CLR_RESET, furi_string_get_cstr(str));
|
||||
furi_string_free(str);
|
||||
}
|
||||
|
||||
void unit_tests_cli(Cli* cli, FuriString* args, void* context) {
|
||||
UNUSED(cli);
|
||||
UNUSED(args);
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="USB Mouse",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="usb_mouse_app",
|
||||
cdefines=["APP_USB_MOUSE"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=60,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="USB Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="usb_test_app",
|
||||
cdefines=["APP_USB_TEST"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=50,
|
||||
|
||||
@@ -3,7 +3,6 @@ App(
|
||||
name="Vibro Test",
|
||||
apptype=FlipperAppType.DEBUG,
|
||||
entry_point="vibro_test_app",
|
||||
cdefines=["APP_VIBRO_TEST"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
|
||||
@@ -12,12 +12,12 @@ static bool archive_favorites_read_line(File* file, FuriString* str_result) {
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
uint16_t read_count = storage_file_read(file, buffer, ARCHIVE_FAV_FILE_BUF_LEN);
|
||||
size_t read_count = storage_file_read(file, buffer, ARCHIVE_FAV_FILE_BUF_LEN);
|
||||
if(storage_file_get_error(file) != FSE_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint16_t i = 0; i < read_count; i++) {
|
||||
for(size_t i = 0; i < read_count; i++) {
|
||||
if(buffer[i] == '\n') {
|
||||
uint32_t position = storage_file_tell(file);
|
||||
if(storage_file_get_error(file) != FSE_OK) {
|
||||
|
||||
@@ -176,22 +176,21 @@ void ibutton_free(iButton* ibutton) {
|
||||
free(ibutton);
|
||||
}
|
||||
|
||||
bool ibutton_load_key(iButton* ibutton) {
|
||||
bool ibutton_load_key(iButton* ibutton, bool show_error) {
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewLoading);
|
||||
|
||||
const bool success = ibutton_protocols_load(
|
||||
ibutton->protocols, ibutton->key, furi_string_get_cstr(ibutton->file_path));
|
||||
|
||||
if(!success) {
|
||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
||||
|
||||
} else {
|
||||
if(success) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
path_extract_filename(ibutton->file_path, tmp, true);
|
||||
strncpy(ibutton->key_name, furi_string_get_cstr(tmp), IBUTTON_KEY_NAME_SIZE);
|
||||
|
||||
furi_string_free(tmp);
|
||||
} else if(show_error) {
|
||||
dialog_message_show_storage_error(ibutton->dialogs, "Cannot load\nkey file");
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -212,7 +211,7 @@ bool ibutton_select_and_load_key(iButton* ibutton) {
|
||||
if(!dialog_file_browser_show(
|
||||
ibutton->dialogs, ibutton->file_path, ibutton->file_path, &browser_options))
|
||||
break;
|
||||
success = ibutton_load_key(ibutton);
|
||||
success = ibutton_load_key(ibutton, true);
|
||||
} while(!success);
|
||||
|
||||
return success;
|
||||
@@ -286,7 +285,7 @@ int32_t ibutton_app(char* arg) {
|
||||
|
||||
} else {
|
||||
furi_string_set(ibutton->file_path, (const char*)arg);
|
||||
key_loaded = ibutton_load_key(ibutton);
|
||||
key_loaded = ibutton_load_key(ibutton, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ typedef enum {
|
||||
} iButtonNotificationMessage;
|
||||
|
||||
bool ibutton_select_and_load_key(iButton* ibutton);
|
||||
bool ibutton_load_key(iButton* ibutton);
|
||||
bool ibutton_load_key(iButton* ibutton, bool show_error);
|
||||
bool ibutton_save_key(iButton* ibutton);
|
||||
bool ibutton_delete_key(iButton* ibutton);
|
||||
void ibutton_reset_key(iButton* ibutton);
|
||||
|
||||
@@ -43,7 +43,7 @@ bool ibutton_scene_add_value_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
// User cancelled editing, reload the key from storage
|
||||
if(scene_manager_has_previous_scene(scene_manager, iButtonSceneSavedKeyMenu)) {
|
||||
if(!ibutton_load_key(ibutton)) {
|
||||
if(!ibutton_load_key(ibutton, true)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
scene_manager, iButtonSceneStart);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ bool ibutton_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == iButtonCustomEventRpcLoadFile) {
|
||||
bool result = false;
|
||||
|
||||
if(ibutton_load_key(ibutton)) {
|
||||
if(ibutton_load_key(ibutton, false)) {
|
||||
popup_set_text(popup, ibutton->key_name, 82, 32, AlignCenter, AlignTop);
|
||||
view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Plantain"
|
||||
|
||||
@@ -91,7 +91,7 @@ static bool plantain_verify_type(Nfc* nfc, MfClassicType type) {
|
||||
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicError error =
|
||||
mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
break;
|
||||
@@ -119,7 +119,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicTypeMini;
|
||||
MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
@@ -134,7 +134,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_read(nfc, &keys, data);
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Troika"
|
||||
|
||||
@@ -91,7 +91,7 @@ static bool troika_verify_type(Nfc* nfc, MfClassicType type) {
|
||||
|
||||
MfClassicAuthContext auth_context;
|
||||
MfClassicError error =
|
||||
mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
break;
|
||||
@@ -118,7 +118,7 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) {
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicTypeMini;
|
||||
MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
@@ -136,7 +136,7 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) {
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_read(nfc, &keys, data);
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "TwoCities"
|
||||
|
||||
@@ -49,7 +49,7 @@ bool two_cities_verify(Nfc* nfc) {
|
||||
|
||||
MfClassicAuthContext auth_ctx = {};
|
||||
MfClassicError error =
|
||||
mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
break;
|
||||
@@ -72,7 +72,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicTypeMini;
|
||||
MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
@@ -84,7 +84,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_read(nfc, &keys, data);
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
|
||||
@@ -466,7 +466,7 @@ static void rpc_system_storage_write_process(const PB_Main* request, void* conte
|
||||
request->content.storage_write_request.file.data->size) {
|
||||
uint8_t* buffer = request->content.storage_write_request.file.data->bytes;
|
||||
size_t buffer_size = request->content.storage_write_request.file.data->size;
|
||||
uint16_t written_size = storage_file_write(file, buffer, buffer_size);
|
||||
size_t written_size = storage_file_write(file, buffer, buffer_size);
|
||||
fs_operation_success = (written_size == buffer_size);
|
||||
}
|
||||
|
||||
|
||||
@@ -173,6 +173,13 @@ typedef struct {
|
||||
* @param total_space pointer to total space value
|
||||
* @param free_space pointer to free space value
|
||||
* @return FS_Error error info
|
||||
*
|
||||
* @var FS_Common_Api::equivalent_path
|
||||
* @brief Test whether two paths are equivalent (e.g differing case on a case-insensitive fs)
|
||||
* @param path1 first path to be compared
|
||||
* @param path2 second path to be compared
|
||||
* @param truncate if set to true, compare only up to the path1's length
|
||||
* @return true if path1 and path2 are considered equivalent
|
||||
*/
|
||||
typedef struct {
|
||||
FS_Error (*const stat)(void* context, const char* path, FileInfo* fileinfo);
|
||||
@@ -183,6 +190,7 @@ typedef struct {
|
||||
const char* fs_path,
|
||||
uint64_t* total_space,
|
||||
uint64_t* free_space);
|
||||
bool (*const equivalent_path)(const char* path1, const char* path2);
|
||||
|
||||
FS_Error (*const rename)(void* context, const char* old, const char* new);
|
||||
} FS_Common_Api;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
/**
|
||||
* @file storage.h
|
||||
* @brief APIs for working with storages, directories and files.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "filesystem_api_defines.h"
|
||||
#include "storage_sd_api.h"
|
||||
@@ -25,43 +30,62 @@ extern "C" {
|
||||
|
||||
typedef struct Storage Storage;
|
||||
|
||||
/** Allocates and initializes a file descriptor
|
||||
* @return File*
|
||||
/**
|
||||
* @brief Allocate and initialize a file instance.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @return pointer to the created instance.
|
||||
*/
|
||||
File* storage_file_alloc(Storage* storage);
|
||||
|
||||
/** Frees the file descriptor. Closes the file if it was open.
|
||||
/**
|
||||
* @brief Free the file instance.
|
||||
*
|
||||
* If the file was open, calling this function will close it automatically.
|
||||
* @param file pointer to the file instance to be freed.
|
||||
*/
|
||||
void storage_file_free(File* file);
|
||||
|
||||
/**
|
||||
* @brief Enumeration of events emitted by the storage through the PubSub system.
|
||||
*/
|
||||
typedef enum {
|
||||
StorageEventTypeCardMount,
|
||||
StorageEventTypeCardUnmount,
|
||||
StorageEventTypeCardMountError,
|
||||
StorageEventTypeFileClose,
|
||||
StorageEventTypeDirClose,
|
||||
StorageEventTypeCardMount, /**< SD card was mounted. */
|
||||
StorageEventTypeCardUnmount, /**< SD card was unmounted. */
|
||||
StorageEventTypeCardMountError, /**< An error occurred during mounting of an SD card. */
|
||||
StorageEventTypeFileClose, /**< A file was closed. */
|
||||
StorageEventTypeDirClose, /**< A directory was closed. */
|
||||
} StorageEventType;
|
||||
|
||||
/**
|
||||
* @brief Storage event (passed to the PubSub callback).
|
||||
*/
|
||||
typedef struct {
|
||||
StorageEventType type;
|
||||
StorageEventType type; /**< Type of the event. */
|
||||
} StorageEvent;
|
||||
|
||||
/**
|
||||
* Get storage pubsub.
|
||||
* @brief Get the storage pubsub instance.
|
||||
*
|
||||
* Storage will send StorageEvent messages.
|
||||
* @param storage
|
||||
* @return FuriPubSub*
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @return pointer to the pubsub instance.
|
||||
*/
|
||||
FuriPubSub* storage_get_pubsub(Storage* storage);
|
||||
|
||||
/******************* File Functions *******************/
|
||||
|
||||
/** Opens an existing file or create a new one.
|
||||
* @param file pointer to file object.
|
||||
* @param path path to file
|
||||
* @param access_mode access mode from FS_AccessMode
|
||||
/**
|
||||
* @brief Open an existing file or create a new one.
|
||||
*
|
||||
* @warning The calling code MUST call storage_file_close() even if the open operation had failed.
|
||||
*
|
||||
* @param file pointer to the file instance to be opened.
|
||||
* @param path pointer to a zero-terminated string containing the path to the file to be opened.
|
||||
* @param access_mode access mode from FS_AccessMode.
|
||||
* @param open_mode open mode from FS_OpenMode
|
||||
* @return success flag. You need to close the file even if the open operation failed.
|
||||
* @return true if the file was successfully opened, false otherwise.
|
||||
*/
|
||||
bool storage_file_open(
|
||||
File* file,
|
||||
@@ -69,217 +93,291 @@ bool storage_file_open(
|
||||
FS_AccessMode access_mode,
|
||||
FS_OpenMode open_mode);
|
||||
|
||||
/** Close the file.
|
||||
* @param file pointer to a file object, the file object will be freed.
|
||||
* @return success flag
|
||||
/**
|
||||
* @brief Close the file.
|
||||
*
|
||||
* @param file pointer to the file instance to be closed.
|
||||
* @return true if the file was successfully closed, false otherwise.
|
||||
*/
|
||||
bool storage_file_close(File* file);
|
||||
|
||||
/** Tells if the file is open
|
||||
* @param file pointer to a file object
|
||||
* @return bool true if file is open
|
||||
/**
|
||||
* @brief Check whether the file is open.
|
||||
*
|
||||
* @param file pointer to the file instance in question.
|
||||
* @return true if the file is open, false otherwise.
|
||||
*/
|
||||
bool storage_file_is_open(File* file);
|
||||
|
||||
/** Tells if the file is a directory
|
||||
* @param file pointer to a file object
|
||||
* @return bool true if file is a directory
|
||||
/**
|
||||
* @brief Check whether a file instance represents a directory.
|
||||
*
|
||||
* @param file pointer to the file instance in question.
|
||||
* @return true if the file instance represents a directory, false otherwise.
|
||||
*/
|
||||
bool storage_file_is_dir(File* file);
|
||||
|
||||
/** Reads bytes from a file into a buffer
|
||||
* @param file pointer to file object.
|
||||
* @param buff pointer to a buffer, for reading
|
||||
* @param bytes_to_read how many bytes to read. Must be less than or equal to the size of the buffer.
|
||||
* @return uint16_t how many bytes were actually read
|
||||
/**
|
||||
* @brief Read bytes from a file into a buffer.
|
||||
*
|
||||
* @param file pointer to the file instance to read from.
|
||||
* @param buff pointer to the buffer to be filled with read data.
|
||||
* @param bytes_to_read number of bytes to read. Must be less than or equal to the size of the buffer.
|
||||
* @return actual number of bytes read (may be fewer than requested).
|
||||
*/
|
||||
uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read);
|
||||
size_t storage_file_read(File* file, void* buff, size_t bytes_to_read);
|
||||
|
||||
/** Writes bytes from a buffer to a file
|
||||
* @param file pointer to file object.
|
||||
* @param buff pointer to buffer, for writing
|
||||
* @param bytes_to_write how many bytes to write. Must be less than or equal to the size of the buffer.
|
||||
* @return uint16_t how many bytes were actually written
|
||||
/**
|
||||
* @brief Write bytes from a buffer to a file.
|
||||
*
|
||||
* @param file pointer to the file instance to write into.
|
||||
* @param buff pointer to the buffer containing the data to be written.
|
||||
* @param bytes_to_write number of bytes to write. Must be less than or equal to the size of the buffer.
|
||||
* @return actual number of bytes written (may be fewer than requested).
|
||||
*/
|
||||
uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write);
|
||||
size_t storage_file_write(File* file, const void* buff, size_t bytes_to_write);
|
||||
|
||||
/** Moves the r/w pointer
|
||||
* @param file pointer to file object.
|
||||
* @param offset offset to move the r/w pointer
|
||||
* @param from_start set an offset from the start or from the current position
|
||||
/**
|
||||
* @brief Change the current access position in a file.
|
||||
*
|
||||
* @param file pointer to the file instance in question.
|
||||
* @param offset access position offset (meaning depends on from_start parameter).
|
||||
* @param from_start if true, set the access position relative to the file start, otherwise relative to the current position.
|
||||
* @return success flag
|
||||
*/
|
||||
bool storage_file_seek(File* file, uint32_t offset, bool from_start);
|
||||
|
||||
/** Gets the position of the r/w pointer
|
||||
* @param file pointer to file object.
|
||||
* @return uint64_t position of the r/w pointer
|
||||
/**
|
||||
* @brief Get the current access position.
|
||||
*
|
||||
* @param file pointer to the file instance in question.
|
||||
* @return current access position.
|
||||
*/
|
||||
uint64_t storage_file_tell(File* file);
|
||||
|
||||
/** Expand the file (allocate space for it)
|
||||
* @param file pointer to file object.
|
||||
* @param size how many bytes to allocate
|
||||
* @return success flag
|
||||
/**
|
||||
* @brief Expand the file (allocate space for it).
|
||||
*
|
||||
* @param file pointer to the file instance to be expanded.
|
||||
* @param size amount of bytes bytes to allocate.
|
||||
* @return true if the file was successfully expanded, false otherwise.
|
||||
*/
|
||||
bool storage_file_expand(File* file, uint64_t size);
|
||||
|
||||
/** Truncates the file size to the current position of the r/w pointer
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
/**
|
||||
* @brief Truncate the file size to the current access position.
|
||||
*
|
||||
* @param file pointer to the file instance to be truncated.
|
||||
* @return true if the file was successfully truncated, false otherwise.
|
||||
*/
|
||||
bool storage_file_truncate(File* file);
|
||||
|
||||
/** Gets the size of the file
|
||||
* @param file pointer to file object.
|
||||
* @return uint64_t size of the file
|
||||
/**
|
||||
* @brief Get the file size.
|
||||
*
|
||||
* @param file pointer to the file instance in question.
|
||||
* @return size of the file, in bytes.
|
||||
*/
|
||||
uint64_t storage_file_size(File* file);
|
||||
|
||||
/** Writes file cache to storage
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
/**
|
||||
* @brief Synchronise the file cache with the actual storage.
|
||||
*
|
||||
* @param file pointer to the file instance in question.
|
||||
* @return true if the file was successfully synchronised, false otherwise.
|
||||
*/
|
||||
bool storage_file_sync(File* file);
|
||||
|
||||
/** Checks that the r/w pointer is at the end of the file
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
/**
|
||||
* @brief Check whether the current access position is at the end of the file.
|
||||
*
|
||||
* @param file pointer to a file instance in question.
|
||||
* @return bool true if the current access position is at the end of the file, false otherwise.
|
||||
*/
|
||||
bool storage_file_eof(File* file);
|
||||
|
||||
/**
|
||||
* @brief Check that file exists
|
||||
* @brief Check whether a file exists.
|
||||
*
|
||||
* @param storage
|
||||
* @param path
|
||||
* @return true if file exists
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path to the file in question.
|
||||
* @return true if the file exists, false otherwise.
|
||||
*/
|
||||
bool storage_file_exists(Storage* storage, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Copy data from one opened file to another opened file
|
||||
* Size bytes will be copied from current position of source file to current position of destination file
|
||||
* @brief Copy data from a source file to the destination file.
|
||||
*
|
||||
* Both files must be opened prior to calling this function.
|
||||
*
|
||||
* The requested amount of bytes will be copied from the current access position
|
||||
* in the source file to the current access position in the destination file.
|
||||
*
|
||||
* @param source source file
|
||||
* @param destination destination file
|
||||
* @param size size of data to copy
|
||||
* @return bool success flag
|
||||
* @param source pointer to a source file instance.
|
||||
* @param destination pointer to a destination file instance.
|
||||
* @param size data size to be copied, in bytes.
|
||||
* @return true if the data was successfully copied, false otherwise.
|
||||
*/
|
||||
bool storage_file_copy_to_file(File* source, File* destination, uint32_t size);
|
||||
bool storage_file_copy_to_file(File* source, File* destination, size_t size);
|
||||
|
||||
/******************* Dir Functions *******************/
|
||||
/******************* Directory Functions *******************/
|
||||
|
||||
/** Opens a directory to get objects from it
|
||||
* @param app pointer to the api
|
||||
* @param file pointer to file object.
|
||||
* @param path path to directory
|
||||
* @return bool success flag. You need to close the directory even if the open operation failed.
|
||||
/**
|
||||
* @brief Open a directory.
|
||||
*
|
||||
* Opening a directory is necessary to be able to read its contents with storage_dir_read().
|
||||
*
|
||||
* @warning The calling code MUST call storage_dir_close() even if the open operation had failed.
|
||||
*
|
||||
* @param file pointer to a file instance representing the directory in question.
|
||||
* @param path pointer to a zero-terminated string containing the path of the directory in question.
|
||||
* @return true if the directory was successfully opened, false otherwise.
|
||||
*/
|
||||
bool storage_dir_open(File* file, const char* path);
|
||||
|
||||
/** Close the directory. Also free file handle structure and point it to the NULL.
|
||||
* @param file pointer to a file object.
|
||||
* @return bool success flag
|
||||
/**
|
||||
* @brief Close the directory.
|
||||
*
|
||||
* @param file pointer to a file instance representing the directory in question.
|
||||
* @return true if the directory was successfully closed, false otherwise.
|
||||
*/
|
||||
bool storage_dir_close(File* file);
|
||||
|
||||
/** Reads the next object in the directory
|
||||
* @param file pointer to file object.
|
||||
* @param fileinfo pointer to the read FileInfo, may be NULL
|
||||
* @param name pointer to name buffer, may be NULL
|
||||
* @param name_length name buffer length
|
||||
* @return success flag (if the next object does not exist, it also returns false and sets the file error id to FSE_NOT_EXIST)
|
||||
/**
|
||||
* @brief Get the next item in the directory.
|
||||
*
|
||||
* If the next object does not exist, this function returns false as well
|
||||
* and sets the file error id to FSE_NOT_EXIST.
|
||||
*
|
||||
* @param file pointer to a file instance representing the directory in question.
|
||||
* @param fileinfo pointer to the FileInfo structure to contain the info (may be NULL).
|
||||
* @param name pointer to the buffer to contain the name (may be NULL).
|
||||
* @param name_length maximum capacity of the name buffer, in bytes.
|
||||
* @return true if the next item was successfully read, false otherwise.
|
||||
*/
|
||||
bool storage_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length);
|
||||
|
||||
/** Rewinds the read pointer to first item in the directory
|
||||
* @param file pointer to file object.
|
||||
* @return bool success flag
|
||||
/**
|
||||
* @brief Change the access position to first item in the directory.
|
||||
*
|
||||
* @param file pointer to a file instance representing the directory in question.
|
||||
* @return true if the access position was successfully changed, false otherwise.
|
||||
*/
|
||||
bool storage_dir_rewind(File* file);
|
||||
|
||||
/**
|
||||
* @brief Check that dir exists
|
||||
* @brief Check whether a directory exists.
|
||||
*
|
||||
* @param storage
|
||||
* @param path
|
||||
* @return bool
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path of the directory in question.
|
||||
* @return true if the directory exists, false otherwise.
|
||||
*/
|
||||
bool storage_dir_exists(Storage* storage, const char* path);
|
||||
|
||||
/******************* Common Functions *******************/
|
||||
|
||||
/** Retrieves unix timestamp of last access
|
||||
/**
|
||||
* @brief Get the last access time in UNIX format.
|
||||
*
|
||||
* @param storage The storage instance
|
||||
* @param path path to file/directory
|
||||
* @param timestamp the timestamp pointer
|
||||
*
|
||||
* @return FS_Error operation result
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path of the item in question.
|
||||
* @param timestamp pointer to a value to contain the timestamp.
|
||||
* @return FSE_OK if the timestamp has been successfully received, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp);
|
||||
|
||||
/** Retrieves information about a file/directory
|
||||
* @param app pointer to the api
|
||||
* @param path path to file/directory
|
||||
* @param fileinfo pointer to the read FileInfo, may be NULL
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Get information about a file or a directory.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path of the item in question.
|
||||
* @param fileinfo pointer to the FileInfo structure to contain the info (may be NULL).
|
||||
* @return FSE_OK if the info has been successfully received, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo);
|
||||
|
||||
/** Removes a file/directory from the repository, the directory must be empty and the file/directory must not be open
|
||||
* @param app pointer to the api
|
||||
* @param path
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Remove a file or a directory.
|
||||
*
|
||||
* The directory must be empty.
|
||||
* The file or the directory must NOT be open.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path of the item to be removed.
|
||||
* @return FSE_OK if the file or directory has been successfully removed, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_remove(Storage* storage, const char* path);
|
||||
|
||||
/** Renames file/directory, file/directory must not be open
|
||||
* @param app pointer to the api
|
||||
* @param old_path old path
|
||||
* @param new_path new path
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Rename a file or a directory.
|
||||
*
|
||||
* The file or the directory must NOT be open.
|
||||
*
|
||||
* Renaming a regular file to itself does nothing and always succeeds.
|
||||
* Renaming a directory to itself or to a subdirectory of itself always fails.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param old_path pointer to a zero-terminated string containing the source path.
|
||||
* @param new_path pointer to a zero-terminated string containing the destination path.
|
||||
* @return FSE_OK if the file or directory has been successfully renamed, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_rename(Storage* storage, const char* old_path, const char* new_path);
|
||||
|
||||
/** Moves file/directory, file/directory must not be open, will overwrite existing destination
|
||||
* @param app pointer to the api
|
||||
* @param old_path old path
|
||||
* @param new_path new path
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Move a file or a directory.
|
||||
*
|
||||
* The file or the directory must NOT be open.
|
||||
* Will overwrite the destination file if it already exists.
|
||||
*
|
||||
* Moving a regular file to itself does nothing and always succeeds.
|
||||
* Moving a directory to itself or to a subdirectory of itself always fails.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param old_path pointer to a zero-terminated string containing the source path.
|
||||
* @param new_path pointer to a zero-terminated string containing the destination path.
|
||||
* @return FSE_OK if the file or directory has been successfully moved, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_move(Storage* storage, const char* old_path, const char* new_path);
|
||||
|
||||
/** Copy file, file must not be open
|
||||
* @param app pointer to the api
|
||||
* @param old_path old path
|
||||
* @param new_path new path
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Copy the file to a new location.
|
||||
*
|
||||
* The file must NOT be open at the time of calling this function.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param old_path pointer to a zero-terminated string containing the source path.
|
||||
* @param new_path pointer to a zero-terminated string containing the destination path.
|
||||
* @return FSE_OK if the file has been successfully copied, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_copy(Storage* storage, const char* old_path, const char* new_path);
|
||||
|
||||
/** Copy one folder contents into another with rename of all conflicting files
|
||||
* @param app pointer to the api
|
||||
* @param old_path old path
|
||||
* @param new_path new path
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Copy the contents of one directory into another and rename all conflicting files.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param old_path pointer to a zero-terminated string containing the source path.
|
||||
* @param new_path pointer to a zero-terminated string containing the destination path.
|
||||
* @return FSE_OK if the directories have been successfully merged, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_merge(Storage* storage, const char* old_path, const char* new_path);
|
||||
|
||||
/** Creates a directory
|
||||
* @param app pointer to the api
|
||||
* @param path directory path
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Create a directory.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param fs_path pointer to a zero-terminated string containing the directory path.
|
||||
* @return FSE_OK if the directory has been successfully created, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_mkdir(Storage* storage, const char* path);
|
||||
|
||||
/** Gets general information about the storage
|
||||
* @param app pointer to the api
|
||||
* @param fs_path the path to the storage of interest
|
||||
* @param total_space pointer to total space record, will be filled
|
||||
* @param free_space pointer to free space record, will be filled
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Get the general information about the storage.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param fs_path pointer to a zero-terminated string containing the path to the storage question.
|
||||
* @param total_space pointer to the value to contain the total capacity, in bytes.
|
||||
* @param free_space pointer to the value to contain the available space, in bytes.
|
||||
* @return FSE_OK if the information has been successfully received, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_fs_info(
|
||||
Storage* storage,
|
||||
@@ -288,150 +386,242 @@ FS_Error storage_common_fs_info(
|
||||
uint64_t* free_space);
|
||||
|
||||
/**
|
||||
* @brief Parse aliases in path and replace them with real path
|
||||
* Also will create special folders if they are not exist
|
||||
* @brief Parse aliases in a path and replace them with the real path.
|
||||
*
|
||||
* Necessary special directories will be created automatically if they did not exist.
|
||||
*
|
||||
* @param storage
|
||||
* @param path
|
||||
* @return bool
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path in question.
|
||||
* @return true if the path was successfully resolved, false otherwise.
|
||||
*/
|
||||
void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path);
|
||||
|
||||
/**
|
||||
* @brief Move content of one folder to another, with rename of all conflicting files.
|
||||
* Source folder will be deleted if the migration is successful.
|
||||
* @brief Move the contents of source folder to destination one and rename all conflicting files.
|
||||
*
|
||||
* Source folder will be deleted if the migration was successful.
|
||||
*
|
||||
* @param storage
|
||||
* @param source
|
||||
* @param dest
|
||||
* @return FS_Error
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param source pointer to a zero-terminated string containing the source path.
|
||||
* @param dest pointer to a zero-terminated string containing the destination path.
|
||||
* @return FSE_OK if the migration was successfull completed, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_common_migrate(Storage* storage, const char* source, const char* dest);
|
||||
|
||||
/**
|
||||
* @brief Check that file or dir exists
|
||||
* @brief Check whether a file or a directory exists.
|
||||
*
|
||||
* @param storage
|
||||
* @param path
|
||||
* @return bool
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the path in question.
|
||||
* @return true if a file or a directory exists, false otherwise.
|
||||
*/
|
||||
bool storage_common_exists(Storage* storage, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Check whether two paths are equivalent.
|
||||
*
|
||||
* This function will resolve aliases and apply filesystem-specific
|
||||
* rules to determine whether the two given paths are equivalent.
|
||||
*
|
||||
* Examples:
|
||||
* - /int/text and /ext/test -> false (Different storages),
|
||||
* - /int/Test and /int/test -> false (Case-sensitive storage),
|
||||
* - /ext/Test and /ext/test -> true (Case-insensitive storage).
|
||||
*
|
||||
* If the truncate parameter is set to true, the second path will be
|
||||
* truncated to be no longer than the first one. It is useful to determine
|
||||
* whether path2 is a subdirectory of path1.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path1 pointer to a zero-terminated string containing the first path.
|
||||
* @param path2 pointer to a zero-terminated string containing the second path.
|
||||
* @param truncate whether to truncate path2 to be no longer than path1.
|
||||
* @return true if paths are equivalent, false otherwise.
|
||||
*/
|
||||
bool storage_common_equivalent_path(
|
||||
Storage* storage,
|
||||
const char* path1,
|
||||
const char* path2,
|
||||
bool truncate);
|
||||
|
||||
/******************* Error Functions *******************/
|
||||
|
||||
/** Retrieves the error text from the error id
|
||||
* @param error_id error id
|
||||
* @return const char* error text
|
||||
/**
|
||||
* @brief Get the textual description of a numeric error identifer.
|
||||
*
|
||||
* @param error_id numeric identifier of the error in question.
|
||||
* @return pointer to a statically allocated zero-terminated string containing the respective error text.
|
||||
*/
|
||||
const char* storage_error_get_desc(FS_Error error_id);
|
||||
|
||||
/** Retrieves the error id from the file object
|
||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR ID IF THE FILE HAS BEEN CLOSED
|
||||
* @return FS_Error error id
|
||||
/**
|
||||
* @brief Get the numeric error identifier from a file instance.
|
||||
*
|
||||
* @warning It is not possible to get the error identifier after the file has been closed.
|
||||
*
|
||||
* @param file pointer to the file instance in question (must NOT be NULL).
|
||||
* @return numeric identifier of the last error associated with the file instance.
|
||||
*/
|
||||
FS_Error storage_file_get_error(File* file);
|
||||
|
||||
/** Retrieves the internal (storage-specific) error id from the file object
|
||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE INTERNAL ERROR ID IF THE FILE HAS BEEN CLOSED
|
||||
* @return FS_Error error id
|
||||
/**
|
||||
* @brief Get the internal (storage-specific) numeric error identifier from a file instance.
|
||||
*
|
||||
* @warning It is not possible to get the internal error identifier after the file has been closed.
|
||||
*
|
||||
* @param file pointer to the file instance in question (must NOT be NULL).
|
||||
* @return numeric identifier of the last internal error associated with the file instance.
|
||||
*/
|
||||
int32_t storage_file_get_internal_error(File* file);
|
||||
|
||||
/** Retrieves the error text from the file object
|
||||
* @param file pointer to file object. Pointer must not point to NULL. YOU CANNOT RETRIEVE THE ERROR TEXT IF THE FILE HAS BEEN CLOSED
|
||||
* @return const char* error text
|
||||
/**
|
||||
* @brief Get the textual description of a the last error associated with a file instance.
|
||||
*
|
||||
* @warning It is not possible to get the error text after the file has been closed.
|
||||
*
|
||||
* @param file pointer to the file instance in question (must NOT be NULL).
|
||||
* @return pointer to a statically allocated zero-terminated string containing the respective error text.
|
||||
*/
|
||||
const char* storage_file_get_error_desc(File* file);
|
||||
|
||||
/******************* SD Card Functions *******************/
|
||||
|
||||
/** Formats SD Card
|
||||
* @param api pointer to the api
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Format the SD Card.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @return FSE_OK if the card was successfully formatted, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_sd_format(Storage* api);
|
||||
FS_Error storage_sd_format(Storage* storage);
|
||||
|
||||
/** Will unmount the SD card.
|
||||
* Will return FSE_NOT_READY if the SD card is not mounted.
|
||||
* Will return FSE_DENIED if there are open files on the SD card.
|
||||
* @param api pointer to the api
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Unmount the SD card.
|
||||
*
|
||||
* These return values have special meaning:
|
||||
* - FSE_NOT_READY if the SD card is not mounted.
|
||||
* - FSE_DENIED if there are open files on the SD card.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @return FSE_OK if the card was successfully formatted, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_sd_unmount(Storage* api);
|
||||
FS_Error storage_sd_unmount(Storage* storage);
|
||||
|
||||
/** Will mount the SD card
|
||||
* @param api pointer to the api
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Mount the SD card.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @return FSE_OK if the card was successfully mounted, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_sd_mount(Storage* api);
|
||||
FS_Error storage_sd_mount(Storage* storage);
|
||||
|
||||
/** Retrieves SD card information
|
||||
* @param api pointer to the api
|
||||
* @param info pointer to the info
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Get SD card information.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param info pointer to the info object to contain the requested information.
|
||||
* @return FSE_OK if the info was successfully received, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_sd_info(Storage* api, SDInfo* info);
|
||||
FS_Error storage_sd_info(Storage* storage, SDInfo* info);
|
||||
|
||||
/** Retrieves SD card status
|
||||
* @param api pointer to the api
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Get SD card status.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @return storage status in the form of a numeric error identifier.
|
||||
*/
|
||||
FS_Error storage_sd_status(Storage* api);
|
||||
FS_Error storage_sd_status(Storage* storage);
|
||||
|
||||
/******************* Internal LFS Functions *******************/
|
||||
|
||||
typedef void (*Storage_name_converter)(FuriString*);
|
||||
|
||||
/** Backs up internal storage to a tar archive
|
||||
* @param api pointer to the api
|
||||
* @param dstname destination archive path
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Back up the internal storage contents to a *.tar archive.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param dstname pointer to a zero-terminated string containing the archive file path.
|
||||
* @return FSE_OK if the storage was successfully backed up, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_int_backup(Storage* api, const char* dstname);
|
||||
FS_Error storage_int_backup(Storage* storage, const char* dstname);
|
||||
|
||||
/** Restores internal storage from a tar archive
|
||||
* @param api pointer to the api
|
||||
* @param dstname archive path
|
||||
* @param converter pointer to filename conversion function, may be NULL
|
||||
* @return FS_Error operation result
|
||||
/**
|
||||
* @brief Restore the internal storage contents from a *.tar archive.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param dstname pointer to a zero-terminated string containing the archive file path.
|
||||
* @param converter pointer to a filename conversion function (may be NULL).
|
||||
* @return FSE_OK if the storage was successfully restored, any other error code on failure.
|
||||
*/
|
||||
FS_Error storage_int_restore(Storage* api, const char* dstname, Storage_name_converter converter);
|
||||
|
||||
/***************** Simplified Functions ******************/
|
||||
|
||||
/**
|
||||
* Removes a file/directory, the directory must be empty and the file/directory must not be open
|
||||
* @param storage pointer to the api
|
||||
* @param path
|
||||
* @return true on success or if file/dir is not exist
|
||||
* @brief Remove a file or a directory.
|
||||
*
|
||||
* The following conditions must be met:
|
||||
* - the directory must be empty.
|
||||
* - the file or the directory must NOT be open.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the item path.
|
||||
* @return true on success or if the item does not exist, false otherwise.
|
||||
*/
|
||||
bool storage_simply_remove(Storage* storage, const char* path);
|
||||
|
||||
/**
|
||||
* Recursively removes a file/directory, the directory can be not empty
|
||||
* @param storage pointer to the api
|
||||
* @param path
|
||||
* @return true on success or if file/dir is not exist
|
||||
* @brief Recursively remove a file or a directory.
|
||||
*
|
||||
* Unlike storage_simply_remove(), the directory does not need to be empty.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the item path.
|
||||
* @return true on success or if the item does not exist, false otherwise.
|
||||
*/
|
||||
bool storage_simply_remove_recursive(Storage* storage, const char* path);
|
||||
|
||||
/**
|
||||
* Creates a directory
|
||||
* @param storage
|
||||
* @param path
|
||||
* @return true on success or if directory is already exist
|
||||
* @brief Create a directory.
|
||||
*
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param path pointer to a zero-terminated string containing the directory path.
|
||||
* @return true on success or if directory does already exist, false otherwise.
|
||||
*/
|
||||
bool storage_simply_mkdir(Storage* storage, const char* path);
|
||||
|
||||
/**
|
||||
* @brief Get next free filename.
|
||||
* @brief Get the next free filename in a directory.
|
||||
*
|
||||
* Usage example:
|
||||
* ```c
|
||||
* FuriString* file_name = furi_string_alloc();
|
||||
* Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
*
|
||||
* storage_get_next_filename(storage,
|
||||
* "/ext/test",
|
||||
* "cookies",
|
||||
* ".yum",
|
||||
* 20);
|
||||
*
|
||||
* furi_record_close(RECORD_STORAGE);
|
||||
*
|
||||
* use_file_name(file_name);
|
||||
*
|
||||
* furi_string_free(file_name);
|
||||
* ```
|
||||
* Possible file_name values after calling storage_get_next_filename():
|
||||
* "cookies", "cookies1", "cookies2", ... etc depending on whether any of
|
||||
* these files have already existed in the directory.
|
||||
*
|
||||
* @note If the resulting next file name length is greater than set by the max_len
|
||||
* parameter, the original filename will be returned instead.
|
||||
*
|
||||
* @param storage
|
||||
* @param dirname
|
||||
* @param filename
|
||||
* @param fileextension
|
||||
* @param nextfilename return name
|
||||
* @param max_len max len name
|
||||
* @param storage pointer to a storage API instance.
|
||||
* @param dirname pointer to a zero-terminated string containing the directory path.
|
||||
* @param filename pointer to a zero-terminated string containing the file name.
|
||||
* @param fileextension pointer to a zero-terminated string containing the file extension.
|
||||
* @param nextfilename pointer to a dynamic string containing the resulting file name.
|
||||
* @param max_len maximum length of the new name.
|
||||
*/
|
||||
void storage_get_next_filename(
|
||||
Storage* storage,
|
||||
|
||||
@@ -200,15 +200,15 @@ static void storage_cli_read(Cli* cli, FuriString* path) {
|
||||
File* file = storage_file_alloc(api);
|
||||
|
||||
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
const uint16_t buffer_size = 128;
|
||||
uint16_t read_size = 0;
|
||||
const size_t buffer_size = 128;
|
||||
size_t read_size = 0;
|
||||
uint8_t* data = malloc(buffer_size);
|
||||
|
||||
printf("Size: %lu\r\n", (uint32_t)storage_file_size(file));
|
||||
|
||||
do {
|
||||
read_size = storage_file_read(file, data, buffer_size);
|
||||
for(uint16_t i = 0; i < read_size; i++) {
|
||||
for(size_t i = 0; i < read_size; i++) {
|
||||
printf("%c", data[i]);
|
||||
}
|
||||
} while(read_size > 0);
|
||||
@@ -229,7 +229,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
|
||||
Storage* api = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(api);
|
||||
|
||||
const uint16_t buffer_size = 512;
|
||||
const size_t buffer_size = 512;
|
||||
uint8_t* buffer = malloc(buffer_size);
|
||||
|
||||
if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) {
|
||||
@@ -241,10 +241,10 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
|
||||
uint8_t symbol = cli_getc(cli);
|
||||
|
||||
if(symbol == CliSymbolAsciiETX) {
|
||||
uint16_t write_size = read_index % buffer_size;
|
||||
size_t write_size = read_index % buffer_size;
|
||||
|
||||
if(write_size > 0) {
|
||||
uint16_t written_size = storage_file_write(file, buffer, write_size);
|
||||
size_t written_size = storage_file_write(file, buffer, write_size);
|
||||
|
||||
if(written_size != write_size) {
|
||||
storage_cli_print_error(storage_file_get_error(file));
|
||||
@@ -259,7 +259,7 @@ static void storage_cli_write(Cli* cli, FuriString* path) {
|
||||
read_index++;
|
||||
|
||||
if(((read_index % buffer_size) == 0)) {
|
||||
uint16_t written_size = storage_file_write(file, buffer, buffer_size);
|
||||
size_t written_size = storage_file_write(file, buffer, buffer_size);
|
||||
|
||||
if(written_size != buffer_size) {
|
||||
storage_cli_print_error(storage_file_get_error(file));
|
||||
@@ -291,7 +291,7 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
|
||||
} else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
uint64_t file_size = storage_file_size(file);
|
||||
|
||||
printf("Size: %lu\r\n", (uint32_t)file_size);
|
||||
printf("Size: %llu\r\n", file_size);
|
||||
|
||||
if(buffer_size) {
|
||||
uint8_t* data = malloc(buffer_size);
|
||||
@@ -299,8 +299,8 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args
|
||||
printf("\r\nReady?\r\n");
|
||||
cli_getc(cli);
|
||||
|
||||
uint16_t read_size = storage_file_read(file, data, buffer_size);
|
||||
for(uint16_t i = 0; i < read_size; i++) {
|
||||
size_t read_size = storage_file_read(file, data, buffer_size);
|
||||
for(size_t i = 0; i < read_size; i++) {
|
||||
putchar(data[i]);
|
||||
}
|
||||
file_size -= read_size;
|
||||
@@ -337,7 +337,7 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args
|
||||
|
||||
size_t read_bytes = cli_read(cli, buffer, buffer_size);
|
||||
|
||||
uint16_t written_size = storage_file_write(file, buffer, read_bytes);
|
||||
size_t written_size = storage_file_write(file, buffer, read_bytes);
|
||||
|
||||
if(written_size != buffer_size) {
|
||||
storage_cli_print_error(storage_file_get_error(file));
|
||||
|
||||
@@ -138,7 +138,7 @@ bool storage_file_close(File* file) {
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
|
||||
static uint16_t storage_file_read_underlying(File* file, void* buff, uint16_t bytes_to_read) {
|
||||
if(bytes_to_read == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -158,7 +158,8 @@ uint16_t storage_file_read(File* file, void* buff, uint16_t bytes_to_read) {
|
||||
return S_RETURN_UINT16;
|
||||
}
|
||||
|
||||
uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_write) {
|
||||
static uint16_t
|
||||
storage_file_write_underlying(File* file, const void* buff, uint16_t bytes_to_write) {
|
||||
if(bytes_to_write == 0) {
|
||||
return 0;
|
||||
}
|
||||
@@ -178,6 +179,40 @@ uint16_t storage_file_write(File* file, const void* buff, uint16_t bytes_to_writ
|
||||
return S_RETURN_UINT16;
|
||||
}
|
||||
|
||||
size_t storage_file_read(File* file, void* buff, size_t to_read) {
|
||||
size_t total = 0;
|
||||
|
||||
const size_t max_chunk = UINT16_MAX;
|
||||
do {
|
||||
const size_t chunk = MIN((to_read - total), max_chunk);
|
||||
size_t read = storage_file_read_underlying(file, buff + total, chunk);
|
||||
total += read;
|
||||
|
||||
if(storage_file_get_error(file) != FSE_OK || read != chunk) {
|
||||
break;
|
||||
}
|
||||
} while(total != to_read);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
size_t storage_file_write(File* file, const void* buff, size_t to_write) {
|
||||
size_t total = 0;
|
||||
|
||||
const size_t max_chunk = UINT16_MAX;
|
||||
do {
|
||||
const size_t chunk = MIN((to_write - total), max_chunk);
|
||||
size_t written = storage_file_write_underlying(file, buff + total, chunk);
|
||||
total += written;
|
||||
|
||||
if(storage_file_get_error(file) != FSE_OK || written != chunk) {
|
||||
break;
|
||||
}
|
||||
} while(total != to_write);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
bool storage_file_seek(File* file, uint32_t offset, bool from_start) {
|
||||
S_FILE_API_PROLOGUE;
|
||||
S_API_PROLOGUE;
|
||||
@@ -266,7 +301,7 @@ bool storage_file_exists(Storage* storage, const char* path) {
|
||||
return exist;
|
||||
}
|
||||
|
||||
bool storage_file_copy_to_file(File* source, File* destination, uint32_t size) {
|
||||
bool storage_file_copy_to_file(File* source, File* destination, size_t size) {
|
||||
uint8_t* buffer = malloc(FILE_BUFFER_SIZE);
|
||||
|
||||
while(size) {
|
||||
@@ -824,6 +859,27 @@ bool storage_common_exists(Storage* storage, const char* path) {
|
||||
return storage_common_stat(storage, path, &file_info) == FSE_OK;
|
||||
}
|
||||
|
||||
bool storage_common_equivalent_path(
|
||||
Storage* storage,
|
||||
const char* path1,
|
||||
const char* path2,
|
||||
bool truncate) {
|
||||
S_API_PROLOGUE;
|
||||
|
||||
SAData data = {
|
||||
.cequivpath = {
|
||||
.path1 = path1,
|
||||
.path2 = path2,
|
||||
.truncate = truncate,
|
||||
.thread_id = furi_thread_get_current_id(),
|
||||
}};
|
||||
|
||||
S_API_MESSAGE(StorageCommandCommonEquivalentPath);
|
||||
S_API_EPILOGUE;
|
||||
|
||||
return S_RETURN_BOOL;
|
||||
}
|
||||
|
||||
/****************** ERROR ******************/
|
||||
|
||||
const char* storage_error_get_desc(FS_Error error_id) {
|
||||
|
||||
@@ -74,6 +74,13 @@ typedef struct {
|
||||
FuriThreadId thread_id;
|
||||
} SADataCResolvePath;
|
||||
|
||||
typedef struct {
|
||||
const char* path1;
|
||||
const char* path2;
|
||||
bool truncate;
|
||||
FuriThreadId thread_id;
|
||||
} SADataCEquivPath;
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
} SADataError;
|
||||
@@ -111,6 +118,7 @@ typedef union {
|
||||
SADataCStat cstat;
|
||||
SADataCFSInfo cfsinfo;
|
||||
SADataCResolvePath cresolvepath;
|
||||
SADataCEquivPath cequivpath;
|
||||
|
||||
SADataError error;
|
||||
|
||||
@@ -155,6 +163,7 @@ typedef enum {
|
||||
StorageCommandSDStatus,
|
||||
StorageCommandCommonResolvePath,
|
||||
StorageCommandSDMount,
|
||||
StorageCommandCommonEquivalentPath,
|
||||
|
||||
StorageCommandFileExpand,
|
||||
StorageCommandCommonRename,
|
||||
|
||||
@@ -98,6 +98,12 @@ FS_Error storage_get_data(Storage* app, FuriString* path, StorageData** storage)
|
||||
}
|
||||
}
|
||||
|
||||
static void storage_path_trim_trailing_slashes(FuriString* path) {
|
||||
while(furi_string_end_with(path, "/")) {
|
||||
furi_string_left(path, furi_string_size(path) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/******************* File Functions *******************/
|
||||
|
||||
bool storage_process_file_open(
|
||||
@@ -369,14 +375,17 @@ static FS_Error storage_process_common_remove(Storage* app, FuriString* path) {
|
||||
StorageData* storage;
|
||||
FS_Error ret = storage_get_data(app, path, &storage);
|
||||
|
||||
if(ret == FSE_OK) {
|
||||
do {
|
||||
if(ret != FSE_OK) break;
|
||||
|
||||
if(storage_path_already_open(path, storage)) {
|
||||
return FSE_ALREADY_OPEN;
|
||||
ret = FSE_ALREADY_OPEN;
|
||||
break;
|
||||
}
|
||||
|
||||
storage_data_timestamp(storage);
|
||||
FS_CALL(storage, common.remove(storage, cstr_path_without_vfs_prefix(path)));
|
||||
}
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -429,6 +438,31 @@ static FS_Error storage_process_common_fs_info(
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
storage_process_common_equivalent_path(Storage* app, FuriString* path1, FuriString* path2) {
|
||||
bool ret = false;
|
||||
|
||||
do {
|
||||
const StorageType storage_type1 = storage_get_type_by_path(path1);
|
||||
const StorageType storage_type2 = storage_get_type_by_path(path2);
|
||||
|
||||
// Paths on different storages are of course not equal
|
||||
if(storage_type1 != storage_type2) break;
|
||||
|
||||
StorageData* storage;
|
||||
const FS_Error status = storage_get_data(app, path1, &storage);
|
||||
|
||||
if(status != FSE_OK) break;
|
||||
|
||||
FS_CALL(
|
||||
storage,
|
||||
common.equivalent_path(furi_string_get_cstr(path1), furi_string_get_cstr(path2)));
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************** Raw SD API ******************/
|
||||
// TODO FL-3521: think about implementing a custom storage API to split that kind of api linkage
|
||||
#include "storages/storage_ext.h"
|
||||
@@ -707,6 +741,23 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) {
|
||||
app, message->data->cresolvepath.path, message->data->cresolvepath.thread_id, true);
|
||||
break;
|
||||
|
||||
case StorageCommandCommonEquivalentPath: {
|
||||
FuriString* path1 = furi_string_alloc_set(message->data->cequivpath.path1);
|
||||
FuriString* path2 = furi_string_alloc_set(message->data->cequivpath.path2);
|
||||
storage_path_trim_trailing_slashes(path1);
|
||||
storage_path_trim_trailing_slashes(path2);
|
||||
storage_process_alias(app, path1, message->data->cequivpath.thread_id, false);
|
||||
storage_process_alias(app, path2, message->data->cequivpath.thread_id, false);
|
||||
if(message->data->cequivpath.truncate) {
|
||||
furi_string_left(path2, furi_string_size(path1));
|
||||
}
|
||||
message->return_data->bool_value =
|
||||
storage_process_common_equivalent_path(app, path1, path2);
|
||||
furi_string_free(path1);
|
||||
furi_string_free(path2);
|
||||
break;
|
||||
}
|
||||
|
||||
// SD operations
|
||||
case StorageCommandSDFormat:
|
||||
message->return_data->error_value = storage_process_sd_format(app);
|
||||
|
||||
@@ -624,6 +624,16 @@ static FS_Error storage_ext_common_fs_info(
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool storage_ext_common_equivalent_path(const char* path1, const char* path2) {
|
||||
#ifdef FURI_RAM_EXEC
|
||||
UNUSED(path1);
|
||||
UNUSED(path2);
|
||||
return false;
|
||||
#else
|
||||
return strcasecmp(path1, path2) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/******************* Init Storage *******************/
|
||||
static const FS_Api fs_api = {
|
||||
.file =
|
||||
@@ -654,6 +664,7 @@ static const FS_Api fs_api = {
|
||||
.remove = storage_ext_common_remove,
|
||||
.rename = storage_ext_common_rename,
|
||||
.fs_info = storage_ext_common_fs_info,
|
||||
.equivalent_path = storage_ext_common_equivalent_path,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -701,6 +701,10 @@ static FS_Error storage_int_common_fs_info(
|
||||
return storage_int_parse_error(result);
|
||||
}
|
||||
|
||||
static bool storage_int_common_equivalent_path(const char* path1, const char* path2) {
|
||||
return strcmp(path1, path2) == 0;
|
||||
}
|
||||
|
||||
/******************* Init Storage *******************/
|
||||
static const FS_Api fs_api = {
|
||||
.file =
|
||||
@@ -731,6 +735,7 @@ static const FS_Api fs_api = {
|
||||
.remove = storage_int_common_remove,
|
||||
.rename = storage_int_common_rename,
|
||||
.fs_info = storage_int_common_fs_info,
|
||||
.equivalent_path = storage_int_common_equivalent_path,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ static bool storage_settings_scene_bench_write(
|
||||
}
|
||||
|
||||
static bool
|
||||
storage_settings_scene_bench_read(Storage* api, uint16_t size, uint8_t* data, uint32_t* speed) {
|
||||
storage_settings_scene_bench_read(Storage* api, size_t size, uint8_t* data, uint32_t* speed) {
|
||||
File* file = storage_file_alloc(api);
|
||||
bool result = true;
|
||||
*speed = -1;
|
||||
@@ -82,7 +82,7 @@ static void storage_settings_scene_benchmark(StorageSettings* app) {
|
||||
bench_data[i] = (uint8_t)i;
|
||||
}
|
||||
|
||||
uint16_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 512, 1024};
|
||||
size_t bench_size[BENCH_COUNT] = {1, 8, 32, 256, 512, 1024};
|
||||
uint32_t bench_w_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||
uint32_t bench_r_speed[BENCH_COUNT] = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) {
|
||||
|
||||
update_task_set_progress(update_task, UpdateTaskStageRadioWrite, 0);
|
||||
uint8_t* fw_block = malloc(FLASH_PAGE_SIZE);
|
||||
uint16_t bytes_read = 0;
|
||||
size_t bytes_read = 0;
|
||||
uint32_t element_offs = 0;
|
||||
|
||||
while(element_offs < stack_size) {
|
||||
|
||||
Reference in New Issue
Block a user