aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/redundancy/video-redundancy.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/redundancy/video-redundancy.ts')
-rw-r--r--server/models/redundancy/video-redundancy.ts158
1 files changed, 120 insertions, 38 deletions
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index 2ebe23ef1..b722bed14 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -15,7 +15,7 @@ import {
15import { ActorModel } from '../activitypub/actor' 15import { ActorModel } from '../activitypub/actor'
16import { getVideoSort, throwIfNotValid } from '../utils' 16import { getVideoSort, throwIfNotValid } from '../utils'
17import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' 17import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
18import { CONFIG, CONSTRAINTS_FIELDS, VIDEO_EXT_MIMETYPE } from '../../initializers' 18import { CONFIG, CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers'
19import { VideoFileModel } from '../video/video-file' 19import { VideoFileModel } from '../video/video-file'
20import { getServerActor } from '../../helpers/utils' 20import { getServerActor } from '../../helpers/utils'
21import { VideoModel } from '../video/video' 21import { VideoModel } from '../video/video'
@@ -28,6 +28,7 @@ import { sample } from 'lodash'
28import { isTestInstance } from '../../helpers/core-utils' 28import { isTestInstance } from '../../helpers/core-utils'
29import * as Bluebird from 'bluebird' 29import * as Bluebird from 'bluebird'
30import * as Sequelize from 'sequelize' 30import * as Sequelize from 'sequelize'
31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
31 32
32export enum ScopeNames { 33export enum ScopeNames {
33 WITH_VIDEO = 'WITH_VIDEO' 34 WITH_VIDEO = 'WITH_VIDEO'
@@ -38,7 +39,17 @@ export enum ScopeNames {
38 include: [ 39 include: [
39 { 40 {
40 model: () => VideoFileModel, 41 model: () => VideoFileModel,
41 required: true, 42 required: false,
43 include: [
44 {
45 model: () => VideoModel,
46 required: true
47 }
48 ]
49 },
50 {
51 model: () => VideoStreamingPlaylistModel,
52 required: false,
42 include: [ 53 include: [
43 { 54 {
44 model: () => VideoModel, 55 model: () => VideoModel,
@@ -97,12 +108,24 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
97 108
98 @BelongsTo(() => VideoFileModel, { 109 @BelongsTo(() => VideoFileModel, {
99 foreignKey: { 110 foreignKey: {
100 allowNull: false 111 allowNull: true
101 }, 112 },
102 onDelete: 'cascade' 113 onDelete: 'cascade'
103 }) 114 })
104 VideoFile: VideoFileModel 115 VideoFile: VideoFileModel
105 116
117 @ForeignKey(() => VideoStreamingPlaylistModel)
118 @Column
119 videoStreamingPlaylistId: number
120
121 @BelongsTo(() => VideoStreamingPlaylistModel, {
122 foreignKey: {
123 allowNull: true
124 },
125 onDelete: 'cascade'
126 })
127 VideoStreamingPlaylist: VideoStreamingPlaylistModel
128
106 @ForeignKey(() => ActorModel) 129 @ForeignKey(() => ActorModel)
107 @Column 130 @Column
108 actorId: number 131 actorId: number
@@ -117,16 +140,27 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
117 140
118 @BeforeDestroy 141 @BeforeDestroy
119 static async removeFile (instance: VideoRedundancyModel) { 142 static async removeFile (instance: VideoRedundancyModel) {
120 // Not us 143 if (!instance.isOwned()) return
121 if (!instance.strategy) return
122 144
123 const videoFile = await VideoFileModel.loadWithVideo(instance.videoFileId) 145 if (instance.videoFileId) {
146 const videoFile = await VideoFileModel.loadWithVideo(instance.videoFileId)
124 147
125 const logIdentifier = `${videoFile.Video.uuid}-${videoFile.resolution}` 148 const logIdentifier = `${videoFile.Video.uuid}-${videoFile.resolution}`
126 logger.info('Removing duplicated video file %s.', logIdentifier) 149 logger.info('Removing duplicated video file %s.', logIdentifier)
127 150
128 videoFile.Video.removeFile(videoFile) 151 videoFile.Video.removeFile(videoFile, true)
129 .catch(err => logger.error('Cannot delete %s files.', logIdentifier, { err })) 152 .catch(err => logger.error('Cannot delete %s files.', logIdentifier, { err }))
153 }
154
155 if (instance.videoStreamingPlaylistId) {
156 const videoStreamingPlaylist = await VideoStreamingPlaylistModel.loadWithVideo(instance.videoStreamingPlaylistId)
157
158 const videoUUID = videoStreamingPlaylist.Video.uuid
159 logger.info('Removing duplicated video streaming playlist %s.', videoUUID)
160
161 videoStreamingPlaylist.Video.removeStreamingPlaylist(true)
162 .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err }))
163 }
130 164
131 return undefined 165 return undefined
132 } 166 }
@@ -144,6 +178,19 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
144 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) 178 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
145 } 179 }
146 180
181 static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) {
182 const actor = await getServerActor()
183
184 const query = {
185 where: {
186 actorId: actor.id,
187 videoStreamingPlaylistId
188 }
189 }
190
191 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
192 }
193
147 static loadByUrl (url: string, transaction?: Sequelize.Transaction) { 194 static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
148 const query = { 195 const query = {
149 where: { 196 where: {
@@ -192,7 +239,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
192 const ids = rows.map(r => r.id) 239 const ids = rows.map(r => r.id)
193 const id = sample(ids) 240 const id = sample(ids)
194 241
195 return VideoModel.loadWithFile(id, undefined, !isTestInstance()) 242 return VideoModel.loadWithFiles(id, undefined, !isTestInstance())
196 } 243 }
197 244
198 static async findMostViewToDuplicate (randomizedFactor: number) { 245 static async findMostViewToDuplicate (randomizedFactor: number) {
@@ -293,6 +340,11 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
293 } 340 }
294 341
295 return VideoFileModel.sum('size', options as any) // FIXME: typings 342 return VideoFileModel.sum('size', options as any) // FIXME: typings
343 .then(v => {
344 if (!v || isNaN(v)) return 0
345
346 return v
347 })
296 } 348 }
297 349
298 static async listLocalExpired () { 350 static async listLocalExpired () {
@@ -329,40 +381,44 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
329 381
330 static async listLocalOfServer (serverId: number) { 382 static async listLocalOfServer (serverId: number) {
331 const actor = await getServerActor() 383 const actor = await getServerActor()
332 384 const buildVideoInclude = () => ({
333 const query = { 385 model: VideoModel,
334 where: { 386 required: true,
335 actorId: actor.id
336 },
337 include: [ 387 include: [
338 { 388 {
339 model: VideoFileModel, 389 attributes: [],
390 model: VideoChannelModel.unscoped(),
340 required: true, 391 required: true,
341 include: [ 392 include: [
342 { 393 {
343 model: VideoModel, 394 attributes: [],
395 model: ActorModel.unscoped(),
344 required: true, 396 required: true,
345 include: [ 397 where: {
346 { 398 serverId
347 attributes: [], 399 }
348 model: VideoChannelModel.unscoped(),
349 required: true,
350 include: [
351 {
352 attributes: [],
353 model: ActorModel.unscoped(),
354 required: true,
355 where: {
356 serverId
357 }
358 }
359 ]
360 }
361 ]
362 } 400 }
363 ] 401 ]
364 } 402 }
365 ] 403 ]
404 })
405
406 const query = {
407 where: {
408 actorId: actor.id
409 },
410 include: [
411 {
412 model: VideoFileModel,
413 required: false,
414 include: [ buildVideoInclude() ]
415 },
416 {
417 model: VideoStreamingPlaylistModel,
418 required: false,
419 include: [ buildVideoInclude() ]
420 }
421 ]
366 } 422 }
367 423
368 return VideoRedundancyModel.findAll(query) 424 return VideoRedundancyModel.findAll(query)
@@ -391,7 +447,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
391 ] 447 ]
392 } 448 }
393 449
394 return VideoRedundancyModel.find(query as any) // FIXME: typings 450 return VideoRedundancyModel.findOne(query as any) // FIXME: typings
395 .then((r: any) => ({ 451 .then((r: any) => ({
396 totalUsed: parseInt(r.totalUsed.toString(), 10), 452 totalUsed: parseInt(r.totalUsed.toString(), 10),
397 totalVideos: r.totalVideos, 453 totalVideos: r.totalVideos,
@@ -399,7 +455,32 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
399 })) 455 }))
400 } 456 }
401 457
458 getVideo () {
459 if (this.VideoFile) return this.VideoFile.Video
460
461 return this.VideoStreamingPlaylist.Video
462 }
463
464 isOwned () {
465 return !!this.strategy
466 }
467
402 toActivityPubObject (): CacheFileObject { 468 toActivityPubObject (): CacheFileObject {
469 if (this.VideoStreamingPlaylist) {
470 return {
471 id: this.url,
472 type: 'CacheFile' as 'CacheFile',
473 object: this.VideoStreamingPlaylist.Video.url,
474 expires: this.expiresOn.toISOString(),
475 url: {
476 type: 'Link',
477 mimeType: 'application/x-mpegURL',
478 mediaType: 'application/x-mpegURL',
479 href: this.fileUrl
480 }
481 }
482 }
483
403 return { 484 return {
404 id: this.url, 485 id: this.url,
405 type: 'CacheFile' as 'CacheFile', 486 type: 'CacheFile' as 'CacheFile',
@@ -407,7 +488,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
407 expires: this.expiresOn.toISOString(), 488 expires: this.expiresOn.toISOString(),
408 url: { 489 url: {
409 type: 'Link', 490 type: 'Link',
410 mimeType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any, 491 mimeType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any,
492 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any,
411 href: this.fileUrl, 493 href: this.fileUrl,
412 height: this.VideoFile.resolution, 494 height: this.VideoFile.resolution,
413 size: this.VideoFile.size, 495 size: this.VideoFile.size,
@@ -422,7 +504,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
422 504
423 const notIn = Sequelize.literal( 505 const notIn = Sequelize.literal(
424 '(' + 506 '(' +
425 `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id}` + 507 `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` +
426 ')' 508 ')'
427 ) 509 )
428 510