KTV / lib /utils /cacheManager.ts
l-g-t's picture
Upload 212 files
3c76719 verified
// Intelligent Cache Manager - Auto-manages video segment caching
const CACHE_NAME = 'video-cache-v1';
const METADATA_STORE = 'cache-metadata';
const CACHE_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days
const MAX_CACHE_SIZE_MB = 1000; // 1GB
interface CacheMetadata { url: string; videoUrl: string; cachedAt: number; size: number; lastAccessed: number; }
interface CacheStats { totalEntries: number; totalSizeMB: number; oldestEntry: number; newestEntry: number; }
class CacheManager {
private metadata = new Map<string, CacheMetadata>();
private initialized = false;
async initialize(): Promise<void> {
if (this.initialized) return;
try {
const stored = localStorage.getItem(METADATA_STORE);
if (stored) this.metadata = new Map(Object.entries(JSON.parse(stored)));
} catch (error) { console.error('[CacheManager] Init failed:', error); }
this.initialized = true;
}
private save(): void {
try { localStorage.setItem(METADATA_STORE, JSON.stringify(Object.fromEntries(this.metadata))); }
catch (e) { console.error('[CacheManager] Save failed:', e); }
}
async addCacheEntry(url: string, videoUrl: string, size: number): Promise<void> {
await this.initialize();
this.metadata.set(url, { url, videoUrl, cachedAt: Date.now(), size, lastAccessed: Date.now() });
this.save();
}
async isCacheValid(url: string): Promise<boolean> {
await this.initialize();
const meta = this.metadata.get(url);
if (!meta) return false;
if (Date.now() - meta.cachedAt > CACHE_TTL) {
return false;
}
meta.lastAccessed = Date.now();
this.save();
return true;
}
async getCacheStats(): Promise<CacheStats> {
await this.initialize();
const entries = Array.from(this.metadata.values());
const totalSize = entries.reduce((sum, e) => sum + e.size, 0);
const times = entries.map(e => e.cachedAt);
return {
totalEntries: entries.length, totalSizeMB: totalSize / (1024 * 1024),
oldestEntry: times.length ? Math.min(...times) : 0, newestEntry: times.length ? Math.max(...times) : 0
};
}
async checkAndCleanup(): Promise<void> {
await this.initialize();
const stats = await this.getCacheStats();
if (stats.totalSizeMB > MAX_CACHE_SIZE_MB) {
await this.cleanupOldEntries();
}
await this.cleanupExpiredEntries();
}
async cleanupExpiredEntries(): Promise<number> {
await this.initialize();
if (!('caches' in window)) return 0;
const cache = await caches.open(CACHE_NAME);
const now = Date.now();
let cleaned = 0;
for (const [url, meta] of this.metadata.entries()) {
if (now - meta.cachedAt > CACHE_TTL) {
await cache.delete(url);
this.metadata.delete(url);
cleaned++;
}
}
if (cleaned > 0) {
this.save();
}
return cleaned;
}
async cleanupOldEntries(): Promise<number> {
await this.initialize();
if (!('caches' in window)) return 0;
const cache = await caches.open(CACHE_NAME);
const sorted = Array.from(this.metadata.entries()).sort(([, a], [, b]) => a.lastAccessed - b.lastAccessed);
const toRemove = Math.ceil(sorted.length * 0.3);
let removed = 0;
for (let i = 0; i < toRemove && i < sorted.length; i++) {
await cache.delete(sorted[i][0]);
this.metadata.delete(sorted[i][0]);
removed++;
}
if (removed > 0) {
this.save();
}
return removed;
}
async clearVideoCache(videoUrl: string): Promise<number> {
await this.initialize();
if (!('caches' in window)) return 0;
const cache = await caches.open(CACHE_NAME);
let cleared = 0;
for (const [url, meta] of this.metadata.entries()) {
if (meta.videoUrl === videoUrl) {
await cache.delete(url);
this.metadata.delete(url);
cleared++;
}
}
if (cleared > 0) {
this.save();
}
return cleared;
}
async clearAllCache(): Promise<number> {
await this.initialize();
if (!('caches' in window)) return 0;
const cache = await caches.open(CACHE_NAME);
await Promise.all((await cache.keys()).map(k => cache.delete(k)));
const count = this.metadata.size;
this.metadata.clear();
this.save();
return count;
}
}
export const cacheManager = new CacheManager();
export const clearSegmentsForUrl = (url: string) => cacheManager.clearVideoCache(url);
export const clearAllCache = () => cacheManager.clearAllCache();
if (typeof window !== 'undefined') {
cacheManager.initialize().then(() => {
setInterval(() => cacheManager.checkAndCleanup(), 5 * 60 * 1000);
setTimeout(() => cacheManager.checkAndCleanup(), 10000);
});
}