]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/lib/activitypub/process/process-update.ts
Add concept of video state, and add ability to wait transcoding before
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / process / process-update.ts
... / ...
CommitLineData
1import * as Bluebird from 'bluebird'
2import { ActivityUpdate } from '../../../../shared/models/activitypub'
3import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
4import { retryTransactionWrapper } from '../../../helpers/database-utils'
5import { logger } from '../../../helpers/logger'
6import { resetSequelizeInstance } from '../../../helpers/utils'
7import { sequelizeTypescript } from '../../../initializers'
8import { AccountModel } from '../../../models/account/account'
9import { ActorModel } from '../../../models/activitypub/actor'
10import { TagModel } from '../../../models/video/tag'
11import { VideoChannelModel } from '../../../models/video/video-channel'
12import { VideoFileModel } from '../../../models/video/video-file'
13import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
14import {
15 fetchRemoteVideo,
16 generateThumbnailFromUrl,
17 getOrCreateAccountAndVideoAndChannel,
18 getOrCreateVideoChannel,
19 videoActivityObjectToDBAttributes,
20 videoFileActivityUrlToDBAttributes
21} from '../videos'
22
23async function processUpdateActivity (activity: ActivityUpdate) {
24 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
25 const objectType = activity.object.type
26
27 if (objectType === 'Video') {
28 return processUpdateVideo(actor, activity)
29 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
30 return processUpdateActor(actor, activity)
31 }
32
33 return
34}
35
36// ---------------------------------------------------------------------------
37
38export {
39 processUpdateActivity
40}
41
42// ---------------------------------------------------------------------------
43
44function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
45 const options = {
46 arguments: [ actor, activity ],
47 errorMessage: 'Cannot update the remote video with many retries'
48 }
49
50 return retryTransactionWrapper(updateRemoteVideo, options)
51}
52
53async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
54 const videoUrl = activity.object.id
55
56 const videoObject = await fetchRemoteVideo(videoUrl)
57 if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
58
59 const res = await getOrCreateAccountAndVideoAndChannel(videoObject.id)
60
61 // Fetch video channel outside the transaction
62 const newVideoChannelActor = await getOrCreateVideoChannel(videoObject)
63 const newVideoChannel = newVideoChannelActor.VideoChannel
64
65 logger.debug('Updating remote video "%s".', videoObject.uuid)
66 let videoInstance = res.video
67 let videoFieldsSave: any
68
69 try {
70 await sequelizeTypescript.transaction(async t => {
71 const sequelizeOptions = {
72 transaction: t
73 }
74
75 videoFieldsSave = videoInstance.toJSON()
76
77 // Check actor has the right to update the video
78 const videoChannel = videoInstance.VideoChannel
79 if (videoChannel.Account.Actor.id !== actor.id) {
80 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
81 }
82
83 const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoObject, activity.to)
84 videoInstance.set('name', videoData.name)
85 videoInstance.set('uuid', videoData.uuid)
86 videoInstance.set('url', videoData.url)
87 videoInstance.set('category', videoData.category)
88 videoInstance.set('licence', videoData.licence)
89 videoInstance.set('language', videoData.language)
90 videoInstance.set('description', videoData.description)
91 videoInstance.set('support', videoData.support)
92 videoInstance.set('nsfw', videoData.nsfw)
93 videoInstance.set('commentsEnabled', videoData.commentsEnabled)
94 videoInstance.set('waitTranscoding', videoData.waitTranscoding)
95 videoInstance.set('state', videoData.state)
96 videoInstance.set('duration', videoData.duration)
97 videoInstance.set('createdAt', videoData.createdAt)
98 videoInstance.set('updatedAt', videoData.updatedAt)
99 videoInstance.set('views', videoData.views)
100 videoInstance.set('privacy', videoData.privacy)
101 videoInstance.set('channelId', videoData.channelId)
102
103 await videoInstance.save(sequelizeOptions)
104
105 // Don't block on request
106 generateThumbnailFromUrl(videoInstance, videoObject.icon)
107 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }))
108
109 // Remove old video files
110 const videoFileDestroyTasks: Bluebird<void>[] = []
111 for (const videoFile of videoInstance.VideoFiles) {
112 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
113 }
114 await Promise.all(videoFileDestroyTasks)
115
116 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoObject)
117 const tasks = videoFileAttributes.map(f => VideoFileModel.create(f))
118 await Promise.all(tasks)
119
120 const tags = videoObject.tag.map(t => t.name)
121 const tagInstances = await TagModel.findOrCreateTags(tags, t)
122 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
123 })
124
125 logger.info('Remote video with uuid %s updated', videoObject.uuid)
126 } catch (err) {
127 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
128 resetSequelizeInstance(videoInstance, videoFieldsSave)
129 }
130
131 // This is just a debug because we will retry the insert
132 logger.debug('Cannot update the remote video.', { err })
133 throw err
134 }
135}
136
137function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
138 const options = {
139 arguments: [ actor, activity ],
140 errorMessage: 'Cannot update the remote actor with many retries'
141 }
142
143 return retryTransactionWrapper(updateRemoteActor, options)
144}
145
146async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
147 const actorAttributesToUpdate = activity.object as ActivityPubActor
148
149 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
150 let accountOrChannelInstance: AccountModel | VideoChannelModel
151 let actorFieldsSave: object
152 let accountOrChannelFieldsSave: object
153
154 // Fetch icon?
155 const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
156
157 try {
158 await sequelizeTypescript.transaction(async t => {
159 actorFieldsSave = actor.toJSON()
160
161 if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
162 else accountOrChannelInstance = actor.Account
163
164 accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
165
166 await updateActorInstance(actor, actorAttributesToUpdate)
167
168 if (avatarName !== undefined) {
169 await updateActorAvatarInstance(actor, avatarName, t)
170 }
171
172 await actor.save({ transaction: t })
173
174 accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername)
175 accountOrChannelInstance.set('description', actorAttributesToUpdate.summary)
176 accountOrChannelInstance.set('support', actorAttributesToUpdate.support)
177 await accountOrChannelInstance.save({ transaction: t })
178 })
179
180 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
181 } catch (err) {
182 if (actor !== undefined && actorFieldsSave !== undefined) {
183 resetSequelizeInstance(actor, actorFieldsSave)
184 }
185
186 if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
187 resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
188 }
189
190 // This is just a debug because we will retry the insert
191 logger.debug('Cannot update the remote account.', { err })
192 throw err
193 }
194}