static void HandDownloadSuccessNotify(Request *request);
static void HandDownloadFailNotify(Request *request);
static void HandDownloadProgressNotify(Request *request);
+static void HandDownloadEtaNotify(Request *request);
static void HandAudioPlaybackStartNotify(Request *request);
static void HandAudioPlaybackEndNotify(Request *request);
static void HandAudioRecordingEndNotify(Request *request);
{HAND_DOWNLOAD_SUCCESS_NOTIFY,HandDownloadSuccessNotify},
{HAND_DOWNLOAD_FAIL_NOTIFY,HandDownloadFailNotify},
{HAND_DOWNLOAD_PROGRESS_NOTIFY,HandDownloadProgressNotify},
+ {HAND_DOWNLOAD_ETA_NOTIFY,HandDownloadEtaNotify},
{HAND_AUDIO_PLAYBACK_START_NOTIFY,HandAudioPlaybackStartNotify},
{HAND_AUDIO_PLAYBACK_END_NOTIFY,HandAudioPlaybackEndNotify},
{HAND_AUDIO_RECORDING_END_NOTIFY,HandAudioRecordingEndNotify},
std::bind(&OHOS::OtaModel::HandleDownloadProgress,OHOS::OtaModel::GetInstance(), progress));
}
+// 下载ETA通知(单位秒,-1表示未知)
+void HandDownloadEtaNotify(Request *request)
+{
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_UI_SERVICE, "HandDownloadEtaNotify msg_id = %d", request->msgId);
+ int seconds = *(int *)request->data;
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_UI_SERVICE, "HandDownloadEtaNotify seconds = %d", seconds);
+ GraphicService::GetInstance()->PostGraphicEvent(
+ std::bind(&OHOS::OtaModel::HandleDownloadEta,OHOS::OtaModel::GetInstance(), seconds));
+}
+
// 下载失败通知
void HandDownloadFailNotify(Request *request)
{
#include <errno.h>
#include <dirent.h>
#include "custom_utils.h"
+#include <sys/time.h>
/* 宏定义 */
#define MAX_PATH_LEN 512 // 最大路径长度
/* 全局变量 */
EmojiPackage g_emoji_package = {0};
+/* ========== ETA 粗略版辅助函数 ========== */
+/* 前置声明,便于在本节工具函数中调用 */
+static int ln_emoji_is_cached(const EmojiPackage *pkg, const EmotionEmoji *emoji);
+static void ln_gen_emoji_cache_path(const EmojiPackage *pkg, const EmotionEmoji *emoji, char *path, size_t path_len);
+/* 毫秒时间戳 */
+static inline unsigned long long ln_now_ms(void)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return (unsigned long long)tv.tv_sec * 1000ULL + (unsigned long long)tv.tv_usec / 1000ULL;
+}
+
+/* 文件大小(不存在返回0)*/
+static unsigned long long ln_get_file_size(const char *path)
+{
+ if (!path)
+ return 0;
+ struct stat st;
+ if (stat(path, &st) == 0 && S_ISREG(st.st_mode) && st.st_size > 0)
+ return (unsigned long long)st.st_size;
+ return 0;
+}
+
+/* 计算表情包总字节:优先用服务端 totalSize,否则汇总每个emoji的 size */
+static unsigned long long ln_calc_total_bytes_from_pkg(const EmojiPackage *pkg)
+{
+ if (!pkg)
+ return 0;
+ if (pkg->totalSize > 0)
+ return (unsigned long long)pkg->totalSize;
+ unsigned long long sum = 0;
+ for (int i = 0; i < pkg->emojiCount; ++i)
+ {
+ if (pkg->emojis[i].size > 0)
+ sum += (unsigned long long)pkg->emojis[i].size;
+ }
+ return sum;
+}
+
+/* 计算已缓存完整文件的总字节 */
+static unsigned long long ln_calc_cached_full_bytes(const EmojiPackage *pkg)
+{
+ if (!pkg)
+ return 0;
+ unsigned long long sum = 0;
+ for (int i = 0; i < pkg->emojiCount; ++i)
+ {
+ if (ln_emoji_is_cached(pkg, &pkg->emojis[i]))
+ {
+ char path[MAX_PATH_LEN] = {0};
+ ln_gen_emoji_cache_path(pkg, &pkg->emojis[i], path, sizeof(path));
+ sum += ln_get_file_size(path);
+ }
+ }
+ return sum;
+}
/* 安全字符串复制函数 */
static inline void ln_safe_strcpy(char *dst, size_t dst_size, const char *src)
int downloaded_count = 0; /* 包含已缓存与成功下载的 */
int failed_count = 0;
+ /* 粗略版 ETA 统计(本次会话)*/
+ unsigned long long batch_start_ms = ln_now_ms();
+ unsigned long long total_bytes_all = ln_calc_total_bytes_from_pkg(pkg);
+ unsigned long long cached_full_bytes = 0;
+ unsigned long long session_downloaded_bytes = 0; /* 仅统计本次会话新下载的字节 */
+ /* 速率平滑(EMA)*/
+ double ema_bps = 0.0;
+ int ema_ready = 0;
+ const double ETA_WARMUP_SECONDS = 5.0; /* 预热期内不推ETA或推未知,避免初期抖动 */
+
/* 若有总大小信息,则进行磁盘空间预检查(考虑已缓存文件大小) */
if (pkg->totalSize > 0)
{
if (ln_emoji_is_cached(pkg, &pkg->emojis[ci]))
{
cached_count++;
+ /* 统计完整缓存字节,用于 ETA 剩余估算 */
+ if (total_bytes_all > 0)
+ {
+ char path_bytes[MAX_PATH_LEN] = {0};
+ ln_gen_emoji_cache_path(pkg, &pkg->emojis[ci], path_bytes, sizeof(path_bytes));
+ cached_full_bytes += ln_get_file_size(path_bytes);
+ }
}
}
downloaded_count = cached_count;
// 下载并缓存(支持断点续传:当目标文件存在且小于应有大小时,从断点续传)
char save_path[MAX_PATH_LEN] = {0};
ln_gen_emoji_cache_path(pkg, &pkg->emojis[i], save_path, sizeof(save_path));
+ unsigned long long pre_size = ln_get_file_size(save_path); /* 记录下载前已有的部分大小(可能为0)*/
+ unsigned long long file_start_ms = ln_now_ms();
/* 确保保存目录存在 */
{
char dir_buf[MAX_PATH_LEN] = {0};
int progress = (int)(((double)downloaded_count / pkg->emojiCount) * 100);
push_download_progress_notification(progress);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] emoji %s 下载完成", pkg->emojis[i].emotion);
+
+ /* 粗略版 ETA(平滑版):在单个文件完成后估算一次 */
+ if (total_bytes_all > 0)
+ {
+ unsigned long long post_size = ln_get_file_size(save_path);
+ unsigned long long inc = 0;
+ if (post_size > pre_size)
+ inc = post_size - pre_size;
+ else if ((unsigned long long)pkg->emojis[i].size > 0)
+ inc = (unsigned long long)pkg->emojis[i].size;
+ else
+ inc = post_size; /* 兜底 */
+
+ session_downloaded_bytes += inc;
+
+ unsigned long long now_ms = ln_now_ms();
+ double elapsed_s = (now_ms > batch_start_ms) ? ((double)(now_ms - batch_start_ms) / 1000.0) : 0.0;
+ double avg_bps = (elapsed_s > 0.0) ? ((double)session_downloaded_bytes / elapsed_s) : 0.0;
+
+ /* 单文件瞬时速率,用于更新EMA */
+ double file_elapsed_s = (now_ms > file_start_ms) ? ((double)(now_ms - file_start_ms) / 1000.0) : 0.0;
+ if (file_elapsed_s > 0.0 && inc > 0)
+ {
+ double inst_bps = (double)inc / file_elapsed_s;
+ if (!ema_ready)
+ {
+ ema_bps = inst_bps;
+ ema_ready = 1;
+ }
+ else
+ {
+ const double alpha = 0.3; /* 平滑系数:越大越敏感 */
+ ema_bps = alpha * inst_bps + (1.0 - alpha) * ema_bps;
+ }
+ }
+
+ /* 选择用于ETA的速率:优先EMA,其次全局平均 */
+ double used_bps = ema_ready ? ema_bps : avg_bps;
+ /* 轻度夹紧,避免异常跳变(以全局平均为中心)*/
+ if (avg_bps > 0.0 && used_bps > 0.0)
+ {
+ double lower = avg_bps * 0.5;
+ double upper = avg_bps * 2.0;
+ if (used_bps < lower) used_bps = lower;
+ if (used_bps > upper) used_bps = upper;
+ }
+
+ unsigned long long consumed = cached_full_bytes + session_downloaded_bytes;
+ unsigned long long remain_bytes = (total_bytes_all > consumed) ? (total_bytes_all - consumed) : 0ULL;
+ int eta_sec = -1;
+ /* 预热期内(或样本太少)不上报具体ETA,之后用平滑速率估算 */
+ if (elapsed_s >= ETA_WARMUP_SECONDS || downloaded_count > 1)
+ {
+ if (used_bps > 1.0)
+ eta_sec = (int)(remain_bytes / used_bps);
+ }
+
+ if (eta_sec >= 0)
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
+ "[DOWNLOAD][ETA] progress=%d%%, remain_bytes=%llu, avg=%.1f KB/s, eta=%ds",
+ progress,
+ (unsigned long long)remain_bytes,
+ used_bps / 1024.0,
+ eta_sec);
+ push_download_eta_notification(eta_sec);
+ }
+ else
+ {
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
+ "[DOWNLOAD][ETA] progress=%d%%, remain_bytes=%llu, avg=%.1f KB/s, eta=未知",
+ progress,
+ (unsigned long long)remain_bytes,
+ (avg_bps > 0.0 ? avg_bps : used_bps) / 1024.0);
+ push_download_eta_notification(-1);
+ }
+ }
}
}