表情下载,屏保下载,日程提醒音频下载支持断点续传,减少下载时间
authorluozheng <1732036298@qq.com>
Mon, 20 Oct 2025 08:31:47 +0000 (16:31 +0800)
committerluozheng <1732036298@qq.com>
Mon, 20 Oct 2025 08:31:47 +0000 (16:31 +0800)
code/sdk/zhihui/leiniao/imsl_ln/ln_api.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api.h
code/sdk/zhihui/leiniao/imsl_ln/ln_api_device_info.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api_emoji.c
code/sdk/zhihui/leiniao/imsl_ln/ln_api_schedule.c

index 013cc24..7ccd43b 100644 (file)
@@ -404,7 +404,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
     WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 开始下载: %s -> %s", url, save_path);
     g_ln_is_file_downloading = 1;
 
-    uapi_pm_add_sleep_veto(PM_ID_DEBUG);
 
     HttpClient client = {0};
     HttpClientData client_data = {0};
@@ -416,7 +415,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
     {
         WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] fopen failed for %s", save_path);
         g_ln_is_file_downloading = 0;
-        uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
         return ERRCODE_FAIL;
     }
 
@@ -426,7 +424,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
         WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] prepare failed: %d", prep);
         fclose(fd);
         g_ln_is_file_downloading = 0;
-        uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
         return ERRCODE_FAIL;
     }
 
@@ -438,7 +435,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
         fclose(fd);
         HttpClientUnprepare(&client_data);
         g_ln_is_file_downloading = 0;
-        uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
         return ERRCODE_FAIL;
     }
 
@@ -451,7 +447,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
         HttpClientClse(&client);
         HttpClientUnprepare(&client_data);
         g_ln_is_file_downloading = 0;
-        uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
         return ERRCODE_FAIL;
     }
 
@@ -473,7 +468,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
                     fclose(fd);
                     HttpClientClse(&client);
                     HttpClientUnprepare(&client_data);
-                    uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
                     return ERRCODE_FAIL;
                 }
                 total_written += written;
@@ -510,7 +504,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
         {
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "删除不完整文件成功\r\n");
         }
-        uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
     }
 
     HttpClientUnprepare(&client_data);
@@ -518,7 +511,6 @@ int uapi_ln_download_file(const char *url, const char *save_path)
     fclose(fd);
     g_ln_is_file_downloading = 0;
     WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 下载过程结束,ret=%d", ret);
-    uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
     return ret;
 }
 
@@ -531,6 +523,7 @@ int uapi_ln_download_with_retry(const char *url, const char *save_path, int max_
         return ERRCODE_FAIL;
     }
     WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 开始下载: %s -> %s", url, save_path);
+    uapi_pm_add_sleep_veto(PM_ID_DEBUG);
     int ret = uapi_ln_download_file(url, save_path);
     if (ERRCODE_SUCC != ret)
     {
@@ -545,6 +538,7 @@ int uapi_ln_download_with_retry(const char *url, const char *save_path, int max_
             }
         }
     }
+    uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
     return ret;
 }
 
