diff --git a/packages/skin-database/addSkin.ts b/packages/skin-database/addSkin.ts index d506bf82..1d86320e 100644 --- a/packages/skin-database/addSkin.ts +++ b/packages/skin-database/addSkin.ts @@ -31,32 +31,41 @@ export async function addSkinFromBuffer( const ctx = new UserContext(); const md5 = md5Buffer(buffer); if (await SkinModel.exists(ctx, md5)) { + console.log("Skin found.") return { md5, status: "FOUND" }; } // Note: This will thrown on invalid skins. + console.log("Getting zip...") const zip = await JSZip.loadAsync(buffer); + console.log("Getting skin type...") const skinType = await getSkinType(zip); switch (skinType) { case "CLASSIC": + console.log("Adding classic skin...") return addClassicSkinFromBuffer(ctx, buffer, md5, filePath, uploader); case "MODERN": - return addModernSkinFromBuffer(buffer, md5, filePath, uploader); + return addModernSkinFromBuffer(ctx, buffer, md5, filePath, uploader); } } async function addModernSkinFromBuffer( + ctx: UserContext, buffer: Buffer, md5: string, filePath: string, uploader: string ): Promise { + console.log("Write temporarty file.") const tempFile = temp.path({ suffix: ".wal" }); fs.writeFileSync(tempFile, buffer); + console.log("Put skin to S3.") await S3.putSkin(md5, buffer, "wal"); + console.log("Add skin to DB") await Skins.addSkin({ + ctx, md5, filePath, uploader, @@ -97,6 +106,7 @@ async function addClassicSkinFromBuffer( await S3.putSkin(md5, buffer, "wsz"); await Skins.addSkin({ + ctx, md5, filePath, uploader, diff --git a/packages/skin-database/cli.ts b/packages/skin-database/cli.ts index 75d6ecce..159df88b 100755 --- a/packages/skin-database/cli.ts +++ b/packages/skin-database/cli.ts @@ -73,7 +73,7 @@ program .command("share") .description( "Share a skin on Twitter and Instagram. If no md5 is " + - "given, random approved skins are shared." + "given, random approved skins are shared." ) .argument("[md5]", "md5 of the skin to share") .option("-t, --twitter", "Share on Twitter") @@ -102,7 +102,12 @@ program .option( "--delete", "Delete a skin from the database, including its S3 files " + - "CloudFlare cache and seach index entries." + "CloudFlare cache and seach index entries." + ) + .option( + "--delete-local", + "Delete a skin from the database only, NOT including its S3 files " + + "CloudFlare cache and seach index entries." ) .option("--index", "Update the seach index for a skin.") .option( @@ -111,11 +116,14 @@ program ) .option("--reject", 'Give a skin a "rejected" review.') .option("--metadata", "Push metadata to the archive.") - .action(async (md5, { delete: del, index, refresh, reject, metadata }) => { + .action(async (md5, { delete: del, deleteLocal, index, refresh, reject, metadata }) => { const ctx = new UserContext("CLI"); if (del) { await Skins.deleteSkin(md5); } + if (deleteLocal) { + await Skins.deleteLocalSkin(md5); + } if (index) { console.log(await Skins.updateSearchIndex(ctx, md5)); } @@ -170,18 +178,18 @@ program .option( "--fetch-metadata ", "Fetch missing metadata for items from the Internet " + - "Archive. Currently it only fetches missing metadata. In the " + - "future it could refresh stale metadata." + "Archive. Currently it only fetches missing metadata. In the " + + "future it could refresh stale metadata." ) .option( "--fetch-items", "Seach the Internet Archive for items that we don't know about" + - "and add them to our database." + "and add them to our database." ) .option( "--update-metadata ", "Find items in our database that have incorrect or incomplete " + - "metadata, and update the Internet Archive" + "metadata, and update the Internet Archive" ) .option( "--upload-new", @@ -215,7 +223,7 @@ program .command("stats") .description( "Report information about skins in the database. " + - "Identical to `!stats` in Discord." + "Identical to `!stats` in Discord." ) .action(async () => { console.table([await Skins.getStats()]); @@ -261,17 +269,17 @@ program .option( "--likes", "Scrape @winampskins tweets for like and retweet counts, " + - "and update the database." + "and update the database." ) .option( "--milestones", "Check the most recent @winampskins tweets to see if they have " + - "passed a milestone. If so, notify the Discord channel." + "passed a milestone. If so, notify the Discord channel." ) .option( "--followers", "Check if @winampskins has passed a follower count milestone. " + - "If so, notify the Discord channel." + "If so, notify the Discord channel." ) .action(async ({ likes, milestones, followers }) => { if (likes) { @@ -299,7 +307,7 @@ program .option( "--upload-ia-screenshot ", "Upload a screenshot of a skin to the skin's Internet Archive itme. " + - "[[Warning!]] This might result in multiple screenshots on the item." + "[[Warning!]] This might result in multiple screenshots on the item." ) .option( "--upload-missing-screenshots", diff --git a/packages/skin-database/data/SkinModel.ts b/packages/skin-database/data/SkinModel.ts index 356bf3bf..b2da4904 100644 --- a/packages/skin-database/data/SkinModel.ts +++ b/packages/skin-database/data/SkinModel.ts @@ -27,7 +27,7 @@ export const IS_NOT_README = /(genex\.txt)|(genexinfo\.txt)|(gen_gslyrics\.txt)|(region\.txt)|(pledit\.txt)|(viscolor\.txt)|(winampmb\.txt)|("gen_ex help\.txt)|(mbinner\.txt)$/i; export default class SkinModel { - constructor(readonly ctx: UserContext, readonly row: SkinRow) {} + constructor(readonly ctx: UserContext, readonly row: SkinRow) { } static async fromMd5( ctx: UserContext, @@ -37,6 +37,13 @@ export default class SkinModel { return row == null ? null : new SkinModel(ctx, row); } + static clearMd5( + ctx: UserContext, + md5: string + ): void { + getSkinLoader(ctx).clear(md5); + } + static async fromMd5Assert( ctx: UserContext, md5: string diff --git a/packages/skin-database/data/skins.ts b/packages/skin-database/data/skins.ts index 14e63ba4..afb6e007 100644 --- a/packages/skin-database/data/skins.ts +++ b/packages/skin-database/data/skins.ts @@ -26,11 +26,13 @@ export function getScreenshotUrl(md5: string): string { } export async function addSkin({ + ctx, md5, filePath, uploader, modern, }: { + ctx: UserContext, md5: string; filePath: string; uploader: string; @@ -51,6 +53,7 @@ export async function addSkin({ }, [] ); + SkinModel.clearMd5(ctx, md5); } const CRUFT_FILENAME = /winampskins\.info\.(html)|(txt)$/; @@ -210,7 +213,21 @@ export async function updateSearchIndex( // Note: This might leave behind some files in file_info. export async function deleteSkin(md5: string): Promise { - console.log(`Deleting skin ${md5}...`); + await deleteLocalSkin(md5); + console.log(`Deleting skin ${md5} from external sources...`); + console.log(`... removing from Algolia index`); + await searchIndex.deleteObjects([md5]); + console.log(`... removing skin from S3`); + await S3.deleteSkin(md5); + console.log(`... removing screenshot from S3`); + await S3.deleteScreenshot(md5); + console.log(`... purging screenshot and skin from CloudFlare`); + await CloudFlare.purgeFiles([getScreenshotUrl(md5), getSkinUrl(md5)]); + console.log(`Done deleting skin ${md5} from external sources.`); +} + +export async function deleteLocalSkin(md5: string): Promise { + console.log(`Deleting skin ${md5} locally...`); console.log(`... sqlite "skins"`); await knex("skins").where({ md5 }).limit(1).delete(); console.log(`... sqlite "refreshes"`); @@ -231,15 +248,7 @@ export async function deleteSkin(md5: string): Promise { await knex("skin_uploads").where({ skin_md5: md5 }).delete(); console.log(`... sqlite "screenshot_updates"`); await knex("screenshot_updates").where({ skin_md5: md5 }).delete(); - console.log(`... removing from Algolia index`); - await searchIndex.deleteObjects([md5]); - console.log(`... removing skin from S3`); - await S3.deleteSkin(md5); - console.log(`... removing screenshot from S3`); - await S3.deleteScreenshot(md5); - console.log(`... purging screenshot and skin from CloudFlare`); - await CloudFlare.purgeFiles([getScreenshotUrl(md5), getSkinUrl(md5)]); - console.log(`Done deleting skin ${md5}.`); + console.log(`Done deleting skin ${md5} locally.`); } export async function recordScreenshotUpdate(