diff options
Diffstat (limited to 'server/models/redundancy')
-rw-r--r-- | server/models/redundancy/video-redundancy.ts | 218 |
1 files changed, 193 insertions, 25 deletions
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 8c9a7eabf..1b63d3818 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -13,13 +13,13 @@ import { | |||
13 | UpdatedAt | 13 | UpdatedAt |
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { ActorModel } from '../activitypub/actor' | 15 | import { ActorModel } from '../activitypub/actor' |
16 | import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' | 16 | import { getSort, getVideoSort, parseAggregateResult, 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 { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' | 18 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' |
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' |
22 | import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' | 22 | import { VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../shared/models/redundancy' |
23 | import { logger } from '../../helpers/logger' | 23 | import { logger } from '../../helpers/logger' |
24 | import { CacheFileObject, VideoPrivacy } from '../../../shared' | 24 | import { CacheFileObject, VideoPrivacy } from '../../../shared' |
25 | import { VideoChannelModel } from '../video/video-channel' | 25 | import { VideoChannelModel } from '../video/video-channel' |
@@ -27,17 +27,23 @@ import { ServerModel } from '../server/server' | |||
27 | import { sample } from 'lodash' | 27 | 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 { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' | 30 | import { col, FindOptions, fn, literal, Op, Transaction, WhereOptions } from 'sequelize' |
31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' | 31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' |
32 | import { CONFIG } from '../../initializers/config' | 32 | import { CONFIG } from '../../initializers/config' |
33 | import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' | 33 | import { MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' |
34 | import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model' | ||
35 | import { | ||
36 | FileRedundancyInformation, | ||
37 | StreamingPlaylistRedundancyInformation, | ||
38 | VideoRedundancy | ||
39 | } from '@shared/models/redundancy/video-redundancy.model' | ||
34 | 40 | ||
35 | export enum ScopeNames { | 41 | export enum ScopeNames { |
36 | WITH_VIDEO = 'WITH_VIDEO' | 42 | WITH_VIDEO = 'WITH_VIDEO' |
37 | } | 43 | } |
38 | 44 | ||
39 | @Scopes(() => ({ | 45 | @Scopes(() => ({ |
40 | [ ScopeNames.WITH_VIDEO ]: { | 46 | [ScopeNames.WITH_VIDEO]: { |
41 | include: [ | 47 | include: [ |
42 | { | 48 | { |
43 | model: VideoFileModel, | 49 | model: VideoFileModel, |
@@ -86,7 +92,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
86 | @UpdatedAt | 92 | @UpdatedAt |
87 | updatedAt: Date | 93 | updatedAt: Date |
88 | 94 | ||
89 | @AllowNull(false) | 95 | @AllowNull(true) |
90 | @Column | 96 | @Column |
91 | expiresOn: Date | 97 | expiresOn: Date |
92 | 98 | ||
@@ -161,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
161 | logger.info('Removing duplicated video streaming playlist %s.', videoUUID) | 167 | logger.info('Removing duplicated video streaming playlist %s.', videoUUID) |
162 | 168 | ||
163 | videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true) | 169 | videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true) |
164 | .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err })) | 170 | .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err })) |
165 | } | 171 | } |
166 | 172 | ||
167 | return undefined | 173 | return undefined |
@@ -193,6 +199,15 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
193 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 199 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) |
194 | } | 200 | } |
195 | 201 | ||
202 | static loadByIdWithVideo (id: number, transaction?: Transaction): Bluebird<MVideoRedundancyVideo> { | ||
203 | const query = { | ||
204 | where: { id }, | ||
205 | transaction | ||
206 | } | ||
207 | |||
208 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | ||
209 | } | ||
210 | |||
196 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { | 211 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { |
197 | const query = { | 212 | const query = { |
198 | where: { | 213 | where: { |
@@ -215,12 +230,12 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
215 | }, | 230 | }, |
216 | include: [ | 231 | include: [ |
217 | { | 232 | { |
218 | attributes: [ ], | 233 | attributes: [], |
219 | model: VideoFileModel, | 234 | model: VideoFileModel, |
220 | required: true, | 235 | required: true, |
221 | include: [ | 236 | include: [ |
222 | { | 237 | { |
223 | attributes: [ ], | 238 | attributes: [], |
224 | model: VideoModel, | 239 | model: VideoModel, |
225 | required: true, | 240 | required: true, |
226 | where: { | 241 | where: { |
@@ -233,7 +248,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
233 | } | 248 | } |
234 | 249 | ||
235 | return VideoRedundancyModel.findOne(query) | 250 | return VideoRedundancyModel.findOne(query) |
236 | .then(r => !!r) | 251 | .then(r => !!r) |
237 | } | 252 | } |
238 | 253 | ||
239 | static async getVideoSample (p: Bluebird<VideoModel[]>) { | 254 | static async getVideoSample (p: Bluebird<VideoModel[]>) { |
@@ -295,7 +310,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
295 | where: { | 310 | where: { |
296 | privacy: VideoPrivacy.PUBLIC, | 311 | privacy: VideoPrivacy.PUBLIC, |
297 | views: { | 312 | views: { |
298 | [ Op.gte ]: minViews | 313 | [Op.gte]: minViews |
299 | } | 314 | } |
300 | }, | 315 | }, |
301 | include: [ | 316 | include: [ |
@@ -318,7 +333,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
318 | actorId: actor.id, | 333 | actorId: actor.id, |
319 | strategy, | 334 | strategy, |
320 | createdAt: { | 335 | createdAt: { |
321 | [ Op.lt ]: expiredDate | 336 | [Op.lt]: expiredDate |
322 | } | 337 | } |
323 | } | 338 | } |
324 | } | 339 | } |
@@ -377,7 +392,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
377 | where: { | 392 | where: { |
378 | actorId: actor.id, | 393 | actorId: actor.id, |
379 | expiresOn: { | 394 | expiresOn: { |
380 | [ Op.lt ]: new Date() | 395 | [Op.lt]: new Date() |
381 | } | 396 | } |
382 | } | 397 | } |
383 | } | 398 | } |
@@ -394,7 +409,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
394 | [Op.ne]: actor.id | 409 | [Op.ne]: actor.id |
395 | }, | 410 | }, |
396 | expiresOn: { | 411 | expiresOn: { |
397 | [ Op.lt ]: new Date() | 412 | [Op.lt]: new Date(), |
413 | [Op.ne]: null | ||
398 | } | 414 | } |
399 | } | 415 | } |
400 | } | 416 | } |
@@ -447,7 +463,112 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
447 | return VideoRedundancyModel.findAll(query) | 463 | return VideoRedundancyModel.findAll(query) |
448 | } | 464 | } |
449 | 465 | ||
450 | static async getStats (strategy: VideoRedundancyStrategy) { | 466 | static listForApi (options: { |
467 | start: number | ||
468 | count: number | ||
469 | sort: string | ||
470 | target: VideoRedundanciesTarget | ||
471 | strategy?: string | ||
472 | }) { | ||
473 | const { start, count, sort, target, strategy } = options | ||
474 | const redundancyWhere: WhereOptions = {} | ||
475 | const videosWhere: WhereOptions = {} | ||
476 | let redundancySqlSuffix = '' | ||
477 | |||
478 | if (target === 'my-videos') { | ||
479 | Object.assign(videosWhere, { remote: false }) | ||
480 | } else if (target === 'remote-videos') { | ||
481 | Object.assign(videosWhere, { remote: true }) | ||
482 | Object.assign(redundancyWhere, { strategy: { [Op.ne]: null } }) | ||
483 | redundancySqlSuffix = ' AND "videoRedundancy"."strategy" IS NOT NULL' | ||
484 | } | ||
485 | |||
486 | if (strategy) { | ||
487 | Object.assign(redundancyWhere, { strategy: strategy }) | ||
488 | } | ||
489 | |||
490 | const videoFilterWhere = { | ||
491 | [Op.and]: [ | ||
492 | { | ||
493 | [Op.or]: [ | ||
494 | { | ||
495 | id: { | ||
496 | [Op.in]: literal( | ||
497 | '(' + | ||
498 | 'SELECT "videoId" FROM "videoFile" ' + | ||
499 | 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoFileId" = "videoFile".id' + | ||
500 | redundancySqlSuffix + | ||
501 | ')' | ||
502 | ) | ||
503 | } | ||
504 | }, | ||
505 | { | ||
506 | id: { | ||
507 | [Op.in]: literal( | ||
508 | '(' + | ||
509 | 'select "videoId" FROM "videoStreamingPlaylist" ' + | ||
510 | 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id' + | ||
511 | redundancySqlSuffix + | ||
512 | ')' | ||
513 | ) | ||
514 | } | ||
515 | } | ||
516 | ] | ||
517 | }, | ||
518 | |||
519 | videosWhere | ||
520 | ] | ||
521 | } | ||
522 | |||
523 | // /!\ On video model /!\ | ||
524 | const findOptions = { | ||
525 | offset: start, | ||
526 | limit: count, | ||
527 | order: getSort(sort), | ||
528 | include: [ | ||
529 | { | ||
530 | required: false, | ||
531 | model: VideoFileModel.unscoped(), | ||
532 | include: [ | ||
533 | { | ||
534 | model: VideoRedundancyModel.unscoped(), | ||
535 | required: false, | ||
536 | where: redundancyWhere | ||
537 | } | ||
538 | ] | ||
539 | }, | ||
540 | { | ||
541 | required: false, | ||
542 | model: VideoStreamingPlaylistModel.unscoped(), | ||
543 | include: [ | ||
544 | { | ||
545 | model: VideoRedundancyModel.unscoped(), | ||
546 | required: false, | ||
547 | where: redundancyWhere | ||
548 | }, | ||
549 | { | ||
550 | model: VideoFileModel.unscoped(), | ||
551 | required: false | ||
552 | } | ||
553 | ] | ||
554 | } | ||
555 | ], | ||
556 | where: videoFilterWhere | ||
557 | } | ||
558 | |||
559 | // /!\ On video model /!\ | ||
560 | const countOptions = { | ||
561 | where: videoFilterWhere | ||
562 | } | ||
563 | |||
564 | return Promise.all([ | ||
565 | VideoModel.findAll(findOptions), | ||
566 | |||
567 | VideoModel.count(countOptions) | ||
568 | ]).then(([ data, total ]) => ({ total, data })) | ||
569 | } | ||
570 | |||
571 | static async getStats (strategy: VideoRedundancyStrategyWithManual) { | ||
451 | const actor = await getServerActor() | 572 | const actor = await getServerActor() |
452 | 573 | ||
453 | const query: FindOptions = { | 574 | const query: FindOptions = { |
@@ -471,11 +592,58 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
471 | } | 592 | } |
472 | 593 | ||
473 | return VideoRedundancyModel.findOne(query) | 594 | return VideoRedundancyModel.findOne(query) |
474 | .then((r: any) => ({ | 595 | .then((r: any) => ({ |
475 | totalUsed: parseAggregateResult(r.totalUsed), | 596 | totalUsed: parseAggregateResult(r.totalUsed), |
476 | totalVideos: r.totalVideos, | 597 | totalVideos: r.totalVideos, |
477 | totalVideoFiles: r.totalVideoFiles | 598 | totalVideoFiles: r.totalVideoFiles |
478 | })) | 599 | })) |
600 | } | ||
601 | |||
602 | static toFormattedJSONStatic (video: MVideoForRedundancyAPI): VideoRedundancy { | ||
603 | const filesRedundancies: FileRedundancyInformation[] = [] | ||
604 | const streamingPlaylistsRedundancies: StreamingPlaylistRedundancyInformation[] = [] | ||
605 | |||
606 | for (const file of video.VideoFiles) { | ||
607 | for (const redundancy of file.RedundancyVideos) { | ||
608 | filesRedundancies.push({ | ||
609 | id: redundancy.id, | ||
610 | fileUrl: redundancy.fileUrl, | ||
611 | strategy: redundancy.strategy, | ||
612 | createdAt: redundancy.createdAt, | ||
613 | updatedAt: redundancy.updatedAt, | ||
614 | expiresOn: redundancy.expiresOn, | ||
615 | size: file.size | ||
616 | }) | ||
617 | } | ||
618 | } | ||
619 | |||
620 | for (const playlist of video.VideoStreamingPlaylists) { | ||
621 | const size = playlist.VideoFiles.reduce((a, b) => a + b.size, 0) | ||
622 | |||
623 | for (const redundancy of playlist.RedundancyVideos) { | ||
624 | streamingPlaylistsRedundancies.push({ | ||
625 | id: redundancy.id, | ||
626 | fileUrl: redundancy.fileUrl, | ||
627 | strategy: redundancy.strategy, | ||
628 | createdAt: redundancy.createdAt, | ||
629 | updatedAt: redundancy.updatedAt, | ||
630 | expiresOn: redundancy.expiresOn, | ||
631 | size | ||
632 | }) | ||
633 | } | ||
634 | } | ||
635 | |||
636 | return { | ||
637 | id: video.id, | ||
638 | name: video.name, | ||
639 | url: video.url, | ||
640 | uuid: video.uuid, | ||
641 | |||
642 | redundancies: { | ||
643 | files: filesRedundancies, | ||
644 | streamingPlaylists: streamingPlaylistsRedundancies | ||
645 | } | ||
646 | } | ||
479 | } | 647 | } |
480 | 648 | ||
481 | getVideo () { | 649 | getVideo () { |
@@ -494,7 +662,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
494 | id: this.url, | 662 | id: this.url, |
495 | type: 'CacheFile' as 'CacheFile', | 663 | type: 'CacheFile' as 'CacheFile', |
496 | object: this.VideoStreamingPlaylist.Video.url, | 664 | object: this.VideoStreamingPlaylist.Video.url, |
497 | expires: this.expiresOn.toISOString(), | 665 | expires: this.expiresOn ? this.expiresOn.toISOString() : null, |
498 | url: { | 666 | url: { |
499 | type: 'Link', | 667 | type: 'Link', |
500 | mediaType: 'application/x-mpegURL', | 668 | mediaType: 'application/x-mpegURL', |
@@ -507,10 +675,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
507 | id: this.url, | 675 | id: this.url, |
508 | type: 'CacheFile' as 'CacheFile', | 676 | type: 'CacheFile' as 'CacheFile', |
509 | object: this.VideoFile.Video.url, | 677 | object: this.VideoFile.Video.url, |
510 | expires: this.expiresOn.toISOString(), | 678 | expires: this.expiresOn ? this.expiresOn.toISOString() : null, |
511 | url: { | 679 | url: { |
512 | type: 'Link', | 680 | type: 'Link', |
513 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any, | 681 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[this.VideoFile.extname] as any, |
514 | href: this.fileUrl, | 682 | href: this.fileUrl, |
515 | height: this.VideoFile.resolution, | 683 | height: this.VideoFile.resolution, |
516 | size: this.VideoFile.size, | 684 | size: this.VideoFile.size, |
@@ -525,7 +693,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
525 | 693 | ||
526 | const notIn = literal( | 694 | const notIn = literal( |
527 | '(' + | 695 | '(' + |
528 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + | 696 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + |
529 | ')' | 697 | ')' |
530 | ) | 698 | ) |
531 | 699 | ||
@@ -535,7 +703,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
535 | required: true, | 703 | required: true, |
536 | where: { | 704 | where: { |
537 | id: { | 705 | id: { |
538 | [ Op.notIn ]: notIn | 706 | [Op.notIn]: notIn |
539 | } | 707 | } |
540 | } | 708 | } |
541 | } | 709 | } |