Files
FamilyBook_harmonyos/entry/src/main/ets/data/FbSyncAll.ets
T

176 lines
6.2 KiB
Plaintext
Raw Normal View History

/**
* 家书 FamilyBook · 全量同步(鸿蒙端)
*
* 一次把所有模块都同步一遍。
*
* 🔴 为什么需要它(用户 2026-10-12:"把同步做成全局的,接进来一个模块,
* 就自动同步一个模块"):
* 之前每个页面**自己同步自己**(进入观影页同步观影、进名册页同步名册),
* 结果是"用户必须把每个页面都点一遍才拿到全量数据",
* 而且设置页那个「保存并同步」只同步了观影 —— 一个入口只干一部分活,
* 用户没法理解。
* ⇒ 加这个"全量同步":**一处触发、全部模块都更新**。
*
* ⚠️ 它与"页面各自同步"**不冲突**,两者都要有:
* · 页面各自同步:保证"刚进这一页就有最新数据";
* · 全量同步:保证"不用点遍每一页"。
* 两条路的终点是同一个缓存(`module_cache` / 实体表),所以谁先谁后都对。
*/
import { hilog } from '@kit.PerformanceAnalysisKit';
import { FbMovieRepo, FbSyncResult } from './FbMovieRepo';
import { FbPersonRepo, FbRosterSyncResult } from './FbPersonRepo';
import { FbModuleLoad, FbModuleRepo } from './FbModuleRepo';
import { FB_MODULE_PATH } from './FbModuleLoad';
import { FB_TOKEN_STORE } from './FbTokenStore';
const DOMAIN: number = 0x0000;
const TAG: string = 'FbSyncAll';
/** 全量同步结果 */
export interface FbSyncAllResult {
ok: boolean;
/** 成功同步的模块数 */
done: number;
/** 总模块数 */
total: number;
/** 失败模块名(用户看得到的人话) */
failed: string[];
/** 当前进度文案(同步中实时更新用) */
note: string;
}
/**
* 全量同步的实时进度(供 UI 显示)。
*
* ⚠️ 为什么用一个**模块级变量**而不是回调:
* 本机设备 `hilog` 读不出内容(已知限制),
* 同步又可能跑十几秒 —— 页面必须能显示"现在到哪一步了",
* 否则用户以为卡死。用轮询读这个变量的方式最省事(见设置页)。
*/
export let FB_SYNC_ALL_PROGRESS: string = '';
/** 模块的中文名(进度提示用) */
const MODULE_CN: Record<string, string> = {
'movies': '观影',
'roster': '名册',
'calendar': '日程',
'tasks': '任务',
'days': '纪念日',
'trips': '旅程',
'ledger': '账本',
'resources': '资源',
'books': '藏书',
'notes': '笔记',
'notifications': '通知',
'home': '首页',
};
/**
* 进度回调(可选)。
*
* 🔴 为什么加这个参数,而不是让调用方去轮询 `FB_SYNC_ALL_PROGRESS`:
* 模块级 `export let` 的"实时值"依赖打包器是否保留 live binding ——
* 这在 esmodule / 混淆 / tree-shaking 下**不保证**,一旦被内联成常量,
* 进度条就永远停在初始值(而调用方还以为在正常工作)。
* ⇒ 登录页这种"用户正盯着屏幕等"的场景,进度必须**从函数里推出来**。
*
* @param label 正在同步的模块中文名(如「观影」)
* @param done 已完成的模块数(**不含**当前这个)
* @param total 模块总数
*/
export type FbSyncStepCb = (label: string, done: number, total: number) => void;
export async function syncAll(onStep?: FbSyncStepCb): Promise<FbSyncAllResult> {
const token: string = FB_TOKEN_STORE.get();
const failed: string[] = [];
if (token === '') {
const r: FbSyncAllResult = {
ok: false, done: 0, total: 12, failed: [],
note: '未登录,无法同步',
};
FB_SYNC_ALL_PROGRESS = r.note;
return r;
}
let done: number = 0;
const total: number = 12;
/** 跑一个模块并记账 */
const step = async (label: string, work: () => Promise<boolean>): Promise<void> => {
FB_SYNC_ALL_PROGRESS = label;
if (onStep !== undefined) {
onStep(label, done, total);
}
try {
const ok: boolean = await work();
if (ok) {
done += 1;
} else {
failed.push(label);
}
} catch (err) {
hilog.warn(DOMAIN, TAG, '同步 %{public}s 失败:%{public}s', label, JSON.stringify(err));
failed.push(label);
}
};
// ---- ① 有实体表的两个模块(先做:它们的数据量大、最该先落地)----
await step(MODULE_CN['movies'], async (): Promise<boolean> => {
const r: FbSyncResult = await FbMovieRepo.inst().sync(token);
return r.ok;
});
await step(MODULE_CN['roster'], async (): Promise<boolean> => {
const r: FbRosterSyncResult = await FbPersonRepo.inst().sync(token);
return r.ok;
});
// ---- ② 其余模块(走通用缓存)----
const cacheModules: string[] = [
'calendar', 'tasks', 'days', 'trips', 'ledger', 'resources', 'books', 'notes',
'notifications', 'home',
];
const repo: FbModuleRepo = FbModuleRepo.inst();
for (const name of cacheModules) {
const label: string = MODULE_CN[name] !== undefined ? MODULE_CN[name] : name;
const path: string = FB_MODULE_PATH[name] !== undefined ? FB_MODULE_PATH[name] : '';
if (path === '') {
continue;
}
await step(label, async (): Promise<boolean> => {
// 这里只关心"有没有成功写进缓存":`load` 的 sink 回调是同步的,
// 用一个 Promise 把它包起来,等到网络那次回来(fromCache=false)才算成功。
const ok: boolean = await new Promise<boolean>((resolve: (v: boolean) => void): void => {
let settled: boolean = false;
repo.load(name, path, (r: FbModuleLoad): void => {
if (r.fromCache || settled) {
return; // 缓存那次不算结果
}
settled = true;
resolve(r.error === '');
});
// 兜底超时(网络卡住时不能让全量同步永远挂着)
setTimeout((): void => {
if (!settled) {
settled = true;
resolve(false);
}
}, 20000);
});
return ok;
});
}
const result: FbSyncAllResult = {
ok: failed.length === 0,
done: done,
total: total,
failed: failed,
note: failed.length === 0
? ('全部 ' + done.toString() + ' 个模块同步完成')
: (done.toString() + '/' + total.toString() + ' 完成,失败:' + failed.join('、')),
};
FB_SYNC_ALL_PROGRESS = result.note;
return result;
}