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