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