import { gunzipSync } from 'node:zlib' import db from '@adonisjs/lucid/services/db' import { decodeChunk, type ChunkFile } from '#lib/chunks/index' import { buildNzb } from '#lib/nzb/index' /** * A release's files and NZB (REBUILD.md, part 9), from its chunks in the * chunk store: every chunk decoded, its files in name order. A file stored * twice (the same name and first article, e.g. a range read again) is taken * once. */ class NzbService { async files(releaseId: number): Promise { const chunks = await db.from('release_chunks').where('release_id', releaseId).orderBy('id').select('payload') const files = new Map() for (const chunk of chunks) { for (const file of decodeChunk(chunk.payload)) files.set(`${file.name}\n${file.segments[0]?.[1] ?? ''}`, file) } return [...files.values()].sort((a, b) => a.name.localeCompare(b.name)) } async build(releaseId: number): Promise<{ name: string; xml: string } | null> { const release = await db.from('releases').where('id', releaseId).select('name', 'source').first() if (!release) return null // A release a spot made of its own (no files matched the header pass's): the spot's NZB as read. if (release.source === 'spot') { const spot = await db.from('spots').where('release_id', releaseId).whereNotNull('nzb').select('nzb').first() if (spot) return { name: release.name, xml: gunzipSync(spot.nzb).toString('utf8') } } const xml = buildNzb( (await this.files(releaseId)).map((f) => ({ name: f.name, poster: f.poster, date: f.postedAt ?? 0, groups: f.groups, segments: f.segments.map(([number, id, size]) => ({ number, id, size })), })), { title: release.name } ) return { name: release.name, xml } } } export default new NzbService()