From a9729e21d1cc5046c1a788448e347b62e0c45c53 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 29 May 2018 11:11:52 +0200 Subject: [PATCH] Add script that prunes storage files --- package.json | 1 + scripts/prune-storage.ts | 96 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100755 scripts/prune-storage.ts diff --git a/package.json b/package.json index 5d8cbd23d..c4ee202e1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "help": "scripty", "generate-api-doc": "scripty", "parse-log": "node ./dist/scripts/parse-log.js", + "prune-storage": "node ./dist/scripts/prune-storage.js", "postinstall": "cd client && yarn install --pure-lockfile", "tsc": "tsc", "spectacle-docs": "node_modules/spectacle-docs/bin/spectacle.js", diff --git a/scripts/prune-storage.ts b/scripts/prune-storage.ts new file mode 100755 index 000000000..1973725c8 --- /dev/null +++ b/scripts/prune-storage.ts @@ -0,0 +1,96 @@ +import * as prompt from 'prompt' +import { createReadStream } from 'fs' +import { join } from 'path' +import { createInterface } from 'readline' +import { readdirPromise, unlinkPromise } from '../server/helpers/core-utils' +import { CONFIG } from '../server/initializers/constants' +import { VideoModel } from '../server/models/video/video' +import { initDatabaseModels } from '../server/initializers' + +run() + .then(() => process.exit(0)) + .catch(err => { + console.error(err) + process.exit(-1) + }) + +async function run () { + await initDatabaseModels(true) + + const storageToPrune = [ + CONFIG.STORAGE.VIDEOS_DIR, + CONFIG.STORAGE.PREVIEWS_DIR, + CONFIG.STORAGE.THUMBNAILS_DIR, + CONFIG.STORAGE.TORRENTS_DIR + ] + + let toDelete: string[] = [] + for (const directory of storageToPrune) { + toDelete = toDelete.concat(await pruneDirectory(directory)) + } + + if (toDelete.length === 0) { + console.log('No files to delete.') + return + } + + console.log('Will delete %d files:\n\n%s\n\n', toDelete.length, toDelete.join('\n')) + + const res = await askConfirmation() + if (res === true) { + console.log('Processing delete...\n') + + for (const path of toDelete) { + await unlinkPromise(path) + } + + console.log('Done!') + } else { + console.log('Exiting without deleting files.') + } +} + +async function pruneDirectory (directory: string) { + const files = await readdirPromise(directory) + + const toDelete: string[] = [] + for (const file of files) { + const uuid = getUUIDFromFilename(file) + let video: VideoModel + + if (uuid) video = await VideoModel.loadByUUID(uuid) + + if (!uuid || !video) toDelete.push(join(directory, file)) + } + + return toDelete +} + +function getUUIDFromFilename (filename: string) { + const regex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ + const result = filename.match(regex) + + if (!result || Array.isArray(result) === false) return null + + return result[0] +} + +async function askConfirmation () { + return new Promise((res, rej) => { + prompt.start() + const schema = { + properties: { + confirm: { + type: 'string', + description: 'Are you sure you want to delete these files? Please check carefully', + default: 'n', + required: true + } + } + } + prompt.get(schema, function (err, result) { + if (err) return rej(err) + return res(result.confirm && result.confirm.match(/y/) !== null) + }) + }) +}