/* 全局变量 */
EmojiPackage g_emoji_package = {0};
+static volatile int g_emoji_download_cancelled = 0; /* 取消下载标志 */
/* ========== ETA 粗略版辅助函数 ========== */
/* 前置声明,便于在本节工具函数中调用 */
static int ln_emoji_is_cached(const EmojiPackage *pkg, const EmotionEmoji *emoji);
return;
}
+ // 重置取消标志
+ g_emoji_download_cancelled = 0;
+
+ // 上报下载开始状态
+ ln_ws_send_emoji_download_status_report(pkg->characterId, pkg->eyesColor, pkg->eyesShape,
+ "downloading", 0, "开始下载表情包");
+
int all_success = 1;
int downloaded_count = 0; /* 包含已缓存与成功下载的 */
int failed_count = 0;
ln_safe_strcpy(mail.status, sizeof(mail.status), "download_fail");
ln_safe_strcpy(mail.description, sizeof(mail.description), "没有足够的空间,跳过下载");
do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_EMOJI_REPORT, (char *)&mail, (uint32_t)sizeof(mail));
+ // 上报下载失败状态
+ ln_ws_send_emoji_download_status_report(g_emoji_package.characterId, g_emoji_package.eyesColor, g_emoji_package.eyesShape,
+ "download_fail", -1, "没有足够的空间,跳过下载");
push_download_fail_notification(DOWNLOAD_FILE_TYPE_EMOJI);
ln_free_emoji_list();
return;
// 遍历下载所有表情
for (int i = 0; i < pkg->emojiCount; i++)
{
+ // 检查是否取消下载
+ if (g_emoji_download_cancelled)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 检测到取消下载标志,停止下载");
+ all_success = 0;
+ break;
+ }
+
// 跳过已缓存的文件
if (ln_emoji_is_cached(pkg, &pkg->emojis[i]))
{
uapi_create_folder(dir_buf);
}
}
+ // 再次检查是否取消下载(在下载前)
+ if (g_emoji_download_cancelled)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 检测到取消下载标志,停止下载");
+ all_success = 0;
+ break;
+ }
+
if (uapi_ln_download_file_resume_with_retry(pkg->emojis[i].emojiUrl, save_path, 10) != ERRCODE_SUCC)
{
+ // 检查是否因为取消而失败
+ if (g_emoji_download_cancelled)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 下载被取消");
+ all_success = 0;
+ break;
+ }
+
all_success = 0;
failed_count++;
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] emoji %s 下载失败", pkg->emojis[i].emotion);
+ // 上报下载失败状态
+ char fail_msg[128];
+ snprintf(fail_msg, sizeof(fail_msg), "表情 %s 下载失败", pkg->emojis[i].emotion);
+ ln_ws_send_emoji_download_status_report(pkg->characterId, pkg->eyesColor, pkg->eyesShape,
+ "download_fail", -1, fail_msg);
/* 单个文件三次重试仍失败,终止后续下载并尽快上报失败 */
break;
}
int progress = (int)(((double)downloaded_count / pkg->emojiCount) * 100);
push_download_progress_notification(progress);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] emoji %s 下载完成", pkg->emojis[i].emotion);
+
+ // 每次进度更新都同步发送协议给服务器更新进度
+ char progress_msg[64];
+ snprintf(progress_msg, sizeof(progress_msg), "下载进度%d%%", progress);
+ ln_ws_send_emoji_download_status_report(pkg->characterId, pkg->eyesColor, pkg->eyesShape,
+ "downloading", progress, progress_msg);
/* 粗略版 ETA(平滑版):在单个文件完成后估算一次 */
if (total_bytes_all > 0)
/* 下载完释放本次表情包占用的内存 */
ln_free_emoji_list();
+ /* 检查是否因为取消而结束 */
+ if (g_emoji_download_cancelled)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 下载已取消");
+ /* 通知ui下载失败 */
+ push_download_fail_notification(DOWNLOAD_FILE_TYPE_EMOJI);
+ // 上报取消状态
+ ln_ws_send_emoji_download_status_report(mail.characterId, mail.eyesColor, mail.eyesShape,
+ "cancelled", -1, "表情包下载已取消");
+ // 清理已下载的部分文件(可选)
+ // 这里可以选择清理部分下载的文件,或者保留以便断点续传
+ }
/* 持久化:仅在整个表情组下载成功时记录该表情组 */
- if (all_success)
+ else if (all_success)
{
/* 通知ui下载成功 */
push_download_success_notification(DOWNLOAD_FILE_TYPE_EMOJI);
ln_persist_add_emoji_group(mail.characterId, mail.eyesColor, mail.eyesShape);
+ // 上报下载成功状态
+ ln_ws_send_emoji_download_status_report(mail.characterId, mail.eyesColor, mail.eyesShape,
+ "download_success", 100, "表情包下载成功");
}
else
{
/* 通知ui下载失败 */
push_download_fail_notification(DOWNLOAD_FILE_TYPE_EMOJI);
+ // 上报下载失败状态(如果之前没有上报过)
+ ln_ws_send_emoji_download_status_report(mail.characterId, mail.eyesColor, mail.eyesShape,
+ "download_fail", -1, "表情包下载失败");
}
}
void ln_start_emoji_download(void)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 启动表情下载任务");
+ // 重置取消标志
+ g_emoji_download_cancelled = 0;
uapi_ln_set_file_download_type(LN_FILE_DOWNLOAD_TYPE_EMOJI);
if (g_emoji_package.emojiCount <= 0 || !g_emoji_package.emojis)
{
if(uapi_ln_get_file_download_type() == LN_FILE_DOWNLOAD_TYPE_NONE && uapi_ln_is_file_downloading() == 0)
{
push_download_file_notification(DOWNLOAD_FILE_TYPE_EMOJI);
- ln_ws_send_download_emoji_ack(g_emoji_package.characterId, g_emoji_package.eyesColor, g_emoji_package.eyesShape, true, "ä¸\8b载表æ\83\85");
+ ln_ws_send_download_emoji_ack(g_emoji_package.characterId, g_emoji_package.eyesColor, g_emoji_package.eyesShape, true, "å¼\80å§\8bä¸\8b载表æ\83\85å\8c\85");
}
else
{
if (ret == 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DELETE_EMOJI] cached files in %s deleted", del_dir);
+ // 删除成功,发送 delete_success 状态上报
+ ln_ws_send_emoji_download_status_report(del_characterId, del_eyesColor, del_eyesShape,
+ "delete_success", -1, "表情包删除成功");
}
else
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DELETE_EMOJI] failed to delete cached files in %s", del_dir);
+ // 删除失败,发送 delete_fail 状态上报
+ ln_ws_send_emoji_download_status_report(del_characterId, del_eyesColor, del_eyesShape,
+ "delete_fail", -1, "表情包删除失败");
}
// [WEARABLE E/] [DELETE_EMOJI] parsed successfully:
if (groups)
free(groups);
cJSON_Delete(root);
+}
+
+/* 解析表情包下载状态上报应答 EmojiDownloadStatusReportAck */
+void ln_parse_emoji_download_status_report_ack(const char *json_str, int json_len)
+{
+ if (!json_str || json_len <= 0)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_STATUS_ACK] Invalid JSON string or length\n");
+ return;
+ }
+
+ cJSON *root = cJSON_Parse(json_str);
+ if (!root)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_STATUS_ACK] Failed to parse JSON\n");
+ return;
+ }
+
+ cJSON *data_obj = cJSON_GetObjectItem(root, "data");
+ if (!data_obj || !cJSON_IsObject(data_obj))
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_STATUS_ACK] Missing or invalid data object\n");
+ cJSON_Delete(root);
+ return;
+ }
+
+ // 解析data字段
+ cJSON *characterId = cJSON_GetObjectItem(data_obj, "characterId");
+ cJSON *eyesColor = cJSON_GetObjectItem(data_obj, "eyesColor");
+ cJSON *eyesShape = cJSON_GetObjectItem(data_obj, "eyesShape");
+ cJSON *success = cJSON_GetObjectItem(data_obj, "success");
+ cJSON *message = cJSON_GetObjectItem(data_obj, "message");
+
+ const char *char_id = characterId && cJSON_IsString(characterId) ? characterId->valuestring : "";
+ const char *ec = eyesColor && cJSON_IsString(eyesColor) ? eyesColor->valuestring : "";
+ const char *es = eyesShape && cJSON_IsString(eyesShape) ? eyesShape->valuestring : "";
+ bool success_flag = success && cJSON_IsBool(success) ? cJSON_IsTrue(success) : false;
+ const char *msg = message && cJSON_IsString(message) ? message->valuestring : "";
+
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_STATUS_ACK] Received EmojiDownloadStatusReportAck: characterId=%s, eyesColor=%s, eyesShape=%s, success=%d, message=%s\n",
+ char_id, ec, es, success_flag, msg);
+
+ // 如果处理失败,可以在这里添加重试逻辑
+ if (!success_flag)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_STATUS_ACK] Server failed to process status report: %s\n", msg);
+ }
+
+ cJSON_Delete(root);
+}
+
+/* 解析取消下载表情命令 CancelDownloadEmoji */
+void ln_parse_cancel_download_emoji(const char *json_str, int json_len)
+{
+ if (!json_str || json_len <= 0)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] Invalid JSON string or length\n");
+ return;
+ }
+
+ // 复制并终止字符串
+ char *text = (char *)malloc((size_t)json_len + 1);
+ if (!text)
+ return;
+ memcpy(text, json_str, (size_t)json_len);
+ text[json_len] = '\0';
+
+ // 解析JSON根对象
+ cJSON *root = cJSON_Parse(text);
+ if (!root)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] json parse failed");
+ free(text);
+ return;
+ }
+
+ // 检查错误码
+ cJSON *errorCode = cJSON_GetObjectItem(root, "errorCode");
+ if (errorCode && !cJSON_IsNull(errorCode))
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] server error: %s",
+ cJSON_IsString(errorCode) ? errorCode->valuestring : "unknown");
+ cJSON_Delete(root);
+ free(text);
+ return;
+ }
+
+ // 获取data对象
+ cJSON *data = cJSON_GetObjectItem(root, "data");
+ if (!data || !cJSON_IsObject(data))
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] no data object");
+ cJSON_Delete(root);
+ free(text);
+ return;
+ }
+
+ // 解析字段
+ cJSON *characterId = cJSON_GetObjectItem(data, "characterId");
+ cJSON *eyesColor = cJSON_GetObjectItem(data, "eyesColor");
+ cJSON *eyesShape = cJSON_GetObjectItem(data, "eyesShape");
+
+ char cancel_characterId[64] = {0};
+ char cancel_eyesColor[32] = {0};
+ char cancel_eyesShape[32] = {0};
+
+ ln_safe_strcpy(cancel_characterId, sizeof(cancel_characterId),
+ (characterId && cJSON_IsString(characterId)) ? characterId->valuestring : "");
+
+ ln_safe_strcpy(cancel_eyesColor, sizeof(cancel_eyesColor),
+ (eyesColor && cJSON_IsString(eyesColor)) ? eyesColor->valuestring : "");
+
+ ln_safe_strcpy(cancel_eyesShape, sizeof(cancel_eyesShape),
+ (eyesShape && cJSON_IsString(eyesShape)) ? eyesShape->valuestring : "");
+
+ // 打印解析结果
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] parsed successfully:");
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, " characterId: %s", cancel_characterId);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, " eyesColor: %s", cancel_eyesColor);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, " eyesShape: %s", cancel_eyesShape);
+
+ // 检查是否正在下载表情
+ if (uapi_ln_get_file_download_type() == LN_FILE_DOWNLOAD_TYPE_EMOJI && uapi_ln_is_file_downloading() == 1)
+ {
+ // 检查是否匹配当前下载的表情包
+ if (g_emoji_package.characterId && g_emoji_package.eyesColor && g_emoji_package.eyesShape &&
+ strcmp(cancel_characterId, g_emoji_package.characterId) == 0 &&
+ strcmp(cancel_eyesColor, g_emoji_package.eyesColor) == 0 &&
+ strcmp(cancel_eyesShape, g_emoji_package.eyesShape) == 0)
+ {
+ // 设置取消标志
+ g_emoji_download_cancelled = 1;
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] 设置取消下载标志,停止下载");
+
+ // 发送取消下载应答
+ ln_ws_send_cancel_download_emoji_ack(cancel_characterId, cancel_eyesColor, cancel_eyesShape,
+ true, "取消下载成功");
+
+ // 发送取消状态上报
+ ln_ws_send_emoji_download_status_report(cancel_characterId, cancel_eyesColor, cancel_eyesShape,
+ "cancelled", -1, "表情包下载已取消");
+ }
+ else
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] 当前下载的表情包不匹配,无法取消");
+ // 发送取消下载应答(失败)
+ ln_ws_send_cancel_download_emoji_ack(cancel_characterId, cancel_eyesColor, cancel_eyesShape,
+ false, "当前下载的表情包不匹配");
+ }
+ }
+ else
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CANCEL_DOWNLOAD_EMOJI] 当前没有正在下载的表情包");
+ // 发送取消下载应答(失败)
+ ln_ws_send_cancel_download_emoji_ack(cancel_characterId, cancel_eyesColor, cancel_eyesShape,
+ false, "当前没有正在下载的表情包");
+ }
+
+ cJSON_Delete(root);
+ free(text);
}
\ No newline at end of file
cJSON_free(json_string);
cJSON_Delete(json);
return ret;
+}
+
+/* 发送表情包下载状态上报 EmojiDownloadStatusReport */
+int ln_ws_send_emoji_download_status_report(const char *character_id, const char *eyesColor, const char *eyesShape,
+ const char *status, int progress, const char *message)
+{
+ if (ln_ws_get_connection_state() != 2)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Cannot send EmojiDownloadStatusReport, connection state: %d\n", ln_ws_get_connection_state());
+ return -1;
+ }
+
+ uint32_t timestamp;
+ uapi_get_timestampe_ss(×tamp);
+
+ cJSON *json = cJSON_CreateObject();
+ if (!json)
+ return -1;
+
+ cJSON_AddStringToObject(json, "deviceId", uapi_ln_get_device_id());
+ cJSON_AddNumberToObject(json, "timestamp", (double)timestamp);
+
+ char message_id[128];
+ // 使用时间戳和计数器生成唯一消息ID
+ static uint32_t emoji_status_counter = 0;
+ emoji_status_counter++;
+ snprintf(message_id, sizeof(message_id), "emoji_status_%u_%u", timestamp, emoji_status_counter);
+ cJSON_AddStringToObject(json, "messageId", message_id);
+ cJSON_AddStringToObject(json, "command", "EmojiDownloadStatusReport");
+
+ cJSON *data = cJSON_CreateObject();
+ if (!data)
+ {
+ cJSON_Delete(json);
+ return -1;
+ }
+ cJSON_AddStringToObject(data, "characterId", character_id ? character_id : "");
+ cJSON_AddStringToObject(data, "eyesColor", eyesColor ? eyesColor : "");
+ cJSON_AddStringToObject(data, "eyesShape", eyesShape ? eyesShape : "");
+ cJSON_AddStringToObject(data, "status", status ? status : "");
+ if (progress >= 0 && progress <= 100)
+ {
+ cJSON_AddNumberToObject(data, "progress", progress);
+ }
+ cJSON_AddStringToObject(data, "message", message ? message : "");
+ cJSON_AddItemToObject(json, "data", data);
+
+ char *json_string = cJSON_Print(json);
+ if (!json_string)
+ {
+ cJSON_Delete(json);
+ return -1;
+ }
+
+ int ret = uapi_ws_send_text(json_string);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent EmojiDownloadStatusReport: %s\n", json_string);
+ cJSON_free(json_string);
+ cJSON_Delete(json);
+ return ret;
+}
+
+/* 发送取消下载表情应答 CancelDownloadEmojiAck */
+int ln_ws_send_cancel_download_emoji_ack(const char *character_id, const char *eyesColor, const char *eyesShape,
+ bool success, const char *message)
+{
+ if (ln_ws_get_connection_state() != 2)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Cannot send CancelDownloadEmojiAck, connection state: %d\n", ln_ws_get_connection_state());
+ return -1;
+ }
+
+ uint32_t timestamp;
+ uapi_get_timestampe_ss(×tamp);
+
+ cJSON *json = cJSON_CreateObject();
+ if (!json)
+ return -1;
+
+ cJSON_AddStringToObject(json, "deviceId", uapi_ln_get_device_id());
+ cJSON_AddNumberToObject(json, "timestamp", (double)timestamp);
+
+ char message_id[128];
+ // 使用时间戳和计数器生成唯一消息ID
+ static uint32_t cancel_emoji_ack_counter = 0;
+ cancel_emoji_ack_counter++;
+ snprintf(message_id, sizeof(message_id), "ack_cancel_download_emoji_%u_%u", timestamp, cancel_emoji_ack_counter);
+ cJSON_AddStringToObject(json, "messageId", message_id);
+ cJSON_AddStringToObject(json, "command", "CancelDownloadEmojiAck");
+
+ cJSON *data = cJSON_CreateObject();
+ if (!data)
+ {
+ cJSON_Delete(json);
+ return -1;
+ }
+ cJSON_AddStringToObject(data, "characterId", character_id ? character_id : "");
+ cJSON_AddStringToObject(data, "eyesColor", eyesColor ? eyesColor : "");
+ cJSON_AddStringToObject(data, "eyesShape", eyesShape ? eyesShape : "");
+ cJSON_AddBoolToObject(data, "success", success);
+ cJSON_AddStringToObject(data, "message", message ? message : (success ? "取消下载成功" : "取消下载失败"));
+ cJSON_AddItemToObject(json, "data", data);
+
+ char *json_string = cJSON_Print(json);
+ if (!json_string)
+ {
+ cJSON_Delete(json);
+ return -1;
+ }
+
+ int ret = uapi_ws_send_text(json_string);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent CancelDownloadEmojiAck: %s\n", json_string);
+ cJSON_free(json_string);
+ cJSON_Delete(json);
+ return ret;
}
\ No newline at end of file