(function (root) { 'use strict'; const pathApi = typeof require === 'function' ? require('path') : null; const protectedStatuses = new Set(['done', 'error', 'aborted', 'skipped']); const metadataVersion = 2; function normalizeFile(file, platform) { const value = typeof file === 'string' ? file.trim() : ''; if (!value) return ''; if (platform === 'win32') { if (pathApi) return pathApi.win32.resolve(value); return value.replace(/\//g, '\\'); } if (pathApi) return pathApi.resolve(value); return value; } function normalizeHoster(hoster) { return typeof hoster === 'string' ? hoster.trim().toLowerCase() : ''; } function uniqueHosters(values) { const hosters = []; const seen = new Set(); for (const value of values) { const hoster = normalizeHoster(value); if (!hoster || seen.has(hoster)) continue; seen.add(hoster); hosters.push(hoster); } return hosters; } function cloneFingerprint(fingerprint) { if (!fingerprint || typeof fingerprint !== 'object' || Array.isArray(fingerprint)) return null; return { ...fingerprint }; } function relatedJobs(queueJobs, job, platform) { if (!Array.isArray(queueJobs) || !job) return []; const file = normalizeFile(job.file, platform); const token = typeof job.sourceCleanupToken === 'string' && job.sourceCleanupToken ? job.sourceCleanupToken : null; return queueJobs.filter((candidate) => { if (!candidate) return false; if (file && normalizeFile(candidate.file, platform) === file) return true; return token !== null && candidate.sourceCleanupToken === token; }); } function storedRequiredHosters(jobs) { const values = []; for (const job of jobs) { if (Array.isArray(job.sourceCleanupRequiredHosters)) { values.push(...job.sourceCleanupRequiredHosters); } } return uniqueHosters(values); } function confirmedHosters(jobs, requiredHosters) { const values = []; for (const job of jobs) { if (job.sourceCleanupMetadataVersion === metadataVersion && Array.isArray(job.sourceCleanupConfirmedHosters)) { values.push(...job.sourceCleanupConfirmedHosters); } } const confirmed = new Set(uniqueHosters(values)); return requiredHosters.filter((hoster) => confirmed.has(hoster)); } function provisionalHosters(jobs, requiredHosters) { const values = []; for (const job of jobs) { if (job.sourceCleanupMetadataVersion === metadataVersion && Array.isArray(job.sourceCleanupProvisionalHosters)) { values.push(...job.sourceCleanupProvisionalHosters); } } const provisional = new Set(uniqueHosters(values)); return requiredHosters.filter((hoster) => provisional.has(hoster)); } function storedToken(jobs) { for (const job of jobs) { if (typeof job.sourceCleanupToken === 'string' && job.sourceCleanupToken) { return job.sourceCleanupToken; } } return null; } function storedFingerprint(jobs) { for (const job of jobs) { const fingerprint = cloneFingerprint(job.sourceCleanupFingerprint); if (fingerprint) return fingerprint; } return null; } function assignMetadata(jobs, token, requiredHosters, confirmed, provisional, fingerprint, touchedJobs, touchedSet) { for (const job of jobs) { job.sourceCleanupMetadataVersion = metadataVersion; job.sourceCleanupToken = token; job.sourceCleanupRequiredHosters = [...requiredHosters]; job.sourceCleanupConfirmedHosters = [...confirmed]; job.sourceCleanupProvisionalHosters = [...provisional]; job.sourceCleanupFingerprint = cloneFingerprint(fingerprint); delete job.sourceCleanupCompletedHosters; if (!touchedSet.has(job)) { touchedSet.add(job); touchedJobs.push(job); } } } function prepareGroups(queueJobs, jobsToStart, createToken, platform) { const groups = []; const touchedJobs = []; const touchedSet = new Set(); const preparedFiles = new Set(); const revokedHosters = []; const revokedSet = new Set(); if (!Array.isArray(queueJobs) || !Array.isArray(jobsToStart)) return { groups, touchedJobs, revokedHosters }; const currentRoundJobs = new Set(jobsToStart); for (const selectedJob of jobsToStart) { const file = normalizeFile(selectedJob && selectedJob.file, platform); if (!file || preparedFiles.has(file)) continue; const siblings = relatedJobs(queueJobs, selectedJob, platform); if (siblings.length === 0) continue; preparedFiles.add(file); const token = storedToken(siblings) || (typeof createToken === 'function' ? createToken(file) : null); if (typeof token !== 'string' || !token) continue; const persistedRequired = storedRequiredHosters(siblings); const requiredHosters = uniqueHosters([ ...persistedRequired, ...siblings.map((job) => job.hoster) ]); const startedHosters = new Set(uniqueHosters( siblings.filter((job) => currentRoundJobs.has(job)).map((job) => job.hoster) )); const storedConfirmed = confirmedHosters(siblings, requiredHosters); const confirmed = storedConfirmed.filter((hoster) => !startedHosters.has(hoster)); const provisional = provisionalHosters(siblings, requiredHosters) .filter((hoster) => !startedHosters.has(hoster)); for (const hoster of storedConfirmed) { if (!startedHosters.has(hoster) || revokedSet.has(hoster)) continue; revokedSet.add(hoster); revokedHosters.push(hoster); } const fingerprint = storedFingerprint(siblings); assignMetadata( siblings, token, requiredHosters, confirmed, provisional, fingerprint, touchedJobs, touchedSet ); groups.push({ token, file: selectedJob.file, requiredHosters: [...requiredHosters], confirmedHosters: [...confirmed], fingerprint: cloneFingerprint(fingerprint), jobs: siblings.map((job) => ({ jobId: job.id, hoster: normalizeHoster(job.hoster), status: job.status, currentRound: currentRoundJobs.has(job) })) }); } return { groups, touchedJobs, revokedHosters }; } function markCompleted(queueJobs, job, platform) { const siblings = relatedJobs(queueJobs, job, platform); if (siblings.length === 0) return []; const requiredHosters = storedRequiredHosters(siblings); const provisional = new Set(provisionalHosters(siblings, requiredHosters)); const hoster = normalizeHoster(job.hoster); if (requiredHosters.includes(hoster)) provisional.add(hoster); const orderedConfirmed = confirmedHosters(siblings, requiredHosters); const orderedProvisional = requiredHosters.filter((required) => provisional.has(required)); for (const sibling of siblings) { sibling.sourceCleanupMetadataVersion = metadataVersion; sibling.sourceCleanupConfirmedHosters = [...orderedConfirmed]; sibling.sourceCleanupProvisionalHosters = [...orderedProvisional]; delete sibling.sourceCleanupCompletedHosters; } return siblings; } async function persistRoundCompletions(queueJobs, options = {}) { if (!Array.isArray(queueJobs) || typeof options.persist !== 'function') return false; const historyPersisted = options.historyPersisted === true; const groupsByToken = new Map(); for (const job of queueJobs) { if (!job || typeof job.sourceCleanupToken !== 'string' || !job.sourceCleanupToken) continue; if (!groupsByToken.has(job.sourceCleanupToken)) groupsByToken.set(job.sourceCleanupToken, []); groupsByToken.get(job.sourceCleanupToken).push(job); } const snapshots = []; for (const jobs of groupsByToken.values()) { const requiredHosters = storedRequiredHosters(jobs); const confirmed = confirmedHosters(jobs, requiredHosters); const provisional = provisionalHosters(jobs, requiredHosters); const promoted = historyPersisted ? uniqueHosters([...confirmed, ...provisional]) : confirmed; const orderedPromoted = requiredHosters.filter((hoster) => promoted.includes(hoster)); for (const job of jobs) { snapshots.push({ job, confirmedHosters: job.sourceCleanupMetadataVersion === metadataVersion ? uniqueHosters(job.sourceCleanupConfirmedHosters) : [] }); job.sourceCleanupMetadataVersion = metadataVersion; job.sourceCleanupConfirmedHosters = [...orderedPromoted]; job.sourceCleanupProvisionalHosters = []; delete job.sourceCleanupCompletedHosters; } } let persisted = false; try { persisted = (await options.persist()) === true; } catch {} if (!persisted) { for (const snapshot of snapshots) { snapshot.job.sourceCleanupMetadataVersion = metadataVersion; snapshot.job.sourceCleanupConfirmedHosters = [...snapshot.confirmedHosters]; snapshot.job.sourceCleanupProvisionalHosters = []; delete snapshot.job.sourceCleanupCompletedHosters; } } return persisted; } function removeRequirement(queueJobs, job, platform) { if (!job || protectedStatuses.has(job.status)) return []; const siblings = relatedJobs(queueJobs, job, platform); const removedHoster = normalizeHoster(job.hoster); for (const sibling of siblings) { const required = Array.isArray(sibling.sourceCleanupRequiredHosters) ? sibling.sourceCleanupRequiredHosters : []; const confirmed = sibling.sourceCleanupMetadataVersion === metadataVersion && Array.isArray(sibling.sourceCleanupConfirmedHosters) ? sibling.sourceCleanupConfirmedHosters : []; const provisional = sibling.sourceCleanupMetadataVersion === metadataVersion && Array.isArray(sibling.sourceCleanupProvisionalHosters) ? sibling.sourceCleanupProvisionalHosters : []; sibling.sourceCleanupMetadataVersion = metadataVersion; sibling.sourceCleanupRequiredHosters = uniqueHosters(required) .filter((hoster) => hoster !== removedHoster); sibling.sourceCleanupConfirmedHosters = uniqueHosters(confirmed) .filter((hoster) => hoster !== removedHoster); sibling.sourceCleanupProvisionalHosters = uniqueHosters(provisional) .filter((hoster) => hoster !== removedHoster); delete sibling.sourceCleanupCompletedHosters; } return siblings; } function fingerprintFor(fingerprints, token) { if (fingerprints instanceof Map) return fingerprints.get(token); if (!fingerprints || typeof fingerprints !== 'object') return undefined; return Object.prototype.hasOwnProperty.call(fingerprints, token) ? fingerprints[token] : undefined; } function applyFingerprints(queueJobs, fingerprints) { if (!Array.isArray(queueJobs)) return []; const touchedJobs = []; for (const job of queueJobs) { if (!job || typeof job.sourceCleanupToken !== 'string' || !job.sourceCleanupToken) continue; const fingerprint = fingerprintFor(fingerprints, job.sourceCleanupToken); if (fingerprint === undefined) continue; job.sourceCleanupFingerprint = cloneFingerprint(fingerprint); touchedJobs.push(job); } return touchedJobs; } const api = { prepareGroups, markCompleted, persistRoundCompletions, removeRequirement, applyFingerprints }; if (typeof module !== 'undefined' && module.exports) { module.exports = api; } else if (root) { root.SourceCleanupPolicy = api; } })(typeof window !== 'undefined' ? window : this);