@@ -561,13 +555,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
         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;
     /* 判断是否存在部分文件,计算续传偏移 */
     {
@@ -589,12 +576,16 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
     int ret = -1;
     size_t total_written = 0;
 
+    if(resume_offset > 0)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] 断点续传,resume_offset=%lld", resume_offset);
+    }
+    
     /* 以追加模式打开(若服务端返回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;
     }
 
@@ -603,7 +594,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
     {
         WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] prepare failed: %d", prep);
         fclose(fd);
-        g_ln_is_file_downloading = 0;
         return ERRCODE_FAIL;
     }
 
@@ -616,7 +606,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
         {
             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);
@@ -625,7 +614,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
             free(header_buf);
             HttpClientUnprepare(&client_data);
             fclose(fd);
-            g_ln_is_file_downloading = 0;
             return ERRCODE_FAIL;
         }
         HttpClientSetCustomHeader(&client, header_buf);
@@ -640,7 +628,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
         if (header_buf) free(header_buf);
         fclose(fd);
         HttpClientUnprepare(&client_data);
-        g_ln_is_file_downloading = 0;
         return ERRCODE_FAIL;
     }
 
@@ -652,7 +639,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
         fclose(fd);
         HttpClientClse(&client);
         HttpClientUnprepare(&client_data);
-        g_ln_is_file_downloading = 0;
         return ERRCODE_FAIL;
     }
 
@@ -666,7 +652,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
             fclose(fd);
             HttpClientClse(&client);
             HttpClientUnprepare(&client_data);
-            g_ln_is_file_downloading = 0;
             return ERRCODE_FAIL;
         }
 
@@ -682,7 +667,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
                 if (header_buf) free(header_buf);
                 HttpClientClse(&client);
                 HttpClientUnprepare(&client_data);
-                g_ln_is_file_downloading = 0;
                 return ERRCODE_FAIL;
             }
             resume_offset = 0;
@@ -700,7 +684,6 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
                 fclose(fd);
                 HttpClientClse(&client);
                 HttpClientUnprepare(&client_data);
-                g_ln_is_file_downloading = 0;
                 return ERRCODE_FAIL;
             }
             total_written += written;
@@ -724,10 +707,10 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
                     fclose(fd);
                     HttpClientClse(&client);
                     HttpClientUnprepare(&client_data);
-                    g_ln_is_file_downloading = 0;
                     return ERRCODE_FAIL;
                 }
                 total_written += written;
+                WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] write first block success, written=%zu", total_written);
             }
         }
         else
@@ -754,7 +737,44 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path)
     HttpClientUnprepare(&client_data);
     HttpClientClse(&client);
     fclose(fd);
+    return ret;
+}
+/* 断点续传下载函数封装:带重试,不做UI通知与资源释放 */
+int uapi_ln_download_file_resume_with_retry(const char *url, const char *save_path, int max_retries)
+{
+    if (url == NULL || save_path == NULL || (max_retries <= 0 || max_retries > 10 ))
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 参数无效");
+        return ERRCODE_FAIL;
+    }
+    WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 开始下载: %s -> %s", url, save_path);
+    if (uapi_ln_is_file_downloading() == 1)
+    {
+        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] 已有文件正在下载中");
+        return ERRCODE_FAIL;
+    }
+    g_ln_is_file_downloading = 1;
+
+    //添加睡眠投票
+    uapi_pm_add_sleep_veto(PM_ID_DEBUG);
+
+    int ret = uapi_ln_download_file_resume(url, save_path);
+    if (ERRCODE_SUCC != ret)
+    {
+        for (int i = 1; i <= max_retries; i++)
+        {
+            WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 重试 %d/%d", i, max_retries);
+            ret = uapi_ln_download_file_resume(url, save_path);
+            if (ERRCODE_SUCC == ret)
+            {
+                WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME_WITH_RETRY] 重试成功 %d", i);
+                break;
+            }
+        }
+    }
     g_ln_is_file_downloading = 0;
+    //移除睡眠投票
+    uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
     return ret;
 }
 /* 自适应申请HttpClient缓冲(保证header>=2048),失败则逐步扩容重试 */
index 5df754c..a3285c1 100644 (file)
@@ -234,6 +234,7 @@ 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);
+int uapi_ln_download_file_resume_with_retry(const char *url, const char *save_path, int max_retries);
 /* 设置ws音频播放状态 */
 void uapi_ln_set_audio_play_status(int status);
 /* 获取ws音频播放状态 */
index a5ff2c8..2c31058 100644 (file)
@@ -5,6 +5,7 @@
 #include <unistd.h>
 #include <cJSON.h>
 #include "wearable_log.h"
+#include "custom_utils.h"
 
 LnCharacterConfig g_ln_character_config = {0};
 
