mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
266 lines
No EOL
8.1 KiB
TypeScript
266 lines
No EOL
8.1 KiB
TypeScript
import { StreamingContent } from './catalogService';
|
|
import { GroupedStreams } from '../types/streams';
|
|
import { TMDBEpisode } from './tmdbService';
|
|
import { Cast } from '../types/cast';
|
|
|
|
interface CachedContent {
|
|
metadata: StreamingContent;
|
|
streams?: GroupedStreams;
|
|
episodes?: TMDBEpisode[];
|
|
cast?: Cast[];
|
|
episodeStreams?: { [episodeId: string]: GroupedStreams };
|
|
timestamp: number;
|
|
}
|
|
|
|
class CacheService {
|
|
private static instance: CacheService;
|
|
private cache: Map<string, CachedContent> = new Map();
|
|
private metadataScreenCache: Map<string, any> = new Map();
|
|
private readonly MAX_METADATA_SCREENS = 5;
|
|
private readonly MAX_CACHE_SIZE = 100; // Max size for the main cache
|
|
private readonly CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours TTL for main cache items
|
|
|
|
private constructor() {
|
|
// Initialize any other necessary properties
|
|
}
|
|
|
|
public static getInstance(): CacheService {
|
|
if (!CacheService.instance) {
|
|
CacheService.instance = new CacheService();
|
|
}
|
|
return CacheService.instance;
|
|
}
|
|
|
|
public getCacheKey(id: string, type: string): string {
|
|
return `${type}:${id}`;
|
|
}
|
|
|
|
// Helper to ensure the main cache does not exceed its size limit
|
|
private ensureCacheLimit(): void {
|
|
if (this.cache.size >= this.MAX_CACHE_SIZE) {
|
|
// Remove the least recently used item (first key in Map iteration order)
|
|
const oldestKey = this.cache.keys().next().value;
|
|
if (oldestKey) {
|
|
this.cache.delete(oldestKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to mark an item as recently used (by deleting and re-inserting)
|
|
// Also ensures the timestamp is updated.
|
|
private touch(key: string, existingData: CachedContent): void {
|
|
this.cache.delete(key);
|
|
this.cache.set(key, { ...existingData, timestamp: Date.now() });
|
|
}
|
|
|
|
public setMetadata(id: string, type: string, metadata: StreamingContent): void {
|
|
const key = this.getCacheKey(id, type);
|
|
let existing = this.cache.get(key);
|
|
|
|
if (existing) {
|
|
// Update existing entry and mark as recent
|
|
existing = { ...existing, metadata, timestamp: Date.now() };
|
|
this.touch(key, existing);
|
|
} else {
|
|
// Adding a new entry, first check limit
|
|
this.ensureCacheLimit();
|
|
// Add the new entry
|
|
this.cache.set(key, {
|
|
metadata,
|
|
timestamp: Date.now()
|
|
} as CachedContent);
|
|
}
|
|
}
|
|
|
|
public setStreams(id: string, type: string, streams: GroupedStreams): void {
|
|
const key = this.getCacheKey(id, type);
|
|
const existing = this.cache.get(key);
|
|
// Can only set streams if metadata already exists in cache
|
|
if (!existing?.metadata) return;
|
|
|
|
const updatedData = {
|
|
...existing,
|
|
streams,
|
|
timestamp: Date.now() // Update timestamp on modification
|
|
};
|
|
this.touch(key, updatedData); // Mark as recently used
|
|
}
|
|
|
|
public setEpisodes(id: string, type: string, episodes: TMDBEpisode[]): void {
|
|
const key = this.getCacheKey(id, type);
|
|
const existing = this.cache.get(key);
|
|
if (!existing?.metadata) return;
|
|
|
|
const updatedData = {
|
|
...existing,
|
|
episodes,
|
|
timestamp: Date.now()
|
|
};
|
|
this.touch(key, updatedData);
|
|
}
|
|
|
|
public setCast(id: string, type: string, cast: Cast[]): void {
|
|
const key = this.getCacheKey(id, type);
|
|
const existing = this.cache.get(key);
|
|
if (!existing?.metadata) return;
|
|
|
|
const updatedData = {
|
|
...existing,
|
|
cast,
|
|
timestamp: Date.now()
|
|
};
|
|
this.touch(key, updatedData);
|
|
}
|
|
|
|
public setEpisodeStreams(id: string, type: string, episodeId: string, streams: GroupedStreams): void {
|
|
const key = this.getCacheKey(id, type);
|
|
const existing = this.cache.get(key);
|
|
if (!existing?.metadata) return;
|
|
|
|
const updatedData = {
|
|
...existing,
|
|
episodeStreams: {
|
|
...(existing.episodeStreams || {}),
|
|
[episodeId]: streams
|
|
},
|
|
timestamp: Date.now()
|
|
};
|
|
this.touch(key, updatedData);
|
|
}
|
|
|
|
// --- Getters for the main cache ---
|
|
|
|
public getMetadata(id: string, type: string): StreamingContent | null {
|
|
const key = this.getCacheKey(id, type);
|
|
const data = this.cache.get(key);
|
|
if (data) {
|
|
// Check for expiration first
|
|
if (Date.now() - data.timestamp > this.CACHE_TTL_MS) {
|
|
this.cache.delete(key); // Remove expired item
|
|
return null;
|
|
}
|
|
// Not expired, proceed with LRU update
|
|
this.touch(key, data); // Mark as recently used on access
|
|
return data.metadata;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public getStreams(id: string, type: string): GroupedStreams | null {
|
|
const key = this.getCacheKey(id, type);
|
|
const data = this.cache.get(key);
|
|
if (data) {
|
|
// Check for expiration first
|
|
if (Date.now() - data.timestamp > this.CACHE_TTL_MS) {
|
|
this.cache.delete(key); // Remove expired item
|
|
return null;
|
|
}
|
|
// Not expired, proceed with LRU update
|
|
this.touch(key, data); // Mark as recently used on access
|
|
return data.streams || null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public getEpisodes(id: string, type: string): TMDBEpisode[] | null {
|
|
const key = this.getCacheKey(id, type);
|
|
const data = this.cache.get(key);
|
|
if (data) {
|
|
// Check for expiration first
|
|
if (Date.now() - data.timestamp > this.CACHE_TTL_MS) {
|
|
this.cache.delete(key); // Remove expired item
|
|
return null;
|
|
}
|
|
// Not expired, proceed with LRU update
|
|
this.touch(key, data); // Mark as recently used on access
|
|
return data.episodes || null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public getCast(id: string, type: string): Cast[] | null {
|
|
const key = this.getCacheKey(id, type);
|
|
const data = this.cache.get(key);
|
|
if (data) {
|
|
// Check for expiration first
|
|
if (Date.now() - data.timestamp > this.CACHE_TTL_MS) {
|
|
this.cache.delete(key); // Remove expired item
|
|
return null;
|
|
}
|
|
// Not expired, proceed with LRU update
|
|
this.touch(key, data); // Mark as recently used on access
|
|
return data.cast || null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public getEpisodeStreams(id: string, type: string, episodeId: string): GroupedStreams | null {
|
|
const key = this.getCacheKey(id, type);
|
|
const data = this.cache.get(key);
|
|
if (data) {
|
|
// Check for expiration first
|
|
if (Date.now() - data.timestamp > this.CACHE_TTL_MS) {
|
|
this.cache.delete(key); // Remove expired item
|
|
return null;
|
|
}
|
|
// Not expired, check if episode stream exists and proceed with LRU update
|
|
if (data.episodeStreams?.[episodeId]) {
|
|
this.touch(key, data); // Mark as recently used on access
|
|
return data.episodeStreams[episodeId];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// --- Cache utility methods ---
|
|
|
|
public clearCache(): void {
|
|
this.cache.clear();
|
|
}
|
|
|
|
// Checks existence without affecting LRU order
|
|
public isCached(id: string, type: string): boolean {
|
|
const key = this.getCacheKey(id, type);
|
|
return this.cache.has(key);
|
|
}
|
|
|
|
// --- Metadata Screen Cache (Separate LRU logic) ---
|
|
|
|
public cacheMetadataScreen(id: string, type: string, data: any) {
|
|
if (!id || !type) return;
|
|
|
|
const key = `${type}:${id}`;
|
|
|
|
// If this item is already in cache, delete to re-insert at the end (most recent)
|
|
if (this.metadataScreenCache.has(key)) {
|
|
this.metadataScreenCache.delete(key);
|
|
}
|
|
// If we've reached the limit, remove the oldest item (first key)
|
|
else if (this.metadataScreenCache.size >= this.MAX_METADATA_SCREENS) {
|
|
const firstKey = this.metadataScreenCache.keys().next().value;
|
|
if (firstKey) {
|
|
this.metadataScreenCache.delete(firstKey);
|
|
}
|
|
}
|
|
|
|
// Add the new/updated item (makes it the most recent)
|
|
this.metadataScreenCache.set(key, data);
|
|
}
|
|
|
|
public getMetadataScreen(id: string, type: string) {
|
|
const key = `${type}:${id}`;
|
|
const data = this.metadataScreenCache.get(key);
|
|
// If found, mark as recently used by re-inserting
|
|
if (data) {
|
|
this.metadataScreenCache.delete(key);
|
|
this.metadataScreenCache.set(key, data);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
public clearMetadataScreenCache() {
|
|
this.metadataScreenCache.clear();
|
|
}
|
|
}
|
|
|
|
export const cacheService = CacheService.getInstance();
|