import type { ChannelConfig } from "../lib/channelConfig"; import type { SyncSchedulerSettings } from "../lib/settings"; // Pure cadence logic for the sync FULL SWEEP — the periodic deep pass that // re-enumerates a channel's entire listing in one yt-dlp spawn and derives three // outputs from it: a refreshed `playlist` file, the maybe-missing (upstream // deletion) diff, and the same newest-first download set the paged walk would // have picked. A full enumeration on a 5,000-VOD channel is far more expensive // than one 50-entry page, so it runs at most once per interval per channel while // ordinary syncs stay on the cheap paged walk. // // Mirrors resolveIntervalMinutes / overdueAmount in syncScheduler.ts: kept pure // and side-effect-free so the due rules are unit-testable without a server. // Resolve a channel's effective full-sweep interval in minutes. undefined // inherits the global default; 0 means the full sweep is disabled for the // channel (every sync stays a cheap paged walk). export function resolveFullSweepIntervalMinutes( config: ChannelConfig, scheduler: SyncSchedulerSettings, ): number { if (config.fullSweepIntervalMinutes === undefined) { return scheduler.fullSweepIntervalMinutes; } return config.fullSweepIntervalMinutes; } // Whether a sync starting at `now` should pay for the full enumeration. A // channel that has never swept is due immediately (that first sweep is what // seeds `playlist` and maybe-missing.json); an unparseable timestamp is treated // the same rather than wedging the channel out of sweeps forever. An interval of // 0 (or less) disables the sweep outright and always wins. export function isFullSweepDue( lastFullSweepAt: string | undefined, intervalMinutes: number, now: number, ): boolean { if (intervalMinutes <= 0) return false; if (!lastFullSweepAt) return true; const last = Date.parse(lastFullSweepAt); if (Number.isNaN(last)) return true; return now - last >= intervalMinutes * 60_000; }