mirror of
https://github.com/jung-geun/NFD2NFC.git
synced 2026-06-21 12:25:14 +09:00
src/main: 트레이/감시자/저장소/IPC/알림 배치 (Electron 메인)
tray.ts: 트레이 아이콘, 팝오버 BrowserWindow 토글, 컨텍스트 메뉴. watcher.ts: chokidar 감시, dedup TTL 2s, auto/manual 모드, 글로벌 일시정지. store.ts: userData/store.json 영속화 (watchedDirs/settings/undoLog). ipc.ts: 18개 IPC 채널 핸들러. notifier.ts: interval 기반 알림 배치 처리 (알림 폭주 방지). settings-window.ts: 설정창 BrowserWindow 라이프사이클. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
126
src/main/ipc.ts
Normal file
126
src/main/ipc.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { ipcMain, dialog, app, BrowserWindow } from 'electron';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { scan } from '../core/scanner';
|
||||
import { normalizeEntry } from '../core/normalizer';
|
||||
import { nanoid } from './nanoid';
|
||||
import * as store from './store';
|
||||
import * as watcher from './watcher';
|
||||
import type { WatchedDir, UndoEntry } from '../core/types';
|
||||
|
||||
export function registerIpcHandlers(): void {
|
||||
// ── 디렉토리 선택 ──
|
||||
ipcMain.handle('dialog:selectDirectory', async (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
const result = await dialog.showOpenDialog(win!, { properties: ['openDirectory'] });
|
||||
return result.canceled ? null : result.filePaths[0];
|
||||
});
|
||||
|
||||
// ── 디렉토리 CRUD ──
|
||||
ipcMain.handle('dirs:list', async () => store.getDirs());
|
||||
|
||||
ipcMain.handle('dirs:add', async (_e, dirPath: string) => {
|
||||
const settings = await store.getSettings();
|
||||
const dir: WatchedDir = {
|
||||
id: randomUUID(),
|
||||
path: dirPath,
|
||||
recursive: false,
|
||||
mode: settings.defaultMode,
|
||||
enabled: true,
|
||||
customRanges: [],
|
||||
};
|
||||
await store.addDir(dir);
|
||||
await watcher.startDir(dir);
|
||||
return dir;
|
||||
});
|
||||
|
||||
ipcMain.handle('dirs:update', async (_e, id: string, patch: Partial<WatchedDir>) => {
|
||||
await store.updateDir(id, patch);
|
||||
// 모드/recursive 변경 시 재시작
|
||||
if ('mode' in patch || 'recursive' in patch || 'enabled' in patch || 'customRanges' in patch) {
|
||||
await watcher.stopDir(id);
|
||||
const dirs = await store.getDirs();
|
||||
const dir = dirs.find((d) => d.id === id);
|
||||
if (dir?.enabled) await watcher.startDir(dir);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('dirs:remove', async (_e, id: string) => {
|
||||
await watcher.stopDir(id);
|
||||
await store.removeDir(id);
|
||||
});
|
||||
|
||||
// ── Dry-run 스캔 ──
|
||||
ipcMain.handle('dirs:scan', async (_e, id: string) => {
|
||||
const dirs = await store.getDirs();
|
||||
const dir = dirs.find((d) => d.id === id);
|
||||
if (!dir) return [];
|
||||
return scan(dir.path, dir.recursive, { customRanges: dir.customRanges });
|
||||
});
|
||||
|
||||
// ── 수동 모드 대기 큐 적용 ──
|
||||
ipcMain.handle('dirs:applyQueue', async (_e, id: string) => {
|
||||
const dirs = await store.getDirs();
|
||||
const dir = dirs.find((d) => d.id === id);
|
||||
if (!dir) return [];
|
||||
return watcher.applyManualQueue(id, dir);
|
||||
});
|
||||
|
||||
ipcMain.handle('dirs:pendingQueue', async (_e, id: string) => {
|
||||
return watcher.getPendingQueue(id);
|
||||
});
|
||||
|
||||
// ── 감시 일시정지/재개/상태 ──
|
||||
ipcMain.handle('watcher:status', () => ({ paused: watcher.isGloballyPaused() }));
|
||||
|
||||
ipcMain.handle('watcher:pauseAll', async () => {
|
||||
await watcher.pauseAll();
|
||||
});
|
||||
|
||||
ipcMain.handle('watcher:resumeAll', async () => {
|
||||
const dirs = await store.getDirs();
|
||||
await watcher.resumeAll(dirs);
|
||||
});
|
||||
|
||||
// ── Undo ──
|
||||
ipcMain.handle('undo:list', async () => store.getUndoLog());
|
||||
|
||||
ipcMain.handle('undo:revertEntry', async (_e, entry: UndoEntry) => {
|
||||
try {
|
||||
const result = await normalizeEntry(entry.newPath, 'file');
|
||||
// 실제 되돌리기: newPath → oldPath
|
||||
const fs = await import('fs/promises');
|
||||
await fs.rename(entry.newPath, entry.oldPath);
|
||||
await store.markUndoReverted([entry.id]);
|
||||
return { success: true, result };
|
||||
} catch (err) {
|
||||
return { success: false, error: String(err) };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('undo:revertLastBatch', async () => {
|
||||
const log = await store.getUndoLog();
|
||||
if (!log.length) return [];
|
||||
const lastTs = log[log.length - 1].ts;
|
||||
// 마지막 5초 이내의 항목을 한 배치로 묶음
|
||||
const batch = log.filter((e) => !e.reverted && lastTs - e.ts < 5000);
|
||||
const fs = await import('fs/promises');
|
||||
const results = [];
|
||||
for (const entry of [...batch].reverse()) {
|
||||
try {
|
||||
await fs.rename(entry.newPath, entry.oldPath);
|
||||
results.push({ ...entry, success: true });
|
||||
} catch (err) {
|
||||
results.push({ ...entry, success: false, error: String(err) });
|
||||
}
|
||||
}
|
||||
await store.markUndoReverted(batch.map((e) => e.id));
|
||||
return results;
|
||||
});
|
||||
|
||||
// ── 설정 ──
|
||||
ipcMain.handle('settings:get', async () => store.getSettings());
|
||||
ipcMain.handle('settings:update', async (_e, patch) => store.updateSettings(patch));
|
||||
|
||||
// ── 앱 정보 ──
|
||||
ipcMain.handle('app:version', () => app.getVersion());
|
||||
}
|
||||
Reference in New Issue
Block a user