1.添加断点续传函数接口,待测试 2507/1 2507/head 2508/target/1 2509/target/1
authorluozheng <1732036298@qq.com>
Tue, 14 Oct 2025 10:07:21 +0000 (18:07 +0800)
committerluozheng <1732036298@qq.com>
Tue, 14 Oct 2025 10:07:21 +0000 (18:07 +0800)
2.添加websocket进入休眠和退出休眠消息供上层调用

code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h

index ccf3d1d..c8ec5a6 100644 (file)
@@ -523,6 +523,216 @@ int uapi_ln_download_with_retry(const char *url, const char *save_path, int max_
     }
     return ret;
 }
+
+/* 下载文件(支持断点续传)
+@param url 文件URL
+@param save_path 保存路径(若存在则尝试从已下载大小处续传)
+@return ERRCODE_SUCC 成功,ERRCODE_FAIL 失败(失败不删除部分文件)
+*/
+int uapi_ln_download_file_resume(const char *url, const char *save_path)
+{
+    if (!url || !save_path)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] url or save_path is NULL");
+        return ERRCODE_FAIL;
+    }
+
+    if (g_ln_is_file_downloading == 1)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] 已有文件正在下载中");
+        return ERRCODE_FAIL;
+    }
+    g_ln_is_file_downloading = 1;
+
+    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_written = 0;
+
+    /* 以追加模式打开(若服务端返回200将切换为覆盖写)*/
+    FILE *fd = fopen(save_path, resume_offset > 0 ? "ab+" : "wb+");
+    if (!fd)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] fopen failed for %s", save_path);
+        g_ln_is_file_downloading = 0;
+        return ERRCODE_FAIL;
+    }
+
+    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] prepare failed: %d", prep);
+        fclose(fd);
+        g_ln_is_file_downloading = 0;
+        return ERRCODE_FAIL;
+    }
+
+    /* 设置Range请求头(仅当存在偏移时)*/
+    char *header_buf = NULL;
+    if (resume_offset > 0)
+    {
+        header_buf = (char *)malloc(128);
+        if (!header_buf)
+        {
+            HttpClientUnprepare(&client_data);
+            fclose(fd);
+            g_ln_is_file_downloading = 0;
+            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);
+            fclose(fd);
+            g_ln_is_file_downloading = 0;
+            return ERRCODE_FAIL;
+        }
+        HttpClientSetCustomHeader(&client, header_buf);
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] set header %s", header_buf);
+    }
+
+    /* 建立连接并发送请求 */
+    ret = HttpClientConn(&client, url);
+    if (ret != 0)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] connection failed, ret=0x%x", ret);
+        if (header_buf) free(header_buf);
+        fclose(fd);
+        HttpClientUnprepare(&client_data);
+        g_ln_is_file_downloading = 0;
+        return ERRCODE_FAIL;
+    }
+
+    ret = HttpClientSend(&client, url, HTTP_GET, &client_data);
+    if (ret != 0)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] send request failed, ret=0x%x", ret);
+        if (header_buf) free(header_buf);
+        fclose(fd);
+        HttpClientClse(&client);
+        HttpClientUnprepare(&client_data);
+        g_ln_is_file_downloading = 0;
+        return ERRCODE_FAIL;
+    }
+
+    /* 首次接收,判定状态码(200则改为覆盖写)*/
+    {
+        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] first recv failed: %d", first_recv);
+            if (header_buf) free(header_buf);
+            fclose(fd);
+            HttpClientClse(&client);
+            HttpClientUnprepare(&client_data);
+            g_ln_is_file_downloading = 0;
+            return ERRCODE_FAIL;
+        }
+
+        int code = HttpClientGetResponseCode(&client);
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] response code: %d (offset=%lld)", code, resume_offset);
+        if (code == 200 && resume_offset > 0)
+        {
+            /* 服务端未按Range返回,改为覆盖写 */
+            fclose(fd);
+            fd = fopen(save_path, "wb+");
+            if (!fd)
+            {
+                if (header_buf) free(header_buf);
+                HttpClientClse(&client);
+                HttpClientUnprepare(&client_data);
+                g_ln_is_file_downloading = 0;
+                return ERRCODE_FAIL;
+            }
+            resume_offset = 0;
+        }
+
+        /* 写入首块内容(若有)*/
+        if (client_data.contentBlockLen > 0)
+        {
+            size_t written = fwrite(client_data.responseBuf, 1, client_data.contentBlockLen, fd);
+            if (written != client_data.contentBlockLen)
+            {
+                WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] write failed on first block, written=%zu, expected=%d",
+                              written, client_data.contentBlockLen);
+                if (header_buf) free(header_buf);
+                fclose(fd);
+                HttpClientClse(&client);
+                HttpClientUnprepare(&client_data);
+                g_ln_is_file_downloading = 0;
+                return ERRCODE_FAIL;
+            }
+            total_written += written;
+        }
+    }
+
+    /* 后续接收循环 */
+    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)
+            {
+                size_t written = fwrite(client_data.responseBuf, 1, client_data.contentBlockLen, fd);
+                if (written != client_data.contentBlockLen)
+                {
+                    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] write failed, written=%zu, expected=%d",
+                                  written, client_data.contentBlockLen);
+                    if (header_buf) free(header_buf);
+                    fclose(fd);
+                    HttpClientClse(&client);
+                    HttpClientUnprepare(&client_data);
+                    g_ln_is_file_downloading = 0;
+                    return ERRCODE_FAIL;
+                }
+                total_written += written;
+            }
+        }
+        else
+        {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] recv failed: %d", recv_res);
+            break;
+        }
+    }
+
+    int final_code = HttpClientGetResponseCode(&client);
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] final code=%d, total_written=%zu", final_code, total_written);
+    if ((final_code == 200 || final_code == 206) && total_written == (size_t)client_data.responseContentLen)
+    {
+        ret = ERRCODE_SUCC;
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] 下载成功 (%s)", resume_offset > 0 ? "续传" : "全量");
+    }
+    else
+    {
+        ret = ERRCODE_FAIL;
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] 下载失败,保留部分文件供下次续传");
+    }
+
+    if (header_buf) free(header_buf);
+    HttpClientUnprepare(&client_data);
+    HttpClientClse(&client);
+    fclose(fd);
+    g_ln_is_file_downloading = 0;
+    return ret;
+}
 /* 自适应申请HttpClient缓冲(保证header>=2048),失败则逐步扩容重试 */
 HTTPC_RESULT ln_http_prepare_auto(HttpClientData *clientData, int header_size, int resp_size)
 {
@@ -1321,6 +1531,16 @@ static void ln_http_server_send_task(void const *arg)
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN_HTTP_SERVICE_CMD_TIMELINE_GET_STATUS] 查询时间线状态");
             ln_timeline_get_status_http(uapi_ln_get_device_id(), uapi_ln_get_device_token());
         }
