ln:表情文件下载实现发送到异步线程,待验证
authorluozheng <1732036298@qq.com>
Sat, 8 Nov 2025 07:00:16 +0000 (15:00 +0800)
committerluozheng <1732036298@qq.com>
Sat, 8 Nov 2025 07:00:16 +0000 (15:00 +0800)
code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt
code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_file_recv_task.c [new file with mode: 0644]

index 2db97e4..e6c09ec 100644 (file)
@@ -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
 )
 
 # 公共头文件路径(供其他模块引用)
index 162a361..92ed8ed 100644 (file)
@@ -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
index b4b3f6f..e401444 100644 (file)
@@ -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 (file)
index 0000000..6d76b5a
--- /dev/null
@@ -0,0 +1,442 @@
+#include "ln_api.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <thread_init.h>
+#include <osal_msgqueue.h>
+#include <soc_osal.h>
+#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;
+}
+