mirror of
https://github.com/misenhower/splatoon3.ink.git
synced 2026-08-23 09:04:03 -05:00
162 lines
4.6 KiB
JavaScript
162 lines
4.6 KiB
JavaScript
import * as Sentry from '@sentry/node';
|
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
import prefixedConsole from '../common/prefixedConsole.mjs';
|
|
import ArchiveBuilder from './ArchiveBuilder.mjs';
|
|
import ArchiveInventory from './ArchiveInventory.mjs';
|
|
import { AppleDoubleArchiveError } from './ArchiveContentVerifier.mjs';
|
|
import RemoteArchiveVerifier from './RemoteArchiveVerifier.mjs';
|
|
|
|
export function verifyArchives(maxDays = Infinity, repair = false) {
|
|
let verification = new ArchiveVerifyCommand;
|
|
verification.maxDays = maxDays;
|
|
verification.repair = repair;
|
|
|
|
return verification.process();
|
|
}
|
|
|
|
export function verifyArchivesFromCli(args) {
|
|
let maxDays = Infinity;
|
|
let repair = false;
|
|
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (args[i] === '--repair') {
|
|
repair = true;
|
|
} else if (args[i] === '--max-days' && /^\d+$/.test(args[i + 1])) {
|
|
maxDays = Number(args[++i]);
|
|
} else {
|
|
throw new Error(
|
|
'Usage: npm run data:archive:verify -- [--repair] [--max-days DAYS]',
|
|
);
|
|
}
|
|
}
|
|
|
|
if (maxDays < 1) {
|
|
throw new Error('--max-days must be at least 1');
|
|
}
|
|
|
|
return verifyArchives(maxDays, repair);
|
|
}
|
|
|
|
export default class ArchiveVerifyCommand
|
|
{
|
|
maxDays = Infinity;
|
|
repair = false;
|
|
|
|
constructor(s3Client, remoteArchiveVerifier, archiveBuilder, archiveInventory) {
|
|
this._client = s3Client;
|
|
this._remoteArchiveVerifier = remoteArchiveVerifier;
|
|
this._archiveBuilder = archiveBuilder;
|
|
this._archiveInventory = archiveInventory;
|
|
}
|
|
|
|
async process() {
|
|
if (!this.canRun) {
|
|
this.console.log('Skipping archive verification');
|
|
|
|
return;
|
|
}
|
|
|
|
let currentDate;
|
|
let affected = 0;
|
|
let checked = 0;
|
|
let repaired = 0;
|
|
|
|
try {
|
|
let candidates = await this.getCandidates();
|
|
this.console.log(`Found ${candidates.length} dates to verify`);
|
|
|
|
for (let candidate of candidates) {
|
|
if ((this.repair ? affected : checked) >= this.maxDays) {
|
|
break;
|
|
}
|
|
|
|
currentDate = candidate.date;
|
|
let sourceObjects = candidate.hasSource
|
|
? await this.archiveInventory.getSourceObjects(candidate.prefix)
|
|
: null;
|
|
checked++;
|
|
|
|
try {
|
|
await this.remoteArchiveVerifier.verify(candidate, sourceObjects);
|
|
this.console.log(`Verified ${candidate.date}`);
|
|
} catch (error) {
|
|
if (!(error instanceof AppleDoubleArchiveError)) {
|
|
throw error;
|
|
}
|
|
|
|
affected++;
|
|
if (!this.repair) {
|
|
this.console.log(`${candidate.date} contains unexpected macOS metadata`);
|
|
} else {
|
|
if (!sourceObjects) {
|
|
throw new Error(`Cannot repair ${candidate.date}; its source files have been pruned`);
|
|
}
|
|
await this.archiveBuilder.build(candidate, sourceObjects);
|
|
repaired++;
|
|
this.console.log(`Repaired ${candidate.date}`);
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
this.console.error(error);
|
|
Sentry.withScope(scope => {
|
|
if (currentDate) {
|
|
scope.setTag('archive.date', currentDate);
|
|
}
|
|
Sentry.captureException(error);
|
|
});
|
|
await Sentry.flush(2000).catch(() => {});
|
|
throw error;
|
|
}
|
|
|
|
if (this.repair) {
|
|
this.console.log(`Verified ${checked} daily archives; repaired ${repaired}`);
|
|
} else {
|
|
this.console.log(`Verified ${checked} daily archives; ${affected} need repair`);
|
|
}
|
|
}
|
|
|
|
get console() {
|
|
this._console ??= prefixedConsole('Archive Verification');
|
|
|
|
return this._console;
|
|
}
|
|
|
|
get canRun() {
|
|
return process.env.AWS_S3_ENDPOINT
|
|
&& process.env.AWS_REGION
|
|
&& process.env.AWS_S3_ARCHIVE_BUCKET
|
|
&& process.env.AWS_ACCESS_KEY_ID
|
|
&& process.env.AWS_SECRET_ACCESS_KEY;
|
|
}
|
|
|
|
get s3Client() {
|
|
return this._client ??= new S3Client({
|
|
endpoint: process.env.AWS_S3_ENDPOINT,
|
|
region: process.env.AWS_REGION,
|
|
requestChecksumCalculation: 'WHEN_REQUIRED',
|
|
responseChecksumValidation: 'WHEN_REQUIRED',
|
|
credentials: {
|
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
},
|
|
});
|
|
}
|
|
|
|
get remoteArchiveVerifier() {
|
|
return this._remoteArchiveVerifier ??= new RemoteArchiveVerifier(this.s3Client);
|
|
}
|
|
|
|
get archiveBuilder() {
|
|
return this._archiveBuilder ??= new ArchiveBuilder(this.s3Client);
|
|
}
|
|
|
|
get archiveInventory() {
|
|
return this._archiveInventory ??= new ArchiveInventory(this.s3Client);
|
|
}
|
|
|
|
async getCandidates() {
|
|
return (await this.archiveInventory.getDates()).filter(item => item.hasArchive);
|
|
}
|
|
}
|