import { Transaction } from 'sequelize/types' ; import { resetSequelizeInstance , runInReadCommittedTransaction } from '@server/helpers/database-utils' ; import { logger , loggerTagsFactory , LoggerTagsFn } from '@server/helpers/logger' ; import { Notifier } from '@server/lib/notifier' ; import { PeerTubeSocket } from '@server/lib/peertube-socket' ; import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' ; import { VideoLiveModel } from '@server/models/video/video-live' ; import { MActor , MChannelAccountLight , MChannelId , MVideoAccountLightBlacklistAllFiles , MVideoFullLight } from '@server/types/models' ; import { VideoObject , VideoPrivacy } from '@shared/models' ; import { APVideoAbstractBuilder , getVideoAttributesFromObject } from './shared' ; export class APVideoUpdater extends APVideoAbstractBuilder { private readonly wasPrivateVideo : boolean ; private readonly wasUnlistedVideo : boolean ; private readonly videoFieldsSave : any ; private readonly oldVideoChannel : MChannelAccountLight ; protected lTags : LoggerTagsFn ; constructor ( protected readonly videoObject , private readonly video ) { super ( ) ; this . wasPrivateVideo = this . video . privacy === VideoPrivacy . PRIVATE ; this . wasUnlistedVideo = this . video . privacy === VideoPrivacy . UNLISTED ; this . oldVideoChannel = this . video . VideoChannel ; this . videoFieldsSave = this . video . toJSON ( ) ; this . lTags = loggerTagsFactory ( 'ap' , 'video' , 'update' , video . uuid , video . url ) ; } async update ( overrideTo ? ) { logger . debug ( 'Updating remote video "%s".' , this . videoObject . uuid , { videoObject : this . videoObject , ... this . lTags ( ) } ) ; try { const channelActor = await this . getOrCreateVideoChannelFromVideoObject ( ) ; const thumbnailModel = await this . tryToGenerateThumbnail ( this . video ) ; this . checkChannelUpdateOrThrow ( channelActor ) ; const videoUpdated = await this . updateVideo ( channelActor . VideoChannel , undefined , overrideTo ) ; if ( thumbnailModel ) await videoUpdated . addAndSaveThumbnail ( thumbnailModel ) ; await runInReadCommittedTransaction ( async ( t ) => { await this . setWebTorrentFiles ( videoUpdated , t ) ; await this . setStreamingPlaylists ( videoUpdated , t ) ; } ) ; await Promise . all ( [ runInReadCommittedTransaction ( t => this . setTags ( videoUpdated , t ) ) , runInReadCommittedTransaction ( t => this . setTrackers ( videoUpdated , t ) ) , this . setOrDeleteLive ( videoUpdated ) , this . setPreview ( videoUpdated ) ] ) ; await runInReadCommittedTransaction ( t => this . setCaptions ( videoUpdated , t ) ) ; await autoBlacklistVideoIfNeeded ( { video : videoUpdated , user : undefined , isRemote : true , isNew : false , transaction : undefined } ) ; if ( this . wasPrivateVideo || this . wasUnlistedVideo ) { Notifier . Instance . notifyOnNewVideoIfNeeded ( videoUpdated ) ; } if ( videoUpdated . isLive ) { PeerTubeSocket . Instance . sendVideoLiveNewState ( videoUpdated ) ; } logger . info ( 'Remote video with uuid %s updated' , this . videoObject . uuid , this . lTags ( ) ) ; return videoUpdated ; } catch ( err ) { this . catchUpdateError ( err ) ; } } private checkChannelUpdateOrThrow ( newChannelActor ) { if ( ! this . oldVideoChannel . Actor . serverId || ! newChannelActor . serverId ) { throw new Error ( 'Cannot check old channel/new channel validity because `serverId` is null' ) ; } if ( this . oldVideoChannel . Actor . serverId !== newChannelActor . serverId ) { throw new Error ( `New channel ${ newChannelActor . url } is not on the same server than new channel ${ this . oldVideoChannel . Actor . url }` ) ; } } private updateVideo ( channel , transaction ? , overrideTo ? ) { const to = overrideTo || this . videoObject . to ; const videoData = getVideoAttributesFromObject ( channel , this . videoObject , to ) ; this . video . name = videoData . name ; this . video . uuid = videoData . uuid ; this . video . url = videoData . url ; this . video . category = videoData . category ; this . video . licence = videoData . licence ; this . video . language = videoData . language ; this . video . description = videoData . description ; this . video . support = videoData . support ; this . video . nsfw = videoData . nsfw ; this . video . commentsEnabled = videoData . commentsEnabled ; this . video . downloadEnabled = videoData . downloadEnabled ; this . video . waitTranscoding = videoData . waitTranscoding ; this . video . state = videoData . state ; this . video . duration = videoData . duration ; this . video . createdAt = videoData . createdAt ; this . video . publishedAt = videoData . publishedAt ; this . video . originallyPublishedAt = videoData . originallyPublishedAt ; this . video . privacy = videoData . privacy ; this . video . channelId = videoData . channelId ; this . video . views = videoData . views ; this . video . isLive = videoData . isLive ; this . video . changed ( 'updatedAt' , true ) ; return this . video . save ( { transaction } ) as Promise < MVideoFullLight > ; } private async setCaptions ( videoUpdated , t ) { await this . insertOrReplaceCaptions ( videoUpdated , t ) ; } private async setOrDeleteLive ( videoUpdated , transaction ? ) { if ( ! this . video . isLive ) return ; if ( this . video . isLive ) return this . insertOrReplaceLive ( videoUpdated , transaction ) ; await VideoLiveModel . destroy ( { where : { videoId : this . video . id } , transaction } ) ; videoUpdated . VideoLive = null ; } private catchUpdateError ( err ) { if ( this . video !== undefined && this . videoFieldsSave !== undefined ) { resetSequelizeInstance ( this . video , this . videoFieldsSave ) ; } logger . debug ( 'Cannot update the remote video.' , { err , ... this . lTags ( ) } ) ; throw err ; } }