aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-06-02 11:54:29 +0200
committerChocobozzz <me@florianbigard.com>2021-06-02 16:57:53 +0200
commitc56faf0d9453490737f283b29a203bb1ca632b95 (patch)
tree6f6f92ae762921112e49cf5a440381053bb5a492
parent08a47c75f992e7138dca5121f227909a8347d365 (diff)
downloadPeerTube-c56faf0d9453490737f283b29a203bb1ca632b95.tar.gz
PeerTube-c56faf0d9453490737f283b29a203bb1ca632b95.tar.zst
PeerTube-c56faf0d9453490737f283b29a203bb1ca632b95.zip
Move AP video channel creation
-rw-r--r--server/lib/activitypub/process/process-update.ts23
-rw-r--r--server/lib/activitypub/videos/fetch.ts26
-rw-r--r--server/lib/activitypub/videos/shared/abstract-builder.ts13
-rw-r--r--server/lib/activitypub/videos/shared/creator.ts21
-rw-r--r--server/lib/activitypub/videos/updater.ts47
5 files changed, 47 insertions, 83 deletions
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index de1ff5d90..516bd8d70 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -12,12 +12,12 @@ import { AccountModel } from '../../../models/account/account'
12import { ActorModel } from '../../../models/actor/actor' 12import { ActorModel } from '../../../models/actor/actor'
13import { VideoChannelModel } from '../../../models/video/video-channel' 13import { VideoChannelModel } from '../../../models/video/video-channel'
14import { APProcessorOptions } from '../../../types/activitypub-processor.model' 14import { APProcessorOptions } from '../../../types/activitypub-processor.model'
15import { MAccountIdActor, MActorSignature } from '../../../types/models' 15import { MActorSignature } from '../../../types/models'
16import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor' 16import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor'
17import { createOrUpdateCacheFile } from '../cache-file' 17import { createOrUpdateCacheFile } from '../cache-file'
18import { createOrUpdateVideoPlaylist } from '../playlist' 18import { createOrUpdateVideoPlaylist } from '../playlist'
19import { forwardVideoRelatedActivity } from '../send/utils' 19import { forwardVideoRelatedActivity } from '../send/utils'
20import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, APVideoUpdater } from '../videos' 20import { APVideoUpdater, getOrCreateVideoAndAccountAndChannel } from '../videos'
21 21
22async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { 22async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
23 const { activity, byActor } = options 23 const { activity, byActor } = options
@@ -25,7 +25,7 @@ async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate
25 const objectType = activity.object.type 25 const objectType = activity.object.type
26 26
27 if (objectType === 'Video') { 27 if (objectType === 'Video') {
28 return retryTransactionWrapper(processUpdateVideo, byActor, activity) 28 return retryTransactionWrapper(processUpdateVideo, activity)
29 } 29 }
30 30
31 if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') { 31 if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
@@ -55,7 +55,7 @@ export {
55 55
56// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
57 57
58async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) { 58async function processUpdateVideo (activity: ActivityUpdate) {
59 const videoObject = activity.object as VideoObject 59 const videoObject = activity.object as VideoObject
60 60
61 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { 61 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
@@ -71,19 +71,8 @@ async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpd
71 // We did not have this video, it has been created so no need to update 71 // We did not have this video, it has been created so no need to update
72 if (created) return 72 if (created) return
73 73
74 // Load new channel 74 const updater = new APVideoUpdater(videoObject, video)
75 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 75 return updater.update(activity.to)
76
77 const account = actor.Account as MAccountIdActor
78 account.Actor = actor
79
80 const updater = new APVideoUpdater({
81 video,
82 videoObject,
83 channel: channelActor.VideoChannel,
84 overrideTo: activity.to
85 })
86 return updater.update()
87} 76}
88 77
89async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) { 78async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) {
diff --git a/server/lib/activitypub/videos/fetch.ts b/server/lib/activitypub/videos/fetch.ts
index 5e7f8552b..5113c9d7e 100644
--- a/server/lib/activitypub/videos/fetch.ts
+++ b/server/lib/activitypub/videos/fetch.ts
@@ -11,7 +11,6 @@ import { VideoModel } from '@server/models/video/video'
11import { MVideoAccountLight, MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models' 11import { MVideoAccountLight, MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models'
12import { HttpStatusCode } from '@shared/core-utils' 12import { HttpStatusCode } from '@shared/core-utils'
13import { VideoObject } from '@shared/models' 13import { VideoObject } from '@shared/models'
14import { getOrCreateActorAndServerAndModel } from '../actor'
15import { APVideoCreator, SyncParam, syncVideoExternalAttributes } from './shared' 14import { APVideoCreator, SyncParam, syncVideoExternalAttributes } from './shared'
16import { APVideoUpdater } from './updater' 15import { APVideoUpdater } from './updater'
17 16
@@ -37,17 +36,6 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
37 return body.description || '' 36 return body.description || ''
38} 37}
39 38
40function getOrCreateVideoChannelFromVideoObject (videoObject: VideoObject) {
41 const channel = videoObject.attributedTo.find(a => a.type === 'Group')
42 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
43
44 if (checkUrlsSameHost(channel.id, videoObject.id) !== true) {
45 throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${videoObject.id}`)
46 }
47
48 return getOrCreateActorAndServerAndModel(channel.id, 'all')
49}
50
51type GetVideoResult <T> = Promise<{ 39type GetVideoResult <T> = Promise<{
52 video: T 40 video: T
53 created: boolean 41 created: boolean
@@ -117,11 +105,8 @@ async function getOrCreateVideoAndAccountAndChannel (
117 const { videoObject } = await fetchRemoteVideo(videoUrl) 105 const { videoObject } = await fetchRemoteVideo(videoUrl)
118 if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl) 106 if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
119 107
120 const actor = await getOrCreateVideoChannelFromVideoObject(videoObject)
121 const videoChannel = actor.VideoChannel
122
123 try { 108 try {
124 const creator = new APVideoCreator({ videoObject, channel: videoChannel }) 109 const creator = new APVideoCreator(videoObject)
125 const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(creator.create.bind(creator), syncParam.thumbnail) 110 const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(creator.create.bind(creator), syncParam.thumbnail)
126 111
127 await syncVideoExternalAttributes(videoCreated, videoObject, syncParam) 112 await syncVideoExternalAttributes(videoCreated, videoObject, syncParam)
@@ -160,13 +145,7 @@ async function refreshVideoIfNeeded (options: {
160 return video 145 return video
161 } 146 }
162 147
163 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 148 const videoUpdater = new APVideoUpdater(videoObject, video)
164
165 const videoUpdater = new APVideoUpdater({
166 video,
167 videoObject,
168 channel: channelActor.VideoChannel
169 })
170 await videoUpdater.update() 149 await videoUpdater.update()
171 150
172 await syncVideoExternalAttributes(video, videoObject, options.syncParam) 151 await syncVideoExternalAttributes(video, videoObject, options.syncParam)
@@ -197,6 +176,5 @@ export {
197 fetchRemoteVideo, 176 fetchRemoteVideo,
198 fetchRemoteVideoDescription, 177 fetchRemoteVideoDescription,
199 refreshVideoIfNeeded, 178 refreshVideoIfNeeded,
200 getOrCreateVideoChannelFromVideoObject,
201 getOrCreateVideoAndAccountAndChannel 179 getOrCreateVideoAndAccountAndChannel
202} 180}
diff --git a/server/lib/activitypub/videos/shared/abstract-builder.ts b/server/lib/activitypub/videos/shared/abstract-builder.ts
index 9d5f37e5f..c7631cd45 100644
--- a/server/lib/activitypub/videos/shared/abstract-builder.ts
+++ b/server/lib/activitypub/videos/shared/abstract-builder.ts
@@ -1,4 +1,5 @@
1import { Transaction } from 'sequelize/types' 1import { Transaction } from 'sequelize/types'
2import { checkUrlsSameHost } from '@server/helpers/activitypub'
2import { deleteNonExistingModels } from '@server/helpers/database-utils' 3import { deleteNonExistingModels } from '@server/helpers/database-utils'
3import { logger } from '@server/helpers/logger' 4import { logger } from '@server/helpers/logger'
4import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '@server/lib/thumbnail' 5import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '@server/lib/thumbnail'
@@ -9,6 +10,7 @@ import { VideoLiveModel } from '@server/models/video/video-live'
9import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' 10import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
10import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models' 11import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
11import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models' 12import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models'
13import { getOrCreateActorAndServerAndModel } from '../../actor'
12import { 14import {
13 getCaptionAttributesFromObject, 15 getCaptionAttributesFromObject,
14 getFileAttributesFromUrl, 16 getFileAttributesFromUrl,
@@ -23,6 +25,17 @@ import { getTrackerUrls, setVideoTrackers } from './trackers'
23export abstract class APVideoAbstractBuilder { 25export abstract class APVideoAbstractBuilder {
24 protected abstract videoObject: VideoObject 26 protected abstract videoObject: VideoObject
25 27
28 protected async getOrCreateVideoChannelFromVideoObject () {
29 const channel = this.videoObject.attributedTo.find(a => a.type === 'Group')
30 if (!channel) throw new Error('Cannot find associated video channel to video ' + this.videoObject.url)
31
32 if (checkUrlsSameHost(channel.id, this.videoObject.id) !== true) {
33 throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${this.videoObject.id}`)
34 }
35
36 return getOrCreateActorAndServerAndModel(channel.id, 'all')
37 }
38
26 protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> { 39 protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> {
27 return createVideoMiniatureFromUrl({ 40 return createVideoMiniatureFromUrl({
28 downloadUrl: getThumbnailFromIcons(this.videoObject).url, 41 downloadUrl: getThumbnailFromIcons(this.videoObject).url,
diff --git a/server/lib/activitypub/videos/shared/creator.ts b/server/lib/activitypub/videos/shared/creator.ts
index 4f2d79374..dd9bfb508 100644
--- a/server/lib/activitypub/videos/shared/creator.ts
+++ b/server/lib/activitypub/videos/shared/creator.ts
@@ -3,29 +3,24 @@ import { logger } from '@server/helpers/logger'
3import { sequelizeTypescript } from '@server/initializers/database' 3import { sequelizeTypescript } from '@server/initializers/database'
4import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' 4import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
5import { VideoModel } from '@server/models/video/video' 5import { VideoModel } from '@server/models/video/video'
6import { MChannelAccountLight, MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models' 6import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
7import { VideoObject } from '@shared/models' 7import { VideoObject } from '@shared/models'
8import { APVideoAbstractBuilder } from './abstract-builder' 8import { APVideoAbstractBuilder } from './abstract-builder'
9import { getVideoAttributesFromObject } from './object-to-model-attributes' 9import { getVideoAttributesFromObject } from './object-to-model-attributes'
10 10
11export class APVideoCreator extends APVideoAbstractBuilder { 11export class APVideoCreator extends APVideoAbstractBuilder {
12 protected readonly videoObject: VideoObject
13 private readonly channel: MChannelAccountLight
14 12
15 constructor (options: { 13 constructor (protected readonly videoObject: VideoObject) {
16 videoObject: VideoObject
17 channel: MChannelAccountLight
18 }) {
19 super() 14 super()
20
21 this.videoObject = options.videoObject
22 this.channel = options.channel
23 } 15 }
24 16
25 async create (waitThumbnail = false) { 17 async create (waitThumbnail = false) {
26 logger.debug('Adding remote video %s.', this.videoObject.id) 18 logger.debug('Adding remote video %s.', this.videoObject.id)
27 19
28 const videoData = await getVideoAttributesFromObject(this.channel, this.videoObject, this.videoObject.to) 20 const channelActor = await this.getOrCreateVideoChannelFromVideoObject()
21 const channel = channelActor.VideoChannel
22
23 const videoData = await getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to)
29 const video = VideoModel.build(videoData) as MVideoThumbnail 24 const video = VideoModel.build(videoData) as MVideoThumbnail
30 25
31 const promiseThumbnail = this.tryToGenerateThumbnail(video) 26 const promiseThumbnail = this.tryToGenerateThumbnail(video)
@@ -38,7 +33,7 @@ export class APVideoCreator extends APVideoAbstractBuilder {
38 const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { 33 const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
39 try { 34 try {
40 const videoCreated = await video.save({ transaction: t }) as MVideoFullLight 35 const videoCreated = await video.save({ transaction: t }) as MVideoFullLight
41 videoCreated.VideoChannel = this.channel 36 videoCreated.VideoChannel = channel
42 37
43 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) 38 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
44 39
@@ -51,7 +46,7 @@ export class APVideoCreator extends APVideoAbstractBuilder {
51 await this.insertOrReplaceLive(videoCreated, t) 46 await this.insertOrReplaceLive(videoCreated, t)
52 47
53 // We added a video in this channel, set it as updated 48 // We added a video in this channel, set it as updated
54 await this.channel.setAsUpdated(t) 49 await channel.setAsUpdated(t)
55 50
56 const autoBlacklisted = await autoBlacklistVideoIfNeeded({ 51 const autoBlacklisted = await autoBlacklistVideoIfNeeded({
57 video: videoCreated, 52 video: videoCreated,
diff --git a/server/lib/activitypub/videos/updater.ts b/server/lib/activitypub/videos/updater.ts
index 4338d1e22..11c177a68 100644
--- a/server/lib/activitypub/videos/updater.ts
+++ b/server/lib/activitypub/videos/updater.ts
@@ -7,17 +7,11 @@ import { PeerTubeSocket } from '@server/lib/peertube-socket'
7import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' 7import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
8import { VideoCaptionModel } from '@server/models/video/video-caption' 8import { VideoCaptionModel } from '@server/models/video/video-caption'
9import { VideoLiveModel } from '@server/models/video/video-live' 9import { VideoLiveModel } from '@server/models/video/video-live'
10import { MChannelAccountLight, MChannelDefault, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models' 10import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models'
11import { VideoObject, VideoPrivacy } from '@shared/models' 11import { VideoObject, VideoPrivacy } from '@shared/models'
12import { APVideoAbstractBuilder, getVideoAttributesFromObject } from './shared' 12import { APVideoAbstractBuilder, getVideoAttributesFromObject } from './shared'
13 13
14export class APVideoUpdater extends APVideoAbstractBuilder { 14export class APVideoUpdater extends APVideoAbstractBuilder {
15 protected readonly videoObject: VideoObject
16
17 private readonly video: MVideoAccountLightBlacklistAllFiles
18 private readonly channel: MChannelDefault
19 private readonly overrideTo: string[]
20
21 private readonly wasPrivateVideo: boolean 15 private readonly wasPrivateVideo: boolean
22 private readonly wasUnlistedVideo: boolean 16 private readonly wasUnlistedVideo: boolean
23 17
@@ -25,19 +19,12 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
25 19
26 private readonly oldVideoChannel: MChannelAccountLight 20 private readonly oldVideoChannel: MChannelAccountLight
27 21
28 constructor (options: { 22 constructor (
29 video: MVideoAccountLightBlacklistAllFiles 23 protected readonly videoObject: VideoObject,
30 videoObject: VideoObject 24 private readonly video: MVideoAccountLightBlacklistAllFiles
31 channel: MChannelDefault 25 ) {
32 overrideTo?: string[]
33 }) {
34 super() 26 super()
35 27
36 this.video = options.video
37 this.videoObject = options.videoObject
38 this.channel = options.channel
39 this.overrideTo = options.overrideTo
40
41 this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE 28 this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE
42 this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED 29 this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED
43 30
@@ -46,16 +33,18 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
46 this.videoFieldsSave = this.video.toJSON() 33 this.videoFieldsSave = this.video.toJSON()
47 } 34 }
48 35
49 async update () { 36 async update (overrideTo?: string[]) {
50 logger.debug('Updating remote video "%s".', this.videoObject.uuid, { videoObject: this.videoObject, channel: this.channel }) 37 logger.debug('Updating remote video "%s".', this.videoObject.uuid, { videoObject: this.videoObject })
51 38
52 try { 39 try {
40 const channelActor = await this.getOrCreateVideoChannelFromVideoObject()
41
53 const thumbnailModel = await this.tryToGenerateThumbnail(this.video) 42 const thumbnailModel = await this.tryToGenerateThumbnail(this.video)
54 43
55 const videoUpdated = await sequelizeTypescript.transaction(async t => { 44 const videoUpdated = await sequelizeTypescript.transaction(async t => {
56 this.checkChannelUpdateOrThrow() 45 this.checkChannelUpdateOrThrow(channelActor)
57 46
58 const videoUpdated = await this.updateVideo(t) 47 const videoUpdated = await this.updateVideo(channelActor.VideoChannel, t, overrideTo)
59 48
60 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) 49 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
61 50
@@ -97,19 +86,19 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
97 } 86 }
98 87
99 // Check we can update the channel: we trust the remote server 88 // Check we can update the channel: we trust the remote server
100 private checkChannelUpdateOrThrow () { 89 private checkChannelUpdateOrThrow (newChannelActor: MActor) {
101 if (!this.oldVideoChannel.Actor.serverId || !this.channel.Actor.serverId) { 90 if (!this.oldVideoChannel.Actor.serverId || !newChannelActor.serverId) {
102 throw new Error('Cannot check old channel/new channel validity because `serverId` is null') 91 throw new Error('Cannot check old channel/new channel validity because `serverId` is null')
103 } 92 }
104 93
105 if (this.oldVideoChannel.Actor.serverId !== this.channel.Actor.serverId) { 94 if (this.oldVideoChannel.Actor.serverId !== newChannelActor.serverId) {
106 throw new Error(`New channel ${this.channel.Actor.url} is not on the same server than new channel ${this.oldVideoChannel.Actor.url}`) 95 throw new Error(`New channel ${newChannelActor.url} is not on the same server than new channel ${this.oldVideoChannel.Actor.url}`)
107 } 96 }
108 } 97 }
109 98
110 private updateVideo (transaction: Transaction) { 99 private updateVideo (channel: MChannelId, transaction: Transaction, overrideTo?: string[]) {
111 const to = this.overrideTo || this.videoObject.to 100 const to = overrideTo || this.videoObject.to
112 const videoData = getVideoAttributesFromObject(this.channel, this.videoObject, to) 101 const videoData = getVideoAttributesFromObject(channel, this.videoObject, to)
113 this.video.name = videoData.name 102 this.video.name = videoData.name
114 this.video.uuid = videoData.uuid 103 this.video.uuid = videoData.uuid
115 this.video.url = videoData.url 104 this.video.url = videoData.url