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