#include "emoji_storage.h" #include "FLASH/flash.h" #include "nvs_flash.h" #include "nvs.h" #include "esp_log.h" #include #include #include #include #include #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; }