]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - scripts/prune-storage.ts
Don't send undefined with HTML input
[github/Chocobozzz/PeerTube.git] / scripts / prune-storage.ts
index c9e4dbd4b8094bead529388148c9ab0dda73a0a1..d19594a60ed45122fb497e83344422a44cd43b5f 100755 (executable)
@@ -1,11 +1,19 @@
-import * as prompt from 'prompt'
-import { join } from 'path'
-import { CONFIG } from '../server/initializers/constants'
-import { VideoModel } from '../server/models/video/video'
-import { initDatabaseModels } from '../server/initializers'
-import { remove, readdir } from 'fs-extra'
-import { VideoRedundancyModel } from '../server/models/redundancy/video-redundancy'
+import { map } from 'bluebird'
+import { readdir, remove, stat } from 'fs-extra'
+import { basename, join } from 'path'
+import { get, start } from 'prompt'
+import { DIRECTORIES } from '@server/initializers/constants'
+import { VideoFileModel } from '@server/models/video/video-file'
+import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
+import { uniqify } from '@shared/core-utils'
+import { ThumbnailType } from '@shared/models'
 import { getUUIDFromFilename } from '../server/helpers/utils'
+import { CONFIG } from '../server/initializers/config'
+import { initDatabaseModels } from '../server/initializers/database'
+import { ActorImageModel } from '../server/models/actor/actor-image'
+import { VideoRedundancyModel } from '../server/models/redundancy/video-redundancy'
+import { ThumbnailModel } from '../server/models/video/thumbnail'
+import { VideoModel } from '../server/models/video/video'
 
 run()
   .then(() => process.exit(0))
@@ -15,27 +23,35 @@ run()
   })
 
 async function run () {
-  await initDatabaseModels(true)
+  const dirs = Object.values(CONFIG.STORAGE)
 
-  const storageOnlyOwnedToPrune = [
-    CONFIG.STORAGE.VIDEOS_DIR,
-    CONFIG.STORAGE.TORRENTS_DIR,
-    CONFIG.STORAGE.REDUNDANCY_DIR
-  ]
+  if (uniqify(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(DIRECTORIES.VIDEOS.PUBLIC, doesWebTorrentFileExist()),
+    await pruneDirectory(DIRECTORIES.VIDEOS.PRIVATE, doesWebTorrentFileExist()),
+
+    await pruneDirectory(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, doesHLSPlaylistExist()),
+    await pruneDirectory(DIRECTORIES.HLS_STREAMING_PLAYLIST.PUBLIC, doesHLSPlaylistExist()),
+
+    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)))
@@ -61,35 +77,92 @@ async function run () {
   }
 }
 
-async function pruneDirectory (directory: string, onlyOwned = false) {
+type ExistFun = (file: string) => Promise<boolean> | 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 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) => {
+    // Don't delete private directory
+    if (filePath === DIRECTORIES.VIDEOS.PRIVATE) return true
+
+    return VideoFileModel.doesOwnedWebTorrentVideoFileExist(basename(filePath))
+  }
+}
+
+function doesHLSPlaylistExist () {
+  return (hlsPath: string) => {
+    // Don't delete private directory
+    if (hlsPath === DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE) return true
+
+    return VideoStreamingPlaylistModel.doesOwnedHLSPlaylistExist(basename(hlsPath))
+  }
+}
 
-    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) {
+    // Don't delete HLS redundancy directory
+    if (filePath === DIRECTORIES.HLS_REDUNDANCY) return true
+
+    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 () {
   return new Promise((res, rej) => {
-    prompt.start()
+    start()
     const schema = {
       properties: {
         confirm: {
@@ -102,9 +175,10 @@ async function askConfirmation () {
         }
       }
     }
-    prompt.get(schema, function (err, result) {
+    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)
     })
   })
 }