Move AP video channel creation

This commit is contained in:
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 { ActorModel } from '../../../models/actor/actor'
import { VideoChannelModel } from '../../../models/video/video-channel' import { VideoChannelModel } from '../../../models/video/video-channel'
import { APProcessorOptions } from '../../../types/activitypub-processor.model' 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 { getImageInfoIfExists, updateActorImageInstance, updateActorInstance } from '../actor'
import { createOrUpdateCacheFile } from '../cache-file' import { createOrUpdateCacheFile } from '../cache-file'
import { createOrUpdateVideoPlaylist } from '../playlist' import { createOrUpdateVideoPlaylist } from '../playlist'
import { forwardVideoRelatedActivity } from '../send/utils' import { forwardVideoRelatedActivity } from '../send/utils'
import { getOrCreateVideoAndAccountAndChannel, getOrCreateVideoChannelFromVideoObject, APVideoUpdater } from '../videos' import { APVideoUpdater, getOrCreateVideoAndAccountAndChannel } from '../videos'
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
const { activity, byActor } = options const { activity, byActor } = options
@ -25,7 +25,7 @@ async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate
const objectType = activity.object.type const objectType = activity.object.type
if (objectType === 'Video') { if (objectType === 'Video') {
return retryTransactionWrapper(processUpdateVideo, byActor, activity) return retryTransactionWrapper(processUpdateVideo, activity)
} }
if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') { 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 const videoObject = activity.object as VideoObject
if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { 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 // We did not have this video, it has been created so no need to update
if (created) return if (created) return
// Load new channel const updater = new APVideoUpdater(videoObject, video)
const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) return updater.update(activity.to)
const account = actor.Account as MAccountIdActor
account.Actor = actor
const updater = new APVideoUpdater({
video,
videoObject,
channel: channelActor.VideoChannel,
overrideTo: activity.to
})
return updater.update()
} }
async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) { 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 { MVideoAccountLight, MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models'
import { HttpStatusCode } from '@shared/core-utils' import { HttpStatusCode } from '@shared/core-utils'
import { VideoObject } from '@shared/models' import { VideoObject } from '@shared/models'
import { getOrCreateActorAndServerAndModel } from '../actor'
import { APVideoCreator, SyncParam, syncVideoExternalAttributes } from './shared' import { APVideoCreator, SyncParam, syncVideoExternalAttributes } from './shared'
import { APVideoUpdater } from './updater' import { APVideoUpdater } from './updater'
@ -37,17 +36,6 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
return body.description || '' 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<{ type GetVideoResult <T> = Promise<{
video: T video: T
created: boolean created: boolean
@ -117,11 +105,8 @@ async function getOrCreateVideoAndAccountAndChannel (
const { videoObject } = await fetchRemoteVideo(videoUrl) const { videoObject } = await fetchRemoteVideo(videoUrl)
if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl) if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
const actor = await getOrCreateVideoChannelFromVideoObject(videoObject)
const videoChannel = actor.VideoChannel
try { try {
const creator = new APVideoCreator({ videoObject, channel: videoChannel }) const creator = new APVideoCreator(videoObject)
const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(creator.create.bind(creator), syncParam.thumbnail) const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(creator.create.bind(creator), syncParam.thumbnail)
await syncVideoExternalAttributes(videoCreated, videoObject, syncParam) await syncVideoExternalAttributes(videoCreated, videoObject, syncParam)
@ -160,13 +145,7 @@ async function refreshVideoIfNeeded (options: {
return video return video
} }
const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) const videoUpdater = new APVideoUpdater(videoObject, video)
const videoUpdater = new APVideoUpdater({
video,
videoObject,
channel: channelActor.VideoChannel
})
await videoUpdater.update() await videoUpdater.update()
await syncVideoExternalAttributes(video, videoObject, options.syncParam) await syncVideoExternalAttributes(video, videoObject, options.syncParam)
@ -197,6 +176,5 @@ export {
fetchRemoteVideo, fetchRemoteVideo,
fetchRemoteVideoDescription, fetchRemoteVideoDescription,
refreshVideoIfNeeded, refreshVideoIfNeeded,
getOrCreateVideoChannelFromVideoObject,
getOrCreateVideoAndAccountAndChannel getOrCreateVideoAndAccountAndChannel
} }

View File

@ -1,4 +1,5 @@
import { Transaction } from 'sequelize/types' import { Transaction } from 'sequelize/types'
import { checkUrlsSameHost } from '@server/helpers/activitypub'
import { deleteNonExistingModels } from '@server/helpers/database-utils' import { deleteNonExistingModels } from '@server/helpers/database-utils'
import { logger } from '@server/helpers/logger' import { logger } from '@server/helpers/logger'
import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '@server/lib/thumbnail' 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 { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models' import { MStreamingPlaylistFilesVideo, MThumbnail, MVideoCaption, MVideoFile, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models' import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models'
import { getOrCreateActorAndServerAndModel } from '../../actor'
import { import {
getCaptionAttributesFromObject, getCaptionAttributesFromObject,
getFileAttributesFromUrl, getFileAttributesFromUrl,
@ -23,6 +25,17 @@ import { getTrackerUrls, setVideoTrackers } from './trackers'
export abstract class APVideoAbstractBuilder { export abstract class APVideoAbstractBuilder {
protected abstract videoObject: VideoObject 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> { protected tryToGenerateThumbnail (video: MVideoThumbnail): Promise<MThumbnail> {
return createVideoMiniatureFromUrl({ return createVideoMiniatureFromUrl({
downloadUrl: getThumbnailFromIcons(this.videoObject).url, downloadUrl: getThumbnailFromIcons(this.videoObject).url,

View File

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

View File

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