@@ -122,9 +123,11 @@ void ln_download_screen_image(void)
     {
         const char *url = g_ln_pending_screen_image_url;
         const char *save_path = LN_DEVICE_SCREEN_IMAGE_FILE;
-        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载URL: %s, 保存路径: %s", url, save_path);
-        errcode_t ret = uapi_ln_download_with_retry(url, save_path, 3);
-        if (ret == ERRCODE_SUCC)
+        //如果本地有文件先删除文件
+        uapi_delete_files(LN_DEVICE_SCREEN_IMAGE_FILE);
+        WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载URL: %s, 保存路径: %s", url, save_path);
+        int ret = uapi_ln_download_file_resume_with_retry(url, save_path, 5);
+        if (ret == 0)
         {
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DEVICE_CHARACTER] 屏保下载成功,通知去切换屏保");
             push_download_success_notification(DOWNLOAD_FILE_TYPE_SCREEN_IMAGE);
index c527355..bf9763b 100644 (file)
@@ -16,8 +16,9 @@
 /* 表情信息结构体定义 */
 typedef struct
 {
-    char *emotion;   // 心情类型(动态分配)
-    char *emojiUrl;  // 表情文件URL(动态分配)
+    char *emotion;     // 心情类型(动态分配)
+    char *emojiUrl;    // 表情文件URL(动态分配)
+    long long size;    // 表情文件大小(字节,支持断点续传判断;0 表示未知)
 } EmotionEmoji;
 
 /* 表情包配置结构体 */
@@ -421,7 +422,7 @@ static void ln_emoji_download(void *arg)
             continue;
         }
 
-        // 下载并缓存
+        // 下载并缓存(支持断点续传:当目标文件存在且小于应有大小时,从断点续传)
         char save_path[MAX_PATH_LEN] = {0};
         ln_gen_emoji_cache_path(pkg, &pkg->emojis[i], save_path, sizeof(save_path));
         /* 确保保存目录存在 */
@@ -438,7 +439,7 @@ static void ln_emoji_download(void *arg)
                 uapi_create_folder(dir_buf);
             }
         }
-        if (uapi_ln_download_with_retry(pkg->emojis[i].emojiUrl, save_path, 3) != ERRCODE_SUCC)
+        if (uapi_ln_download_file_resume_with_retry(pkg->emojis[i].emojiUrl, save_path, 3) != ERRCODE_SUCC)
         {
             all_success = 0;
             failed_count++;
@@ -615,9 +616,29 @@ void ln_parse_download_emoji(const char *json_buf, int json_len)
                     {
                         cJSON *emotion = cJSON_GetObjectItem(item, "emotion");
                         cJSON *emojiUrl = cJSON_GetObjectItem(item, "emojiUrl");
+                        cJSON *size_node = cJSON_GetObjectItem(item, "size");
 
                         g_emoji_package.emojis[i].emotion = ln_safe_strdup((emotion && cJSON_IsString(emotion)) ? emotion->valuestring : "");
                         g_emoji_package.emojis[i].emojiUrl = ln_safe_strdup((emojiUrl && cJSON_IsString(emojiUrl)) ? emojiUrl->valuestring : "");
+                        /* 解析单个表情大小(字节) */
+                        long long emoji_size = 0;
+                        if (size_node)
+                        {
+                            if (cJSON_IsNumber(size_node))
+                            {
+                                emoji_size = (long long)size_node->valuedouble;
+                            }
+                            else if (cJSON_IsString(size_node) && size_node->valuestring)
+                            {
+                                emoji_size = atoll(size_node->valuestring);
+                            }
+                        }
+                        g_emoji_package.emojis[i].size = (emoji_size > 0) ? emoji_size : 0;
+                        WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "  emoji %d: emotion=%s, url=%s, size=%lld",
+                                      i + 1,
+                                      g_emoji_package.emojis[i].emotion,
+                                      g_emoji_package.emojis[i].emojiUrl,
+                                      g_emoji_package.emojis[i].size);
                     }
                 }
             }
index ca1ea1b..9b79fde 100644 (file)
@@ -205,7 +205,8 @@ void ln_parse_schedule_reminder(const char *json_buf, int json_len)
     {
         // 目前是立即提醒的所以直接下载并播放
         WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[JSON_PARSE] download audio:执行下载并播放\n");
-        int ret = uapi_ln_download_with_retry(g_schedule_reminder.reminderAudioUrl, LN_REMINDER_AUDIO_FILE, 2);
+        uapi_delete_files(LN_REMINDER_AUDIO_FILE);
+        int ret = uapi_ln_download_file_resume_with_retry(g_schedule_reminder.reminderAudioUrl, LN_REMINDER_AUDIO_FILE, 3);
         if (ret == ERRCODE_SUCC)
         {
             WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[JSON_PARSE] download audio:下载成功\n");