Files
claude 9e33a343ab
Some checks failed
release / build-release (push) Has been cancelled
Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
- IChannel: creators -> sub-channels -> video posts (public listing API, search mapped)
- IRequiresMediaInfoCallback -> signed proxy URLs; stream controller rewrites HLS so the
  AES-128 key is fetched server-side with the session cookie, segments stay on the CDN
- delivery/info cached per token lifetime and throttled (~2/min)
- config page: paste sails.sid + validate, extra creators, quality, search test
- scripts/package.sh -> plugin folder + zip + md5 + manifest.json; Gitea release workflow
- research docs, HAR analysis (HAR itself gitignored), install-pi5.sh

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-25 09:42:27 +01:00

62 lines
1.6 KiB
TypeScript

import type { JSONSafeValue } from "@inrixia/db";
import { writeFile } from "fs/promises";
import { readFileSync } from "fs";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type FetchItem<T> = (id: string, options?: any) => Promise<T>;
export class ItemCache<T extends JSONSafeValue> {
private cache: Record<string, { i: T; t: number }>;
private cachePath: string;
private fetchItem: FetchItem<T>;
private expireAgeMs: number;
constructor(cachePath: string, fetchItem: FetchItem<T>, expireAgeMins: number = 24 * 60) {
this.cachePath = cachePath;
this.fetchItem = fetchItem;
this.expireAgeMs = expireAgeMins * 60 * 1000;
try {
this.cache = JSON.parse(readFileSync(this.cachePath).toString());
} catch {
this.cache = {};
}
}
private async writeOut() {
try {
await writeFile(this.cachePath, JSON.stringify(this.cache));
} catch {
return;
}
}
private async set(key: string, i: T) {
this.cache[key] = { t: Date.now(), i };
await this.writeOut();
return i;
}
private deepCopy(i: T): T {
return JSON.parse(JSON.stringify(i));
}
public async get(id: string, options?: unknown, noCache: boolean = false): Promise<T> {
const key = options !== undefined ? JSON.stringify([id, options]) : id;
if (noCache) {
delete this.cache[key];
return this.get(id, options);
}
const cacheItem = this.cache[key];
if (cacheItem !== undefined) {
// Remove expired entries older than expireAge
if (Date.now() - cacheItem.t > this.expireAgeMs) {
delete this.cache[key];
return this.get(id, options);
}
return this.deepCopy(cacheItem.i);
}
return this.set(key, await this.fetchItem(id, options));
}
}