One table and one trigram index for every string search matches against: the release name (kind 1, stored once more) and what anyone learns later (the prober's file names inside an archive, NFO titles, par2 set names, media titles), appended by insert, never an update of the releases row. Kinds and the per-kind cap in lib/release/text_kind.ts (textsToStore). The judge's dirty mark follows an insert or delete. Migration 6000 loses its two trigram indexes on releases. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
59 lines
2.3 KiB
TypeScript
59 lines
2.3 KiB
TypeScript
/**
|
|
* 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<number, TextKind>
|
|
|
|
/** 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 | null | undefined>): string[] {
|
|
const seen = new Set<string>()
|
|
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
|
|
}
|