ota添加断点续传功能 2619/1 2619/head 2620/target/1
authorluozheng <1732036298@qq.com>
Tue, 28 Oct 2025 01:58:17 +0000 (09:58 +0800)
committerluozheng <1732036298@qq.com>
Tue, 28 Oct 2025 01:58:17 +0000 (09:58 +0800)
code/sdk/zhihui/leiniao/imsl_ln/ln_api_ota.c

index ce867d0..eac265b 100644 (file)
@@ -80,7 +80,6 @@ static int ln_ota_is_md5_ok(const char *file_path, const char *expected_md5)
 
 static void ln_ota_handle_download_msg(const OtaDownloadMsg *msg)
 {
-    static int reset_cnt = 0;
     if (!msg) return;
     switch (msg->status) {
         case OTA_DOWNLOAD_STATUS_PROGRESS:
@@ -110,14 +109,8 @@ static void ln_ota_handle_download_msg(const OtaDownloadMsg *msg)
             break;
         case OTA_DOWNLOAD_STATUS_FAILED:
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 下载失败: %s", msg->error_msg);
-            reset_cnt++;
-            if (reset_cnt < 3) {
-                do_ln_http_server_send_data(LN_HTTP_SERVICE_CMD_OTA_DOWNLOAD, NULL, 0);
-            }
-            else {
-                uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
-                push_ota_status(OTA_UI_STATUS_FAILED);
-            }
+            uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
+            push_ota_status(OTA_UI_STATUS_FAILED);
             break;
         default:
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,"[OTA_DOWNLOAD] 未知状态: %d", msg->status);
@@ -374,18 +367,43 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path,
     HttpClient client = {0};
     HttpClientData client_data = {0};
     int ret = -1;
-    size_t total_written = 0; // 已写入文件的总字节数
+    size_t total_written = 0; // 本次会话写入的字节数
     int last_percent = -1;    // 上次上报的进度百分比
 
-    // 打开文件准备写入
-    FILE *fd = fopen(save_path, "wb+");
+    /* 计算断点偏移:已存在部分文件则续传 */
+    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);
+        }
+    }
+    if (resume_offset > 0)
+    {
+        printf("[OTA] resume enabled, offset=%lld\n", resume_offset);
+        if (resume_offset >= (long long)total_size)
+        {
+            printf("[OTA] local file already complete: %lld >= %d\n", resume_offset, total_size);
+            return 0; // 视为已完成
+        }
+    }
+
+    // 打开文件(续传用 ab+,全量用 wb+)
+    FILE *fd = fopen(save_path, resume_offset > 0 ? "ab+" : "wb+");
     if (!fd)
     {
         printf("[OTA] fopen failed for %s:\n", save_path);
         return -1;
     }
 
-    HTTPC_RESULT prep = ln_http_prepare_auto(&client_data, 2048, LN_CLIENT_PREPARE_SIZE * 4);
+    HTTPC_RESULT prep = ln_http_prepare_auto(&client_data, 2048, LN_CLIENT_PREPARE_SIZE * 2);
     if (prep != HTTP_SUCCESS)
     {
         printf("[OTA] range prepare failed: %d\n", prep);
@@ -393,11 +411,34 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path,
         return -1;
     }
 
