diff --git a/lib/content/probe.ts b/lib/content/probe.ts index 32c4388..6b32173 100644 --- a/lib/content/probe.ts +++ b/lib/content/probe.ts @@ -93,32 +93,29 @@ export function decodeBody(body: Buffer): DecodedSegment { } } -/** All of a (small) file whose first segment is already decoded; null when it is too big. */ -export async function readWhole(first: Buffer, segments: TaskSegment[], fetch: BodyFetcher): Promise { +/** + * All of a (small) file whose first segment is already decoded; null when it is too big. + * The first segment isn't downloaded again (it was, for every par2 set, NZB and SFV). + */ +export async function readWhole(first: DecodedSegment, segments: TaskSegment[], fetch: BodyFetcher): Promise { const bytes = segments.reduce((sum, p) => sum + (p.size ?? 0), 0) if (segments.length > WHOLE_MAX_PARTS || bytes > WHOLE_MAX_BYTES) return null - if (segments.length === 1) return first - let assembled: YencFile | undefined - for (const part of segments) { - const segment = decodeSegment(await fetch(part.id), { lenientSize: true }) - assembled ??= YencFile.fromSegment(segment) - assembled.add(segment) - } - return assembled!.getBuffer() + if (segments.length === 1) return first.data + const assembled = YencFile.fromSegment(first) + assembled.add(first) + for (const part of segments.slice(1)) assembled.add(decodeSegment(await fetch(part.id), { lenientSize: true })) + return assembled.getBuffer() } /** A par2 file whose first segment is already decoded, with its other parts if any; null when it is not par2. */ -export async function readPar2(first: Buffer, segments: TaskSegment[], fetch: BodyFetcher): Promise { - if (!isPar2(first)) return null +export async function readPar2(first: DecodedSegment, segments: TaskSegment[], fetch: BodyFetcher): Promise { + if (!isPar2(first.data)) return null if (segments.length > PAR2_MAX_PARTS) return null - if (segments.length === 1) return parsePar2(first) - let assembled: YencFile | undefined - for (const part of segments) { - const segment = decodeSegment(await fetch(part.id)) - assembled ??= YencFile.fromSegment(segment) - assembled.add(segment) - } - return parsePar2(assembled!.getBuffer()) + if (segments.length === 1) return parsePar2(first.data) + const assembled = YencFile.fromSegment(first) + assembled.add(first) + for (const part of segments.slice(1)) assembled.add(decodeSegment(await fetch(part.id))) + return parsePar2(assembled.getBuffer()) } export function isNzbEntry(entry: Pick): boolean { @@ -151,14 +148,14 @@ export async function probeInspection( return probe } if (/\.sfv$/i.test(task.name)) { - probe.text = (await readWhole(segment.data, task.segments, fetch))?.toString('latin1') ?? null + probe.text = (await readWhole(segment, task.segments, fetch))?.toString('latin1') ?? null return probe } if (contentType === 'nzb') { - probe.text = (await readWhole(segment.data, task.segments, fetch))?.toString('utf8') ?? null + probe.text = (await readWhole(segment, task.segments, fetch))?.toString('utf8') ?? null return probe } - probe.par2 = await readPar2(segment.data, task.segments, fetch) + probe.par2 = await readPar2(segment, task.segments, fetch) if (probe.par2) return probe if (contentType === '7z') { probe.archive = task.sevenZip ? await listSevenZip(segment.data, task.sevenZip.volumes, task.sevenZip.last, fetch, warn) : null @@ -168,7 +165,7 @@ export async function probeInspection( probe.archive = info if (info && hasNzbEntries(info)) { probe.archiveNzbs = [] - const archive = await readWhole(segment.data, task.segments, fetch) + const archive = await readWhole(segment, task.segments, fetch) const archived = archive && parseRarHeaders(archive) const entries = archived ? archived.entries.filter(isNzbEntry) : [] probe.archiveNzbEntries = entries.length diff --git a/tests/unit/probe.spec.ts b/tests/unit/probe.spec.ts index c8752a6..1b306a0 100644 --- a/tests/unit/probe.spec.ts +++ b/tests/unit/probe.spec.ts @@ -59,7 +59,8 @@ test.group('content probe', () => { const probe = await probeInspection(task, fetch) assert.equal(probe.contentType, 'nzb') assert.equal(probe.text, xml) - assert.lengthOf(fetched, task.segments.length + 1) // the first segment twice: once to sniff, once in the whole + // Every segment once: the first, decoded to sniff, is reused for the whole. + assert.deepEqual(fetched, task.segments.map((s) => s.id)) }) test('a 7z archive with encrypted headers is known from its tail, without bsdtar', async ({ assert }) => {