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