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'
22 import { VideoCaptionModel } from '../../../models/video/video-caption'
24 async function processUpdateActivity (activity: ActivityUpdate) {
25 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
26 const objectType = activity.object.type
28 if (objectType === 'Video') {
29 return retryTransactionWrapper(processUpdateVideo, actor, activity)
30 } else if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
31 return retryTransactionWrapper(processUpdateActor, actor, activity)
37 // ---------------------------------------------------------------------------
43 // ---------------------------------------------------------------------------
45 async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
46 const videoObject = activity.object as VideoTorrentObject
48 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
49 logger.debug('Video sent by update is not valid.', { videoObject })
53 const res = await getOrCreateAccountAndVideoAndChannel(videoObject.id)
55 // Fetch video channel outside the transaction
56 const newVideoChannelActor = await getOrCreateVideoChannel(videoObject)
57 const newVideoChannel = newVideoChannelActor.VideoChannel
59 logger.debug('Updating remote video "%s".', videoObject.uuid)
60 let videoInstance = res.video
61 let videoFieldsSave: any
64 await sequelizeTypescript.transaction(async t => {
65 const sequelizeOptions = {
69 videoFieldsSave = videoInstance.toJSON()
71 // Check actor has the right to update the video
72 const videoChannel = videoInstance.VideoChannel
73 if (videoChannel.Account.Actor.id !== actor.id) {
74 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
77 const videoData = await videoActivityObjectToDBAttributes(newVideoChannel, videoObject, activity.to)
78 videoInstance.set('name', videoData.name)
79 videoInstance.set('uuid', videoData.uuid)
80 videoInstance.set('url', videoData.url)
81 videoInstance.set('category', videoData.category)
82 videoInstance.set('licence', videoData.licence)
83 videoInstance.set('language', videoData.language)
84 videoInstance.set('description', videoData.description)
85 videoInstance.set('support', videoData.support)
86 videoInstance.set('nsfw', videoData.nsfw)
87 videoInstance.set('commentsEnabled', videoData.commentsEnabled)
88 videoInstance.set('waitTranscoding', videoData.waitTranscoding)
89 videoInstance.set('state', videoData.state)
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, videoObject.icon)
101 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.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, videoObject)
111 const tasks = videoFileAttributes.map(f => VideoFileModel.create(f))
112 await Promise.all(tasks)
115 const tags = videoObject.tag.map(tag => tag.name)
116 const tagInstances = await TagModel.findOrCreateTags(tags, t)
117 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
120 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoInstance.id, t)
122 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
123 return VideoCaptionModel.insertOrReplaceLanguage(videoInstance.id, c.identifier, t)
125 await Promise.all(videoCaptionsPromises)
128 logger.info('Remote video with uuid %s updated', videoObject.uuid)
130 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
131 resetSequelizeInstance(videoInstance, videoFieldsSave)
134 // This is just a debug because we will retry the insert
135 logger.debug('Cannot update the remote video.', { err })
140 async function processUpdateActor (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 })