Files
MTGcompanion/main/storage_manager.c

200 lines
5.2 KiB
C

/*
* MTG RFID Companion - LittleFS storage manager
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include "esp_log.h"
#include "esp_littlefs.h"
#include "storage_manager.h"
static const char *TAG = "storage";
#define STORAGE_PARTITION_LABEL "storage"
#define STORAGE_BOOT_COUNT_FILE STORAGE_BASE_PATH "/boot_count.txt"
#define STORAGE_TEST_FILE STORAGE_BASE_PATH "/test.txt"
static const char SAMPLE_MAPPINGS[] =
"{\n"
" \"04A2B3C4\": \"Sol Ring\",\n"
" \"04E1F2A3\": \"Atraxa, Praetors' Voice\"\n"
"}\n";
static const char SAMPLE_CARD[] =
"{\n"
" \"name\": \"Sol Ring\",\n"
" \"mana_cost\": \"{1}\",\n"
" \"type\": \"Artifact\",\n"
" \"oracle\": \"Tap: Add CC.\",\n"
" \"price_usd\": \"1.25\"\n"
"}\n";
bool storage_file_exists(const char *path)
{
struct stat st;
return (stat(path, &st) == 0) && S_ISREG(st.st_mode);
}
esp_err_t storage_read_text(const char *path, char **out_buf, size_t *out_len)
{
if (out_buf == NULL || out_len == NULL) {
return ESP_ERR_INVALID_ARG;
}
*out_buf = NULL;
*out_len = 0;
FILE *f = fopen(path, "rb");
if (f == NULL) {
return ESP_ERR_NOT_FOUND;
}
if (fseek(f, 0, SEEK_END) != 0) {
fclose(f);
return ESP_FAIL;
}
long size = ftell(f);
if (size < 0) {
fclose(f);
return ESP_FAIL;
}
rewind(f);
char *buf = malloc((size_t)size + 1);
if (buf == NULL) {
fclose(f);
return ESP_ERR_NO_MEM;
}
size_t rd = fread(buf, 1, (size_t)size, f);
fclose(f);
if (rd != (size_t)size) {
free(buf);
return ESP_FAIL;
}
buf[rd] = '\0';
*out_buf = buf;
*out_len = rd;
return ESP_OK;
}
esp_err_t storage_write_text(const char *path, const char *text)
{
char tmp[288];
const size_t len = strlen(path);
if (len > sizeof(tmp) - 8) {
return ESP_ERR_INVALID_ARG;
}
snprintf(tmp, sizeof(tmp), "%s.tmp", path);
FILE *f = fopen(tmp, "wb");
if (f == NULL) {
ESP_LOGE(TAG, "open %s failed", tmp);
return ESP_FAIL;
}
size_t wr = fwrite(text, 1, strlen(text), f);
if (wr != strlen(text) || fflush(f) != 0 || fclose(f) != 0) {
ESP_LOGE(TAG, "write %s failed", tmp);
remove(tmp);
return ESP_FAIL;
}
if (rename(tmp, path) != 0) {
ESP_LOGE(TAG, "rename %s -> %s failed", tmp, path);
remove(tmp);
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t seed_defaults(void)
{
/* Create /spiffs/cards directory */
mkdir(STORAGE_CARDS_DIR, 0755);
if (!storage_file_exists(STORAGE_MAPPINGS)) {
ESP_LOGI(TAG, "seeding %s", STORAGE_MAPPINGS);
esp_err_t ret = storage_write_text(STORAGE_MAPPINGS, SAMPLE_MAPPINGS);
if (ret != ESP_OK) {
return ret;
}
}
char sample_path[64];
snprintf(sample_path, sizeof(sample_path), STORAGE_CARDS_DIR "/sample.json");
if (!storage_file_exists(sample_path)) {
ESP_LOGI(TAG, "seeding %s", sample_path);
esp_err_t ret = storage_write_text(sample_path, SAMPLE_CARD);
if (ret != ESP_OK) {
return ret;
}
}
return ESP_OK;
}
static void cross_reboot_persistence_test(void)
{
/* Persistent boot counter: proves writes survive a reset/reboot. */
char buf[16] = {0};
uint32_t count = 0;
FILE *f = fopen(STORAGE_BOOT_COUNT_FILE, "rb");
if (f != NULL) {
if (fread(buf, 1, sizeof(buf) - 1, f) > 0) {
count = (uint32_t)strtoul(buf, NULL, 10);
ESP_LOGI(TAG, "boot_count read back = %lu (persistence across reboots OK)", (unsigned long)count);
}
fclose(f);
}
count++;
snprintf(buf, sizeof(buf), "%lu\n", (unsigned long)count);
storage_write_text(STORAGE_BOOT_COUNT_FILE, buf);
ESP_LOGI(TAG, "boot_count updated to %lu", (unsigned long)count);
/* Sanity write/read within the same boot. */
storage_write_text(STORAGE_TEST_FILE, "MTG-RFID-COMPANION persistence test\n");
char *rd = NULL;
size_t rd_len = 0;
if (storage_read_text(STORAGE_TEST_FILE, &rd, &rd_len) == ESP_OK && rd != NULL) {
ESP_LOGI(TAG, "test.txt echoed back: %s", rd);
free(rd);
} else {
ESP_LOGE(TAG, "test.txt read-back failed");
}
}
esp_err_t storage_manager_init(void)
{
esp_vfs_littlefs_conf_t conf = {
.base_path = STORAGE_BASE_PATH,
.partition_label = STORAGE_PARTITION_LABEL,
.format_if_mount_failed = true,
};
esp_err_t ret = esp_vfs_littlefs_register(&conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "LittleFS mount failed: %s", esp_err_to_name(ret));
return ret;
}
size_t total = 0;
size_t used = 0;
if (esp_littlefs_info(STORAGE_PARTITION_LABEL, &total, &used) == ESP_OK) {
ESP_LOGI(TAG, "LittleFS mounted at %s: %d bytes total, %d used",
STORAGE_BASE_PATH, (int)total, (int)used);
} else {
ESP_LOGI(TAG, "LittleFS mounted at %s", STORAGE_BASE_PATH);
}
ret = seed_defaults();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "seeding default files failed: %s", esp_err_to_name(ret));
return ret;
}
cross_reboot_persistence_test();
return ESP_OK;
}