]>
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' | |
c3441b03 | 6 | import { Hooks } from '@server/lib/plugins/hooks' |
08a47c75 | 7 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' |
08a47c75 | 8 | import { VideoLiveModel } from '@server/models/video/video-live' |
c56faf0d | 9 | import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models' |
08a47c75 | 10 | import { VideoObject, VideoPrivacy } from '@shared/models' |
57e4e1c1 | 11 | import { APVideoAbstractBuilder, getVideoAttributesFromObject, updateVideoRates } from './shared' |
08a47c75 C |
12 | |
13 | export class APVideoUpdater extends APVideoAbstractBuilder { | |
08a47c75 C |
14 | private readonly wasPrivateVideo: boolean |
15 | private readonly wasUnlistedVideo: boolean | |
16 | ||
08a47c75 C |
17 | private readonly oldVideoChannel: MChannelAccountLight |
18 | ||
908e6ead | 19 | protected lTags: LoggerTagsFn |
46320694 | 20 | |
c56faf0d C |
21 | constructor ( |
22 | protected readonly videoObject: VideoObject, | |
23 | private readonly video: MVideoAccountLightBlacklistAllFiles | |
24 | ) { | |
08a47c75 C |
25 | super() |
26 | ||
08a47c75 C |
27 | this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE |
28 | this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED | |
29 | ||
30 | this.oldVideoChannel = this.video.VideoChannel | |
31 | ||
908e6ead | 32 | this.lTags = loggerTagsFactory('ap', 'video', 'update', video.uuid, video.url) |
08a47c75 C |
33 | } |
34 | ||
c56faf0d | 35 | async update (overrideTo?: string[]) { |
46320694 C |
36 | logger.debug( |
37 | 'Updating remote video "%s".', this.videoObject.uuid, | |
908e6ead | 38 | { videoObject: this.videoObject, ...this.lTags() } |
46320694 | 39 | ) |
08a47c75 C |
40 | |
41 | try { | |
c56faf0d C |
42 | const channelActor = await this.getOrCreateVideoChannelFromVideoObject() |
43 | ||
08a47c75 C |
44 | const thumbnailModel = await this.tryToGenerateThumbnail(this.video) |
45 | ||
28dfb44b | 46 | this.checkChannelUpdateOrThrow(channelActor) |
08a47c75 | 47 | |
28dfb44b | 48 | const videoUpdated = await this.updateVideo(channelActor.VideoChannel, undefined, overrideTo) |
08a47c75 | 49 | |
28dfb44b | 50 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel) |
08a47c75 | 51 | |
28dfb44b | 52 | await runInReadCommittedTransaction(async t => { |
08a47c75 C |
53 | await this.setWebTorrentFiles(videoUpdated, t) |
54 | await this.setStreamingPlaylists(videoUpdated, t) | |
08a47c75 C |
55 | }) |
56 | ||
28dfb44b C |
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 | ||
08a47c75 C |
66 | await autoBlacklistVideoIfNeeded({ |
67 | video: videoUpdated, | |
68 | user: undefined, | |
69 | isRemote: true, | |
70 | isNew: false, | |
71 | transaction: undefined | |
72 | }) | |
73 | ||
57e4e1c1 C |
74 | await updateVideoRates(videoUpdated, this.videoObject) |
75 | ||
08a47c75 C |
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) | |
08a47c75 C |
83 | } |
84 | ||
c3441b03 C |
85 | Hooks.runAction('action:activity-pub.remote-video.updated', { video: videoUpdated, videoAPObject: this.videoObject }) |
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) { | |
823c34c0 | 91 | await this.catchUpdateError(err) |
08a47c75 C |
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 | ||
823c34c0 | 157 | private async catchUpdateError (err: Error) { |
45657746 | 158 | if (this.video !== undefined) { |
823c34c0 | 159 | await resetSequelizeInstance(this.video) |
08a47c75 C |
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 | } |