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