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