]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/lib/activitypub/videos/updater.ts
Avoid concurrency issue on transcoding
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / videos / updater.ts
index 4338d1e2226ddd28d551c6a6e40629e6d1e1f332..32cbf7e07d75f0fb5a7a3266f0cead7620c0c73c 100644 (file)
@@ -1,23 +1,15 @@
 import { Transaction } from 'sequelize/types'
-import { resetSequelizeInstance } from '@server/helpers/database-utils'
-import { logger } from '@server/helpers/logger'
-import { sequelizeTypescript } from '@server/initializers/database'
+import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/helpers/database-utils'
+import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
 import { Notifier } from '@server/lib/notifier'
 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'
+import { APVideoAbstractBuilder, getVideoAttributesFromObject, updateVideoRates } 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,18 +17,13 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
 
   private readonly oldVideoChannel: MChannelAccountLight
 
-  constructor (options: {
-    video: MVideoAccountLightBlacklistAllFiles
-    videoObject: VideoObject
-    channel: MChannelDefault
-    overrideTo?: string[]
-  }) {
-    super()
+  protected lTags: LoggerTagsFn
 
-    this.video = options.video
-    this.videoObject = options.videoObject
-    this.channel = options.channel
-    this.overrideTo = options.overrideTo
+  constructor (
+    protected readonly videoObject: VideoObject,
+    private readonly video: MVideoAccountLightBlacklistAllFiles
+  ) {
+    super()
 
     this.wasPrivateVideo = this.video.privacy === VideoPrivacy.PRIVATE
     this.wasUnlistedVideo = this.video.privacy === VideoPrivacy.UNLISTED
@@ -44,32 +31,41 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
     this.oldVideoChannel = this.video.VideoChannel
 
     this.videoFieldsSave = this.video.toJSON()
+
+    this.lTags = loggerTagsFactory('ap', 'video', 'update', video.uuid, video.url)
   }
 
-  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, ...this.lTags() }
+    )
 
     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, undefined, overrideTo)
 
-        if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
+      if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel)
 
-        await this.setPreview(videoUpdated, t)
+      await runInReadCommittedTransaction(async t => {
         await this.setWebTorrentFiles(videoUpdated, t)
         await this.setStreamingPlaylists(videoUpdated, t)
-        await this.setTags(videoUpdated, t)
-        await this.setTrackers(videoUpdated, t)
-        await this.setCaptions(videoUpdated, t)
-        await this.setOrDeleteLive(videoUpdated, t)
-
-        return videoUpdated
       })
 
+      await Promise.all([
+        runInReadCommittedTransaction(t => this.setTags(videoUpdated, t)),
+        runInReadCommittedTransaction(t => this.setTrackers(videoUpdated, t)),
+        this.setOrDeleteLive(videoUpdated),
+        this.setPreview(videoUpdated)
+      ])
+
+      await runInReadCommittedTransaction(t => this.setCaptions(videoUpdated, t))
+
       await autoBlacklistVideoIfNeeded({
         video: videoUpdated,
         user: undefined,
@@ -78,6 +74,8 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
         transaction: undefined
       })
 
+      await updateVideoRates(videoUpdated, this.videoObject)
+
       // Notify our users?
       if (this.wasPrivateVideo || this.wasUnlistedVideo) {
         Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated)
@@ -85,10 +83,9 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
 
       if (videoUpdated.isLive) {
         PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
-        PeerTubeSocket.Instance.sendVideoViewsUpdate(videoUpdated)
       }
 
-      logger.info('Remote video with uuid %s updated', this.videoObject.uuid)
+      logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags())
 
       return videoUpdated
     } catch (err) {
@@ -97,19 +94,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
@@ -132,19 +129,19 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
     this.video.views = videoData.views
     this.video.isLive = videoData.isLive
 
-    // Ensures we update the updated video attribute
+    // Ensures we update the updatedAt attribute, even if main attributes did not change
     this.video.changed('updatedAt', true)
 
     return this.video.save({ transaction }) as Promise<MVideoFullLight>
   }
 
   private async setCaptions (videoUpdated: MVideoFullLight, t: Transaction) {
-    await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
-
     await this.insertOrReplaceCaptions(videoUpdated, t)
   }
 
-  private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction: Transaction) {
+  private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction?: Transaction) {
+    if (!this.video.isLive) return
+
     if (this.video.isLive) return this.insertOrReplaceLive(videoUpdated, transaction)
 
     // Delete existing live if it exists
@@ -164,7 +161,7 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
     }
 
     // This is just a debug because we will retry the insert
-    logger.debug('Cannot update the remote video.', { err })
+    logger.debug('Cannot update the remote video.', { err, ...this.lTags() })
     throw err
   }
 }