]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/controllers/api/remote/videos.js
Server: implement video views
[github/Chocobozzz/PeerTube.git] / server / controllers / api / remote / videos.js
index 33b521c5f7f741693147e75c8e2bf8d5f76fca99..39c9579c1a21fd3c5346b9b6f3c4b5a7184b8656 100644 (file)
@@ -5,6 +5,7 @@ const express = require('express')
 const waterfall = require('async/waterfall')
 
 const db = require('../../../initializers/database')
+const constants = require('../../../initializers/constants')
 const middlewares = require('../../../middlewares')
 const secureMiddleware = middlewares.secure
 const videosValidators = middlewares.validators.remote.videos
@@ -12,6 +13,15 @@ const signatureValidators = middlewares.validators.remote.signature
 const logger = require('../../../helpers/logger')
 const databaseUtils = require('../../../helpers/database-utils')
 
+const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS]
+
+// Functions to call when processing a remote request
+const functionsHash = {}
+functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
+functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
+functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideo
+
 const router = express.Router()
 
 router.post('/',
@@ -21,6 +31,20 @@ router.post('/',
   remoteVideos
 )
 
+router.post('/qadu',
+  signatureValidators.signature,
+  secureMiddleware.checkSignature,
+  videosValidators.remoteQaduVideos,
+  remoteVideosQadu
+)
+
+router.post('/events',
+  signatureValidators.signature,
+  secureMiddleware.checkSignature,
+  videosValidators.remoteEventsVideos,
+  remoteVideosEvents
+)
+
 // ---------------------------------------------------------------------------
 
 module.exports = router
@@ -36,34 +60,167 @@ function remoteVideos (req, res, next) {
   eachSeries(requests, function (request, callbackEach) {
     const data = request.data
 
-    switch (request.type) {
-      case 'add':
-        addRemoteVideoRetryWrapper(data, fromPod, callbackEach)
-        break
+    // Get the function we need to call in order to process the request
+    const fun = functionsHash[request.type]
+    if (fun === undefined) {
+      logger.error('Unkown remote request type %s.', request.type)
+      return callbackEach(null)
+    }
+
+    fun.call(this, data, fromPod, callbackEach)
+  }, function (err) {
+    if (err) logger.error('Error managing remote videos.', { error: err })
+  })
 
-      case 'update':
-        updateRemoteVideoRetryWrapper(data, fromPod, callbackEach)
-        break
+  // We don't need to keep the other pod waiting
+  return res.type('json').status(204).end()
+}
 
-      case 'remove':
-        removeRemoteVideo(data, fromPod, callbackEach)
-        break
+function remoteVideosQadu (req, res, next) {
+  const requests = req.body.data
+  const fromPod = res.locals.secure.pod
 
-      case 'report-abuse':
-        reportAbuseRemoteVideo(data, fromPod, callbackEach)
-        break
+  eachSeries(requests, function (request, callbackEach) {
+    const videoData = request.data
 
-      default:
-        logger.error('Unkown remote request type %s.', request.type)
-    }
+    quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod, callbackEach)
+  }, function (err) {
+    if (err) logger.error('Error managing remote videos.', { error: err })
+  })
+
+  return res.type('json').status(204).end()
+}
+
+function remoteVideosEvents (req, res, next) {
+  const requests = req.body.data
+  const fromPod = res.locals.secure.pod
+
+  eachSeries(requests, function (request, callbackEach) {
+    const eventData = request.data
+
+    processVideosEventsRetryWrapper(eventData, fromPod, callbackEach)
   }, function (err) {
     if (err) logger.error('Error managing remote videos.', { error: err })
   })
 
-  // We don't need to keep the other pod waiting
   return res.type('json').status(204).end()
 }
 
