]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - scripts/prune-storage.ts
Limit github actions on PR
[github/Chocobozzz/PeerTube.git] / scripts / prune-storage.ts
index 4953a74399eb47a7239646bc5fee59b798149cca..1def1d792f882d7098445c07f9420d15bbb4c02e 100755 (executable)
@@ -1,11 +1,18 @@
+import { registerTSPaths } from '../server/helpers/register-ts-paths'
+registerTSPaths()
+
 import * as prompt from 'prompt'
 import { join } 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 } 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 { AvatarModel } from '../server/models/avatar/avatar'
+import { uniq, values } from 'lodash'
 
 run()
   .then(() => process.exit(0))
@@ -15,27 +22,28 @@ run()
   })
 
 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))
-  }
+  toDelete = toDelete.concat(
+    await pruneDirectory(CONFIG.STORAGE.VIDEOS_DIR, doesVideoExist(true)),
+    await pruneDirectory(CONFIG.STORAGE.TORRENTS_DIR, doesVideoExist(true)),
+
+    await pruneDirectory(CONFIG.STORAGE.REDUNDANCY_DIR, doesRedundancyExist),
+
+    await pruneDirectory(CONFIG.STORAGE.PREVIEWS_DIR, doesThumbnailExist(true)),
+    await pruneDirectory(CONFIG.STORAGE.THUMBNAILS_DIR, doesThumbnailExist(false)),
+
+    await pruneDirectory(CONFIG.STORAGE.AVATARS_DIR, doesAvatarExist)
+  )
 
   const tmpFiles = await readdir(CONFIG.STORAGE.TMP_DIR)
   toDelete = toDelete.concat(tmpFiles.map(t => join(CONFIG.STORAGE.TMP_DIR, t)))
@@ -61,30 +69,79 @@ async function run () {
   }
 }
 
-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) {
+  await Bluebird.map(files, async file => {
+    if (await existFun(file) !== true) {
+      toDelete.push(join(directory, file))
+    }
+  }, { concurrency: 20 })
+
+  return toDelete
+}
+
+function doesVideoExist (keepOnlyOwned: boolean) {
+  return async (file: string) => {
     const uuid = getUUIDFromFilename(file)
-    let video: VideoModel
-    let localRedundancy: boolean
+    const video = await VideoModel.loadByUUID(uuid)
 
-    if (uuid) {
-      video = await VideoModel.loadByUUIDWithFile(uuid)
-      localRedundancy = await VideoRedundancyModel.isLocalByVideoUUIDExists(uuid)
-    }
+    return video && (keepOnlyOwned === false || video.isOwned())
+  }
+}
 
-    if (
-      !uuid ||
-      !video ||
-      (onlyOwned === true && (video.isOwned() === false && localRedundancy === false))
-    ) {
-      toDelete.push(join(directory, file))
+function doesThumbnailExist (keepOnlyOwned: boolean) {
+  return async (file: string) => {
+    const thumbnail = await ThumbnailModel.loadByName(file)
+    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 doesAvatarExist (file: string) {
+  const avatar = await AvatarModel.loadByName(file)
+
+  return !!avatar
+}
+
+async function doesRedundancyExist (file: string) {
+  const uuid = getUUIDFromFilename(file)
+  const video = await VideoModel.loadWithFiles(uuid)
+
+  if (!video) return false
+
+  const isPlaylist = file.includes('.') === false
+
+  if (isPlaylist) {
+    const p = video.getHLSPlaylist()
+    if (!p) return false
+
+    const redundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(p.id)
+    return !!redundancy
+  }
+
+  const resolution = parseInt(file.split('-')[5], 10)
+  if (isNaN(resolution)) {
+    console.error('Cannot prune %s because we cannot guess guess the resolution.', file)
+    return true
+  }
+
+  const videoFile = video.getWebTorrentFile(resolution)
+  if (!videoFile) {
+    console.error('Cannot find webtorrent file of video %s - %d', video.url, resolution)
+    return true
+  }
+
+  const redundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id)
+  return !!redundancy
 }
 
 async function askConfirmation () {
@@ -104,7 +161,8 @@ 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)
     })
   })
 }