| 1234567891011121314151617181920212223 |
- import { readFile, writeFile, rename } from 'node:fs/promises';
- import { join } from 'node:path';
- import { execFile } from 'node:child_process';
- import { promisify } from 'node:util';
- const execute=promisify(execFile);
- export async function macNotify(message) {
- if(process.platform!=='darwin')throw Error('notification_platform_unavailable');
- await execute('/usr/bin/osascript',['-e','on run argv\ndisplay notification (item 1 of argv) with title "小树夜间同步"\nend run',message],{timeout:10000});
- }
- // Only fixed text and aggregate counts are shown; raw errors and credentials never enter notifications.
- export async function syncAlert(report,root,notify=macNotify,now=Date.now()) {
- const path=join(root,'direct-alert-state.json');let prior={};try{prior=JSON.parse(await readFile(path,'utf8'));}catch(e){if(e.code!=='ENOENT')throw e;}
- const healthy=['completed','completed-with-preserved-conflicts','already_completed'].includes(report.status);
- if(!healthy&&report.status!=='blocked')return;
- const count=Number(report.conflicts??report.previous?.conflicts??0),growth=healthy&&Number.isFinite(prior.conflicts)&&count>=prior.conflicts+Math.max(100,Math.ceil(prior.conflicts*.25));
- let kind=!healthy?'failure':prior.failed?'recovered':growth?'conflicts':'';
- const key=kind==='failure'?'failure':kind==='conflicts'?'conflicts:'+count:kind;
- const due=kind&&(prior.lastKey!==key||now-Number(prior.notifiedAt||0)>=24*3600000);
- const next={...prior,failed:!healthy,...(healthy?{conflicts:count,lastSuccessAt:now}:{})};
- if(due){await notify(kind==='failure'?'今晚的数据同步未完成,系统会重试。请查看同步报告并检查登录凭据。':kind==='recovered'?'数据同步已恢复,请查看本次报告。':'同步保留差异明显增加('+count+' 条),请在后台核对。');next.lastKey=key;next.notifiedAt=now;}
- await writeFile(path+'.tmp',JSON.stringify(next),{mode:0o600});await rename(path+'.tmp',path);
- return {notified:Boolean(due),kind};
- }
|