+function processVideosEventsRetryWrapper (eventData, fromPod, finalCallback) {
+  const options = {
+    arguments: [ eventData, fromPod ],
+    errorMessage: 'Cannot process videos events with many retries.'
+  }
+
+  databaseUtils.retryTransactionWrapper(processVideosEvents, options, finalCallback)
+}
+
+function processVideosEvents (eventData, fromPod, finalCallback) {
+  waterfall([
+    databaseUtils.startSerializableTransaction,
+
+    function findVideo (t, callback) {
+      fetchOwnedVideo(eventData.remoteId, function (err, videoInstance) {
+        return callback(err, t, videoInstance)
+      })
+    },
+
+    function updateVideoIntoDB (t, videoInstance, callback) {
+      const options = { transaction: t }
+
+      let columnToUpdate
+
+      switch (eventData.eventType) {
+        case constants.REQUEST_VIDEO_EVENT_TYPES.VIEWS:
+          columnToUpdate = 'views'
+          break
+
+        case constants.REQUEST_VIDEO_EVENT_TYPES.LIKES:
+          columnToUpdate = 'likes'
+          break
+
+        case constants.REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
+          columnToUpdate = 'dislikes'
+          break
+
+        default:
+          return callback(new Error('Unknown video event type.'))
+      }
+
+      const query = {}
+      query[columnToUpdate] = eventData.count
+
+      videoInstance.increment(query, options).asCallback(function (err) {
+        return callback(err, t)
+      })
+    },
+
+    databaseUtils.commitTransaction
+
+  ], function (err, t) {
+    if (err) {
+      console.log(err)
+      logger.debug('Cannot process a video event.', { error: err })
+      return databaseUtils.rollbackTransaction(err, t, finalCallback)
+    }
+
+    logger.info('Remote video event processed for video %s.', eventData.remoteId)
+    return finalCallback(null)
+  })
+}
+
+function quickAndDirtyUpdateVideoRetryWrapper (videoData, fromPod, finalCallback) {
+  const options = {
+    arguments: [ videoData, fromPod ],
+    errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
+  }
+
+  databaseUtils.retryTransactionWrapper(quickAndDirtyUpdateVideo, options, finalCallback)
+}
+
+function quickAndDirtyUpdateVideo (videoData, fromPod, finalCallback) {
+  waterfall([
+    databaseUtils.startSerializableTransaction,
+
+    function findVideo (t, callback) {
+      fetchRemoteVideo(fromPod.host, videoData.remoteId, function (err, videoInstance) {
+        return callback(err, t, videoInstance)
+      })
+    },
+
+    function updateVideoIntoDB (t, videoInstance, callback) {
+      const options = { transaction: t }
+
+      if (videoData.views) {
+        videoInstance.set('views', videoData.views)
+      }
+
+      if (videoData.likes) {
+        videoInstance.set('likes', videoData.likes)
+      }
+
+      if (videoData.dislikes) {
+        videoInstance.set('dislikes', videoData.dislikes)
+      }
+
+      videoInstance.save(options).asCallback(function (err) {
+        return callback(err, t)
+      })
+    },
+
+    databaseUtils.commitTransaction
+
+  ], function (err, t) {
+    if (err) {
+      logger.debug('Cannot quick and dirty update the remote video.', { error: err })
+      return databaseUtils.rollbackTransaction(err, t, finalCallback)
+    }
+
+    logger.info('Remote video %s quick and dirty updated', videoData.name)
+    return finalCallback(null)
+  })
+}
+
 // Handle retries on fail
 function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) {
   const options = {
@@ -81,6 +238,16 @@ function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
 
     databaseUtils.startSerializableTransaction,
 
+    function assertRemoteIdAndHostUnique (t, callback) {
+      db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) {
+        if (err) return callback(err)
+
+        if (video) return callback(new Error('RemoteId and host pair is not unique.'))
+
+        return callback(null, t)
+      })
+    },
+
     function findOrCreateAuthor (t, callback) {
       const name = videoToCreateData.author
       const podId = fromPod.id
@@ -141,42 +308,37 @@ function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
     },
 
     function associateTagsToVideo (t, tagInstances, video, callback) {
-      const options = { transaction: t }
+      const options = {
+        transaction: t
+      }
 
       video.setTags(tagInstances, options).asCallback(function (err) {
         return callback(err, t)
       })
-    }
+    },
+
+    databaseUtils.commitTransaction
 
   ], function (err, t) {
     if (err) {
       // This is just a debug because we will retry the insert
       logger.debug('Cannot insert the remote video.', { error: err })
-
-      // Abort transaction?
-      if (t) t.rollback()
-
-      return finalCallback(err)
+      return databaseUtils.rollbackTransaction(err, t, finalCallback)
     }
 
-    // Commit transaction
-    t.commit().asCallback(function (err) {
-      if (err) return finalCallback(err)
-
-      logger.info('Remote video %s inserted.', videoToCreateData.name)
-      return finalCallback(null)
-    })
+    logger.info('Remote video %s inserted.', videoToCreateData.name)
+    return finalCallback(null)
   })
 }
 
 // Handle retries on fail
 function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) {
   const options = {
-    arguments: [ fromPod, videoAttributesToUpdate ],
+    arguments: [ videoAttributesToUpdate, fromPod ],
     errorMessage: 'Cannot update the remote video with many retries'
   }
 
-  databaseUtils.retryWrapper(updateRemoteVideo, options, finalCallback)
+  databaseUtils.retryTransactionWrapper(updateRemoteVideo, options, finalCallback)
 }
 
 function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
