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,
18 getOrCreateVideoChannel,
19 videoActivityObjectToDBAttributes,
20 videoFileActivityUrlToDBAttributes
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 videoAttributesToUpdate = activity.object as VideoTorrentObject
56 const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id)
58 // Fetch video channel outside the transaction
59 const newVideoChannelActor = await getOrCreateVideoChannel(videoAttributesToUpdate)
60 const newVideoChannel = newVideoChannelActor.VideoChannel
62 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
63 let videoInstance = res.video
64 let videoFieldsSave: any
67 await sequelizeTypescript.transaction(async t => {
68 const sequelizeOptions = {
72 videoFieldsSave = videoInstance.toJSON()
74 // Check actor has the right to update the video
75 const videoChannel = videoInstance.VideoChannel
76 if (videoChannel.Account.Actor.id !== actor.id) {
77 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
80 const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoAttributesToUpdate, activity.to)
81 videoInstance.set('name', videoData.name)
82 videoInstance.set('uuid', videoData.uuid)
83 videoInstance.set('url', videoData.url)
84 videoInstance.set('category', videoData.category)
85 videoInstance.set('licence', videoData.licence)
86 videoInstance.set('language', videoData.language)
87 videoInstance.set('description', videoData.description)
88 videoInstance.set('support', videoData.support)
89 videoInstance.set('nsfw', videoData.nsfw)
90 videoInstance.set('commentsEnabled', videoData.commentsEnabled)
91 videoInstance.set('duration', videoData.duration)
92 videoInstance.set('createdAt', videoData.createdAt)
93 videoInstance.set('updatedAt', videoData.updatedAt)
94 videoInstance.set('views', videoData.views)
95 videoInstance.set('privacy', videoData.privacy)
96 videoInstance.set('channelId', videoData.channelId)
98 await videoInstance.save(sequelizeOptions)
100 // Don't block on request
101 generateThumbnailFromUrl(videoInstance, videoAttributesToUpdate.icon)
102 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoAttributesToUpdate.id, { err }))
104 // Remove old video files
105 const videoFileDestroyTasks: Bluebird<void>[] = []
106 for (const videoFile of videoInstance.VideoFiles) {
107 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
109 await Promise.all(videoFileDestroyTasks)
111 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
112 const tasks = videoFileAttributes.map(f => VideoFileModel.create(f))
113 await Promise.all(tasks)
115 const tags = videoAttributesToUpdate.tag.map(t => t.name)
116 const tagInstances = await TagModel.findOrCreateTags(tags, t)
117 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
120 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
122 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
123 resetSequelizeInstance(videoInstance, videoFieldsSave)
126 // This is just a debug because we will retry the insert
127 logger.debug('Cannot update the remote video.', { err })
132 function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
134 arguments: [ actor, activity ],
135 errorMessage: 'Cannot update the remote actor with many retries'
138 return retryTransactionWrapper(updateRemoteActor, options)
141 async function updateRemoteActor (actor: ActorModel, activity: ActivityUpdate) {
142 const actorAttributesToUpdate = activity.object as ActivityPubActor
144 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid)
145 let accountOrChannelInstance: AccountModel | VideoChannelModel
146 let actorFieldsSave: object
147 let accountOrChannelFieldsSave: object
150 const avatarName = await fetchAvatarIfExists(actorAttributesToUpdate)
153 await sequelizeTypescript.transaction(async t => {
154 actorFieldsSave = actor.toJSON()
156 if (actorAttributesToUpdate.type === 'Group') accountOrChannelInstance = actor.VideoChannel
157 else accountOrChannelInstance = actor.Account
159 accountOrChannelFieldsSave = accountOrChannelInstance.toJSON()
161 await updateActorInstance(actor, actorAttributesToUpdate)
163 if (avatarName !== undefined) {
164 await updateActorAvatarInstance(actor, avatarName, t)
167 await actor.save({ transaction: t })
169 accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername)
170 accountOrChannelInstance.set('description', actorAttributesToUpdate.summary)
171 accountOrChannelInstance.set('support', actorAttributesToUpdate.support)
172 await accountOrChannelInstance.save({ transaction: t })
175 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid)
177 if (actor !== undefined && actorFieldsSave !== undefined) {
178 resetSequelizeInstance(actor, actorFieldsSave)
181 if (accountOrChannelInstance !== undefined && accountOrChannelFieldsSave !== undefined) {
182 resetSequelizeInstance(accountOrChannelInstance, accountOrChannelFieldsSave)
185 // This is just a debug because we will retry the insert
186 logger.debug('Cannot update the remote account.', { err })