+        else if(mail.cmd == LN_HTTP_SERVICE_CMD_WS_ENTER_SLEEP)
+        {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN_HTTP_SERVICE_CMD_WS_ENTER_SLEEP] 进入休眠");
+            ln_ws_enter_sleep();
+        }
+        else if(mail.cmd == LN_HTTP_SERVICE_CMD_WS_EXIT_SLEEP)
+        {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[LN_HTTP_SERVICE_CMD_WS_EXIT_SLEEP] 退出休眠");
+            ln_ws_exit_sleep();
+        }
         if (mail.data != NULL)
         {
             printf("free(mail.data)\n");
index c7c3b9e..36bce24 100644 (file)
@@ -124,6 +124,10 @@ typedef struct
 // 时间线状态相关命令
 #define LN_HTTP_SERVICE_CMD_TIMELINE_GET_STATUS 0x1012 /* 查询时间线状态 */
 
+//websocket相关命令
+#define LN_HTTP_SERVICE_CMD_WS_ENTER_SLEEP 0x1013 /* 进入休眠 */
+#define LN_HTTP_SERVICE_CMD_WS_EXIT_SLEEP 0x1014 /* 退出休眠 */
+
 /* 下载文件相关结构体 */
 typedef struct
 {
@@ -227,6 +231,8 @@ void uapi_ln_set_ota_package_size(int size);
 /*文件下载函数:从URL下载到指定路径*/
 int uapi_ln_download_file(const char *url, const char *save_path);
 int uapi_ln_download_with_retry(const char *url, const char *save_path, int max_retries);
+/* 带断点续传的下载:支持 Range 续传;失败不删除部分文件 */
+int uapi_ln_download_file_resume(const char *url, const char *save_path);
 
 /* ============================== 共享全局状态(跨文件) ============================== */
 extern char *g_device_token;