feat(backup): wire BackupService — whenReady + before-quit + tray

Instantiate BackupService at app.whenReady, run daily snapshot then
again before quit (synchronous-blocking via preventDefault). Tray menu
gets '지금 백업' entry that triggers manual runDaily with native
toast feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-04-26 03:10:49 +09:00
parent 4898e13308
commit 06817f2b0b
2 changed files with 59 additions and 11 deletions

View File

@@ -24,6 +24,7 @@ import {
} from './windows/quickCaptureWindow.js';
import { createTray } from './tray.js';
import { MediaGc } from './services/MediaGc.js';
import { BackupService } from './services/BackupService.js';
const HIDDEN_ARG = '--hidden';
const startedHidden = process.argv.includes(HIDDEN_ARG);
@@ -96,19 +97,55 @@ app.whenReady().then(async () => {
createInboxWindow();
}
createQuickCaptureWindow();
createTray(
() => createInboxWindow(),
() => showQuickCapture()
);
await worker.loadFromDb();
const gc = new MediaGc(db, store);
void gc.run().then((r) => logger.info('media.gc', { ...r } as Record<string, unknown>));
const backup = new BackupService(db, join(paths.profileDir, 'backups'));
void backup.runDaily()
.then((r) => logger.info('backup.daily', { ...r } as Record<string, unknown>))
.catch((e) => logger.warn('backup.daily.failed', { reason: String(e) }));
let backupOnQuitDone = false;
app.on('before-quit', (e) => {
if (backupOnQuitDone) return;
e.preventDefault();
backup.runDaily()
.then((r) => logger.info('backup.beforeQuit', { ...r } as Record<string, unknown>))
.catch((e2) => logger.warn('backup.beforeQuit.failed', { reason: String(e2) }))
.finally(() => {
backupOnQuitDone = true;
app.isQuitting = true;
app.quit();
});
});
createTray(
() => createInboxWindow(),
() => showQuickCapture(),
async () => {
try {
const r = await backup.runDaily();
new Notification({
title: 'Inkling',
body: r.snapshotted
? `백업 완료 — ${r.removed?.length ?? 0}개 정리`
: `오늘 백업이 이미 있습니다`,
silent: true
}).show();
} catch (e) {
logger.warn('backup.manual.failed', { reason: String(e) });
new Notification({
title: 'Inkling',
body: '백업을 만들지 못했습니다.',
silent: true
}).show();
}
}
);
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createInboxWindow();
});
});
app.on('before-quit', () => { app.isQuitting = true; });

View File

@@ -4,11 +4,16 @@ const { app, Tray, Menu, nativeImage } = electron;
let tray: TrayType | null = null;
function buildMenu(showInbox: () => void, showCapture: () => void) {
function buildMenu(
showInbox: () => void,
showCapture: () => void,
runBackup: () => void
) {
const items: MenuItemConstructorOptions[] = [
{ label: '구출한 메모 보기', click: showInbox },
{ label: '기억 구출하기', click: showCapture },
{ type: 'separator' }
{ type: 'separator' },
{ label: '지금 백업', click: runBackup }
];
if (app.isPackaged) {
const { openAtLogin } = app.getLoginItemSettings();
@@ -24,16 +29,22 @@ function buildMenu(showInbox: () => void, showCapture: () => void) {
}
});
items.push({ type: 'separator' });
} else {
items.push({ type: 'separator' });
}
items.push({ label: '종료', click: () => { app.isQuitting = true; app.quit(); } });
return Menu.buildFromTemplate(items);
}
export function createTray(showInbox: () => void, showCapture: () => void): TrayType {
export function createTray(
showInbox: () => void,
showCapture: () => void,
runBackup: () => void
): TrayType {
const icon = nativeImage.createEmpty();
tray = new Tray(icon);
tray.setToolTip('Inkling');
tray.setContextMenu(buildMenu(showInbox, showCapture));
tray.setContextMenu(buildMenu(showInbox, showCapture, runBackup));
tray.on('click', showInbox);
return tray;
}