Files
P4-USB-transfer/main/EMOJI_STORAGE/emoji_storage.c
T

639 lines
19 KiB
C
Raw Normal View History

2026-10-13 21:54:50 +08:00
#include "emoji_storage.h"
#include "FLASH/flash.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include "cjson.h"
static const char *TAG = "emoji_storage";
// NVS命名空间
#define NVS_NAMESPACE "emoji_config"//"emoji_config"
#define USING_PACK_KEY "using_pack"
/* NVS key 最大 15 字符,原 "downloaded_packs" 超长导致 ESP_ERR_NVS_KEY_TOO_LONG */
#define DOWNLOADED_PACKS_KEY "dl_packs"
#define EXTFLASH_BASE "/extflash"
// 内存缓存
static char g_using_pack = '0'; // 默认表情包
static char g_downloaded_packs[256] = {0}; // 存储JSON格式的已下载列表
/**
* @brief 根据 /extflash 下实际存在的 A/B/C/D 目录同步已下载列表并写回 NVS
* 解决“昨天下载了 A 但当时 NVS key 超长保存失败、查询只返回 0”的问题
*/
static void emoji_storage_sync_downloaded_packs_from_fs(void)
{
extflash_lock();
DIR *dir = opendir(EXTFLASH_BASE);
if (!dir) {
extflash_unlock();
return;
}
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (!json_array || !cJSON_IsArray(json_array)) {
if (json_array) {
cJSON_Delete(json_array);
}
json_array = cJSON_CreateArray();
if (!json_array) {
closedir(dir);
extflash_unlock();
return;
}
cJSON_AddItemToArray(json_array, cJSON_CreateString("0"));
}
bool changed = false;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
if (entry->d_name[1] != '\0') {
continue;
}
char pack_id = entry->d_name[0];
if (pack_id != 'A' && pack_id != 'B' && pack_id != 'C' && pack_id != 'D') {
continue;
}
char path[32];
snprintf(path, sizeof(path), "%s/%c", EXTFLASH_BASE, pack_id);
struct stat st;
if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
continue;
}
bool found = false;
int n = cJSON_GetArraySize(json_array);
for (int i = 0; i < n; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] == pack_id) {
found = true;
break;
}
}
if (!found) {
char pack_str[2] = {pack_id, '\0'};
cJSON_AddItemToArray(json_array, cJSON_CreateString(pack_str));
changed = true;
ESP_LOGI(TAG, "从 /extflash 同步到已下载列表: %c", pack_id);
}
}
closedir(dir);
extflash_unlock();
/* 已安装列表始终包含默认表情包系列 0(内置) */
{
bool has_zero = false;
int n = cJSON_GetArraySize(json_array);
for (int i = 0; i < n; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] == '0') {
has_zero = true;
break;
}
}
if (!has_zero) {
cJSON_AddItemToArray(json_array, cJSON_CreateString("0"));
changed = true;
}
}
if (!changed) {
cJSON_Delete(json_array);
return;
}
char *json_string = cJSON_PrintUnformatted(json_array);
cJSON_Delete(json_array);
if (!json_string) {
return;
}
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
nvs_handle_t nvs_handle;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle) != ESP_OK) {
return;
}
if (nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs) == ESP_OK) {
nvs_commit(nvs_handle);
ESP_LOGI(TAG, "已下载列表已同步到 NVS: %s", g_downloaded_packs);
}
nvs_close(nvs_handle);
}
/**
* @brief 从NVS加载表情包信息到内存
*/
esp_err_t emoji_storage_load(void)
{
nvs_handle_t nvs_handle;
esp_err_t err;
size_t required_size;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "打开NVS命名空间失败: %s,使用默认值", esp_err_to_name(err));
g_using_pack = '0';
strcpy(g_downloaded_packs, "[\"0\"]"); // 默认只有'0'
return err;
}
// 读取正在使用的表情包
char using_pack_str[2] = {0};
required_size = sizeof(using_pack_str);
err = nvs_get_str(nvs_handle, USING_PACK_KEY, using_pack_str, &required_size);
if (err == ESP_OK && required_size > 0) {
g_using_pack = using_pack_str[0];
ESP_LOGI(TAG, "加载正在使用的表情包: %c", g_using_pack);
} else {
ESP_LOGW(TAG, "读取using_pack失败: %s,使用默认值'0'", esp_err_to_name(err));
g_using_pack = '0';
}
// 读取已下载的表情包列表
required_size = sizeof(g_downloaded_packs);
err = nvs_get_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs, &required_size);
if (err == ESP_OK && required_size > 0) {
ESP_LOGI(TAG, "加载已下载表情包列表: %s", g_downloaded_packs);
} else {
strcpy(g_downloaded_packs, "[\"0\"]");
}
nvs_close(nvs_handle);
/* 根据 /extflash 下实际存在的 A/B/C/D 目录同步已下载列表,避免“昨天下了 A 但 NVS 未保存”只返回 0 */
emoji_storage_sync_downloaded_packs_from_fs();
return ESP_OK;
}
/**
* @brief 保存当前使用的表情包ID到NVS
*/
esp_err_t emoji_storage_save_using_pack(char pack_id)
{
ESP_LOGI(TAG, "[EMOJI_DEBUG] emoji_storage_save_using_pack('%c') 被调用, 当前 g_using_pack='%c'", pack_id, g_using_pack);
nvs_handle_t nvs_handle;
esp_err_t err;
char pack_str[2] = {pack_id, '\0'};
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
err = nvs_set_str(nvs_handle, USING_PACK_KEY, pack_str);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 保存 using_pack 到 NVS 失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "[EMOJI_DEBUG] 提交NVS更改失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
g_using_pack = pack_id;
ESP_LOGI(TAG, "[EMOJI_DEBUG] 成功保存 using_pack='%c' 到 NVS, g_using_pack 已更新", pack_id);
nvs_close(nvs_handle);
return ESP_OK;
}
/**
* @brief 添加已下载的表情包ID到列表并保存到NVS
*/
esp_err_t emoji_storage_add_downloaded_pack(char pack_id)
{
ESP_LOGI(TAG, "[EMOJI_DEBUG] emoji_storage_add_downloaded_pack('%c') 被调用", pack_id);
ESP_LOGI(TAG, "[EMOJI_DEBUG] 当前 g_using_pack='%c', g_downloaded_packs=%s", g_using_pack, g_downloaded_packs);
nvs_handle_t nvs_handle;
esp_err_t err;
cJSON *json_array = NULL;
cJSON *item = NULL;
char pack_str[2] = {pack_id, '\0'};
bool found = false;
if (emoji_storage_is_downloaded(pack_id)) {
ESP_LOGW(TAG, "[EMOJI_DEBUG] 表情包 %c 已存在于下载列表中, 直接返回 ESP_OK", pack_id);
return ESP_OK;
}
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 读取现有的JSON数组
size_t required_size = sizeof(g_downloaded_packs);
err = nvs_get_str(nvs_handle, DOWNLOADED_PACKS_KEY, g_downloaded_packs, &required_size);
if (err != ESP_OK) {
// 如果不存在,创建新数组
json_array = cJSON_CreateArray();
} else {
// 解析现有JSON
json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
// 解析失败,创建新数组
if (json_array) {
cJSON_Delete(json_array);
}
json_array = cJSON_CreateArray();
}
}
// 检查是否已存在
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *existing_item = cJSON_GetArrayItem(json_array, i);
if (existing_item && cJSON_IsString(existing_item)) {
if (existing_item->valuestring[0] == pack_id) {
found = true;
break;
}
}
}
// 如果不存在,添加到数组
if (!found) {
item = cJSON_CreateString(pack_str);
cJSON_AddItemToArray(json_array, item);
ESP_LOGI(TAG, "添加表情包 %c 到下载列表", pack_id);
}
// 转换为JSON字符串
char *json_string = cJSON_PrintUnformatted(json_array);
if (json_string == NULL) {
ESP_LOGE(TAG, "JSON序列化失败");
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_ERR_NO_MEM;
}
// 保存到NVS
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, json_string);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_OK;
}
/**
* @brief 获取当前使用的表情包ID
*/
char emoji_storage_get_using_pack(void)
{
return g_using_pack;
}
/**
* @brief 获取已下载的表情包列表(JSON格式字符串)
*/
int emoji_storage_get_downloaded_packs_json(char *packs, int max_len)
{
if (packs == NULL || max_len <= 0) {
return -1;
}
int len = strlen(g_downloaded_packs);
if (len >= max_len) {
ESP_LOGW(TAG, "缓冲区太小,需要 %d 字节,但只有 %d 字节", len + 1, max_len);
return -1;
}
strncpy(packs, g_downloaded_packs, max_len - 1);
packs[max_len - 1] = '\0';
return len;
}
/**
* @brief 获取已下载的表情包列表(逗号分隔字符串)
*/
int emoji_storage_get_downloaded_packs(char *packs, int max_len)
{
if (packs == NULL || max_len <= 0) {
return -1;
}
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
ESP_LOGE(TAG, "解析JSON失败");
if (json_array) {
cJSON_Delete(json_array);
}
return -1;
}
int array_size = cJSON_GetArraySize(json_array);
int pos = 0;
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item) && item->valuestring[0] != '\0') {
if (pos > 0 && pos < max_len - 1) {
packs[pos++] = ',';
}
if (pos < max_len - 1) {
packs[pos++] = item->valuestring[0];
}
}
}
packs[pos] = '\0';
cJSON_Delete(json_array);
return pos;
}
/**
* @brief 检查指定表情包是否已下载
*/
bool emoji_storage_is_downloaded(char pack_id)
{
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
if (json_array) {
cJSON_Delete(json_array);
}
return false;
}
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item)) {
if (item->valuestring[0] == pack_id) {
cJSON_Delete(json_array);
return true;
}
}
}
cJSON_Delete(json_array);
return false;
}
/**
* @brief 从列表中删除指定的表情包
*/
esp_err_t emoji_storage_remove_downloaded_pack(char pack_id)
{
// 不允许删除默认表情包'0'
if (pack_id == '0') {
ESP_LOGW(TAG, "不允许删除默认表情包'0'");
return ESP_ERR_INVALID_ARG;
}
nvs_handle_t nvs_handle;
esp_err_t err;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 解析JSON数组
cJSON *json_array = cJSON_Parse(g_downloaded_packs);
if (json_array == NULL || !cJSON_IsArray(json_array)) {
ESP_LOGE(TAG, "解析JSON失败");
if (json_array) {
cJSON_Delete(json_array);
}
nvs_close(nvs_handle);
return ESP_ERR_INVALID_STATE;
}
// 查找并删除
int array_size = cJSON_GetArraySize(json_array);
for (int i = 0; i < array_size; i++) {
cJSON *item = cJSON_GetArrayItem(json_array, i);
if (item && cJSON_IsString(item)) {
if (item->valuestring[0] == pack_id) {
cJSON_DeleteItemFromArray(json_array, i);
ESP_LOGI(TAG, "从下载列表删除表情包: %c", pack_id);
break;
}
}
}
// 转换为JSON字符串
char *json_string = cJSON_PrintUnformatted(json_array);
if (json_string == NULL) {
ESP_LOGE(TAG, "JSON序列化失败");
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return ESP_ERR_NO_MEM;
}
// 保存到NVS
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, json_string);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strncpy(g_downloaded_packs, json_string, sizeof(g_downloaded_packs) - 1);
g_downloaded_packs[sizeof(g_downloaded_packs) - 1] = '\0';
free(json_string);
cJSON_Delete(json_array);
nvs_close(nvs_handle);
if (g_using_pack == pack_id)
{
ESP_LOGW(TAG, "被删除的表情包 '%c' 正是当前使用中的,重置为默认 '0'", pack_id);
emoji_storage_save_using_pack('0');
}
return ESP_OK;
}
/**
* @brief 递归删除指定目录及其内容
*/
static esp_err_t remove_directory_recursive(const char *path)
{
DIR *dir = opendir(path);
if (!dir) {
ESP_LOGW(TAG, "无法打开目录: %s", path);
return ESP_ERR_NOT_FOUND;
}
struct dirent *entry;
char filepath[512];
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
snprintf(filepath, sizeof(filepath), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(filepath, &st) != 0) {
continue;
}
if (S_ISDIR(st.st_mode)) {
remove_directory_recursive(filepath);
} else {
if (unlink(filepath) != 0) {
ESP_LOGW(TAG, "删除文件失败: %s", filepath);
}
}
}
closedir(dir);
if (rmdir(path) != 0) {
ESP_LOGW(TAG, "删除目录失败: %s", path);
return ESP_FAIL;
}
ESP_LOGI(TAG, "已删除目录: %s", path);
return ESP_OK;
}
/**
* @brief 删除指定表情包的文件(目录 + zip)
*/
esp_err_t emoji_storage_remove_pack_files(char pack_id)
{
if (pack_id == '0') {
ESP_LOGW(TAG, "不允许删除默认表情包'0'的文件");
return ESP_ERR_INVALID_ARG;
}
char dir_path[32];
char zip_path[32];
snprintf(dir_path, sizeof(dir_path), "%s/%c", EXTFLASH_BASE, pack_id);
snprintf(zip_path, sizeof(zip_path), "%s/%c.zip", EXTFLASH_BASE, pack_id);
struct stat st;
if (stat(dir_path, &st) == 0 && S_ISDIR(st.st_mode)) {
remove_directory_recursive(dir_path);
}
if (stat(zip_path, &st) == 0 && !S_ISDIR(st.st_mode)) {
if (unlink(zip_path) == 0) {
ESP_LOGI(TAG, "已删除zip文件: %s", zip_path);
} else {
ESP_LOGW(TAG, "删除zip文件失败: %s", zip_path);
}
}
return ESP_OK;
}
/**
* @brief 清除所有已下载的表情包(删除文件 + 清空NVS + 重置为默认表情包)
*/
esp_err_t emoji_storage_clear_all_packs(void)
{
const char packs[] = {'A', 'B', 'C', 'D'};
for (int i = 0; i < (int)(sizeof(packs) / sizeof(packs[0])); i++) {
emoji_storage_remove_pack_files(packs[i]);
}
esp_err_t err = emoji_storage_clear_downloaded_packs();
if (err != ESP_OK) {
ESP_LOGE(TAG, "清空NVS已下载列表失败: %s", esp_err_to_name(err));
return err;
}
if (g_using_pack != '0') {
err = emoji_storage_save_using_pack('0');
if (err != ESP_OK) {
ESP_LOGE(TAG, "重置使用中的表情包失败: %s", esp_err_to_name(err));
}
}
ESP_LOGI(TAG, "已清除所有已下载的表情包,恢复为默认表情包'0'");
return ESP_OK;
}
/**
* @brief 清空已下载的表情包列表(保留默认表情包'0')
*/
esp_err_t emoji_storage_clear_downloaded_packs(void)
{
nvs_handle_t nvs_handle;
esp_err_t err;
// 打开NVS命名空间
err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "打开NVS命名空间失败: %s", esp_err_to_name(err));
return err;
}
// 只保留默认表情包'0'
const char *default_packs = "[\"0\"]";
err = nvs_set_str(nvs_handle, DOWNLOADED_PACKS_KEY, default_packs);
if (err != ESP_OK) {
ESP_LOGE(TAG, "保存downloaded_packs失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
// 提交更改
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "提交NVS更改失败: %s", esp_err_to_name(err));
nvs_close(nvs_handle);
return err;
}
// 更新内存缓存
strcpy(g_downloaded_packs, default_packs);
ESP_LOGI(TAG, "清空已下载表情包列表,保留默认表情包'0'");
nvs_close(nvs_handle);
return ESP_OK;
}