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 { AvatarModel } from '../../../models/avatar/avatar'
12 import { TagModel } from '../../../models/video/tag'
13 import { VideoModel } from '../../../models/video/video'
14 import { VideoFileModel } from '../../../models/video/video-file'
15 import { fetchActorTotalItems, fetchAvatarIfExists, getOrCreateActorAndServerAndModel } from '../actor'
16 import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
18 async function processUpdateActivity (activity: ActivityUpdate) {
19 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
21 if (activity.object.type === 'Video') {
22 return processUpdateVideo(actor, activity)
23 } else if (activity.object.type === 'Person') {
24 return processUpdateAccount(actor, activity)
30 // ---------------------------------------------------------------------------
36 // ---------------------------------------------------------------------------
38 function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
40 arguments: [ actor, activity ],
41 errorMessage: 'Cannot update the remote video with many retries'
44 return retryTransactionWrapper(updateRemoteVideo, options)
47 async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
48 const videoAttributesToUpdate = activity.object as VideoTorrentObject
50 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
51 let videoInstance: VideoModel
52 let videoFieldsSave: any
55 await sequelizeTypescript.transaction(async t => {
56 const sequelizeOptions = {
60 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
61 if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
63 videoFieldsSave = videoInstance.toJSON()
65 const videoChannel = videoInstance.VideoChannel
66 if (videoChannel.Account.Actor.id !== actor.id) {
67 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
70 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to, activity.cc)
71 videoInstance.set('name', videoData.name)
72 videoInstance.set('category', videoData.category)
73 videoInstance.set('licence', videoData.licence)
74 videoInstance.set('language', videoData.language)
75 videoInstance.set('nsfw', videoData.nsfw)
76 videoInstance.set('commentsEnabled', videoData.commentsEnabled)
77 videoInstance.set('privacy', videoData.privacy)
78 videoInstance.set('description', videoData.description)
79 videoInstance.set('duration', videoData.duration)
80 videoInstance.set('createdAt', videoData.createdAt)
81 videoInstance.set('updatedAt', videoData.updatedAt)
82 videoInstance.set('views', videoData.views)
84 await videoInstance.save(sequelizeOptions)
86 // Remove old video files
87 const videoFileDestroyTasks: Bluebird<void>[] = []
88 for (const videoFile of videoInstance.VideoFiles) {
89 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
91 await Promise.all(videoFileDestroyTasks)
93 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
94 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f))
95 await Promise.all(tasks)
97 const tags = videoAttributesToUpdate.tag.map(t => t.name)
98 const tagInstances = await TagModel.findOrCreateTags(tags, t)
99 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
102 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
104 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
105 resetSequelizeInstance(videoInstance, videoFieldsSave)
108 // This is just a debug because we will retry the insert
109 logger.debug('Cannot update the remote video.', err)
114 function processUpdateAccount (actor: ActorModel, activity: ActivityUpdate) {
116 arguments: [ actor, activity ],
117 errorMessage: 'Cannot update the remote account with many retries'
120 return retryTransactionWrapper(updateRemoteAccount, options)
123 async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate) {
124 const accountAttributesToUpdate = activity.object as ActivityPubActor
126 logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid)
127 let actorInstance: ActorModel
128 let accountInstance: AccountModel
129 let actorFieldsSave: object
130 let accountFieldsSave: object
133 const avatarName = await fetchAvatarIfExists(accountAttributesToUpdate)
136 await sequelizeTypescript.transaction(async t => {
137 actorInstance = await ActorModel.loadByUrl(accountAttributesToUpdate.id, t)
138 if (!actorInstance) throw new Error('Actor ' + accountAttributesToUpdate.id + ' not found.')
140 actorFieldsSave = actorInstance.toJSON()
141 accountInstance = actorInstance.Account
142 accountFieldsSave = actorInstance.Account.toJSON()
144 const followersCount = await fetchActorTotalItems(accountAttributesToUpdate.followers)
145 const followingCount = await fetchActorTotalItems(accountAttributesToUpdate.following)
147 actorInstance.set('type', accountAttributesToUpdate.type)
148 actorInstance.set('uuid', accountAttributesToUpdate.uuid)
149 actorInstance.set('preferredUsername', accountAttributesToUpdate.preferredUsername)
150 actorInstance.set('url', accountAttributesToUpdate.id)
151 actorInstance.set('publicKey', accountAttributesToUpdate.publicKey.publicKeyPem)
152 actorInstance.set('followersCount', followersCount)
153 actorInstance.set('followingCount', followingCount)
154 actorInstance.set('inboxUrl', accountAttributesToUpdate.inbox)
155 actorInstance.set('outboxUrl', accountAttributesToUpdate.outbox)
156 actorInstance.set('sharedInboxUrl', accountAttributesToUpdate.endpoints.sharedInbox)
157 actorInstance.set('followersUrl', accountAttributesToUpdate.followers)
158 actorInstance.set('followingUrl', accountAttributesToUpdate.following)
160 if (avatarName !== undefined) {
161 if (actorInstance.avatarId) {
162 await actorInstance.Avatar.destroy({ transaction: t })
165 const avatar = await AvatarModel.create({
167 }, { transaction: t })
169 actor.set('avatarId', avatar.id)
172 await actor.save({ transaction: t })
174 actor.Account.set('name', accountAttributesToUpdate.name || accountAttributesToUpdate.preferredUsername)
175 await actor.Account.save({ transaction: t })
178 logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid)
180 if (actorInstance !== undefined && actorFieldsSave !== undefined) {
181 resetSequelizeInstance(actorInstance, actorFieldsSave)
184 if (accountInstance !== undefined && accountFieldsSave !== undefined) {
185 resetSequelizeInstance(accountInstance, accountFieldsSave)
188 // This is just a debug because we will retry the insert
189 logger.debug('Cannot update the remote account.', err)