+    // 当存在偏移时,设置 Range 头
+    char *header_buf = NULL;
+    if (resume_offset > 0)
+    {
+        header_buf = (char *)malloc(128);
+        if (!header_buf)
+        {
+            HttpClientUnprepare(&client_data);
+            fclose(fd);
+            return -1;
+        }
+        int hdr = snprintf(header_buf, 128, "Range: bytes=%lld-\r\n", resume_offset);
+        if (hdr <= 0)
+        {
+            free(header_buf);
+            HttpClientUnprepare(&client_data);
+            fclose(fd);
+            return -1;
+        }
+        HttpClientSetCustomHeader(&client, header_buf);
+    }
+
     // 建立连接(与http_download流程一致)
     ret = HttpClientConn(&client, full_url);
     if (ret != 0)
     {
         printf("[OTA] connection failed, ret=0x%x\n", ret);
+        if (header_buf) free(header_buf);
         fclose(fd);
         HttpClientUnprepare(&client_data);
         return ret;
@@ -408,38 +449,97 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path,
     if (ret != 0)
     {
         printf("[OTA] send request failed, ret=0x%x\n", ret);
+        if (header_buf) free(header_buf);
         HttpClientClse(&client);
         fclose(fd);
         HttpClientUnprepare(&client_data);
         return ret;
     }
 
-    // 循环接收响应数据(参考http_download的do-while循环)
     printf("[OTA] starting to receive data...\n");
-    do
+    // 先接收首包以判定状态码;如服务端未按 Range 返回(200),需覆盖写
+    HTTPC_RESULT first_recv = HttpClientRecvResponse(&client, &client_data);
+    if (!(first_recv == HTTP_SUCCESS || first_recv == HTTP_EAGAIN))
+    {
+        printf("[OTA] first recv failed: %d\n", first_recv);
+        if (header_buf) free(header_buf);
+        HttpClientClse(&client);
+        fclose(fd);
+        HttpClientUnprepare(&client_data);
+        return -1;
+    }
+
+    int code = HttpClientGetResponseCode(&client);
+    printf("[OTA] response code: %d (offset=%lld)\n", 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);
+            return -1;
+        }
+        resume_offset = 0;
+        total_written = 0;
+        last_percent = -1;
+    }
+
+    // 写入首块内容(若有)
+    if (client_data.contentBlockLen > 0)
     {
-        ret = HttpClientRecvResponse(&client, &client_data);
-        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ln_ota_download_flow_http:%d", ret);
-        if (ret == HTTP_SUCCESS || ret == HTTP_EAGAIN)
+        size_t written = fwrite(client_data.responseBuf, 1, client_data.contentBlockLen, fd);
+        if (written != client_data.contentBlockLen)
+        {
+            printf("[OTA] write failed on first block, written=%zu, expected=%d\n", written, client_data.contentBlockLen);
+            if (header_buf) free(header_buf);
+            fclose(fd);
+            HttpClientClse(&client);
+            HttpClientUnprepare(&client_data);
+            return -1;
+        }
+        total_written += written;
+
+        // 进度上报(基于 resume_offset + total_written)
+        long long current_total = resume_offset + (long long)total_written;
+        int percent = (int)((current_total * 100) / total_size);
+        if (percent > 100) percent = 100;
+        if (percent != last_percent)
+        {
+            last_percent = percent;
+            OtaDownloadMsg om = {0};
+            om.status = OTA_DOWNLOAD_STATUS_PROGRESS;
+            om.progress = percent;
+            ln_ota_handle_download_msg(&om);
+        }
+    }
+
+    // 后续接收循环
+    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)
             {
-                // 写入文件(与http_download的写入逻辑一致)
-                size_t written = fwrite(client_data.responseBuf, 1,
-                                        client_data.contentBlockLen, fd);
+                size_t written = fwrite(client_data.responseBuf, 1, client_data.contentBlockLen, fd);
                 if (written != client_data.contentBlockLen)
                 {
-                    printf("[OTA] write file failed, written=%zu, expected=%d\n",
-                           written, client_data.contentBlockLen);
-                    ret = -1;
-                    break;
+                    printf("[OTA] write failed, written=%zu, expected=%d\n", written, client_data.contentBlockLen);
+                    if (header_buf) free(header_buf);
+                    fclose(fd);
+                    HttpClientClse(&client);
+                    HttpClientUnprepare(&client_data);
+                    return -1;
                 }
                 total_written += written;
-                // 进度上报(使用已知的total_size计算)
-                int percent = (int)((total_written * 100) / total_size);
-                if (percent > 100)
-                    percent = 100; // 防止超过100%
+
+                long long current_total = resume_offset + (long long)total_written;
+                int percent = (int)((current_total * 100) / total_size);
+                if (percent > 100) percent = 100;
                 if (percent != last_percent)
                 {
                     last_percent = percent;
@@ -449,56 +549,50 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path,
                     ln_ota_handle_download_msg(&om);
                 }
 
-                // 检查是否超过预期总大小
-                if (total_written > (size_t)total_size)
+                if (current_total > (long long)total_size)
                 {
-                    printf("[OTA] received more than expected! total_written=%zu > total_size=%d\n",
-                           total_written, total_size);
-                    ret = -1;
-                    break;
+                    printf("[OTA] received more than expected! current_total=%lld > total_size=%d\n", current_total, total_size);
+                    if (header_buf) free(header_buf);
+                    fclose(fd);
+                    HttpClientClse(&client);
+                    HttpClientUnprepare(&client_data);
+                    return -1;
                 }
             }
             else
             {
-                // 避免空块忙轮询占用CPU
+                // 避免空块忙轮询
                 LOS_Msleep(50);
             }
         }
         else
         {
-            printf("[OTA] receive failed, ret=0x%x\n", ret);
+            printf("[OTA] recv failed: %d\n", recv_res);
             break;
         }
