}
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)
{
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");