1 import { Transaction } from 'sequelize/types'
2 import { resetSequelizeInstance } from '@server/helpers/database-utils'
3 import { logger } from '@server/helpers/logger'
4 import { sequelizeTypescript } from '@server/initializers/database'
5 import { Notifier } from '@server/lib/notifier'
6 import { PeerTubeSocket } from '@server/lib/peertube-socket'
7 import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
8 import { VideoCaptionModel } from '@server/models/video/video-caption'
9 import { VideoLiveModel } from '@server/models/video/video-live'
10 import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models'
11 import { VideoObject, VideoPrivacy } from '@shared/models'
12 import { APVideoAbstractBuilder, getVideoAttributesFromObject } from './shared'
14 export class APVideoUpdater extends APVideoAbstractBuilder {
15 private readonly wasPrivateVideo: boolean
16 private readonly wasUnlistedVideo: boolean
18 private readonly videoFieldsSave: any
20 private readonly oldVideoChannel: MChannelAccountLight
23 protected readonly videoObject: VideoObject,
24 private readonly video: MVideoAccountLightBlacklistAllFiles
28 this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE
29 this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED
31 this.oldVideoChannel = this.video.VideoChannel
33 this.videoFieldsSave = this.video.toJSON()
36 async update (overrideTo?: string[]) {
37 logger.debug('Updating remote video "%s".', this.videoObject.uuid, { videoObject: this.videoObject })
40 const channelActor = await this.getOrCreateVideoChannelFromVideoObject()
42 const thumbnailModel = await this.tryToGenerateThumbnail(this.video)
44 const videoUpdated = await sequelizeTypescript.transaction(async t => {
45 this.checkChannelUpdateOrThrow(channelActor)
47 const videoUpdated = await this.updateVideo(channelActor.VideoChannel, t, overrideTo)
49 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
51 await this.setPreview(videoUpdated, t)
52 await this.setWebTorrentFiles(videoUpdated, t)
53 await this.setStreamingPlaylists(videoUpdated, t)
54 await this.setTags(videoUpdated, t)
55 await this.setTrackers(videoUpdated, t)
56 await this.setCaptions(videoUpdated, t)
57 await this.setOrDeleteLive(videoUpdated, t)
62 await autoBlacklistVideoIfNeeded({
67 transaction: undefined
71 if (this.wasPrivateVideo || this.wasUnlistedVideo) {
72 Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated)
75 if (videoUpdated.isLive) {
76 PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
77 PeerTubeSocket.Instance.sendVideoViewsUpdate(videoUpdated)
80 logger.info('Remote video with uuid %s updated', this.videoObject.uuid)
84 this.catchUpdateError(err)
88 // Check we can update the channel: we trust the remote server
89 private checkChannelUpdateOrThrow (newChannelActor: MActor) {
90 if (!this.oldVideoChannel.Actor.serverId || !newChannelActor.serverId) {
91 throw new Error('Cannot check old channel/new channel validity because `serverId` is null')
94 if (this.oldVideoChannel.Actor.serverId !== newChannelActor.serverId) {
95 throw new Error(`New channel ${newChannelActor.url} is not on the same server than new channel ${this.oldVideoChannel.Actor.url}`)
99 private updateVideo (channel: MChannelId, transaction: Transaction, overrideTo?: string[]) {
100 const to = overrideTo || this.videoObject.to
101 const videoData = getVideoAttributesFromObject(channel, this.videoObject, to)
102 this.video.name = videoData.name
103 this.video.uuid = videoData.uuid
104 this.video.url = videoData.url
105 this.video.category = videoData.category
106 this.video.licence = videoData.licence
107 this.video.language = videoData.language
108 this.video.description = videoData.description
109 this.video.support = videoData.support
110 this.video.nsfw = videoData.nsfw
111 this.video.commentsEnabled = videoData.commentsEnabled
112 this.video.downloadEnabled = videoData.downloadEnabled
113 this.video.waitTranscoding = videoData.waitTranscoding
114 this.video.state = videoData.state
115 this.video.duration = videoData.duration
116 this.video.createdAt = videoData.createdAt
117 this.video.publishedAt = videoData.publishedAt
118 this.video.originallyPublishedAt = videoData.originallyPublishedAt
119 this.video.privacy = videoData.privacy
120 this.video.channelId = videoData.channelId
121 this.video.views = videoData.views
122 this.video.isLive = videoData.isLive
124 // Ensures we update the updated video attribute
125 this.video.changed('updatedAt', true)
127 return this.video.save({ transaction }) as Promise<MVideoFullLight>
130 private async setCaptions (videoUpdated: MVideoFullLight, t: Transaction) {
131 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
133 await this.insertOrReplaceCaptions(videoUpdated, t)
136 private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction: Transaction) {
137 if (this.video.isLive) return this.insertOrReplaceLive(videoUpdated, transaction)
139 // Delete existing live if it exists
140 await VideoLiveModel.destroy({
142 videoId: this.video.id
147 videoUpdated.VideoLive = null
150 private catchUpdateError (err: Error) {
151 if (this.video !== undefined && this.videoFieldsSave !== undefined) {
152 resetSequelizeInstance(this.video, this.videoFieldsSave)
155 // This is just a debug because we will retry the insert
156 logger.debug('Cannot update the remote video.', { err })