+import { registerTSPaths } from '../server/helpers/register-ts-paths'
+registerTSPaths()
+
import * as prompt from 'prompt'
-import { join } from 'path'
+import { join, basename } from 'path'
import { CONFIG } from '../server/initializers/config'
import { VideoModel } from '../server/models/video/video'
-import { initDatabaseModels } from '../server/initializers'
-import { remove, readdir } from 'fs-extra'
+import { initDatabaseModels } from '../server/initializers/database'
+import { readdir, remove, stat } from 'fs-extra'
import { VideoRedundancyModel } from '../server/models/redundancy/video-redundancy'
+import * as Bluebird from 'bluebird'
import { getUUIDFromFilename } from '../server/helpers/utils'
+import { ThumbnailModel } from '../server/models/video/thumbnail'
+import { ActorImageModel } from '../server/models/actor/actor-image'
+import { uniq, values } from 'lodash'
+import { ThumbnailType } from '@shared/models'
+import { VideoFileModel } from '@server/models/video/video-file'
run()
.then(() => process.exit(0))
})
async function run () {
- await initDatabaseModels(true)
+ const dirs = values(CONFIG.STORAGE)
- const storageOnlyOwnedToPrune = [
- CONFIG.STORAGE.VIDEOS_DIR,
- CONFIG.STORAGE.TORRENTS_DIR,
- CONFIG.STORAGE.REDUNDANCY_DIR
- ]
+ if (uniq(dirs).length !== dirs.length) {
+ console.error('Cannot prune storage because you put multiple storage keys in the same directory.')
+ process.exit(0)
+ }
- const storageForAllToPrune = [
- CONFIG.STORAGE.PREVIEWS_DIR,
- CONFIG.STORAGE.THUMBNAILS_DIR
- ]
+ await initDatabaseModels(true)
let toDelete: string[] = []
- for (const directory of storageOnlyOwnedToPrune) {
- toDelete = toDelete.concat(await pruneDirectory(directory, true))
- }
- for (const directory of storageForAllToPrune) {
- toDelete = toDelete.concat(await pruneDirectory(directory, false))
- }
+ console.log('Detecting files to remove, it could take a while...')
+
+ toDelete = toDelete.concat(
+ await pruneDirectory(CONFIG.STORAGE.VIDEOS_DIR, doesWebTorrentFileExist()),
+ await pruneDirectory(CONFIG.STORAGE.TORRENTS_DIR, doesTorrentFileExist()),
+
+ await pruneDirectory(CONFIG.STORAGE.REDUNDANCY_DIR, doesRedundancyExist),
+
+ await pruneDirectory(CONFIG.STORAGE.PREVIEWS_DIR, doesThumbnailExist(true, ThumbnailType.PREVIEW)),
+ await pruneDirectory(CONFIG.STORAGE.THUMBNAILS_DIR, doesThumbnailExist(false, ThumbnailType.MINIATURE)),
+
+ await pruneDirectory(CONFIG.STORAGE.ACTOR_IMAGES, doesActorImageExist)
+ )
const tmpFiles = await readdir(CONFIG.STORAGE.TMP_DIR)
toDelete = toDelete.concat(tmpFiles.map(t => join(CONFIG.STORAGE.TMP_DIR, t)))
}
}
-async function pruneDirectory (directory: string, onlyOwned = false) {
+type ExistFun = (file: string) => Promise<boolean>
+async function pruneDirectory (directory: string, existFun: ExistFun) {
const files = await readdir(directory)
const toDelete: string[] = []
- for (const file of files) {
- const uuid = getUUIDFromFilename(file)
- let video: VideoModel
- let localRedundancy: boolean
-
- if (uuid) {
- video = await VideoModel.loadByUUIDWithFile(uuid)
- localRedundancy = await VideoRedundancyModel.isLocalByVideoUUIDExists(uuid)
+ await Bluebird.map(files, async file => {
+ const filePath = join(directory, file)
+
+ if (await existFun(filePath) !== true) {
+ toDelete.push(filePath)
}
+ }, { concurrency: 20 })
+
+ return toDelete
+}
+
+function doesWebTorrentFileExist () {
+ return (filePath: string) => VideoFileModel.doesOwnedWebTorrentVideoFileExist(basename(filePath))
+}
- if (
- !uuid ||
- !video ||
- (onlyOwned === true && (video.isOwned() === false && localRedundancy === false))
- ) {
- toDelete.push(join(directory, file))
+function doesTorrentFileExist () {
+ return (filePath: string) => VideoFileModel.doesOwnedTorrentFileExist(basename(filePath))
+}
+
+function doesThumbnailExist (keepOnlyOwned: boolean, type: ThumbnailType) {
+ return async (filePath: string) => {
+ const thumbnail = await ThumbnailModel.loadByFilename(basename(filePath), type)
+ if (!thumbnail) return false
+
+ if (keepOnlyOwned) {
+ const video = await VideoModel.load(thumbnail.videoId)
+ if (video.isOwned() === false) return false
}
+
+ return true
}
+}
- return toDelete
+async function doesActorImageExist (filePath: string) {
+ const image = await ActorImageModel.loadByName(basename(filePath))
+
+ return !!image
+}
+
+async function doesRedundancyExist (filePath: string) {
+ const isPlaylist = (await stat(filePath)).isDirectory()
+
+ if (isPlaylist) {
+ const uuid = getUUIDFromFilename(filePath)
+ const video = await VideoModel.loadWithFiles(uuid)
+ if (!video) return false
+
+ const p = video.getHLSPlaylist()
+ if (!p) return false
+
+ const redundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(p.id)
+ return !!redundancy
+ }
+
+ const file = await VideoFileModel.loadByFilename(basename(filePath))
+ if (!file) return false
+
+ const redundancy = await VideoRedundancyModel.loadLocalByFileId(file.id)
+ return !!redundancy
}
async function askConfirmation () {
}
prompt.get(schema, function (err, result) {
if (err) return rej(err)
- return res(result.confirm && result.confirm.match(/y/) !== null)
+
+ return res(result.confirm?.match(/y/) !== null)
})
})
}