/** * What a searchable string of a release is, as release_texts.kind stores it (a smallint; voyager 3, migration 6200). * The table holds every string search matches against (the release name too), appended by whoever learns one: the * batch store the name, the prober the file names inside an archive, later passes NFO titles, par2 set names, * media titles. The numbers never change meaning once written. */ export const TEXT_KINDS = { /** The release name (releases.name, once more for the one trigram index). */ name: 1, /** A file name inside an archive (the prober's inner names). */ file: 2, /** A title read from an NFO. */ nfo: 3, /** A par2 set's name when it differs from the release name. */ par2: 4, /** A title from a media container's metadata. */ media: 5, } as const export type TextKind = keyof typeof TEXT_KINDS /** The longest string stored (release_texts.text is a varchar(255)); longer ones are cut, not refused. */ export const TEXT_MAX = 255 /** The most strings one writer appends per release and kind (an archive of two thousand files is not two thousand rows). */ export const TEXTS_PER_KIND = 20 const NAMES = Object.fromEntries( Object.entries(TEXT_KINDS).map(([name, code]) => [code, name]) ) as Record /** The code of a kind; throws for an unknown one (a typo would write a wrong number for good). */ export function textKindCode(name: string): number { const code = TEXT_KINDS[name as TextKind] if (code === undefined) throw new Error(`Unknown release text kind "${name}"`) return code } /** The name of a stored code; "unknown" for one not listed. */ export function textKindName(code: number | string | null | undefined): TextKind | 'unknown' { return NAMES[Number(code)] ?? 'unknown' } /** * The strings a writer appends for one kind: trimmed, cut to TEXT_MAX, empty ones and repeats dropped, the first * TEXTS_PER_KIND kept in the order given (the writer puts the most telling first). */ export function textsToStore(texts: Iterable): string[] { const seen = new Set() const out: string[] = [] for (const raw of texts) { const text = (raw ?? '').trim().slice(0, TEXT_MAX) if (!text || seen.has(text)) continue seen.add(text) out.push(text) if (out.length === TEXTS_PER_KIND) break } return out }