// eslint-disable-next-line import/no-named-as-default import KeyvSqlite from '@keyv/sqlite'; import { Cacheable, CacheableMemory, Keyv } from 'cacheable'; import { ContentType } from 'stremio-addon-sdk'; import { NotFoundError } from '../error'; import { Context, CountryCode } from '../types'; import { getCacheDir, Id } from '../utils'; export interface SourceResult { countryCode: CountryCode; title?: string; url: URL; } export abstract class Source { public abstract readonly id: string; public abstract readonly label: string; public readonly ttl: number = 43200000; // 12h public abstract readonly contentTypes: ContentType[]; public abstract readonly countryCodes: CountryCode[]; public abstract readonly baseUrl: string; private readonly sourceResultCache: Cacheable; public constructor() { this.sourceResultCache = new Cacheable({ primary: new Keyv({ store: new CacheableMemory({ lruSize: 1024, ttl: this.ttl }) }), secondary: new Keyv(new KeyvSqlite(`sqlite://${getCacheDir()}/webstreamr-source-cache.sqlite`)), }); } protected abstract handleInternal(ctx: Context, type: ContentType, id: Id): Promise<(SourceResult[])>; public async handle(ctx: Context, type: ContentType, id: Id): Promise<(SourceResult[])> { const cacheKey = `${this.id}_${id.toString()}`; let sourceResults = await this.sourceResultCache.get(cacheKey); if (sourceResults) { return sourceResults.map(sourceResult => ({ ...sourceResult, url: new URL(sourceResult.url) })); } try { sourceResults = await this.handleInternal(ctx, type, id); } catch (error) { if (error instanceof NotFoundError) { sourceResults = []; } else { throw error; } } await this.sourceResultCache.set(cacheKey, sourceResults); return sourceResults; } }