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'
15 generateThumbnailFromUrl,
16 getOrCreateAccountAndVideoAndChannel,
17 getOrCreateVideoChannel,
18 videoActivityObjectToDBAttributes,
19 videoFileActivityUrlToDBAttributes
21 import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos'
23 async function processUpdateActivity (activity: ActivityUpdate) {
24 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
25 const objectType = activity.object.type
27 if (objectType === 'Video') {
28 return processUpdateVideo(actor, activity)
29 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
30 return processUpdateActor(actor, activity)
36 // ---------------------------------------------------------------------------
42 // ---------------------------------------------------------------------------
44 function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
46 arguments: [ actor, activity ],
47 errorMessage: 'Cannot update the remote video with many retries'
50 return retryTransactionWrapper(updateRemoteVideo, options)
53 async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
54 const videoObject = activity.object as VideoTorrentObject
56 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
57 logger.debug('Video sent by update is not valid.', { videoObject })
61 const res = await getOrCreateAccountAndVideoAndChannel(videoObject.id)
63 // Fetch video channel outside the transaction
64 const newVideoChannelActor = await getOrCreateVideoChannel(videoObject)
65 const newVideoChannel = newVideoChannelActor.VideoChannel
67 logger.debug('Updating remote video "%s".', videoObject.uuid)
68 let videoInstance = res.video
69 let videoFieldsSave: any
72 await sequelizeTypescript.transaction(async t => {
73 const sequelizeOptions = {
77 videoFieldsSave = videoInstance.toJSON()
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)
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)
105 await videoInstance.save(sequelizeOptions)
107 // Don't block on request
108 generateThumbnailFromUrl(videoInstance, videoObject.icon)
109 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }))
111 // Remove old video files
112 const videoFileDestroyTasks: Bluebird<void>[] = []
113 for (const videoFile of videoInstance.VideoFiles) {
114 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
116 await Promise.all(videoFileDestroyTasks)
118 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoObject)
119 const tasks = videoFileAttributes.map(f => VideoFileModel.create(f))
120 await Promise.all(tasks)
122 const tags = videoObject.tag.map(t => t.name)
123 const tagInstances = await TagModel.findOrCreateTags(tags, t)
124 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
127 logger.info('Remote video with uuid %s updated', videoObject.uuid)
129 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
130 resetSequelizeInstance(videoInstance, videoFieldsSave)
133 // This is just a debug because we will retry the insert
134 logger.debug('Cannot update the remote video.', { err })
139 function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
141 arguments: [ actor, activity ],
142 errorMessage: 'Cannot update the remote actor with many retries'
145 return retryTransactionWrapper(updateRemoteActor, options)
148 async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
149 const actorAttributesToUpdate = activity.object as ActivityPubActor
151 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
152 let accountOrChannelInstance: AccountModel | VideoChannelModel
153 let actorFieldsSave: object
154 let accountOrChannelFieldsSave: object
157 const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
160 await sequelizeTypescript.transaction(async t => {
161 actorFieldsSave = actor.toJSON()
163 if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
164 else accountOrChannelInstance = actor.Account
166 accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
168 await updateActorInstance(actor, actorAttributesToUpdate)
170 if (avatarName !== undefined) {
171 await updateActorAvatarInstance(actor, avatarName, t)
174 await actor.save({ transaction: t })
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 })
182 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
184 if (actor !== undefined && actorFieldsSave !== undefined) {
185 resetSequelizeInstance(actor, actorFieldsSave)
188 if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
189 resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
192 // This is just a debug because we will retry the insert
193 logger.debug('Cannot update the remote account.', { err })