2021-06-02 03:41:46 -05:00
|
|
|
import { Transaction } from 'sequelize/types'
|
2021-06-08 10:29:45 -05:00
|
|
|
import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/helpers/database-utils'
|
2021-06-03 09:56:42 -05:00
|
|
|
import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
|
2021-06-02 03:41:46 -05:00
|
|
|
import { Notifier } from '@server/lib/notifier'
|
|
|
|
import { PeerTubeSocket } from '@server/lib/peertube-socket'
|
2023-03-16 04:36:33 -05:00
|
|
|
import { Hooks } from '@server/lib/plugins/hooks'
|
2021-06-02 03:41:46 -05:00
|
|
|
import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
|
|
|
|
import { VideoLiveModel } from '@server/models/video/video-live'
|
2021-06-02 04:54:29 -05:00
|
|
|
import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models'
|
2021-06-02 03:41:46 -05:00
|
|
|
import { VideoObject, VideoPrivacy } from '@shared/models'
|
2022-03-18 05:17:35 -05:00
|
|
|
import { APVideoAbstractBuilder, getVideoAttributesFromObject, updateVideoRates } from './shared'
|
2021-06-02 03:41:46 -05:00
|
|
|
|
|
|
|
export class APVideoUpdater extends APVideoAbstractBuilder {
|
|
|
|
private readonly wasPrivateVideo: boolean
|
|
|
|
private readonly wasUnlistedVideo: boolean
|
|
|
|
|
|
|
|
private readonly oldVideoChannel: MChannelAccountLight
|
|
|
|
|
2021-06-03 09:56:42 -05:00
|
|
|
protected lTags: LoggerTagsFn
|
2021-06-02 09:49:59 -05:00
|
|
|
|
2021-06-02 04:54:29 -05:00
|
|
|
constructor (
|
|
|
|
protected readonly videoObject: VideoObject,
|
|
|
|
private readonly video: MVideoAccountLightBlacklistAllFiles
|
|
|
|
) {
|
2021-06-02 03:41:46 -05:00
|
|
|
super()
|
|
|
|
|
|
|
|
this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE
|
|
|
|
this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED
|
|
|
|
|
|
|
|
this.oldVideoChannel = this.video.VideoChannel
|
|
|
|
|
2021-06-03 09:56:42 -05:00
|
|
|
this.lTags = loggerTagsFactory('ap', 'video', 'update', video.uuid, video.url)
|
2021-06-02 03:41:46 -05:00
|
|
|
}
|
|
|
|
|
2021-06-02 04:54:29 -05:00
|
|
|
async update (overrideTo?: string[]) {
|
2021-06-02 09:49:59 -05:00
|
|
|
logger.debug(
|
|
|
|
'Updating remote video "%s".', this.videoObject.uuid,
|
2021-06-03 09:56:42 -05:00
|
|
|
{ videoObject: this.videoObject, ...this.lTags() }
|
2021-06-02 09:49:59 -05:00
|
|
|
)
|
2021-06-02 03:41:46 -05:00
|
|
|
|
|
|
|
try {
|
2021-06-02 04:54:29 -05:00
|
|
|
const channelActor = await this.getOrCreateVideoChannelFromVideoObject()
|
|
|
|
|
2023-06-07 01:53:14 -05:00
|
|
|
const thumbnailModel = await this.setThumbnail(this.video)
|
2021-06-02 03:41:46 -05:00
|
|
|
|
2021-06-08 10:29:45 -05:00
|
|
|
this.checkChannelUpdateOrThrow(channelActor)
|
2021-06-02 03:41:46 -05:00
|
|
|
|
2021-06-08 10:29:45 -05:00
|
|
|
const videoUpdated = await this.updateVideo(channelActor.VideoChannel, undefined, overrideTo)
|
2021-06-02 03:41:46 -05:00
|
|
|
|
2021-06-08 10:29:45 -05:00
|
|
|
if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel)
|
2021-06-02 03:41:46 -05:00
|
|
|
|
2021-06-08 10:29:45 -05:00
|
|
|
await runInReadCommittedTransaction(async t => {
|
2021-06-02 03:41:46 -05:00
|
|
|
await this.setWebTorrentFiles(videoUpdated, t)
|
|
|
|
await this.setStreamingPlaylists(videoUpdated, t)
|
|
|
|
})
|
|
|
|
|
2021-06-08 10:29:45 -05:00
|
|
|
await Promise.all([
|
|
|
|
runInReadCommittedTransaction(t => this.setTags(videoUpdated, t)),
|
|
|
|
runInReadCommittedTransaction(t => this.setTrackers(videoUpdated, t)),
|
2023-06-01 07:51:16 -05:00
|
|
|
runInReadCommittedTransaction(t => this.setStoryboard(videoUpdated, t)),
|
2023-06-07 01:53:14 -05:00
|
|
|
runInReadCommittedTransaction(t => {
|
|
|
|
return Promise.all([
|
|
|
|
this.setPreview(videoUpdated, t),
|
|
|
|
this.setThumbnail(videoUpdated, t)
|
|
|
|
])
|
|
|
|
}),
|
|
|
|
this.setOrDeleteLive(videoUpdated)
|
2021-06-08 10:29:45 -05:00
|
|
|
])
|
|
|
|
|
|
|
|
await runInReadCommittedTransaction(t => this.setCaptions(videoUpdated, t))
|
|
|
|
|
2021-06-02 03:41:46 -05:00
|
|
|
await autoBlacklistVideoIfNeeded({
|
|
|
|
video: videoUpdated,
|
|
|
|
user: undefined,
|
|
|
|
isRemote: true,
|
|
|
|
isNew: false,
|
|
|
|
transaction: undefined
|
|
|
|
})
|
|
|
|
|
2022-03-18 05:17:35 -05:00
|
|
|
await updateVideoRates(videoUpdated, this.videoObject)
|
|
|
|
|
2021-06-02 03:41:46 -05:00
|
|
|
// Notify our users?
|
|
|
|
if (this.wasPrivateVideo || this.wasUnlistedVideo) {
|
|
|
|
Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (videoUpdated.isLive) {
|
|
|
|
PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
|
|
|
|
}
|
|
|
|
|
2023-03-16 04:36:33 -05:00
|
|
|
Hooks.runAction('action:activity-pub.remote-video.updated', { video: videoUpdated, videoAPObject: this.videoObject })
|
|
|
|
|
2021-06-03 09:56:42 -05:00
|
|
|
logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags())
|
2021-06-02 03:41:46 -05:00
|
|
|
|
|
|
|
return videoUpdated
|
|
|
|
} catch (err) {
|
2023-05-11 08:02:53 -05:00
|
|
|
await this.catchUpdateError(err)
|
2021-06-02 03:41:46 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check we can update the channel: we trust the remote server
|
2021-06-02 04:54:29 -05:00
|
|
|
private checkChannelUpdateOrThrow (newChannelActor: MActor) {
|
|
|
|
if (!this.oldVideoChannel.Actor.serverId || !newChannelActor.serverId) {
|
2021-06-02 03:41:46 -05:00
|
|
|
throw new Error('Cannot check old channel/new channel validity because `serverId` is null')
|
|
|
|
}
|
|
|
|
|
2021-06-02 04:54:29 -05:00
|
|
|
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}`)
|
2021-06-02 03:41:46 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-08 10:29:45 -05:00
|
|
|
private updateVideo (channel: MChannelId, transaction?: Transaction, overrideTo?: string[]) {
|
2021-06-02 04:54:29 -05:00
|
|
|
const to = overrideTo || this.videoObject.to
|
|
|
|
const videoData = getVideoAttributesFromObject(channel, this.videoObject, to)
|
2021-06-02 03:41:46 -05:00
|
|
|
this.video.name = videoData.name
|
|
|
|
this.video.uuid = videoData.uuid
|
|
|
|
this.video.url = videoData.url
|
|
|
|
this.video.category = videoData.category
|
|
|
|
this.video.licence = videoData.licence
|
|
|
|
this.video.language = videoData.language
|
|
|
|
this.video.description = videoData.description
|
|
|
|
this.video.support = videoData.support
|
|
|
|
this.video.nsfw = videoData.nsfw
|
|
|
|
this.video.commentsEnabled = videoData.commentsEnabled
|
|
|
|
this.video.downloadEnabled = videoData.downloadEnabled
|
|
|
|
this.video.waitTranscoding = videoData.waitTranscoding
|
|
|
|
this.video.state = videoData.state
|
|
|
|
this.video.duration = videoData.duration
|
|
|
|
this.video.createdAt = videoData.createdAt
|
|
|
|
this.video.publishedAt = videoData.publishedAt
|
|
|
|
this.video.originallyPublishedAt = videoData.originallyPublishedAt
|
|
|
|
this.video.privacy = videoData.privacy
|
|
|
|
this.video.channelId = videoData.channelId
|
|
|
|
this.video.views = videoData.views
|
|
|
|
this.video.isLive = videoData.isLive
|
|
|
|
|
2021-06-03 09:02:29 -05:00
|
|
|
// Ensures we update the updatedAt attribute, even if main attributes did not change
|
2021-06-02 03:41:46 -05:00
|
|
|
this.video.changed('updatedAt', true)
|
|
|
|
|
|
|
|
return this.video.save({ transaction }) as Promise<MVideoFullLight>
|
|
|
|
}
|
|
|
|
|
|
|
|
private async setCaptions (videoUpdated: MVideoFullLight, t: Transaction) {
|
|
|
|
await this.insertOrReplaceCaptions(videoUpdated, t)
|
|
|
|
}
|
|
|
|
|
2023-06-01 07:51:16 -05:00
|
|
|
private async setStoryboard (videoUpdated: MVideoFullLight, t: Transaction) {
|
|
|
|
await this.insertOrReplaceStoryboard(videoUpdated, t)
|
|
|
|
}
|
|
|
|
|
2021-06-08 10:29:45 -05:00
|
|
|
private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction?: Transaction) {
|
|
|
|
if (!this.video.isLive) return
|
|
|
|
|
2021-06-02 03:41:46 -05:00
|
|
|
if (this.video.isLive) return this.insertOrReplaceLive(videoUpdated, transaction)
|
|
|
|
|
|
|
|
// Delete existing live if it exists
|
|
|
|
await VideoLiveModel.destroy({
|
|
|
|
where: {
|
|
|
|
videoId: this.video.id
|
|
|
|
},
|
|
|
|
transaction
|
|
|
|
})
|
|
|
|
|
|
|
|
videoUpdated.VideoLive = null
|
|
|
|
}
|
|
|
|
|
2023-05-11 08:02:53 -05:00
|
|
|
private async catchUpdateError (err: Error) {
|
2023-02-14 01:59:27 -06:00
|
|
|
if (this.video !== undefined) {
|
2023-05-11 08:02:53 -05:00
|
|
|
await resetSequelizeInstance(this.video)
|
2021-06-02 03:41:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// This is just a debug because we will retry the insert
|
2021-06-03 09:56:42 -05:00
|
|
|
logger.debug('Cannot update the remote video.', { err, ...this.lTags() })
|
2021-06-02 03:41:46 -05:00
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|