import db from '@adonisjs/lucid/services/db' import type { QueryClientContract } from '@adonisjs/lucid/types/database' import { packSegments, unpackSegments, type Segment } from '#lib/nzb/segments' /** * The only code that knows how `files` keeps its segment lists (CONCEPT.md * D31): compressed in `files.segments`, with the first segment's message-id * in the indexed `first_message_id` for lookups. Read them with * `segmentsOf(file)`; write new files with `FileStore.insert`. */ /** A new `files` row, with its segments as a list. */ export type NewFile = Record & { segments: Segment[] } const unpacked = new WeakMap() /** The segments of a `files` row (a row from `db.from('files')`), in order. */ export function segmentsOf(file: { segments?: Buffer | Segment[] | null }): Segment[] { const { segments } = file if (!segments) return [] if (Array.isArray(segments)) return segments let list = unpacked.get(file) if (!list) { list = unpackSegments(segments) unpacked.set(file, list) } return list } /** Stored form of a segment list, for the File model's column. */ export function storedSegments(segments: Segment[]) { return packSegments(segments) } export function loadedSegments(stored: Buffer | null): Segment[] { return stored ? unpackSegments(stored) : [] } class FileStore { /** * Inserts files, skipping those whose `file_key` exists. Returns the rows * that were inserted (id and filename). */ async insert(files: NewFile[], client: Pick = db): Promise<{ id: number; filename: string }[]> { if (files.length === 0) return [] const rows = files.map(({ segments, ...file }) => { const sorted = [...segments].sort((a, b) => a.number - b.number) return { ...file, segments: packSegments(sorted), first_message_id: sorted[0]?.id ?? null } }) const inserted = await client.knexQuery().table('files').insert(rows).onConflict('file_key').ignore().returning(['id', 'filename']) return inserted.map((row: any) => ({ id: Number(row.id), filename: row.filename })) } } export default new FileStore()