diff options
Diffstat (limited to 'server/models/redundancy/video-redundancy.ts')
-rw-r--r-- | server/models/redundancy/video-redundancy.ts | 158 |
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 { | |||
15 | import { ActorModel } from '../activitypub/actor' | 15 | import { ActorModel } from '../activitypub/actor' |
16 | import { getVideoSort, throwIfNotValid } from '../utils' | 16 | import { getVideoSort, throwIfNotValid } from '../utils' |
17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
18 | import { CONFIG, CONSTRAINTS_FIELDS, VIDEO_EXT_MIMETYPE } from '../../initializers' | 18 | import { CONFIG, CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers' |
19 | import { VideoFileModel } from '../video/video-file' | 19 | import { VideoFileModel } from '../video/video-file' |
20 | import { getServerActor } from '../../helpers/utils' | 20 | import { getServerActor } from '../../helpers/utils' |
21 | import { VideoModel } from '../video/video' | 21 | import { VideoModel } from '../video/video' |
@@ -28,6 +28,7 @@ import { sample } from 'lodash' | |||
28 | import { isTestInstance } from '../../helpers/core-utils' | 28 | import { isTestInstance } from '../../helpers/core-utils' |
29 | import * as Bluebird from 'bluebird' | 29 | import * as Bluebird from 'bluebird' |
30 | import * as Sequelize from 'sequelize' | 30 | import * as Sequelize from 'sequelize' |
31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' | ||
31 | 32 | ||
32 | export enum ScopeNames { | 33 | export 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 | ||