--- /dev/null
+#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;
+}
+