export function sanitize(name: string): string { return name.replace(/[/\\:*?"<>|]/g, '_').trim(); } export function sanitizePathSegment(name: unknown, fallback: string): string { const value = sanitize(String(name || '')).replace(/[. ]+$/g, ''); return value && value !== '.' && value !== '..' ? value : fallback; } // "A/B" "A /B" -> "A / B" export function formatPersonNames(value: string): string; export function formatPersonNames(value: string | undefined): string | undefined; export function formatPersonNames(value: string | undefined): string | undefined { return typeof value === 'string' ? value.replace(/\s*\/\s*/g, ' / ') : value; } export function joinNames(names: Array<{ name?: string } | string | undefined> | undefined): string { return (names || []) .map((item) => (typeof item === 'string' ? item : item?.name)) .filter(Boolean) .join(' / '); } export function sleep(ms: number, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { if (signal?.aborted) return reject(signal.reason); const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms); const onAbort = () => { clearTimeout(timer); reject(signal!.reason); }; signal?.addEventListener('abort', onAbort, { once: true }); }); } export function errorMessage(e: unknown): string { return e instanceof Error ? e.message : String(e); } // 简单的带过期时间的内存缓存,用于专辑详情等批量下载时反复请求的数据 export class TtlCache { private map = new Map(); private readonly ttlMs: number; private readonly max: number; constructor(ttlMs: number, max = 500) { this.ttlMs = ttlMs; this.max = max; } async getOrSet(key: string, load: () => Promise): Promise { const hit = this.map.get(key); if (hit && hit.expires > Date.now()) return hit.value; const value = await load(); if (this.map.size >= this.max) this.map.delete(this.map.keys().next().value!); this.map.set(key, { value, expires: Date.now() + this.ttlMs }); return value; } }