Skip to main content
Glama
FileWatcherService.ts5.21 kB
import { EventEmitter } from 'events'; import { watch } from 'chokidar'; import * as path from 'path'; import { LoggingService } from './LoggingService.js'; /** * Service for monitoring file system changes */ export class FileWatcherService extends EventEmitter { private watchers: Map<string, any> = new Map(); private logger?: LoggingService; constructor(logger?: LoggingService) { super(); this.logger = logger; } /** * Start watching a path for changes */ public async watch(targetPath: string): Promise<void> { if (this.watchers.has(targetPath)) { return; } if (!this.watchers.has(targetPath)) { this.watchers.set(targetPath, watch(targetPath, { persistent: true, ignoreInitial: true, usePolling: true, interval: 100, binaryInterval: 300, awaitWriteFinish: { stabilityThreshold: 2000, pollInterval: 100 } })); this.setupWatcherEvents(targetPath); } else { this.watchers.get(targetPath).add(targetPath); } await this.logger?.info('Started watching path', { targetPath, operation: 'file_watch_start' }); } /** * Stop watching a path */ public async unwatch(targetPath: string): Promise<void> { if (this.watchers.has(targetPath)) { await this.watchers.get(targetPath).unwatch(targetPath); this.watchers.delete(targetPath); await this.logger?.info('Stopped watching path', { targetPath, operation: 'file_watch_stop' }); } } /** * Close all watchers */ public async close(): Promise<void> { if (this.watchers.size > 0) { for (const watcher of this.watchers.values()) { await watcher.close(); } this.watchers.clear(); await this.logger?.info('File watcher closed', { operation: 'file_watch_close' }); } } /** * Get list of watched paths */ public getWatchedPaths(): string[] { return Array.from(this.watchers.keys()); } /** * Setup watcher event handlers */ private setupWatcherEvents(targetPath: string): void { if (!this.watchers.has(targetPath)) return; const watcher = this.watchers.get(targetPath); // File added watcher.on('add', (filePath: string) => { this.logger?.debug('File added', { filePath, targetPath, operation: 'file_watch_event' }); this.emit('fileAdded', filePath); }); // File changed watcher.on('change', (filePath: string) => { this.logger?.debug('File changed', { filePath, targetPath, operation: 'file_watch_event' }); this.emit('fileChanged', filePath); }); // File deleted watcher.on('unlink', (filePath: string) => { this.logger?.debug('File removed', { filePath, targetPath, operation: 'file_watch_event' }); this.emit('fileDeleted', filePath); }); // Directory added watcher.on('addDir', (dirPath: string) => { this.logger?.debug('Directory added', { dirPath, targetPath, operation: 'file_watch_event' }); this.emit('directoryAdded', dirPath); }); // Directory deleted watcher.on('unlinkDir', (dirPath: string) => { this.logger?.debug('Directory removed', { dirPath, targetPath, operation: 'file_watch_event' }); this.emit('directoryDeleted', dirPath); }); // Error handling watcher.on('error', (error: Error) => { this.logger?.error('Watcher error', error, { targetPath, operation: 'file_watch_event' }); this.emit('error', error); }); // Ready event watcher.on('ready', () => { this.logger?.info('Initial scan complete, ready for changes', { targetPath, operation: 'file_watch_ready' }); this.emit('ready'); }); } /** * Check if a path is being watched */ public isWatching(targetPath: string): boolean { return this.watchers.has(targetPath); } /** * Get watcher status */ public getStatus(): { isWatching: boolean; watchedPaths: string[]; ready: boolean } { return { isWatching: this.watchers.size > 0, watchedPaths: Array.from(this.watchers.keys()), ready: this.watchers.size > 0 && this.watchers.get(Array.from(this.watchers.keys())[0])?.getWatched !== undefined }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bsmi021/mcp-file-context-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server