From 61439247aca8cd87273dae2266cbb2c606759743 Mon Sep 17 00:00:00 2001 From: luozheng <1732036298@qq.com> Date: Tue, 14 Oct 2025 18:07:21 +0800 Subject: [PATCH] =?utf8?q?1.=E6=B7=BB=E5=8A=A0=E6=96=AD=E7=82=B9=E7=BB=AD?= =?utf8?q?=E4=BC=A0=E5=87=BD=E6=95=B0=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=BE=85?= =?utf8?q?=E6=B5=8B=E8=AF=95=202.=E6=B7=BB=E5=8A=A0websocket=E8=BF=9B?= =?utf8?q?=E5=85=A5=E4=BC=91=E7=9C=A0=E5=92=8C=E9=80=80=E5=87=BA=E4=BC=91?= =?utf8?q?=E7=9C=A0=E6=B6=88=E6=81=AF=E4=BE=9B=E4=B8=8A=E5=B1=82=E8=B0=83?= =?utf8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- code/sdk/zhihui/leiniao/imsl_ln/ln_api.c | 220 +++++++++++++++++++++++ code/sdk/zhihui/leiniao/imsl_ln/ln_api.h | 6 + 2 files changed, 226 insertions(+) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c index ccf3d1d45..c8ec5a6c1 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c @@ -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"); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index c7c3b9e62..36bce2407 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -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; -- 2.17.1