From 66725dacaec3c1f54445066402ccbc03d7dc0d15 Mon Sep 17 00:00:00 2001 From: altair823 Date: Sun, 26 Apr 2026 11:40:50 +0900 Subject: [PATCH] =?UTF-8?q?feat(sync):=20wire=20SyncService=20=E2=80=94=20?= =?UTF-8?q?tray=20'=EC=A7=80=EA=B8=88=20=EB=8F=99=EA=B8=B0=ED=99=94'=20+?= =?UTF-8?q?=20on-quit=20drain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- src/main/index.ts | 36 ++++++++++++++++++++++++++++++++++++ src/main/tray.ts | 11 +++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 5e04a1a..49f84e9 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -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)) .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)) + .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(); + } } ); diff --git a/src/main/tray.ts b/src/main/tray.ts index 161e7cf..fa3b333 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -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; }