import { BaseCommand, flags } from '@adonisjs/core/ace' import { CommandOptions } from '@adonisjs/core/types/ace' import env from '#start/env' import Shutdown from '#services/Shutdown' import NntpService from '#services/NntpService' import SpotService from '#services/SpotService' const IDLE_MS = 30_000 /** * Works through queued Spotnet spots (CONCEPT.md D29): verifies each one, * downloads its compressed NZB (a few KB to a few MB) and imports it. */ export default class SpotsWorker extends BaseCommand { public static commandName = 'worker:spots' public static description = 'Reads Spotnet spots: verifies them and imports their NZBs.' public static options: CommandOptions = { startApp: true, } @flags.number({ description: 'Stop after this many spots (default: keep running)' }) declare limit?: number public async run() { const concurrency = env.get('SPOT_CONCURRENCY', 4) this.logger.info(`Spot worker started (concurrency ${concurrency}).`) const counts: Record = {} let done = 0 try { while (!Shutdown.requested && (!this.limit || done < this.limit)) { const batch = await SpotService.claim(Math.min(concurrency * 4, this.limit ? this.limit - done : Infinity)) if (batch.length === 0) { if (this.limit) break await Shutdown.sleep(IDLE_MS) continue } let next = 0 await Promise.all( Array.from({ length: concurrency }, async () => { while (next < batch.length) { const status = await SpotService.process(batch[next++]) counts[status] = (counts[status] ?? 0) + 1 } }) ) done += batch.length this.logger.info(`${done} spots: ${Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(', ')}.`) } } finally { await NntpService.shutdown() } } }