feat(sync): wire SyncService — tray '지금 동기화' + on-quit drain

- src/main/index.ts: SyncService instantiate with paths.profileDir + exportSvc
- 트레이 6번째 콜백 — 토스트로 not_configured / done / unchanged / failed 안내
- before-quit 훅에 sync drain 추가 (backup 완료 후, syncSvc.isConfigured() 인 경우만)
- src/main/tray.ts: 6번째 callback runSync, '지금 동기화' 메뉴 (내보내기/복원 다음)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-04-26 11:40:50 +09:00
parent eaf66e6c10
commit 66725dacae
2 changed files with 43 additions and 4 deletions

View File

@@ -27,6 +27,7 @@ import { MediaGc } from './services/MediaGc.js';
import { BackupService } from './services/BackupService.js';
import { ExportService } from './services/ExportService.js';
import { ImportService } from './services/ImportService.js';
import { SyncService } from './services/SyncService.js';
const HIDDEN_ARG = '--hidden';
const startedHidden = process.argv.includes(HIDDEN_ARG);
@@ -106,6 +107,7 @@ app.whenReady().then(async () => {
const exportSvc = new ExportService(repo, store);
const importSvc = new ImportService(repo, store);
const syncSvc = new SyncService(paths.profileDir, exportSvc);
const backup = new BackupService(db, join(paths.profileDir, 'backups'));
void backup.runDaily()
@@ -119,6 +121,17 @@ app.whenReady().then(async () => {
backup.runDaily()
.then((r) => logger.info('backup.beforeQuit', { ...r } as Record<string, unknown>))
.catch((e2) => logger.warn('backup.beforeQuit.failed', { reason: String(e2) }))
.then(() => syncSvc.isConfigured().then((cfg) => {
if (!cfg) return;
return syncSvc.sync()
.then((r) => logger.info('sync.beforeQuit', {
ok: r.ok,
changed: r.changed ?? false,
pushed: r.pushed ?? false,
reason: r.reason
} as Record<string, unknown>))
.catch((e3) => logger.warn('sync.beforeQuit.failed', { reason: String(e3) }));
}))
.finally(() => {
backupOnQuitDone = true;
app.isQuitting = true;
@@ -243,6 +256,29 @@ app.whenReady().then(async () => {
silent: true
}).show();
}
},
async () => {
// runSync — 트레이 "지금 동기화"
try {
const r = await syncSvc.sync();
if (!r.ok) {
logger.warn('sync.failed', { reason: r.reason });
const body = r.reason === 'not_configured'
? `${syncSvc.getSyncDir()} 에서 git init + remote 설정이 필요합니다.`
: '동기화를 완료하지 못했습니다.';
new Notification({ title: 'Inkling', body, silent: true }).show();
return;
}
if (r.changed) {
logger.info('sync.done', { sha: r.sha, pushed: r.pushed });
new Notification({ title: 'Inkling', body: '동기화 완료', silent: true }).show();
} else {
new Notification({ title: 'Inkling', body: '변경 사항 없음', silent: true }).show();
}
} catch (e) {
logger.warn('sync.exception', { reason: String(e) });
new Notification({ title: 'Inkling', body: '동기화를 완료하지 못했습니다.', silent: true }).show();
}
}
);

View File

@@ -9,7 +9,8 @@ function buildMenu(
showCapture: () => void,
runBackup: () => void,
runExport: () => void,
runImport: () => void
runImport: () => void,
runSync: () => void
) {
const items: MenuItemConstructorOptions[] = [
{ label: '보관한 메모 보기', click: showInbox },
@@ -17,7 +18,8 @@ function buildMenu(
{ type: 'separator' },
{ label: '지금 백업', click: runBackup },
{ label: '내보내기...', click: runExport },
{ label: '백업에서 복원...', click: runImport }
{ label: '백업에서 복원...', click: runImport },
{ label: '지금 동기화', click: runSync }
];
if (app.isPackaged) {
const { openAtLogin } = app.getLoginItemSettings();
@@ -45,12 +47,13 @@ export function createTray(
showCapture: () => void,
runBackup: () => void,
runExport: () => void,
runImport: () => void
runImport: () => void,
runSync: () => void
): TrayType {
const icon = nativeImage.createEmpty();
tray = new Tray(icon);
tray.setToolTip('Inkling');
tray.setContextMenu(buildMenu(showInbox, showCapture, runBackup, runExport, runImport));
tray.setContextMenu(buildMenu(showInbox, showCapture, runBackup, runExport, runImport, runSync));
tray.on('click', showInbox);
return tray;
}