Move AP video channel creation

pull/4158/head
Chocobozzz 2021-06-02 11:54:29 +02:00
parent 08a47c75f9
commit c56faf0d94
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
5 changed files with 47 additions and 83 deletions

View File

@ -12,12 +12,12 @@ import { AccountModel } from '../../../models/account/account'
import { ActorModel } from '../../../models/actor/actor'
import { VideoChannelModel } from '../../../models/video/video-channel'
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MAccountIdActor, MActorSignature } from '../../../types/models'
import { MActorSignature } from '../../../types/models'
import { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor'
import { createOrUpdateCacheFile } from '../cache-file'
import { createOrUpdateVideoPlaylist } from '../playlist'
import { forwardVideoRelatedActivity } from '../send/utils'
import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, APVideoUpdater } from '../videos'
import { APVideoUpdater, getOrCreateVideoAndAccountAndChannel } from '../videos'
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
const { activity, byActor } = options
@ -25,7 +25,7 @@ async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate
const objectType = activity.object.type
if (objectType === 'Video') {
return retryTransactionWrapper(processUpdateVideo, byActor, activity)
return retryTransactionWrapper(processUpdateVideo, activity)
}
if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
@ -55,7 +55,7 @@ export {
// ---------------------------------------------------------------------------
async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) {
async function processUpdateVideo (activity: ActivityUpdate) {
const videoObject = activity.object as VideoObject
if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
@ -71,19 +71,8 @@ async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpd
// We did not have this video, it has been created so no need to update
if (created) return
// Load new channel
const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
const account = actor.Account as MAccountIdActor
account.Actor = actor
const updater = new APVideoUpdater({
video,
videoObject,
channel: channelActor.VideoChannel,
overrideTo: activity.to
})
return updater.update()
const updater = new APVideoUpdater(videoObject, video)
return updater.update(activity.to)
}
async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) {

View File

@ -11,7 +11,6 @@ import { VideoModel } from '@server/models/video/video'
import { MVideoAccountLight, MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models'
import { HttpStatusCode } from '@shared/core-utils'
import { VideoObject } from '@shared/models'
import { getOrCreateActorAndServerAndModel } from '../actor'
import { APVideoCreator, SyncParam, syncVideoExternalAttributes } from './shared'
import { APVideoUpdater } from './updater'
@ -37,17 +36,6 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
return body.description || ''
}
function getOrCreateVideoChannelFromVideoObject (videoObject: VideoObject) {
const channel = videoObject.attributedTo.find(a => a.type === 'Group')
if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
if (checkUrlsSameHost(channel.id, videoObject.id) !== true) {
throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${videoObject.id}`)
}
return getOrCreateActorAndServerAndModel(channel.id, 'all')
}
type GetVideoResult <T> = Promise<{
video: T
created: boolean
@ -117,11 +105,8 @@ async function getOrCreateVideoAndAccountAndChannel (
const { videoObject } = await fetchRemoteVideo(videoUrl)
if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
const actor = await getOrCreateVideoChannelFromVideoObject(videoObject)
const videoChannel = actor.VideoChannel
try {
const creator = new APVideoCreator({ videoObject, channel: videoChannel })
const creator = new APVideoCreator(videoObject)
const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(creator.create.bind(creator), syncParam.thumbnail)
await syncVideoExternalAttributes(videoCreated, videoObject, syncParam)
@ -160,13 +145,7 @@ async function refreshVideoIfNeeded (options: {
return video
}
const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
const videoUpdater = new APVideoUpdater({
video,
videoObject,
channel: channelActor.VideoChannel
})
const videoUpdater = new APVideoUpdater(videoObject, video)
await videoUpdater.update()
await syncVideoExternalAttributes(video, videoObject, options.syncParam)
@ -197,6 +176,5 @@ export {
fetchRemoteVideo,
fetchRemoteVideoDescription,
refreshVideoIfNeeded,
getOrCreateVideoChannelFromVideoObject,
getOrCreateVideoAndAccountAndChannel
}

View File

@ -1,4 +1,5 @@
import { Transaction } from 'sequelize/types'
import { checkUrlsSameHost } from '@server/helpers/activitypub'
import { deleteNonExistingModels } from '@server/helpers/database-utils'
import { logger } from '@server/helpers/logger'
import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '@server/lib/thumbnail'
@ -9,6 +10,7 @@ import { VideoLiveModel } from '@server/models/video/video-live'
import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models'
import { getOrCreateActorAndServerAndModel } from '../../actor'
import {
getCaptionAttributesFromObject,
getFileAttributesFromUrl,
@ -23,6 +25,17 @@ import { getTrackerUrls, setVideoTrackers } from './trackers'
export abstract class APVideoAbstractBuilder {
protected abstract videoObject: VideoObject
protected async getOrCreateVideoChannelFromVideoObject () {
const channel = this.videoObject.attributedTo.find(a => a.type === 'Group')
if (!channel) throw new Error('Cannot find associated video channel to video ' + this.videoObject.url)
if (checkUrlsSameHost(channel.id, this.videoObject.id) !== true) {
throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${this.videoObject.id}`)
}
return getOrCreateActorAndServerAndModel(channel.id, 'all')
}
protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> {
return createVideoMiniatureFromUrl({
downloadUrl: getThumbnailFromIcons(this.videoObject).url,

View File

@ -3,29 +3,24 @@ import { logger } from '@server/helpers/logger'
import { sequelizeTypescript } from '@server/initializers/database'
import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
import { VideoModel } from '@server/models/video/video'
import { MChannelAccountLight, MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
import { VideoObject } from '@shared/models'
import { APVideoAbstractBuilder } from './abstract-builder'
import { getVideoAttributesFromObject } from './object-to-model-attributes'
export class APVideoCreator extends APVideoAbstractBuilder {
protected readonly videoObject: VideoObject
private readonly channel: MChannelAccountLight
constructor (options: {
videoObject: VideoObject
channel: MChannelAccountLight
}) {
constructor (protected readonly videoObject: VideoObject) {
super()
this.videoObject = options.videoObject
this.channel = options.channel
}
async create (waitThumbnail = false) {
logger.debug('Adding remote video %s.', this.videoObject.id)
const videoData = await getVideoAttributesFromObject(this.channel, this.videoObject, this.videoObject.to)
const channelActor = await this.getOrCreateVideoChannelFromVideoObject()
const channel = channelActor.VideoChannel
const videoData = await getVideoAttributesFromObject(channel, this.videoObject, this.videoObject.to)
const video = VideoModel.build(videoData) as MVideoThumbnail
const promiseThumbnail = this.tryToGenerateThumbnail(video)
@ -38,7 +33,7 @@ export class APVideoCreator extends APVideoAbstractBuilder {
const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
try {
const videoCreated = await video.save({ transaction: t }) as MVideoFullLight
videoCreated.VideoChannel = this.channel
videoCreated.VideoChannel = channel
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
@ -51,7 +46,7 @@ export class APVideoCreator extends APVideoAbstractBuilder {
await this.insertOrReplaceLive(videoCreated, t)
// We added a video in this channel, set it as updated
await this.channel.setAsUpdated(t)
await channel.setAsUpdated(t)
const autoBlacklisted = await autoBlacklistVideoIfNeeded({
video: videoCreated,

View File

@ -7,17 +7,11 @@ import { PeerTubeSocket } from '@server/lib/peertube-socket'
import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
import { VideoCaptionModel } from '@server/models/video/video-caption'
import { VideoLiveModel } from '@server/models/video/video-live'
import { MChannelAccountLight, MChannelDefault, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models'
import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models'
import { VideoObject, VideoPrivacy } from '@shared/models'
import { APVideoAbstractBuilder, getVideoAttributesFromObject } from './shared'
export class APVideoUpdater extends APVideoAbstractBuilder {
protected readonly videoObject: VideoObject
private readonly video: MVideoAccountLightBlacklistAllFiles
private readonly channel: MChannelDefault
private readonly overrideTo: string[]
private readonly wasPrivateVideo: boolean
private readonly wasUnlistedVideo: boolean
@ -25,19 +19,12 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
private readonly oldVideoChannel: MChannelAccountLight
constructor (options: {
video: MVideoAccountLightBlacklistAllFiles
videoObject: VideoObject
channel: MChannelDefault
overrideTo?: string[]
}) {
constructor (
protected readonly videoObject: VideoObject,
private readonly video: MVideoAccountLightBlacklistAllFiles
) {
super()
this.video = options.video
this.videoObject = options.videoObject
this.channel = options.channel
this.overrideTo = options.overrideTo
this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE
this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED
@ -46,16 +33,18 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
this.videoFieldsSave = this.video.toJSON()
}
async update () {
logger.debug('Updating remote video "%s".', this.videoObject.uuid, { videoObject: this.videoObject, channel: this.channel })
async update (overrideTo?: string[]) {
logger.debug('Updating remote video "%s".', this.videoObject.uuid, { videoObject: this.videoObject })
try {
const channelActor = await this.getOrCreateVideoChannelFromVideoObject()
const thumbnailModel = await this.tryToGenerateThumbnail(this.video)
const videoUpdated = await sequelizeTypescript.transaction(async t => {
this.checkChannelUpdateOrThrow()
this.checkChannelUpdateOrThrow(channelActor)
const videoUpdated = await this.updateVideo(t)
const videoUpdated = await this.updateVideo(channelActor.VideoChannel, t, overrideTo)
if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
@ -97,19 +86,19 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
}
// Check we can update the channel: we trust the remote server
private checkChannelUpdateOrThrow () {
if (!this.oldVideoChannel.Actor.serverId || !this.channel.Actor.serverId) {
private checkChannelUpdateOrThrow (newChannelActor: MActor) {
if (!this.oldVideoChannel.Actor.serverId || !newChannelActor.serverId) {
throw new Error('Cannot check old channel/new channel validity because `serverId` is null')
}
if (this.oldVideoChannel.Actor.serverId !== this.channel.Actor.serverId) {
throw new Error(`New channel ${this.channel.Actor.url} is not on the same server than new channel ${this.oldVideoChannel.Actor.url}`)
if (this.oldVideoChannel.Actor.serverId !== newChannelActor.serverId) {
throw new Error(`New channel ${newChannelActor.url} is not on the same server than new channel ${this.oldVideoChannel.Actor.url}`)
}
}
private updateVideo (transaction: Transaction) {
const to = this.overrideTo || this.videoObject.to
const videoData = getVideoAttributesFromObject(this.channel, this.videoObject, to)
private updateVideo (channel: MChannelId, transaction: Transaction, overrideTo?: string[]) {
const to = overrideTo || this.videoObject.to
const videoData = getVideoAttributesFromObject(channel, this.videoObject, to)
this.video.name = videoData.name
this.video.uuid = videoData.uuid
this.video.url = videoData.url