1
0
Fork 0

Use separate queries for video files

This commit is contained in:
Chocobozzz 2021-06-10 16:57:13 +02:00
parent d9bf974f5d
commit 1d43c3a613
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
9 changed files with 232 additions and 57 deletions

View File

@ -147,7 +147,7 @@ async function getVideo (_req: express.Request, res: express.Response) {
const video = await Hooks.wrapPromiseFun(
VideoModel.loadForGetAPI,
{ id: res.locals.onlyVideoWithRights.id, userId },
{ id: _req.params.id, userId },
'filter:api.video.get.result'
)

View File

@ -1,19 +1,24 @@
import validator from 'validator'
import { AbstractVideosQueryBuilder } from './abstract-videos-query-builder'
import { VideoAttributes } from './video-attributes'
import { VideoModelBuilder } from './video-model-builder'
/**
*
* Abstract builder to create SQL query and fetch video models
*
*/
export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder {
protected attributes: { [key: string]: string } = {}
protected joins: string[] = []
protected where: string
protected videoAttributes: VideoAttributes
protected videoModelBuilder: VideoModelBuilder
constructor (private readonly mode: 'list' | 'get') {
constructor (protected readonly mode: 'list' | 'get') {
super()
this.videoAttributes = new VideoAttributes(this.mode)
this.videoModelBuilder = new VideoModelBuilder(this.mode, this.videoAttributes)
}
protected buildSelect () {
@ -78,21 +83,30 @@ export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder
}
}
protected includeFiles () {
protected includeWebtorrentFiles (required: boolean) {
const joinType = required ? 'INNER' : 'LEFT'
this.joins.push(joinType + ' JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"')
this.attributes = {
...this.attributes,
...this.buildAttributesObject('VideoFiles', this.videoAttributes.getFileAttributes())
}
}
protected includeStreamingPlaylistFiles (required: boolean) {
const joinType = required ? 'INNER' : 'LEFT'
this.joins.push(
'LEFT JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"',
joinType + ' JOIN "videoStreamingPlaylist" AS "VideoStreamingPlaylists" ON "VideoStreamingPlaylists"."videoId" = "video"."id"',
'LEFT JOIN "videoStreamingPlaylist" AS "VideoStreamingPlaylists" ON "VideoStreamingPlaylists"."videoId" = "video"."id"',
'LEFT JOIN "videoFile" AS "VideoStreamingPlaylists->VideoFiles" ' +
joinType + ' JOIN "videoFile" AS "VideoStreamingPlaylists->VideoFiles" ' +
'ON "VideoStreamingPlaylists->VideoFiles"."videoStreamingPlaylistId" = "VideoStreamingPlaylists"."id"'
)
this.attributes = {
...this.attributes,
...this.buildAttributesObject('VideoFiles', this.videoAttributes.getFileAttributes()),
...this.buildAttributesObject('VideoStreamingPlaylists', this.videoAttributes.getStreamingPlaylistAttributes()),
...this.buildAttributesObject('VideoStreamingPlaylists->VideoFiles', this.videoAttributes.getFileAttributes())
}
@ -196,11 +210,8 @@ export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder
}
}
protected includeRedundancies () {
protected includeWebTorrentRedundancies () {
this.joins.push(
'LEFT OUTER JOIN "videoRedundancy" AS "VideoStreamingPlaylists->RedundancyVideos" ' +
'ON "VideoStreamingPlaylists"."id" = "VideoStreamingPlaylists->RedundancyVideos"."videoStreamingPlaylistId"',
'LEFT OUTER JOIN "videoRedundancy" AS "VideoFiles->RedundancyVideos" ON ' +
'"VideoFiles"."id" = "VideoFiles->RedundancyVideos"."videoFileId"'
)
@ -208,7 +219,19 @@ export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder
this.attributes = {
...this.attributes,
...this.buildAttributesObject('VideoFiles->RedundancyVideos', this.videoAttributes.getRedundancyAttributes()),
...this.buildAttributesObject('VideoFiles->RedundancyVideos', this.videoAttributes.getRedundancyAttributes())
}
}
protected includeStreamingPlaylistRedundancies () {
this.joins.push(
'LEFT OUTER JOIN "videoRedundancy" AS "VideoStreamingPlaylists->RedundancyVideos" ' +
'ON "VideoStreamingPlaylists"."id" = "VideoStreamingPlaylists->RedundancyVideos"."videoStreamingPlaylistId"'
)
this.attributes = {
...this.attributes,
...this.buildAttributesObject('VideoStreamingPlaylists->RedundancyVideos', this.videoAttributes.getRedundancyAttributes())
}
}
@ -236,4 +259,14 @@ export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder
return result
}
protected whereId (id: string | number) {
if (validator.isInt('' + id)) {
this.where = 'WHERE "video".id = :videoId'
} else {
this.where = 'WHERE uuid = :videoId'
}
this.replacements.videoId = id
}
}

View File

@ -1,6 +1,12 @@
import { QueryTypes, Sequelize, Transaction } from 'sequelize'
import { logger } from '@server/helpers/logger'
/**
*
* Abstact builder to run video SQL queries
*
*/
export class AbstractVideosQueryBuilder {
protected sequelize: Sequelize

View File

@ -1,3 +1,9 @@
/**
*
* Class to build video attributes we want to fetch from the database
*
*/
export class VideoAttributes {
constructor (readonly mode: 'get' | 'list') {

View File

@ -0,0 +1,66 @@
import { Sequelize } from 'sequelize'
import { BuildVideoGetQueryOptions } from '../video-model-get-query-builder'
import { AbstractVideosModelQueryBuilder } from './abstract-videos-model-query-builder'
/**
*
* Fetch files (webtorrent and streaming playlist) according to a video
*
*/
export class VideoFileQueryBuilder extends AbstractVideosModelQueryBuilder {
protected attributes: { [key: string]: string }
protected joins: string[] = []
constructor (protected readonly sequelize: Sequelize) {
super('get')
}
queryWebTorrentVideos (options: BuildVideoGetQueryOptions) {
this.buildWebtorrentFilesQuery(options)
return this.runQuery(options.transaction, true)
}
queryStreamingPlaylistVideos (options: BuildVideoGetQueryOptions) {
this.buildVideoStreamingPlaylistFilesQuery(options)
return this.runQuery(options.transaction, true)
}
private buildWebtorrentFilesQuery (options: BuildVideoGetQueryOptions) {
this.attributes = {
'"video"."id"': ''
}
this.includeWebtorrentFiles(true)
if (options.forGetAPI === true) {
this.includeWebTorrentRedundancies()
}
this.whereId(options.id)
this.query = this.buildQuery()
}
private buildVideoStreamingPlaylistFilesQuery (options: BuildVideoGetQueryOptions) {
this.attributes = {
'"video"."id"': ''
}
this.includeStreamingPlaylistFiles(true)
if (options.forGetAPI === true) {
this.includeStreamingPlaylistRedundancies()
}
this.whereId(options.id)
this.query = this.buildQuery()
}
private buildQuery () {
return `${this.buildSelect()} FROM "video" ${this.joins.join(' ')} ${this.where}`
}
}

View File

@ -17,6 +17,12 @@ import { VideoLiveModel } from '../../video-live'
import { VideoStreamingPlaylistModel } from '../../video-streaming-playlist'
import { VideoAttributes } from './video-attributes'
/**
*
* Build video models from SQL rows
*
*/
export class VideoModelBuilder {
private videosMemo: { [ id: number ]: VideoModel }
private videoStreamingPlaylistMemo: { [ id: number ]: VideoStreamingPlaylistModel }
@ -43,7 +49,7 @@ export class VideoModelBuilder {
}
buildVideosFromRows (rows: any[]) {
buildVideosFromRows (rows: any[], rowsWebtorrentFiles?: any[], rowsStreamingPlaylist?: any[]) {
this.reinit()
for (const row of rows) {
@ -53,10 +59,15 @@ export class VideoModelBuilder {
this.setUserHistory(row, videoModel)
this.addThumbnail(row, videoModel)
this.addWebTorrentFile(row, videoModel)
this.addStreamingPlaylist(row, videoModel)
this.addStreamingPlaylistFile(row)
if (!rowsWebtorrentFiles) {
this.addWebTorrentFile(row, videoModel)
}
if (!rowsStreamingPlaylist) {
this.addStreamingPlaylist(row, videoModel)
this.addStreamingPlaylistFile(row)
}
if (this.mode === 'get') {
this.addTag(row, videoModel)
@ -65,16 +76,30 @@ export class VideoModelBuilder {
this.setScheduleVideoUpdate(row, videoModel)
this.setLive(row, videoModel)
if (row.VideoFiles.id) {
if (!rowsWebtorrentFiles && row.VideoFiles.id) {
this.addRedundancy(row.VideoFiles.RedundancyVideos, this.videoFileMemo[row.VideoFiles.id])
}
if (row.VideoStreamingPlaylists.id) {
if (!rowsStreamingPlaylist && row.VideoStreamingPlaylists.id) {
this.addRedundancy(row.VideoStreamingPlaylists.RedundancyVideos, this.videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id])
}
}
}
for (const row of rowsWebtorrentFiles || []) {
const videoModel = this.videosMemo[row.id]
this.addWebTorrentFile(row, videoModel)
this.addRedundancy(row.VideoFiles.RedundancyVideos, this.videoFileMemo[row.VideoFiles.id])
}
for (const row of rowsStreamingPlaylist || []) {
const videoModel = this.videosMemo[row.id]
this.addStreamingPlaylist(row, videoModel)
this.addStreamingPlaylistFile(row)
this.addRedundancy(row.VideoStreamingPlaylists.RedundancyVideos, this.videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id])
}
return this.videos
}

View File

@ -1,6 +1,14 @@
import { Sequelize, Transaction } from 'sequelize'
import validator from 'validator'
import { AbstractVideosModelQueryBuilder } from './shared/abstract-videos-model-query-builder'
import { VideoAttributes } from './shared/video-attributes'
import { VideoFileQueryBuilder } from './shared/video-file-query-builder'
import { VideoModelBuilder } from './shared/video-model-builder'
/**
*
* Build a GET SQL query, fetch rows and create the video model
*
*/
export type BuildVideoGetQueryOptions = {
id: number | string
@ -9,31 +17,57 @@ export type BuildVideoGetQueryOptions = {
forGetAPI?: boolean
}
export class VideosModelGetQueryBuilder extends AbstractVideosModelQueryBuilder {
export class VideosModelGetQueryBuilder {
videoQueryBuilder: VideosModelGetQuerySubBuilder
webtorrentFilesQueryBuilder: VideoFileQueryBuilder
streamingPlaylistFilesQueryBuilder: VideoFileQueryBuilder
private readonly videoModelBuilder: VideoModelBuilder
constructor (protected readonly sequelize: Sequelize) {
this.videoQueryBuilder = new VideosModelGetQuerySubBuilder(sequelize)
this.webtorrentFilesQueryBuilder = new VideoFileQueryBuilder(sequelize)
this.streamingPlaylistFilesQueryBuilder = new VideoFileQueryBuilder(sequelize)
this.videoModelBuilder = new VideoModelBuilder('get', new VideoAttributes('get'))
}
async queryVideos (options: BuildVideoGetQueryOptions) {
const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([
this.videoQueryBuilder.queryVideos(options),
this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(options),
this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(options)
])
const videos = this.videoModelBuilder.buildVideosFromRows(videoRows, webtorrentFilesRows, streamingPlaylistFilesRows)
if (videos.length > 1) {
throw new Error('Video results is more than ')
}
if (videos.length === 0) return null
return videos[0]
}
}
export class VideosModelGetQuerySubBuilder extends AbstractVideosModelQueryBuilder {
protected attributes: { [key: string]: string }
protected joins: string[] = []
protected where: string
protected webtorrentFilesQuery: string
protected streamingPlaylistFilesQuery: string
constructor (protected readonly sequelize: Sequelize) {
super('get')
}
queryVideos (options: BuildVideoGetQueryOptions) {
this.buildGetQuery(options)
this.buildMainGetQuery(options)
return this.runQuery(options.transaction, true).then(rows => {
const videos = this.videoModelBuilder.buildVideosFromRows(rows)
if (videos.length > 1) {
throw new Error('Video results is more than ')
}
if (videos.length === 0) return null
return videos[0]
})
return this.runQuery(options.transaction, true)
}
private buildGetQuery (options: BuildVideoGetQueryOptions) {
private buildMainGetQuery (options: BuildVideoGetQueryOptions) {
this.attributes = {
'"video".*': ''
}
@ -45,8 +79,6 @@ export class VideosModelGetQueryBuilder extends AbstractVideosModelQueryBuilder
this.includeThumbnails()
this.includeFiles()
this.includeBlacklisted()
this.includeScheduleUpdate()
@ -59,28 +91,17 @@ export class VideosModelGetQueryBuilder extends AbstractVideosModelQueryBuilder
if (options.forGetAPI === true) {
this.includeTrackers()
this.includeRedundancies()
}
this.whereId(options.id)
const select = this.buildSelect()
const order = this.buildOrder()
this.query = `${select} FROM "video" ${this.joins.join(' ')} ${this.where} ${order}`
this.query = this.buildQuery()
}
private whereId (id: string | number) {
if (validator.isInt('' + id)) {
this.where = 'WHERE "video".id = :videoId'
} else {
this.where = 'WHERE uuid = :videoId'
}
private buildQuery () {
const order = 'ORDER BY "Tags"."name" ASC'
const from = `SELECT * FROM "video" ${this.where} LIMIT 1`
this.replacements.videoId = id
}
private buildOrder () {
return 'ORDER BY "Tags"."name" ASC'
return `${this.buildSelect()} FROM (${from}) AS "video" ${this.joins.join(' ')} ${this.where} ${order}`
}
}

View File

@ -6,6 +6,12 @@ import { MUserAccountId, MUserId } from '@server/types/models'
import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models'
import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-builder'
/**
*
* Build videos list SQL query to fetch rows
*
*/
export type BuildVideosListQueryOptions = {
attributes?: string[]

View File

@ -1,7 +1,14 @@
import { Sequelize } from 'sequelize'
import { AbstractVideosModelQueryBuilder } from './shared/abstract-videos-model-query-builder'
import { VideoModelBuilder } from './shared/video-model-builder'
import { BuildVideosListQueryOptions, VideosIdListQueryBuilder } from './videos-id-list-query-builder'
/**
*
* Build videos list SQL query and create video models
*
*/
export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder {
protected attributes: { [key: string]: string }
protected joins: string[] = []
@ -9,8 +16,12 @@ export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder
private innerQuery: string
private innerSort: string
private readonly videoModelBuilder: VideoModelBuilder
constructor (protected readonly sequelize: Sequelize) {
super('list')
this.videoModelBuilder = new VideoModelBuilder(this.mode, this.videoAttributes)
}
queryVideos (options: BuildVideosListQueryOptions) {
@ -41,7 +52,8 @@ export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder
this.includeThumbnails()
if (options.withFiles) {
this.includeFiles()
this.includeWebtorrentFiles(false)
this.includeStreamingPlaylistFiles(false)
}
if (options.user) {