]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/activitypub/process/process-update.ts
Activity Pub improvements
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / process / process-update.ts
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 { VideoFileModel } from '../../../models/video/video-file'
13 import { fetchAvatarIfExists, getOrCreateActorAndServerAndModel, updateActorAvatarInstance, updateActorInstance } from '../actor'
14 import { getOrCreateAccountAndVideoAndChannel, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from '../videos'
15
16 async function processUpdateActivity (activity: ActivityUpdate) {
17 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
18
19 if (activity.object.type === 'Video') {
20 return processUpdateVideo(actor, activity)
21 } else if (activity.object.type === 'Person') {
22 return processUpdateAccount(actor, activity)
23 }
24
25 return
26 }
27
28 // ---------------------------------------------------------------------------
29
30 export {
31 processUpdateActivity
32 }
33
34 // ---------------------------------------------------------------------------
35
36 function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
37 const options = {
38 arguments: [ actor, activity ],
39 errorMessage: 'Cannot update the remote video with many retries'
40 }
41
42 return retryTransactionWrapper(updateRemoteVideo, options)
43 }
44
45 async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
46 const videoAttributesToUpdate = activity.object as VideoTorrentObject
47
48 const res = await getOrCreateAccountAndVideoAndChannel(videoAttributesToUpdate.id)
49
50 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
51 let videoInstance = res.video
52 let videoFieldsSave: any
53
54 try {
55 await sequelizeTypescript.transaction(async t => {
56 const sequelizeOptions = {
57 transaction: t
58 }
59
60 videoFieldsSave = videoInstance.toJSON()
61
62 const videoChannel = videoInstance.VideoChannel
63 if (videoChannel.Account.Actor.id !== actor.id) {
64 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
65 }
66
67 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to, activity.cc)
68 videoInstance.set('name', videoData.name)
69 videoInstance.set('uuid', videoData.uuid)
70 videoInstance.set('url', videoData.url)
71 videoInstance.set('category', videoData.category)
72 videoInstance.set('licence', videoData.licence)
73 videoInstance.set('language', videoData.language)
74 videoInstance.set('description', videoData.description)
75 videoInstance.set('nsfw', videoData.nsfw)
76 videoInstance.set('commentsEnabled', videoData.commentsEnabled)
77 videoInstance.set('duration', videoData.duration)
78 videoInstance.set('createdAt', videoData.createdAt)
79 videoInstance.set('updatedAt', videoData.updatedAt)
80 videoInstance.set('views', videoData.views)
81 videoInstance.set('privacy', videoData.privacy)
82
83 await videoInstance.save(sequelizeOptions)
84
85 // Remove old video files
86 const videoFileDestroyTasks: Bluebird<void>[] = []
87 for (const videoFile of videoInstance.VideoFiles) {
88 videoFileDestroyTasks.push(videoFile.destroy(sequelizeOptions))
89 }
90 await Promise.all(videoFileDestroyTasks)
91
92 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
93 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f))
94 await Promise.all(tasks)
95
96 const tags = videoAttributesToUpdate.tag.map(t => t.name)
97 const tagInstances = await TagModel.findOrCreateTags(tags, t)
98 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
99 })
100
101 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
102 } catch (err) {
103 if (videoInstance !== undefined && videoFieldsSave !== undefined) {
104 resetSequelizeInstance(videoInstance, videoFieldsSave)
105 }
106
107 // This is just a debug because we will retry the insert
108 logger.debug('Cannot update the remote video.', err)
109 throw err
110 }
111 }
112
113 function processUpdateAccount (actor: ActorModel, activity: ActivityUpdate) {
114 const options = {
115 arguments: [ actor, activity ],
116 errorMessage: 'Cannot update the remote account with many retries'
117 }
118
119 return retryTransactionWrapper(updateRemoteAccount, options)
120 }
121
122 async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate) {
123 const accountAttributesToUpdate = activity.object as ActivityPubActor
124
125 logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid)
126 let accountInstance: AccountModel
127 let actorFieldsSave: object
128 let accountFieldsSave: object
129
130 // Fetch icon?
131 const avatarName = await fetchAvatarIfExists(accountAttributesToUpdate)
132
133 try {
134 await sequelizeTypescript.transaction(async t => {
135 actorFieldsSave = actor.toJSON()
136 accountInstance = actor.Account
137 accountFieldsSave = actor.Account.toJSON()
138
139 await updateActorInstance(actor, accountAttributesToUpdate)
140
141 if (avatarName !== undefined) {
142 await updateActorAvatarInstance(actor, avatarName, t)
143 }
144
145 await actor.save({ transaction: t })
146
147 actor.Account.set('name', accountAttributesToUpdate.name || accountAttributesToUpdate.preferredUsername)
148 await actor.Account.save({ transaction: t })
149 })
150
151 logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid)
152 } catch (err) {
153 if (actor !== undefined && actorFieldsSave !== undefined) {
154 resetSequelizeInstance(actor, actorFieldsSave)
155 }
156
157 if (accountInstance !== undefined && accountFieldsSave !== undefined) {
158 resetSequelizeInstance(accountInstance, accountFieldsSave)
159 }
160
161 // This is just a debug because we will retry the insert
162 logger.debug('Cannot update the remote account.', err)
163 throw err
164 }
165 }