@@ -187,7 +349,7 @@ function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
     databaseUtils.startSerializableTransaction,
 
     function findVideo (t, callback) {
-      fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
+      fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
         return callback(err, t, videoInstance)
       })
     },
@@ -222,32 +384,25 @@ function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
       videoInstance.setTags(tagInstances, options).asCallback(function (err) {
         return callback(err, t)
       })
-    }
+    },
+
+    databaseUtils.commitTransaction
 
   ], function (err, t) {
     if (err) {
       // This is just a debug because we will retry the insert
       logger.debug('Cannot update the remote video.', { error: err })
-
-      // Abort transaction?
-      if (t) t.rollback()
-
-      return finalCallback(err)
+      return databaseUtils.rollbackTransaction(err, t, finalCallback)
     }
 
-   // Commit transaction
-    t.commit().asCallback(function (err) {
-      if (err) return finalCallback(err)
-
-      logger.info('Remote video %s updated', videoAttributesToUpdate.name)
-      return finalCallback(null)
-    })
+    logger.info('Remote video %s updated', videoAttributesToUpdate.name)
+    return finalCallback(null)
   })
 }
 
 function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
   // We need the instance because we have to remove some other stuffs (thumbnail etc)
-  fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
+  fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
     // Do not return the error, continue the process
     if (err) return callback(null)
 
@@ -264,7 +419,7 @@ function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
 }
 
 function reportAbuseRemoteVideo (reportData, fromPod, callback) {
-  db.Video.load(reportData.videoRemoteId, function (err, video) {
+  fetchOwnedVideo(reportData.videoRemoteId, function (err, video) {
     if (err || !video) {
       if (!err) err = new Error('video not found')
 
@@ -292,7 +447,20 @@ function reportAbuseRemoteVideo (reportData, fromPod, callback) {
   })
 }
 
-function fetchVideo (podHost, remoteId, callback) {
+function fetchOwnedVideo (id, callback) {
+  db.Video.load(id, function (err, video) {
+    if (err || !video) {
+      if (!err) err = new Error('video not found')
+
+      logger.error('Cannot load owned video from id.', { error: err, id })
+      return callback(err)
+    }
+
+    return callback(null, video)
+  })
+}
+
+function fetchRemoteVideo (podHost, remoteId, callback) {
   db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
     if (err || !video) {
       if (!err) err = new Error('video not found')