Files
usenet-indexer/lib/files/volumes.ts
T
Daan MeijerandClaude Opus 5.5 d773c91ad0
Test and deploy / test (push) Successful in 29s
Test and deploy / deploy (push) Skipped
Rebuild: phase 1 modules from the current app, ordered by pipeline stage
lib/nntp (XOVER client, Xref, GROUP), lib/headers (subjects, file keys,
segments from overview lines; the yEnc path stays behind for phase 2),
lib/release (identity, random names, now in lib), lib/files (aggregator,
volume names), lib/rules (posting tools, owner by priority, release
verdicts), lib/chunks (release chunk codec). Their unit tests came along;
new ones for codec, rules and volumes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:08:44 +02:00

46 lines
1.9 KiB
TypeScript

/**
* Files whose first segment says what an archive holds: named first RAR
* volumes (.part01.rar, .rar), 7z (.7z, .7z.001) and `.bin`, an extension
* uploaders use for archive volumes too (sniffed by content).
*/
export function isArchiveStart(name: string): boolean {
if (/\.bin$/i.test(name)) return true
const volume = sevenZipVolume(name)
if (volume === 0 || volume === 1) return true
return /\.part0*1\.rar$/i.test(name) || (/\.rar$/i.test(name) && !/\.part\d+\.rar$/i.test(name))
}
/**
* A later RAR volume (.part02.rar, .r00) or a par2 recovery volume
* (.vol03+04.par2): the volume number is readable even under a random stem.
* The set is listed from its first volume and named from its index .par2,
* so the first segment of these says nothing new (LOG.md 2026-10-04).
*/
export function isLaterVolume(name: string): boolean {
const part = /\.part(\d+)\.rar$/i.exec(name)
if (part) return Number(part[1]) > 1
return /\.r\d{2,3}$/i.test(name) || /\.vol\d+[+-]\d+\.par2$/i.test(name)
}
/**
* The set and 1-based number of a RAR volume: `x.part03.rar` is volume 3 of
* `x`, and in the old naming `x.rar` is 1, `x.r00` 2, `x.r01` 3. Null for
* other names.
*/
export function rarVolume(name: string): { base: string; number: number } | null {
const part = /^(.*)\.part(\d+)\.rar$/i.exec(name)
if (part) return { base: part[1].toLowerCase(), number: Number(part[2]) }
const old = /^(.*)\.r(\d{2,3})$/i.exec(name)
if (old) return { base: old[1].toLowerCase(), number: Number(old[2]) + 2 }
const first = /^(.*)\.rar$/i.exec(name)
if (first) return { base: first[1].toLowerCase(), number: 1 }
return null
}
/** Volume number of a split 7z name (`x.7z.001` → 1), 0 for an unsplit `.7z`, null otherwise. */
export function sevenZipVolume(name: string): number | null {
const split = /\.7z\.(\d{1,4})$/i.exec(name)
if (split) return Number(split[1])
return /\.7z$/i.test(name) ? 0 : null
}