-    } while (client_data.isMore && total_written < (size_t)total_size); // 双重条件判断
-    // 检查下载结果(参考http_download的结果校验)
-    if (ret == 0 && client.responseCode == 200)
+    }
+
+    int final_code = HttpClientGetResponseCode(&client);
+    long long downloaded = resume_offset + (long long)total_written;
+    printf("[OTA] final code=%d, downloaded=%lld/%d\n", final_code, downloaded, total_size);
+
+    if ((final_code == 200 || final_code == 206) && downloaded == (long long)total_size)
     {
-        // 验证是否下载完整(使用已知的total_size)
-        if (total_written == (size_t)total_size)
-        {
-            printf("[OTA] download complete! total size: %d bytes\n", total_size);
-            ret = 0;
-        }
-        else
-        {
-            printf("[OTA] download incomplete! received=%zu, expected=%d\n",
-                   total_written, total_size);
-            ret = -1;
-        }
+        ret = 0;
+        printf("[OTA] download complete! total size: %d bytes (%s)\n", total_size, resume_offset > 0 ? "续传resume" : "全量full");
     }
     else
     {
-        printf("[OTA] download failed! response code: %d, ret=0x%x\n",
-               client.responseCode, ret);
+        ret = -1;
+        printf("[OTA] download incomplete or failed (code=%d)\n", final_code);
     }
-    printf("[OTA] received=%zu, expected=%d\n",
-                   total_written, total_size);
 
-    // 资源清理(与http_download保持一致)
+    // 资源清理
+    if (header_buf) free(header_buf);
     fclose(fd);
     HttpClientClse(&client);
     HttpClientUnprepare(&client_data);
-    
+
     printf("[OTA] download process finished\n");
 
     return ret;
@@ -550,7 +644,23 @@ uint32_t ln_ota_download_flow_http(const char *device_id,
     int dres;
     if (g_ln_last_package_size > 0)
     {
-        dres = ln_download_file_to_path(fixed_url, save_path, g_ln_last_package_size);
+        // 若文件存在,则删除文件
+        if(access(save_path, F_OK) == 0)
+        {
+            if(remove(save_path) != 0)
+            {
+                printf("[OTA] remove file failed: %s\n", save_path);
+                return ERRCODE_FAIL;
+            }
+        }
+        for(int i = 0; i < 3; i++)
+        {
+            dres = ln_download_file_to_path(fixed_url, save_path, g_ln_last_package_size);
+            if(dres == 0)
+            {
+                break;
+            }
+        }
     }
     if (dres != 0)
     {