]>
Commit | Line | Data |
---|---|---|
08a47c75 | 1 | import { Transaction } from 'sequelize/types' |
28dfb44b | 2 | import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/helpers/database-utils' |
908e6ead | 3 | import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' |
08a47c75 C |
4 | import { Notifier } from '@server/lib/notifier' |
5 | import { PeerTubeSocket } from '@server/lib/peertube-socket' | |
6 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' | |
08a47c75 | 7 | import { VideoLiveModel } from '@server/models/video/video-live' |
c56faf0d | 8 | import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models' |
08a47c75 C |
9 | import { VideoObject, VideoPrivacy } from '@shared/models' |
10 | import { APVideoAbstractBuilder, getVideoAttributesFromObject } from './shared' | |
11 | ||
12 | export class APVideoUpdater extends APVideoAbstractBuilder { | |
08a47c75 C |
13 | private readonly wasPrivateVideo: boolean |
14 | private readonly wasUnlistedVideo: boolean | |
15 | ||
16 | private readonly videoFieldsSave: any | |
17 | ||
18 | private readonly oldVideoChannel: MChannelAccountLight | |
19 | ||
908e6ead | 20 | protected lTags: LoggerTagsFn |
46320694 | 21 | |
c56faf0d C |
22 | constructor ( |
23 | protected readonly videoObject: VideoObject, | |
24 | private readonly video: MVideoAccountLightBlacklistAllFiles | |
25 | ) { | |
08a47c75 C |
26 | super() |
27 | ||
08a47c75 C |
28 | this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE |
29 | this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED | |
30 | ||
31 | this.oldVideoChannel = this.video.VideoChannel | |
32 | ||
33 | this.videoFieldsSave = this.video.toJSON() | |
908e6ead C |
34 | |
35 | this.lTags = loggerTagsFactory('ap', 'video', 'update', video.uuid, video.url) | |
08a47c75 C |
36 | } |
37 | ||
c56faf0d | 38 | async update (overrideTo?: string[]) { |
46320694 C |
39 | logger.debug( |
40 | 'Updating remote video "%s".', this.videoObject.uuid, | |
908e6ead | 41 | { videoObject: this.videoObject, ...this.lTags() } |
46320694 | 42 | ) |
08a47c75 C |
43 | |
44 | try { | |
c56faf0d C |
45 | const channelActor = await this.getOrCreateVideoChannelFromVideoObject() |
46 | ||
08a47c75 C |
47 | const thumbnailModel = await this.tryToGenerateThumbnail(this.video) |
48 | ||
28dfb44b | 49 | this.checkChannelUpdateOrThrow(channelActor) |
08a47c75 | 50 | |
28dfb44b | 51 | const videoUpdated = await this.updateVideo(channelActor.VideoChannel, undefined, overrideTo) |
08a47c75 | 52 | |
28dfb44b | 53 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel) |
08a47c75 | 54 | |
28dfb44b | 55 | await runInReadCommittedTransaction(async t => { |
08a47c75 C |
56 | await this.setWebTorrentFiles(videoUpdated, t) |
57 | await this.setStreamingPlaylists(videoUpdated, t) | |
08a47c75 C |
58 | }) |
59 | ||
28dfb44b C |
60 | await Promise.all([ |
61 | runInReadCommittedTransaction(t => this.setTags(videoUpdated, t)), | |
62 | runInReadCommittedTransaction(t => this.setTrackers(videoUpdated, t)), | |
63 | this.setOrDeleteLive(videoUpdated), | |
64 | this.setPreview(videoUpdated) | |
65 | ]) | |
66 | ||
67 | await runInReadCommittedTransaction(t => this.setCaptions(videoUpdated, t)) | |
68 | ||
08a47c75 C |
69 | await autoBlacklistVideoIfNeeded({ |
70 | video: videoUpdated, | |
71 | user: undefined, | |
72 | isRemote: true, | |
73 | isNew: false, | |
74 | transaction: undefined | |
75 | }) | |
76 | ||
77 | // Notify our users? | |
78 | if (this.wasPrivateVideo || this.wasUnlistedVideo) { | |
79 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) | |
80 | } | |
81 | ||
82 | if (videoUpdated.isLive) { | |
83 | PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated) | |
84 | PeerTubeSocket.Instance.sendVideoViewsUpdate(videoUpdated) | |
85 | } | |
86 | ||
908e6ead | 87 | logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags()) |
08a47c75 C |
88 | |
89 | return videoUpdated | |
90 | } catch (err) { | |
91 | this.catchUpdateError(err) | |
92 | } | |
93 | } | |
94 | ||
95 | // Check we can update the channel: we trust the remote server | |
c56faf0d C |
96 | private checkChannelUpdateOrThrow (newChannelActor: MActor) { |
97 | if (!this.oldVideoChannel.Actor.serverId || !newChannelActor.serverId) { | |
08a47c75 C |
98 | throw new Error('Cannot check old channel/new channel validity because `serverId` is null') |
99 | } | |
100 | ||
c56faf0d C |
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}`) | |
08a47c75 C |
103 | } |
104 | } | |
105 | ||
28dfb44b | 106 | private updateVideo (channel: MChannelId, transaction?: Transaction, overrideTo?: string[]) { |
c56faf0d C |
107 | const to = overrideTo || this.videoObject.to |
108 | const videoData = getVideoAttributesFromObject(channel, this.videoObject, to) | |
08a47c75 C |
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 | ||
136d7efd | 131 | // Ensures we update the updatedAt attribute, even if main attributes did not change |
08a47c75 C |
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) { | |
08a47c75 C |
138 | await this.insertOrReplaceCaptions(videoUpdated, t) |
139 | } | |
140 | ||
28dfb44b C |
141 | private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction?: Transaction) { |
142 | if (!this.video.isLive) return | |
143 | ||
08a47c75 C |
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 catchUpdateError (err: Error) { | |
158 | if (this.video !== undefined && this.videoFieldsSave !== undefined) { | |
159 | resetSequelizeInstance(this.video, this.videoFieldsSave) | |
160 | } | |
161 | ||
162 | // This is just a debug because we will retry the insert | |
908e6ead | 163 | logger.debug('Cannot update the remote video.', { err, ...this.lTags() }) |
08a47c75 C |
164 | throw err |
165 | } | |
166 | } |