From db89b5ebcdae7ef5a58e8da6f68129359bd38dc9 Mon Sep 17 00:00:00 2001 From: luozheng <1732036298@qq.com> Date: Sat, 8 Nov 2025 15:00:16 +0800 Subject: [PATCH] =?utf8?q?ln=EF=BC=9A=E8=A1=A8=E6=83=85=E6=96=87=E4=BB=B6?= =?utf8?q?=E4=B8=8B=E8=BD=BD=E5=AE=9E=E7=8E=B0=E5=8F=91=E9=80=81=E5=88=B0?= =?utf8?q?=E5=BC=82=E6=AD=A5=E7=BA=BF=E7=A8=8B=EF=BC=8C=E5=BE=85=E9=AA=8C?= =?utf8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt | 1 + code/sdk/zhihui/leiniao/imsl_ln/ln_api.c | 41 ++ code/sdk/zhihui/leiniao/imsl_ln/ln_api.h | 5 + .../leiniao/imsl_ln/ln_file_recv_task.c | 442 ++++++++++++++++++ 4 files changed, 489 insertions(+) create mode 100644 code/sdk/zhihui/leiniao/imsl_ln/ln_file_recv_task.c diff --git a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt index 2db97e411..e6c09ec5d 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt +++ b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt @@ -21,6 +21,7 @@ set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ln_api_emoji.c ${CMAKE_CURRENT_SOURCE_DIR}/ln_api_timeline_activities.c ${CMAKE_CURRENT_SOURCE_DIR}/ln_api_web_ack.c + ${CMAKE_CURRENT_SOURCE_DIR}/ln_file_recv_task.c ) # 公共头文件路径(供其他模块引用) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c index 162a3616b..92ed8ed00 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c @@ -834,6 +834,44 @@ int uapi_ln_download_file_resume_with_retry(const char *url, const char *save_pa uapi_pm_remove_sleep_veto(PM_ID_DEBUG); return ret; } +// 断点续传下载函数封装:带重试,不做UI通知与资源释放,文件写入发送到队列 +int uapi_ln_download_file_resume_queue_with_retry(const char *url, const char *save_path, int max_retries) +{ + if (url == NULL || save_path == NULL || (max_retries <= 0 || max_retries > 10 )) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 参数无效"); + return ERRCODE_FAIL; + } + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 开始下载: %s -> %s", url, save_path); + if (uapi_ln_is_file_downloading() == 1) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] 已有文件正在下载中"); + return ERRCODE_FAIL; + } + g_ln_is_file_downloading = 1; + + //添加睡眠投票 + uapi_pm_add_sleep_veto(PM_ID_DEBUG); + + int ret = uapi_ln_download_file_resume_queue(url, save_path); + if (ERRCODE_SUCC != ret) + { + for (int i = 1; i <= max_retries; i++) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 重试 %d/%d", i, max_retries); + ret = uapi_ln_download_file_resume_queue(url, save_path); + if (ERRCODE_SUCC == ret) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 重试成功 %d", i); + break; + } + } + } + g_ln_is_file_downloading = 0; + //移除睡眠投票 + uapi_pm_remove_sleep_veto(PM_ID_DEBUG); + return ret; +} /* 自适应申请HttpClient缓冲(保证header>=2048),失败则逐步扩容重试 */ HTTPC_RESULT ln_http_prepare_auto(HttpClientData *clientData, int header_size, int resp_size) { @@ -1790,5 +1828,8 @@ errcode_t uapi_ln_http_server_init(void) ln_init_required_dirs(); + /* 初始化文件接收服务 */ + // uapi_ln_file_recv_init(); + return ERRCODE_SUCC; } \ No newline at end of file diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index b4b3f6fda..e401444ef 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -262,6 +262,11 @@ int uapi_ln_download_with_retry(const char *url, const char *save_path, int max_ /* 带断点续传的下载:支持 Range 续传;失败不删除部分文件 */ int uapi_ln_download_file_resume(const char *url, const char *save_path); int uapi_ln_download_file_resume_with_retry(const char *url, const char *save_path, int max_retries); +/* 初始化文件接收服务 */ +errcode_t uapi_ln_file_recv_init(void); +/* 带断点续传的下载(队列版本):使用队列异步写文件,提高下载速度 */ +int uapi_ln_download_file_resume_queue(const char *url, const char *save_path); +int uapi_ln_download_file_resume_queue_with_retry(const char *url, const char *save_path, int max_retries); /* 设置ws音频播放状态 */ void uapi_ln_set_audio_play_status(int status); /* 获取ws音频播放状态 */ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_file_recv_task.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_file_recv_task.c new file mode 100644 index 000000000..6d76b5a1a --- /dev/null +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_file_recv_task.c @@ -0,0 +1,442 @@ +#include "ln_api.h" +#include +#include +#include +#include +#include +#include +#include "custom_utils.h" + +/* 文件接收队列消息类型 */ +#define LN_FILE_RECV_CMD_DATA 0x1 /* 文件数据块 */ +#define LN_FILE_RECV_CMD_START 0x2 /* 开始下载 */ +#define LN_FILE_RECV_CMD_END 0x3 /* 结束下载 */ +#define LN_FILE_RECV_CMD_ERROR 0x4 /* 下载错误 */ + +/* 文件接收消息结构体 */ +typedef struct +{ + int cmd; /* 命令类型 */ + char *save_path; /* 保存路径,使用malloc分配,接收方负责free */ + char *data; /* 数据块,使用malloc分配,接收方负责free */ + uint32_t data_len; /* 数据长度 */ + long long resume_offset; /* 续传偏移 */ + int response_code; /* HTTP响应码 */ + size_t total_content_len; /* 总内容长度 */ + int is_first_block; /* 是否首块 */ +} ln_file_recv_mail_t; + +/* 文件接收任务配置 */ +static app_task_definition_t ln_file_recv_taskcfg = {0}; +static unsigned long g_ln_file_recv_queue = 0xFFFFFFFF; +#define LN_FILE_RECV_TASK_SIZE 0x500 + +/* 当前下载状态 */ +typedef struct +{ + FILE *fd; /* 文件句柄 */ + char *save_path; /* 保存路径 */ + long long resume_offset; /* 续传偏移 */ + size_t total_written; /* 已写入总字节数 */ + size_t total_content_len; /* 总内容长度 */ + int is_active; /* 是否激活 */ +} ln_file_recv_state_t; + +static ln_file_recv_state_t g_file_recv_state = {0}; + +/* 文件接收任务线程 */ +static void ln_file_recv_task(void const *arg) +{ + errcode_t ret; + ln_file_recv_mail_t mail = {0}; + uint32_t msgsize = sizeof(ln_file_recv_mail_t); + + while (1) + { + /* 每轮重置 msgsize,兼容底层可能改写该值的实现 */ + msgsize = sizeof(ln_file_recv_mail_t); + ret = osal_msg_queue_read_copy(g_ln_file_recv_queue, (void *)&mail, &msgsize, + OSAL_MSGQ_WAIT_FOREVER); + if (ret != OSAL_SUCCESS) + { + LOS_Msleep(100); + continue; + } + + if (mail.cmd == LN_FILE_RECV_CMD_START) + { + /* 开始下载:打开文件 */ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] 开始下载: %s, offset=%lld", + mail.save_path, mail.resume_offset); + + /* 关闭之前的文件(如果有) */ + if (g_file_recv_state.fd) + { + fclose(g_file_recv_state.fd); + g_file_recv_state.fd = NULL; + } + if (g_file_recv_state.save_path) + { + free(g_file_recv_state.save_path); + g_file_recv_state.save_path = NULL; + } + + /* 打开新文件 */ + FILE *fd = fopen(mail.save_path, mail.resume_offset > 0 ? "ab+" : "wb+"); + if (!fd) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] fopen failed for %s", mail.save_path); + g_file_recv_state.is_active = 0; + } + else + { + g_file_recv_state.fd = fd; + g_file_recv_state.save_path = mail.save_path; + g_file_recv_state.resume_offset = mail.resume_offset; + g_file_recv_state.total_written = 0; + g_file_recv_state.total_content_len = mail.total_content_len; + g_file_recv_state.is_active = 1; + } + } + else if (mail.cmd == LN_FILE_RECV_CMD_DATA) + { + /* 写入数据块 */ + if (g_file_recv_state.is_active && g_file_recv_state.fd && mail.data && mail.data_len > 0) + { + size_t written = fwrite(mail.data, 1, mail.data_len, g_file_recv_state.fd); + if (written != mail.data_len) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] write failed, written=%zu, expected=%u", + written, mail.data_len); + } + else + { + g_file_recv_state.total_written += written; + if (mail.is_first_block) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] 首块写入成功, written=%zu", written); + } + } + } + + /* 释放数据内存 */ + if (mail.data) + { + free(mail.data); + mail.data = NULL; + } + } + else if (mail.cmd == LN_FILE_RECV_CMD_END) + { + /* 结束下载:关闭文件 */ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] 下载结束: %s, total_written=%zu, content_len=%zu", + mail.save_path ? mail.save_path : "unknown", + g_file_recv_state.total_written, + g_file_recv_state.total_content_len); + + if (g_file_recv_state.fd) + { + fclose(g_file_recv_state.fd); + g_file_recv_state.fd = NULL; + } + + if (g_file_recv_state.save_path) + { + free(g_file_recv_state.save_path); + g_file_recv_state.save_path = NULL; + } + + g_file_recv_state.is_active = 0; + g_file_recv_state.total_written = 0; + g_file_recv_state.total_content_len = 0; + } + else if (mail.cmd == LN_FILE_RECV_CMD_ERROR) + { + /* 下载错误:关闭文件 */ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] 下载错误: %s", + mail.save_path ? mail.save_path : "unknown"); + + if (g_file_recv_state.fd) + { + fclose(g_file_recv_state.fd); + g_file_recv_state.fd = NULL; + } + + if (g_file_recv_state.save_path) + { + free(g_file_recv_state.save_path); + g_file_recv_state.save_path = NULL; + } + + g_file_recv_state.is_active = 0; + } + + /* 释放消息中的动态内存 */ + if (mail.save_path) + { + free(mail.save_path); + mail.save_path = NULL; + } + } +} + +/* 创建文件接收队列 */ +static void create_ln_file_recv_queue(void) +{ + if (osal_msg_queue_create("ln_file_recv_queue", 32, &g_ln_file_recv_queue, + 0, sizeof(ln_file_recv_mail_t)) != ERRCODE_SUCC) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] create queue fail"); + } +} + +/* 发送数据到文件接收队列 */ +static errcode_t do_ln_file_recv_send_data(int cmd, const char *save_path, + const char *data, uint32_t data_len, + long long resume_offset, + int response_code, + size_t total_content_len, + int is_first_block) +{ + errcode_t ret; + ln_file_recv_mail_t send_data = {0}; + send_data.cmd = cmd; + send_data.resume_offset = resume_offset; + send_data.response_code = response_code; + send_data.total_content_len = total_content_len; + send_data.is_first_block = is_first_block; + + if (save_path) + { + send_data.save_path = (char *)malloc(strlen(save_path) + 1); + if (send_data.save_path) + { + strcpy(send_data.save_path, save_path); + } + } + + if (data && data_len > 0) + { + send_data.data_len = data_len; + send_data.data = (char *)malloc(data_len); + if (send_data.data) + { + memcpy(send_data.data, data, data_len); + } + else + { + if (send_data.save_path) free(send_data.save_path); + return ERRCODE_FAIL; + } + } + + uint32_t send_len = sizeof(send_data); + ret = osal_msg_queue_write_copy(g_ln_file_recv_queue, (void *)&send_data, + send_len, 0); + if (ret != OSAL_SUCCESS) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] send data failed! err=0x%x", ret); + if (send_data.save_path) free(send_data.save_path); + if (send_data.data) free(send_data.data); + return ERRCODE_FAIL; + } + + return ERRCODE_SUCC; +} + +/* 初始化文件接收服务 */ +errcode_t uapi_ln_file_recv_init(void) +{ + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] uapi_ln_file_recv_init"); + + ln_file_recv_taskcfg.create_queue_handle = create_ln_file_recv_queue; + ln_file_recv_taskcfg.attr.name = "ln_file_recv_task"; + ln_file_recv_taskcfg.attr.priority = osPriorityNormal; + ln_file_recv_taskcfg.attr.stack_size = LN_FILE_RECV_TASK_SIZE; + ln_file_recv_taskcfg.func = ln_file_recv_task; + ln_file_recv_taskcfg.use_mem = USE_EXTEND_MEM; + + errcode_t ret = uapi_create_osThread_custom(&ln_file_recv_taskcfg); + if (ret != ERRCODE_SUCC) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[FILE_RECV] create task failed %d!", ret); + return ERRCODE_FAIL; + } + + return ERRCODE_SUCC; +} + +/* 新的下载函数:使用队列异步写文件 */ +int uapi_ln_download_file_resume_queue(const char *url, const char *save_path) +{ + if (!url || !save_path) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] url or save_path is NULL"); + return ERRCODE_FAIL; + } + + long long resume_offset = 0; + /* 判断是否存在部分文件,计算续传偏移 */ + { + FILE *fexist = fopen(save_path, "rb"); + if (fexist) + { + if (fseek(fexist, 0, SEEK_END) == 0) + { + long pos = ftell(fexist); + if (pos > 0) + resume_offset = (long long)pos; + } + fclose(fexist); + } + } + + HttpClient client = {0}; + HttpClientData client_data = {0}; + int ret = -1; + size_t total_sent = 0; + int is_first_block = 1; + + if(resume_offset > 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] 断点续传,resume_offset=%lld", resume_offset); + } + + HTTPC_RESULT prep = ln_http_prepare_auto(&client_data, 2048, LN_CLIENT_PREPARE_SIZE * 2); + if (prep != HTTP_SUCCESS) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] prepare failed: %d", prep); + return ERRCODE_FAIL; + } + + /* 设置Range请求头(仅当存在偏移时)*/ + char *header_buf = NULL; + if (resume_offset > 0) + { + header_buf = (char *)malloc(128); + if (!header_buf) + { + HttpClientUnprepare(&client_data); + return ERRCODE_FAIL; + } + int hdr = snprintf(header_buf, 128, "Range: bytes=%lld-\r\n", resume_offset); + if (hdr <= 0) + { + free(header_buf); + HttpClientUnprepare(&client_data); + return ERRCODE_FAIL; + } + HttpClientSetCustomHeader(&client, header_buf); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] set header %s", header_buf); + } + + /* 建立连接并发送请求 */ + ret = HttpClientConn(&client, url); + if (ret != 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] connection failed, ret=0x%x", ret); + if (header_buf) free(header_buf); + HttpClientUnprepare(&client_data); + return ERRCODE_FAIL; + } + + ret = HttpClientSend(&client, url, HTTP_GET, &client_data); + if (ret != 0) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] send request failed, ret=0x%x", ret); + if (header_buf) free(header_buf); + HttpClientClse(&client); + HttpClientUnprepare(&client_data); + return ERRCODE_FAIL; + } + + /* 首次接收,判定状态码(200则改为覆盖写)*/ + long long actual_resume_offset = resume_offset; + { + HTTPC_RESULT first_recv = HttpClientRecvResponse(&client, &client_data); + if (!(first_recv == HTTP_SUCCESS || first_recv == HTTP_EAGAIN)) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] first recv failed: %d", first_recv); + if (header_buf) free(header_buf); + HttpClientClse(&client); + HttpClientUnprepare(&client_data); + do_ln_file_recv_send_data(LN_FILE_RECV_CMD_ERROR, save_path, NULL, 0, 0, 0, 0, 0); + return ERRCODE_FAIL; + } + + int code = HttpClientGetResponseCode(&client); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] response code: %d (offset=%lld)", code, resume_offset); + if (code == 200 && resume_offset > 0) + { + /* 服务端未按Range返回,改为覆盖写 */ + actual_resume_offset = 0; + } + + /* 发送开始下载消息 */ + do_ln_file_recv_send_data(LN_FILE_RECV_CMD_START, save_path, NULL, 0, + actual_resume_offset, code, + client_data.responseContentLen, 0); + + /* 发送首块内容(若有)*/ + if (client_data.contentBlockLen > 0) + { + do_ln_file_recv_send_data(LN_FILE_RECV_CMD_DATA, NULL, + client_data.responseBuf, + client_data.contentBlockLen, + 0, code, 0, is_first_block); + total_sent += client_data.contentBlockLen; + is_first_block = 0; + } + } + + /* 后续接收循环 */ + while (client_data.isMore) + { + HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &client_data); + if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN) + { + if (client_data.contentBlockLen > 0) + { + /* 发送数据到队列 */ + do_ln_file_recv_send_data(LN_FILE_RECV_CMD_DATA, NULL, + client_data.responseBuf, + client_data.contentBlockLen, + 0, 0, 0, is_first_block); + total_sent += client_data.contentBlockLen; + is_first_block = 0; + } + } + else + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] recv failed: %d", recv_res); + break; + } + } + + int final_code = HttpClientGetResponseCode(&client); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] final code=%d, total_sent=%zu, content_len=%zu", + final_code, total_sent, client_data.responseContentLen); + + /* 发送结束下载消息 */ + do_ln_file_recv_send_data(LN_FILE_RECV_CMD_END, save_path, NULL, 0, + actual_resume_offset, final_code, + client_data.responseContentLen, 0); + + if ((final_code == 200 || final_code == 206) && total_sent == (size_t)client_data.responseContentLen) + { + ret = ERRCODE_SUCC; + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] 下载成功 (%s)", + actual_resume_offset > 0 ? "续传" : "全量"); + } + else + { + ret = ERRCODE_FAIL; + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_QUEUE] 下载失败,保留部分文件供下次续传"); + } + + if (header_buf) free(header_buf); + HttpClientUnprepare(&client_data); + HttpClientClse(&client); + return ret; +} + -- 2.17.1