]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/video/sql/videos-id-list-query-builder.ts
Add ability to filter by file type
[github/Chocobozzz/PeerTube.git] / server / models / video / sql / videos-id-list-query-builder.ts
index 054f71c8c0e48a5b5166ebef9d2c4da93edbb562..4a882e7905b3fdf08a2fb59b728fa0c24e5430ee 100644 (file)
@@ -1,10 +1,11 @@
 import { Sequelize } from 'sequelize'
 import validator from 'validator'
 import { exists } from '@server/helpers/custom-validators/misc'
+import { WEBSERVER } from '@server/initializers/constants'
 import { buildDirectionAndField, createSafeIn } from '@server/models/utils'
 import { MUserAccountId, MUserId } from '@server/types/models'
-import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models'
-import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-builder'
+import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models'
+import { AbstractRunQuery } from './shared/abstract-run-query'
 
 /**
  *
@@ -12,20 +13,27 @@ import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-build
  *
  */
 
+export type DisplayOnlyForFollowerOptions = {
+  actorId: number
+  orLocalVideos: boolean
+}
+
 export type BuildVideosListQueryOptions = {
   attributes?: string[]
 
-  serverAccountId: number
-  followerActorId: number
-  includeLocalVideos: boolean
+  serverAccountIdForBlock: number
+
+  displayOnlyForFollower: DisplayOnlyForFollowerOptions
 
   count: number
   start: number
   sort: string
 
   nsfw?: boolean
-  filter?: VideoFilter
+  host?: string
   isLive?: boolean
+  isLocal?: boolean
+  include?: VideoInclude
 
   categoryOneOf?: number[]
   licenceOneOf?: number[]
@@ -33,7 +41,11 @@ export type BuildVideosListQueryOptions = {
   tagsOneOf?: string[]
   tagsAllOf?: string[]
 
-  withFiles?: boolean
+  uuids?: string[]
+
+  hasFiles?: boolean
+  hasHLSFiles?: boolean
+  hasWebtorrentFiles?: boolean
 
   accountId?: number
   videoChannelId?: number
@@ -62,7 +74,7 @@ export type BuildVideosListQueryOptions = {
   having?: string
 }
 
-export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
+export class VideosIdListQueryBuilder extends AbstractRunQuery {
   protected replacements: any = {}
 
   private attributes: string[]
@@ -95,8 +107,9 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
     return this.runQuery().then(rows => rows.length !== 0 ? rows[0].total : 0)
   }
 
-  getIdsListQueryAndSort (options: BuildVideosListQueryOptions) {
+  getQuery (options: BuildVideosListQueryOptions) {
     this.buildIdsListQuery(options)
+
     return { query: this.query, sort: this.sort, replacements: this.replacements }
   }
 
@@ -112,23 +125,34 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
       'INNER JOIN "actor" "accountActor" ON "account"."actorId" = "accountActor"."id"'
     ])
 
-    this.whereNotBlacklisted()
+    if (!(options.include & VideoInclude.BLACKLISTED)) {
+      this.whereNotBlacklisted()
+    }
+
+    if (options.serverAccountIdForBlock && !(options.include & VideoInclude.BLOCKED_OWNER)) {
+      this.whereNotBlocked(options.serverAccountIdForBlock, options.user)
+    }
 
-    if (options.serverAccountId) {
-      this.whereNotBlocked(options.serverAccountId, options.user)
+    // Only list published videos
+    if (!(options.include & VideoInclude.NOT_PUBLISHED_STATE)) {
+      this.whereStateAvailable()
     }
 
-    // Only list public/published videos
-    if (!options.filter || (options.filter !== 'all-local' && options.filter !== 'all')) {
-      this.whereStateAndPrivacyAvailable(options.user)
+    // Only list videos with the appropriate priavcy
+    if (!(options.include & VideoInclude.HIDDEN_PRIVACY)) {
+      this.wherePrivacyAvailable(options.user)
     }
 
     if (options.videoPlaylistId) {
       this.joinPlaylist(options.videoPlaylistId)
     }
 
-    if (options.filter && (options.filter === 'local' || options.filter === 'all-local')) {
-      this.whereOnlyLocal()
+    if (exists(options.isLocal)) {
+      this.whereLocal(options.isLocal)
+    }
+
+    if (options.host) {
+      this.whereHost(options.host)
     }
 
     if (options.accountId) {
@@ -139,14 +163,22 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
       this.whereChannelId(options.videoChannelId)
     }
 
-    if (options.followerActorId) {
-      this.whereFollowerActorId(options.followerActorId, options.includeLocalVideos)
+    if (options.displayOnlyForFollower) {
+      this.whereFollowerActorId(options.displayOnlyForFollower)
     }
 
-    if (options.withFiles === true) {
+    if (options.hasFiles === true) {
       this.whereFileExists()
     }
 
+    if (exists(options.hasWebtorrentFiles)) {
+      this.whereWebTorrentFileExists(options.hasWebtorrentFiles)
+    }
+
+    if (exists(options.hasHLSFiles)) {
+      this.whereHLSFileExists(options.hasHLSFiles)
+    }
+
     if (options.tagsOneOf) {
       this.whereTagsOneOf(options.tagsOneOf)
     }
@@ -155,6 +187,10 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
       this.whereTagsAllOf(options.tagsAllOf)
     }
 
+    if (options.uuids) {
+      this.whereUUIDs(options.uuids)
+    }
+
     if (options.nsfw === true) {
       this.whereNSFW()
     } else if (options.nsfw === false) {
@@ -270,12 +306,14 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
     this.replacements.videoPlaylistId = playlistId
   }
 
-  private whereStateAndPrivacyAvailable (user?: MUserAccountId) {
+  private whereStateAvailable () {
     this.and.push(
       `("video"."state" = ${VideoState.PUBLISHED} OR ` +
       `("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))`
     )
+  }
 
+  private wherePrivacyAvailable (user?: MUserAccountId) {
     if (user) {
       this.and.push(
         `("video"."privacy" = ${VideoPrivacy.PUBLIC} OR "video"."privacy" = ${VideoPrivacy.INTERNAL})`
@@ -287,8 +325,23 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
     }
   }
 
-  private whereOnlyLocal () {
-    this.and.push('"video"."remote" IS FALSE')
+  private whereLocal (isLocal: boolean) {
+    const isRemote = isLocal ? 'FALSE' : 'TRUE'
+
+    this.and.push('"video"."remote" IS ' + isRemote)
+  }
+
+  private whereHost (host: string) {
+    // Local instance
+    if (host === WEBSERVER.HOST) {
+      this.and.push('"accountActor"."serverId" IS NULL')
+      return
+    }
+
+    this.joins.push('INNER JOIN "server" ON "server"."id" = "accountActor"."serverId"')
+
+    this.and.push('"server"."host" = :host')
+    this.replacements.host = host
   }
 
   private whereAccountId (accountId: number) {
@@ -301,7 +354,7 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
     this.replacements.videoChannelId = channelId
   }
 
-  private whereFollowerActorId (followerActorId: number, includeLocalVideos: boolean) {
+  private whereFollowerActorId (options: { actorId: number, orLocalVideos: boolean }) {
     let query =
     '(' +
     '  EXISTS (' + // Videos shared by actors we follow
@@ -317,27 +370,42 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
     '    AND "actorFollow"."state" = \'accepted\'' +
     '  )'
 
-    if (includeLocalVideos) {
+    if (options.orLocalVideos) {
       query += '  OR "video"."remote" IS FALSE'
     }
 
     query += ')'
 
     this.and.push(query)
-    this.replacements.followerActorId = followerActorId
+    this.replacements.followerActorId = options.actorId
   }
 
   private whereFileExists () {
-    this.and.push(
-      '(' +
-      '  EXISTS (SELECT 1 FROM "videoFile" WHERE "videoFile"."videoId" = "video"."id") ' +
-      '  OR EXISTS (' +
-      '    SELECT 1 FROM "videoStreamingPlaylist" ' +
-      '    INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist"."id" ' +
-      '    WHERE "videoStreamingPlaylist"."videoId" = "video"."id"' +
-      '  )' +
-      ')'
-    )
+    this.and.push(`(${this.buildWebTorrentFileExistsQuery(true)} OR ${this.buildHLSFileExistsQuery(true)})`)
+  }
+
+  private whereWebTorrentFileExists (exists: boolean) {
+    this.and.push(this.buildWebTorrentFileExistsQuery(exists))
+  }
+
+  private whereHLSFileExists (exists: boolean) {
+    this.and.push(this.buildHLSFileExistsQuery(exists))
+  }
+
+  private buildWebTorrentFileExistsQuery (exists: boolean) {
+    const prefix = exists ? '' : 'NOT '
+
+    return prefix + 'EXISTS (SELECT 1 FROM "videoFile" WHERE "videoFile"."videoId" = "video"."id")'
+  }
+
+  private buildHLSFileExistsQuery (exists: boolean) {
+    const prefix = exists ? '' : 'NOT '
+
+    return prefix + 'EXISTS (' +
+    '  SELECT 1 FROM "videoStreamingPlaylist" ' +
+    '  INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist"."id" ' +
+    '  WHERE "videoStreamingPlaylist"."videoId" = "video"."id"' +
+    ')'
   }
 
   private whereTagsOneOf (tagsOneOf: string[]) {
@@ -367,6 +435,10 @@ export class VideosIdListQueryBuilder extends AbstractVideosQueryBuilder {
     )
   }
 
+  private whereUUIDs (uuids: string[]) {
+    this.and.push('"video"."uuid" IN (' + createSafeIn(this.sequelize, uuids) + ')')
+  }
+
   private whereCategoryOneOf (categoryOneOf: number[]) {
     this.and.push('"video"."category" IN (:categoryOneOf)')
     this.replacements.categoryOneOf = categoryOneOf