UNPKG

omnifocus-mcp

Version:

Model Context Protocol (MCP) server that integrates with OmniFocus for AI assistant interaction

188 lines (182 loc) 5.65 kB
import { executeOmniFocusScript } from './scriptExecution.js'; class OmniFocusCacheManager { cache = new Map(); options; totalSize = 0; constructor(options = {}) { this.options = { ttlSeconds: options.ttlSeconds ?? 300, // 5 minutes default maxSize: options.maxSize ?? 50, // 50MB default useChecksum: options.useChecksum ?? true }; } /** * Get cached data if valid, otherwise return null */ async get(key) { const entry = this.cache.get(key); if (!entry) { return null; } // Check if cache has expired const age = Date.now() - entry.timestamp.getTime(); if (age > this.options.ttlSeconds * 1000) { this.cache.delete(key); return null; } // If using checksums, validate the cache is still current if (this.options.useChecksum && entry.checksum) { const currentChecksum = await this.getDatabaseChecksum(); if (currentChecksum !== entry.checksum) { this.cache.delete(key); return null; } } return entry.data; } /** * Store data in cache */ async set(key, data) { // Estimate size (rough approximation) const dataSize = JSON.stringify(data).length / (1024 * 1024); // Convert to MB // Check if adding this would exceed max size if (this.totalSize + dataSize > this.options.maxSize) { this.evictOldest(); } const checksum = this.options.useChecksum ? await this.getDatabaseChecksum() : undefined; this.cache.set(key, { data, timestamp: new Date(), checksum }); this.totalSize += dataSize; } /** * Invalidate specific cache entry */ invalidate(key) { this.cache.delete(key); } /** * Clear all cache entries */ clear() { this.cache.clear(); this.totalSize = 0; } /** * Get cache statistics */ getStats() { let oldestEntry = null; this.cache.forEach(entry => { if (!oldestEntry || entry.timestamp < oldestEntry) { oldestEntry = entry.timestamp; } }); return { entries: this.cache.size, sizeEstimateMB: this.totalSize, oldestEntry, hitRate: this.calculateHitRate() }; } /** * Get a lightweight checksum of the database state */ async getDatabaseChecksum() { try { const script = ` (() => { try { // Get counts and latest modification times as a simple checksum const taskCount = flattenedTasks.length; const projectCount = flattenedProjects.length; // Get the most recent modification time let latestMod = new Date(0); flattenedTasks.forEach(task => { if (task.modificationDate && task.modificationDate > latestMod) { latestMod = task.modificationDate; } }); flattenedProjects.forEach(project => { if (project.modificationDate && project.modificationDate > latestMod) { latestMod = project.modificationDate; } }); // Create a simple checksum string const checksum = taskCount + "-" + projectCount + "-" + latestMod.getTime(); return JSON.stringify({ checksum }); } catch (error) { return JSON.stringify({ checksum: "error-" + Date.now() }); } })(); `; // Write to temp file and execute const fs = await import('fs'); const tempFile = `/tmp/omnifocus_checksum_${crypto.randomUUID()}.js`; fs.writeFileSync(tempFile, script); const result = await executeOmniFocusScript(tempFile); fs.unlinkSync(tempFile); return result.checksum || `fallback-${Date.now()}`; } catch (error) { console.error('Error getting database checksum:', error); return `error-${Date.now()}`; } } /** * Evict oldest cache entries when size limit is reached */ evictOldest() { let oldestKey = null; let oldestTime = new Date(); this.cache.forEach((entry, key) => { if (entry.timestamp < oldestTime) { oldestTime = entry.timestamp; oldestKey = key; } }); if (oldestKey) { this.cache.delete(oldestKey); // Recalculate total size (simplified for now) this.totalSize *= 0.9; } } // Tracking for hit rate calculation hits = 0; misses = 0; calculateHitRate() { const total = this.hits + this.misses; if (total === 0) return 0; return (this.hits / total) * 100; } trackHit() { this.hits++; } trackMiss() { this.misses++; } } // Singleton instance let cacheManager = null; /** * Get or create the cache manager instance */ export function getCacheManager(options) { if (!cacheManager) { cacheManager = new OmniFocusCacheManager(options); } return cacheManager; } /** * Reset the cache manager (useful for testing) */ export function resetCacheManager() { if (cacheManager) { cacheManager.clear(); } cacheManager = null; }