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'
16 generateThumbnailFromUrl,
17 getOrCreateAccountAndVideoAndChannel, getOrCreateVideoChannel,
18 videoActivityObjectToDBAttributes,
19 videoFileActivityUrlToDBAttributes
22 async function processUpdateActivity (activity: ActivityUpdate) {
23 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
24 const objectType = activity.object.type
26 if (objectType === 'Video') {
27 return processUpdateVideo(actor, activity)
28 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
29 return processUpdateActor(actor, activity)
35 // ---------------------------------------------------------------------------
41 // ---------------------------------------------------------------------------
43 function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
45 arguments: [ actor, activity ],
46 errorMessage: 'Cannot update the remote video with many retries'
49 return retryTransactionWrapper(updateRemoteVideo, options)
52 async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
53 const videoAttributesToUpdate = activity.object as VideoTorrentObject
55 const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id)
57 // Fetch video channel outside the transaction
58 const newVideoChannelActor = await getOrCreateVideoChannel(videoAttributesToUpdate)
59 const newVideoChannel = newVideoChannelActor.VideoChannel
61 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
62 let videoInstance = res.video
63 let videoFieldsSave: any
66 await sequelizeTypescript.transaction(async t => {
67 const sequelizeOptions = {
71 videoFieldsSave = videoInstance.toJSON()
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)
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)
97 await videoInstance.save(sequelizeOptions)
99 // Don't block on request
100 generateThumbnailFromUrl(videoInstance, videoAttributesToUpdate.icon)
101 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoAttributesToUpdate.id, { err }))
103 // Remove old video files
104 const videoFileDestroyTasks: Bluebird<void>[] = []
105 for (const videoFile of videoInstance.VideoFiles) {
106 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
108 await Promise.all(videoFileDestroyTasks)
110 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
111 const tasks = videoFileAttributes.map(f => VideoFileModel.create(f))
112 await Promise.all(tasks)
114 const tags = videoAttributesToUpdate.tag.map(t => t.name)
115 const tagInstances = await TagModel.findOrCreateTags(tags, t)
116 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
119 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
121 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
122 resetSequelizeInstance(videoInstance, videoFieldsSave)
125 // This is just a debug because we will retry the insert
126 logger.debug('Cannot update the remote video.', { err })
131 function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
133 arguments: [ actor, activity ],
134 errorMessage: 'Cannot update the remote actor with many retries'
137 return retryTransactionWrapper(updateRemoteActor, options)
140 async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
141 const actorAttributesToUpdate = activity.object as ActivityPubActor
143 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
144 let accountOrChannelInstance: AccountModel | VideoChannelModel
145 let actorFieldsSave: object
146 let accountOrChannelFieldsSave: object
149 const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
152 await sequelizeTypescript.transaction(async t => {
153 actorFieldsSave = actor.toJSON()
155 if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
156 else accountOrChannelInstance = actor.Account
158 accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
160 await updateActorInstance(actor, actorAttributesToUpdate)
162 if (avatarName !== undefined) {
163 await updateActorAvatarInstance(actor, avatarName, t)
166 await actor.save({ transaction: t })
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 })
174 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
176 if (actor !== undefined && actorFieldsSave !== undefined) {
177 resetSequelizeInstance(actor, actorFieldsSave)
180 if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
181 resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
184 // This is just a debug because we will retry the insert
185 logger.debug('Cannot update the remote account.', { err })