Files
zhengxinandCursor 851e347cf5 初始提交卡包 USB 工程
纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <[email protected]>
2026-10-13 21:54:50 +08:00

639 lines
19 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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;
}