]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/activitypub/videos/updater.ts
Refactor AP actors
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / videos / updater.ts
1 import { Transaction } from 'sequelize/types'
2 import { resetSequelizeInstance } from '@server/helpers/database-utils'
3 import { logger, loggerTagsFactory } 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'
13
14 export class APVideoUpdater extends APVideoAbstractBuilder {
15 private readonly wasPrivateVideo: boolean
16 private readonly wasUnlistedVideo: boolean
17
18 private readonly videoFieldsSave: any
19
20 private readonly oldVideoChannel: MChannelAccountLight
21
22 protected lTags = loggerTagsFactory('ap', 'video', 'update')
23
24 constructor (
25 protected readonly videoObject: VideoObject,
26 private readonly video: MVideoAccountLightBlacklistAllFiles
27 ) {
28 super()
29
30 this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE
31 this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED
32
33 this.oldVideoChannel = this.video.VideoChannel
34
35 this.videoFieldsSave = this.video.toJSON()
36 }
37
38 async update (overrideTo?: string[]) {
39 logger.debug(
40 'Updating remote video "%s".', this.videoObject.uuid,
41 { videoObject: this.videoObject, ...this.lTags(this.videoObject.uuid) }
42 )
43
44 try {
45 const channelActor = await this.getOrCreateVideoChannelFromVideoObject()
46
47 const thumbnailModel = await this.tryToGenerateThumbnail(this.video)
48
49 const videoUpdated = await sequelizeTypescript.transaction(async t => {
50 this.checkChannelUpdateOrThrow(channelActor)
51
52 const videoUpdated = await this.updateVideo(channelActor.VideoChannel, t, overrideTo)
53
54 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
55
56 await this.setPreview(videoUpdated, t)
57 await this.setWebTorrentFiles(videoUpdated, t)
58 await this.setStreamingPlaylists(videoUpdated, t)
59 await this.setTags(videoUpdated, t)
60 await this.setTrackers(videoUpdated, t)
61 await this.setCaptions(videoUpdated, t)
62 await this.setOrDeleteLive(videoUpdated, t)
63
64 return videoUpdated
65 })
66
67 await autoBlacklistVideoIfNeeded({
68 video: videoUpdated,
69 user: undefined,
70 isRemote: true,
71 isNew: false,
72 transaction: undefined
73 })
74
75 // Notify our users?
76 if (this.wasPrivateVideo || this.wasUnlistedVideo) {
77 Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated)
78 }
79
80 if (videoUpdated.isLive) {
81 PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
82 PeerTubeSocket.Instance.sendVideoViewsUpdate(videoUpdated)
83 }
84
85 logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags(this.videoObject.uuid))
86
87 return videoUpdated
88 } catch (err) {
89 this.catchUpdateError(err)
90 }
91 }
92
93 // Check we can update the channel: we trust the remote server
94 private checkChannelUpdateOrThrow (newChannelActor: MActor) {
95 if (!this.oldVideoChannel.Actor.serverId || !newChannelActor.serverId) {
96 throw new Error('Cannot check old channel/new channel validity because `serverId` is null')
97 }
98
99 if (this.oldVideoChannel.Actor.serverId !== newChannelActor.serverId) {
100 throw new Error(`New channel ${newChannelActor.url} is not on the same server than new channel ${this.oldVideoChannel.Actor.url}`)
101 }
102 }
103
104 private updateVideo (channel: MChannelId, transaction: Transaction, overrideTo?: string[]) {
105 const to = overrideTo || this.videoObject.to
106 const videoData = getVideoAttributesFromObject(channel, this.videoObject, to)
107 this.video.name = videoData.name
108 this.video.uuid = videoData.uuid
109 this.video.url = videoData.url
110 this.video.category = videoData.category
111 this.video.licence = videoData.licence
112 this.video.language = videoData.language
113 this.video.description = videoData.description
114 this.video.support = videoData.support
115 this.video.nsfw = videoData.nsfw
116 this.video.commentsEnabled = videoData.commentsEnabled
117 this.video.downloadEnabled = videoData.downloadEnabled
118 this.video.waitTranscoding = videoData.waitTranscoding
119 this.video.state = videoData.state
120 this.video.duration = videoData.duration
121 this.video.createdAt = videoData.createdAt
122 this.video.publishedAt = videoData.publishedAt
123 this.video.originallyPublishedAt = videoData.originallyPublishedAt
124 this.video.privacy = videoData.privacy
125 this.video.channelId = videoData.channelId
126 this.video.views = videoData.views
127 this.video.isLive = videoData.isLive
128
129 // Ensures we update the updatedAt attribute, even if main attributes did not change
130 this.video.changed('updatedAt', true)
131
132 return this.video.save({ transaction }) as Promise<MVideoFullLight>
133 }
134
135 private async setCaptions (videoUpdated: MVideoFullLight, t: Transaction) {
136 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
137
138 await this.insertOrReplaceCaptions(videoUpdated, t)
139 }
140
141 private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction: Transaction) {
142 if (this.video.isLive) return this.insertOrReplaceLive(videoUpdated, transaction)
143
144 // Delete existing live if it exists
145 await VideoLiveModel.destroy({
146 where: {
147 videoId: this.video.id
148 },
149 transaction
150 })
151
152 videoUpdated.VideoLive = null
153 }
154
155 private catchUpdateError (err: Error) {
156 if (this.video !== undefined && this.videoFieldsSave !== undefined) {
157 resetSequelizeInstance(this.video, this.videoFieldsSave)
158 }
159
160 // This is just a debug because we will retry the insert
161 logger.debug('Cannot update the remote video.', { err, ...this.lTags(this.videoObject.uuid) })
162 throw err
163 }
164 }