refactor(02-02): main/index.ts to use ServiceContextRegistry
- Replace global service variables with ServiceContextRegistry - Create local context at startup with LocalFileSystemProvider - Register local context in registry and start it - Wire file watcher events via wireFileWatcherEvents helper - Export onContextSwitched callback for SSH handler - Remove handleModeSwitch callback (replaced by registry pattern) - Update initializeIpcHandlers to accept registry instead of individual services - Remove reinitializeServiceHandlers entirely from handlers.ts - ServiceContextRegistry pattern eliminates need for service recreation on mode switch
This commit is contained in:
parent
1b4f180e81
commit
5bf41c6ed8
2 changed files with 117 additions and 167 deletions
|
|
@ -4,30 +4,23 @@
|
|||
* Responsibilities:
|
||||
* - Initialize Electron app and main window
|
||||
* - Set up IPC handlers for data access
|
||||
* - Initialize services (ProjectScanner, SessionParser, etc.)
|
||||
* - Initialize ServiceContextRegistry with local context
|
||||
* - Start file watcher for live updates
|
||||
* - Manage application lifecycle
|
||||
*/
|
||||
|
||||
import {
|
||||
CACHE_CLEANUP_INTERVAL_MINUTES,
|
||||
CACHE_TTL_MINUTES,
|
||||
DEFAULT_WINDOW_HEIGHT,
|
||||
DEFAULT_WINDOW_WIDTH,
|
||||
DEV_SERVER_PORT,
|
||||
getTrafficLightPositionForZoom,
|
||||
MAX_CACHE_SESSIONS,
|
||||
WINDOW_ZOOM_FACTOR_CHANGED_CHANNEL,
|
||||
} from '@shared/constants';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import { join } from 'path';
|
||||
|
||||
import {
|
||||
initializeIpcHandlers,
|
||||
reinitializeServiceHandlers,
|
||||
removeIpcHandlers,
|
||||
} from './ipc/handlers';
|
||||
import { initializeIpcHandlers, removeIpcHandlers } from './ipc/handlers';
|
||||
|
||||
// Icon path - works for both dev and production
|
||||
const getIconPath = (): string => {
|
||||
|
|
@ -42,15 +35,12 @@ const logger = createLogger('App');
|
|||
import { SSH_STATUS } from '@preload/constants/ipcChannels';
|
||||
|
||||
import {
|
||||
ChunkBuilder,
|
||||
configManager,
|
||||
DataCache,
|
||||
FileWatcher,
|
||||
LocalFileSystemProvider,
|
||||
NotificationManager,
|
||||
ProjectScanner,
|
||||
SessionParser,
|
||||
ServiceContext,
|
||||
ServiceContextRegistry,
|
||||
SshConnectionManager,
|
||||
SubagentResolver,
|
||||
UpdaterService,
|
||||
} from './services';
|
||||
|
||||
|
|
@ -60,17 +50,71 @@ import {
|
|||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
// Service instances
|
||||
let projectScanner: ProjectScanner;
|
||||
let sessionParser: SessionParser;
|
||||
let subagentResolver: SubagentResolver;
|
||||
let chunkBuilder: ChunkBuilder;
|
||||
let dataCache: DataCache;
|
||||
let fileWatcher: FileWatcher;
|
||||
// Service registry and global services
|
||||
let contextRegistry: ServiceContextRegistry;
|
||||
let notificationManager: NotificationManager;
|
||||
let updaterService: UpdaterService;
|
||||
let sshConnectionManager: SshConnectionManager;
|
||||
let cleanupInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
// File watcher event cleanup functions
|
||||
let fileChangeCleanup: (() => void) | null = null;
|
||||
let todoChangeCleanup: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* Wires file watcher events from a ServiceContext to the renderer.
|
||||
* Cleans up previous listeners before adding new ones.
|
||||
*/
|
||||
function wireFileWatcherEvents(context: ServiceContext): void {
|
||||
logger.info(`Wiring FileWatcher events for context: ${context.id}`);
|
||||
|
||||
// Clean up previous listeners
|
||||
if (fileChangeCleanup) {
|
||||
fileChangeCleanup();
|
||||
fileChangeCleanup = null;
|
||||
}
|
||||
if (todoChangeCleanup) {
|
||||
todoChangeCleanup();
|
||||
todoChangeCleanup = null;
|
||||
}
|
||||
|
||||
// Wire file-change events
|
||||
const fileChangeHandler = (event: unknown) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('file-change', event);
|
||||
}
|
||||
};
|
||||
context.fileWatcher.on('file-change', fileChangeHandler);
|
||||
fileChangeCleanup = () => context.fileWatcher.off('file-change', fileChangeHandler);
|
||||
|
||||
// Wire todo-change events
|
||||
const todoChangeHandler = (event: unknown) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('todo-change', event);
|
||||
}
|
||||
};
|
||||
context.fileWatcher.on('todo-change', todoChangeHandler);
|
||||
todoChangeCleanup = () => context.fileWatcher.off('todo-change', todoChangeHandler);
|
||||
|
||||
logger.info(`FileWatcher events wired for context: ${context.id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when context switches (called by SSH IPC handler).
|
||||
* Re-wires file watcher events and notifies renderer.
|
||||
*/
|
||||
export function onContextSwitched(context: ServiceContext): void {
|
||||
// Re-wire file watcher events to new context
|
||||
wireFileWatcherEvents(context);
|
||||
|
||||
// Notify renderer of context change
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send(SSH_STATUS, sshConnectionManager.getStatus());
|
||||
mainWindow.webContents.send('context-changed', {
|
||||
contextId: context.id,
|
||||
type: context.type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all services.
|
||||
|
|
@ -81,71 +125,36 @@ function initializeServices(): void {
|
|||
// Initialize SSH connection manager
|
||||
sshConnectionManager = new SshConnectionManager();
|
||||
|
||||
// Initialize services (paths are set automatically from environment)
|
||||
projectScanner = new ProjectScanner();
|
||||
sessionParser = new SessionParser(projectScanner);
|
||||
subagentResolver = new SubagentResolver(projectScanner);
|
||||
chunkBuilder = new ChunkBuilder();
|
||||
const disableCache = process.env.CLAUDE_CONTEXT_DISABLE_CACHE === '1';
|
||||
dataCache = new DataCache(MAX_CACHE_SESSIONS, CACHE_TTL_MINUTES, !disableCache);
|
||||
// Create ServiceContextRegistry
|
||||
contextRegistry = new ServiceContextRegistry();
|
||||
|
||||
// Create local context
|
||||
const localContext = new ServiceContext({
|
||||
id: 'local',
|
||||
type: 'local',
|
||||
fsProvider: new LocalFileSystemProvider(),
|
||||
});
|
||||
|
||||
// Register and start local context
|
||||
contextRegistry.registerContext(localContext);
|
||||
localContext.start();
|
||||
|
||||
logger.info(`Projects directory: ${localContext.projectScanner.getProjectsDir()}`);
|
||||
|
||||
// Initialize notification manager (singleton, not context-scoped)
|
||||
notificationManager = NotificationManager.getInstance();
|
||||
|
||||
// Set notification manager on local context's file watcher
|
||||
localContext.fileWatcher.setNotificationManager(notificationManager);
|
||||
|
||||
// Wire file watcher events for local context
|
||||
wireFileWatcherEvents(localContext);
|
||||
|
||||
// Initialize updater service
|
||||
updaterService = new UpdaterService();
|
||||
|
||||
logger.info(`Projects directory: ${projectScanner.getProjectsDir()}`);
|
||||
|
||||
// Mode switch callback: recreates services with new provider when switching local↔SSH
|
||||
const handleModeSwitch = async (mode: 'local' | 'ssh'): Promise<void> => {
|
||||
logger.info(`Switching to ${mode} mode`);
|
||||
|
||||
// Stop file watcher
|
||||
fileWatcher.stop();
|
||||
|
||||
// Clear data cache
|
||||
dataCache.clear();
|
||||
|
||||
// Get provider and projects path from connection manager
|
||||
const provider = sshConnectionManager.getProvider();
|
||||
const projectsDir =
|
||||
mode === 'ssh' ? (sshConnectionManager.getRemoteProjectsPath() ?? undefined) : undefined;
|
||||
|
||||
// Recreate services with new provider
|
||||
projectScanner = new ProjectScanner(projectsDir, undefined, provider);
|
||||
sessionParser = new SessionParser(projectScanner);
|
||||
subagentResolver = new SubagentResolver(projectScanner);
|
||||
|
||||
// Re-initialize IPC handler service references so subsequent calls use new instances
|
||||
reinitializeServiceHandlers(
|
||||
projectScanner,
|
||||
sessionParser,
|
||||
subagentResolver,
|
||||
chunkBuilder,
|
||||
dataCache
|
||||
);
|
||||
|
||||
// Update file watcher provider
|
||||
fileWatcher.setFileSystemProvider(provider);
|
||||
|
||||
// Restart file watcher
|
||||
fileWatcher.start();
|
||||
|
||||
// Notify renderer to re-fetch all data
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send(SSH_STATUS, sshConnectionManager.getStatus());
|
||||
}
|
||||
|
||||
logger.info(`Mode switch to ${mode} complete`);
|
||||
};
|
||||
|
||||
// Initialize IPC handlers (including SSH)
|
||||
initializeIpcHandlers(
|
||||
projectScanner,
|
||||
sessionParser,
|
||||
subagentResolver,
|
||||
chunkBuilder,
|
||||
dataCache,
|
||||
updaterService,
|
||||
sshConnectionManager,
|
||||
handleModeSwitch
|
||||
);
|
||||
// Initialize IPC handlers with registry
|
||||
initializeIpcHandlers(contextRegistry, updaterService, sshConnectionManager);
|
||||
|
||||
// Forward SSH state changes to renderer
|
||||
sshConnectionManager.on('state-change', (status: unknown) => {
|
||||
|
|
@ -154,33 +163,6 @@ function initializeServices(): void {
|
|||
}
|
||||
});
|
||||
|
||||
// Initialize notification manager using singleton pattern
|
||||
// This ensures IPC handlers and FileWatcher use the same instance
|
||||
// Note: mainWindow will be set later via setMainWindow() when window is created
|
||||
notificationManager = NotificationManager.getInstance();
|
||||
|
||||
// Start file watcher with notification manager for error detection
|
||||
fileWatcher = new FileWatcher(dataCache);
|
||||
fileWatcher.setNotificationManager(notificationManager);
|
||||
fileWatcher.start();
|
||||
|
||||
// Forward file change events to renderer
|
||||
// Note: Error detection is handled internally by FileWatcher via NotificationManager
|
||||
fileWatcher.on('file-change', (event) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('file-change', event);
|
||||
}
|
||||
});
|
||||
|
||||
fileWatcher.on('todo-change', (event) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('todo-change', event);
|
||||
}
|
||||
});
|
||||
|
||||
// Start automatic cache cleanup
|
||||
cleanupInterval = dataCache.startAutoCleanup(CACHE_CLEANUP_INTERVAL_MINUTES);
|
||||
|
||||
logger.info('Services initialized successfully');
|
||||
}
|
||||
|
||||
|
|
@ -190,15 +172,19 @@ function initializeServices(): void {
|
|||
function shutdownServices(): void {
|
||||
logger.info('Shutting down services...');
|
||||
|
||||
// Stop file watcher
|
||||
if (fileWatcher) {
|
||||
fileWatcher.stop();
|
||||
// Clean up file watcher event listeners
|
||||
if (fileChangeCleanup) {
|
||||
fileChangeCleanup();
|
||||
fileChangeCleanup = null;
|
||||
}
|
||||
if (todoChangeCleanup) {
|
||||
todoChangeCleanup();
|
||||
todoChangeCleanup = null;
|
||||
}
|
||||
|
||||
// Stop cache cleanup
|
||||
if (cleanupInterval) {
|
||||
clearInterval(cleanupInterval);
|
||||
cleanupInterval = null;
|
||||
// Dispose all contexts (including local)
|
||||
if (contextRegistry) {
|
||||
contextRegistry.dispose();
|
||||
}
|
||||
|
||||
// Dispose SSH connection manager
|
||||
|
|
|
|||
|
|
@ -45,38 +45,23 @@ import {
|
|||
import { registerUtilityHandlers, removeUtilityHandlers } from './utility';
|
||||
import { registerValidationHandlers, removeValidationHandlers } from './validation';
|
||||
|
||||
import type {
|
||||
ChunkBuilder,
|
||||
DataCache,
|
||||
ProjectScanner,
|
||||
SessionParser,
|
||||
SshConnectionManager,
|
||||
SubagentResolver,
|
||||
UpdaterService,
|
||||
} from '../services';
|
||||
import type { ServiceContextRegistry, SshConnectionManager, UpdaterService } from '../services';
|
||||
|
||||
/**
|
||||
* Initializes IPC handlers with service instances.
|
||||
* Initializes IPC handlers with service registry.
|
||||
*/
|
||||
export function initializeIpcHandlers(
|
||||
scanner: ProjectScanner,
|
||||
parser: SessionParser,
|
||||
resolver: SubagentResolver,
|
||||
builder: ChunkBuilder,
|
||||
cache: DataCache,
|
||||
registry: ServiceContextRegistry,
|
||||
updater: UpdaterService,
|
||||
sshManager?: SshConnectionManager,
|
||||
sshModeSwitchCallback?: (mode: 'local' | 'ssh') => Promise<void>
|
||||
sshManager: SshConnectionManager
|
||||
): void {
|
||||
// Initialize domain handlers with their required services
|
||||
initializeProjectHandlers(scanner);
|
||||
initializeSessionHandlers(scanner, parser, resolver, builder, cache);
|
||||
initializeSearchHandlers(scanner);
|
||||
initializeSubagentHandlers(builder, cache, parser, resolver, scanner);
|
||||
// Initialize domain handlers with registry
|
||||
initializeProjectHandlers(registry);
|
||||
initializeSessionHandlers(registry);
|
||||
initializeSearchHandlers(registry);
|
||||
initializeSubagentHandlers(registry);
|
||||
initializeUpdaterHandlers(updater);
|
||||
if (sshManager && sshModeSwitchCallback) {
|
||||
initializeSshHandlers(sshManager, sshModeSwitchCallback);
|
||||
}
|
||||
initializeSshHandlers(sshManager, registry);
|
||||
|
||||
// Register all handlers
|
||||
registerProjectHandlers(ipcMain);
|
||||
|
|
@ -88,32 +73,11 @@ export function initializeIpcHandlers(
|
|||
registerNotificationHandlers(ipcMain);
|
||||
registerConfigHandlers(ipcMain);
|
||||
registerUpdaterHandlers(ipcMain);
|
||||
if (sshManager) {
|
||||
registerSshHandlers(ipcMain);
|
||||
}
|
||||
registerSshHandlers(ipcMain);
|
||||
|
||||
logger.info('All handlers registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initializes service-dependent IPC handlers after a mode switch (local ↔ SSH).
|
||||
* This updates the module-level service references held by each domain handler module,
|
||||
* ensuring IPC calls after the switch use the new service instances.
|
||||
*/
|
||||
export function reinitializeServiceHandlers(
|
||||
scanner: ProjectScanner,
|
||||
parser: SessionParser,
|
||||
resolver: SubagentResolver,
|
||||
builder: ChunkBuilder,
|
||||
cache: DataCache
|
||||
): void {
|
||||
initializeProjectHandlers(scanner);
|
||||
initializeSessionHandlers(scanner, parser, resolver, builder, cache);
|
||||
initializeSearchHandlers(scanner);
|
||||
initializeSubagentHandlers(builder, cache, parser, resolver, scanner);
|
||||
logger.info('Service handlers re-initialized after mode switch');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all IPC handlers.
|
||||
* Should be called when shutting down.
|
||||
|
|
|
|||
Loading…
Reference in a new issue