X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fcontrollers%2Fapi%2Fremote%2Fvideos.js;h=39c9579c1a21fd3c5346b9b6f3c4b5a7184b8656;hb=e4c87ec26962e359d1c70b03ed188a3f19d6a25b;hp=6d768eae885a54d62b6fb306308ba56a090d1fe4;hpb=ed04d94f6d7132055f97a2f757b85c03c5f2a0b6;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/controllers/api/remote/videos.js b/server/controllers/api/remote/videos.js index 6d768eae8..39c9579c1 100644 --- a/server/controllers/api/remote/videos.js +++ b/server/controllers/api/remote/videos.js @@ -5,12 +5,22 @@ 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 const signatureValidators = middlewares.validators.remote.signature const logger = require('../../../helpers/logger') -const utils = require('../../../helpers/utils') +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() @@ -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,49 +60,175 @@ 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) + } - case 'update': - updateRemoteVideoRetryWrapper(data, fromPod, callbackEach) - break + fun.call(this, data, 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() +} - 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 }) }) - // We don't need to keep the other pod waiting return res.type('json').status(204).end() } -// Handle retries on fail -function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) { - utils.transactionRetryer( - function (callback) { - return addRemoteVideo(videoToCreateData, fromPod, callback) +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 }) + }) + + 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 (err) { - if (err) { - logger.error('Cannot insert the remote video with many retries.', { error: err }) - return finalCallback(err) + + 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.')) } - return finalCallback() + 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 = { + arguments: [ videoToCreateData, fromPod ], + errorMessage: 'Cannot insert the remote video with many retries.' + } + + databaseUtils.retryTransactionWrapper(addRemoteVideo, options, finalCallback) } function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { @@ -86,9 +236,15 @@ function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { waterfall([ - function startTransaction (callback) { - db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { - return callback(err, t) + 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) }) }, @@ -152,46 +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() - - return finalCallback() + logger.info('Remote video %s inserted.', videoToCreateData.name) + return finalCallback(null) }) } // Handle retries on fail function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) { - utils.transactionRetryer( - function (callback) { - return updateRemoteVideo(videoAttributesToUpdate, fromPod, callback) - }, - function (err) { - if (err) { - logger.error('Cannot update the remote video with many retries.', { error: err }) - return finalCallback(err) - } + const options = { + arguments: [ videoAttributesToUpdate, fromPod ], + errorMessage: 'Cannot update the remote video with many retries' + } - return finalCallback() - } - ) + databaseUtils.retryTransactionWrapper(updateRemoteVideo, options, finalCallback) } function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { @@ -199,14 +346,10 @@ function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { waterfall([ - function startTransaction (callback) { - db.sequelize.transaction().asCallback(function (err, t) { - return callback(err, t) - }) - }, + 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) }) }, @@ -241,43 +384,48 @@ 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() - - return finalCallback() + 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) { - if (err) return callback(err) + fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) { + // Do not return the error, continue the process + if (err) return callback(null) logger.debug('Removing remote video %s.', video.remoteId) - video.destroy().asCallback(callback) + video.destroy().asCallback(function (err) { + // Do not return the error, continue the process + if (err) { + logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err }) + } + + return callback(null) + }) }) } 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') logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId }) - return callback(err) + // Do not return the error, continue the process + return callback(null) } logger.debug('Reporting remote abuse for video %s.', video.id) @@ -289,11 +437,30 @@ function reportAbuseRemoteVideo (reportData, fromPod, callback) { videoId: video.id } - db.VideoAbuse.create(videoAbuseData).asCallback(callback) + db.VideoAbuse.create(videoAbuseData).asCallback(function (err) { + if (err) { + logger.error('Cannot create remote abuse video.', { error: err }) + } + + return callback(null) + }) + }) +} + +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 fetchVideo (podHost, remoteId, callback) { +function fetchRemoteVideo (podHost, remoteId, callback) { db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) { if (err || !video) { if (!err) err = new Error('video not found')