/** * File-based Cache Service * Stores cached data as JSON files in data/cache/ */ const fs = require('fs'); const path = require('path'); // Cache directory const cacheDir = path.join(__dirname, '..', '..', 'data', 'cache'); // Ensure cache directories exist function ensureCacheDir(type, sourceId) { const dir = path.join(cacheDir, type, String(sourceId)); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } return dir; } // Get cache file path function getCachePath(type, sourceId, key) { const dir = ensureCacheDir(type, sourceId); // Sanitize key for filename const safeKey = String(key || 'default').replace(/[^a-zA-Z0-9_-]/g, '_'); return path.join(dir, `${safeKey}.json`); } /** * Get cached data if not expired * @param {string} type - Cache type (epg, m3u, xtream) * @param {number|string} sourceId - Source ID * @param {string} key - Cache key (e.g., action name) * @param {number} maxAgeMs - Maximum age in milliseconds * @returns {any|null} - Cached data or null if expired/missing */ function get(type, sourceId, key, maxAgeMs) { try { const cachePath = getCachePath(type, sourceId, key); if (!fs.existsSync(cachePath)) { return null; } const cached = JSON.parse(fs.readFileSync(cachePath, 'utf-8')); const age = Date.now() - cached.timestamp; if (age > maxAgeMs) { return null; // Expired } return cached.data; } catch (err) { console.warn(`Cache read error for ${type}/${sourceId}/${key}:`, err.message); return null; } } /** * Store data in cache * @param {string} type - Cache type * @param {number|string} sourceId - Source ID * @param {string} key - Cache key * @param {any} data - Data to cache */ function set(type, sourceId, key, data) { try { const cachePath = getCachePath(type, sourceId, key); const cached = { timestamp: Date.now(), data: data }; fs.writeFileSync(cachePath, JSON.stringify(cached)); } catch (err) { console.error(`Cache write error for ${type}/${sourceId}/${key}:`, err.message); } } /** * Clear specific cache entry */ function clear(type, sourceId, key) { try { const cachePath = getCachePath(type, sourceId, key); if (fs.existsSync(cachePath)) { fs.unlinkSync(cachePath); } } catch (err) { console.warn(`Cache clear error:`, err.message); } } /** * Clear all cache for a source */ function clearSource(sourceId) { try { const types = ['epg', 'm3u', 'xtream']; for (const type of types) { const dir = path.join(cacheDir, type, String(sourceId)); if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true }); } } } catch (err) { console.warn(`Cache clear source error:`, err.message); } } /** * Clear all cache */ function clearAll() { try { if (fs.existsSync(cacheDir)) { fs.rmSync(cacheDir, { recursive: true }); } } catch (err) { console.warn(`Cache clear all error:`, err.message); } } /** * Get cache info for debugging */ function getInfo(type, sourceId, key) { try { const cachePath = getCachePath(type, sourceId, key); if (!fs.existsSync(cachePath)) { return null; } const cached = JSON.parse(fs.readFileSync(cachePath, 'utf-8')); const stats = fs.statSync(cachePath); return { timestamp: cached.timestamp, age: Date.now() - cached.timestamp, size: stats.size }; } catch (err) { return null; } } module.exports = { get, set, clear, clearSource, clearAll, getInfo };