From 6fcd19ba737f1f5614a56c6925adb882dea43b8d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 5 Jul 2017 13:26:25 +0200 Subject: Move to promises Closes https://github.com/Chocobozzz/PeerTube/issues/74 --- server/controllers/api/oauth-clients.ts | 21 +- server/controllers/api/pods.ts | 71 ++-- server/controllers/api/remote/pods.ts | 20 +- server/controllers/api/remote/videos.ts | 528 +++++++++++---------------- server/controllers/api/request-schedulers.ts | 38 +- server/controllers/api/users.ts | 86 ++--- server/controllers/api/videos/abuse.ts | 78 ++-- server/controllers/api/videos/blacklist.ts | 10 +- server/controllers/api/videos/index.ts | 381 +++++++++---------- server/controllers/api/videos/rate.ts | 207 +++++------ server/controllers/client.ts | 26 +- 11 files changed, 586 insertions(+), 880 deletions(-) (limited to 'server/controllers') diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts index b9bc0534f..f7dac598c 100644 --- a/server/controllers/api/oauth-clients.ts +++ b/server/controllers/api/oauth-clients.ts @@ -24,16 +24,17 @@ function getLocalClient (req: express.Request, res: express.Response, next: expr return res.type('json').status(403).end() } - db.OAuthClient.loadFirstClient(function (err, client) { - if (err) return next(err) - if (!client) return next(new Error('No client available.')) - - const json: OAuthClientLocal = { - client_id: client.clientId, - client_secret: client.clientSecret - } - res.json(json) - }) + db.OAuthClient.loadFirstClient() + .then(client => { + if (!client) throw new Error('No client available.') + + const json: OAuthClientLocal = { + client_id: client.clientId, + client_secret: client.clientSecret + } + res.json(json) + }) + .catch(err => next(err)) } // --------------------------------------------------------------------------- diff --git a/server/controllers/api/pods.ts b/server/controllers/api/pods.ts index 283105f6c..0f85ab51d 100644 --- a/server/controllers/api/pods.ts +++ b/server/controllers/api/pods.ts @@ -1,5 +1,4 @@ import * as express from 'express' -import { waterfall } from 'async' import { database as db } from '../../initializers/database' import { CONFIG } from '../../initializers' @@ -57,65 +56,39 @@ export { function addPods (req: express.Request, res: express.Response, next: express.NextFunction) { const informations = req.body - waterfall([ - function addPod (callback) { - const pod = db.Pod.build(informations) - pod.save().asCallback(function (err, podCreated) { - // Be sure about the number of parameters for the callback - return callback(err, podCreated) - }) - }, - - function sendMyVideos (podCreated: PodInstance, callback) { - sendOwnedVideosToPod(podCreated.id) - - callback(null) - }, - - function fetchMyCertificate (callback) { - getMyPublicCert(function (err, cert) { - if (err) { - logger.error('Cannot read cert file.') - return callback(err) - } - - return callback(null, cert) - }) - } - ], function (err, cert) { - if (err) return next(err) - - return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL }) - }) + const pod = db.Pod.build(informations) + pod.save() + .then(podCreated => { + return sendOwnedVideosToPod(podCreated.id) + }) + .then(() => { + return getMyPublicCert() + }) + .then(cert => { + return res.json({ cert: cert, email: CONFIG.ADMIN.EMAIL }) + }) + .catch(err => next(err)) } function listPods (req: express.Request, res: express.Response, next: express.NextFunction) { - db.Pod.list(function (err, podsList) { - if (err) return next(err) - - res.json(getFormatedObjects(podsList, podsList.length)) - }) + db.Pod.list() + .then(podsList => res.json(getFormatedObjects(podsList, podsList.length))) + .catch(err => next(err)) } function makeFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { const hosts = req.body.hosts as string[] - makeFriends(hosts, function (err) { - if (err) { - logger.error('Could not make friends.', { error: err }) - return - } - - logger.info('Made friends!') - }) + makeFriends(hosts) + .then(() => logger.info('Made friends!')) + .catch(err => logger.error('Could not make friends.', { error: err })) + // Don't wait the process that could be long res.type('json').status(204).end() } function quitFriendsController (req: express.Request, res: express.Response, next: express.NextFunction) { - quitFriends(function (err) { - if (err) return next(err) - - res.type('json').status(204).end() - }) + quitFriends() + .then(() => res.type('json').status(204).end()) + .catch(err => next(err)) } diff --git a/server/controllers/api/remote/pods.ts b/server/controllers/api/remote/pods.ts index b0d6642c1..6319957d3 100644 --- a/server/controllers/api/remote/pods.ts +++ b/server/controllers/api/remote/pods.ts @@ -1,5 +1,4 @@ import * as express from 'express' -import * as waterfall from 'async/waterfall' import { database as db } from '../../../initializers/database' import { checkSignature, signatureValidator } from '../../../middlewares' @@ -24,17 +23,10 @@ export { function removePods (req: express.Request, res: express.Response, next: express.NextFunction) { const host = req.body.signature.host - waterfall([ - function loadPod (callback) { - db.Pod.loadByHost(host, callback) - }, - - function deletePod (pod, callback) { - pod.destroy().asCallback(callback) - } - ], function (err) { - if (err) return next(err) - - return res.type('json').status(204).end() - }) + db.Pod.loadByHost(host) + .then(pod => { + return pod.destroy() + }) + .then(() => res.type('json').status(204).end()) + .catch(err => next(err)) } diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts index d9cc08fb4..ebe4eca36 100644 --- a/server/controllers/api/remote/videos.ts +++ b/server/controllers/api/remote/videos.ts @@ -1,6 +1,5 @@ import * as express from 'express' -import * as Sequelize from 'sequelize' -import { eachSeries, waterfall } from 'async' +import * as Promise from 'bluebird' import { database as db } from '../../../initializers/database' import { @@ -16,20 +15,14 @@ import { remoteQaduVideosValidator, remoteEventsVideosValidator } from '../../../middlewares' -import { - logger, - commitTransaction, - retryTransactionWrapper, - rollbackTransaction, - startSerializableTransaction -} from '../../../helpers' +import { logger, retryTransactionWrapper } from '../../../helpers' import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' import { PodInstance, VideoInstance } from '../../../models' const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] // Functions to call when processing a remote request -const functionsHash = {} +const functionsHash: { [ id: string ]: (...args) => Promise } = {} functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo @@ -72,20 +65,19 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres // We need to process in the same order to keep consistency // TODO: optimization - eachSeries(requests, function (request: any, callbackEach) { + Promise.mapSeries(requests, (request: any) => { const data = request.data // 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) + return } - fun.call(this, data, fromPod, callbackEach) - }, function (err) { - if (err) logger.error('Error managing remote videos.', { error: err }) + return fun.call(this, data, fromPod) }) + .catch(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() @@ -95,13 +87,12 @@ function remoteVideosQadu (req: express.Request, res: express.Response, next: ex const requests = req.body.data const fromPod = res.locals.secure.pod - eachSeries(requests, function (request: any, callbackEach) { + Promise.mapSeries(requests, (request: any) => { const videoData = request.data - quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod, callbackEach) - }, function (err) { - if (err) logger.error('Error managing remote videos.', { error: err }) + return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod) }) + .catch(err => logger.error('Error managing remote videos.', { error: err })) return res.type('json').status(204).end() } @@ -110,414 +101,303 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next: const requests = req.body.data const fromPod = res.locals.secure.pod - eachSeries(requests, function (request: any, callbackEach) { + Promise.mapSeries(requests, (request: any) => { const eventData = request.data - processVideosEventsRetryWrapper(eventData, fromPod, callbackEach) - }, function (err) { - if (err) logger.error('Error managing remote videos.', { error: err }) + return processVideosEventsRetryWrapper(eventData, fromPod) }) + .catch(err => logger.error('Error managing remote videos.', { error: err })) return res.type('json').status(204).end() } -function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { +function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance) { const options = { arguments: [ eventData, fromPod ], errorMessage: 'Cannot process videos events with many retries.' } - retryTransactionWrapper(processVideosEvents, options, finalCallback) + return retryTransactionWrapper(processVideosEvents, options) } -function processVideosEvents (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { - waterfall([ - startSerializableTransaction, - - function findVideo (t, callback) { - fetchOwnedVideo(eventData.remoteId, function (err, videoInstance) { - return callback(err, t, videoInstance) - }) - }, +function processVideosEvents (eventData: any, fromPod: PodInstance) { - function updateVideoIntoDB (t, videoInstance, callback) { - const options = { transaction: t } + return db.sequelize.transaction(t => { + return fetchOwnedVideo(eventData.remoteId) + .then(videoInstance => { + const options = { transaction: t } - let columnToUpdate - let qaduType + let columnToUpdate + let qaduType - switch (eventData.eventType) { - case REQUEST_VIDEO_EVENT_TYPES.VIEWS: - columnToUpdate = 'views' - qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS - break + switch (eventData.eventType) { + case REQUEST_VIDEO_EVENT_TYPES.VIEWS: + columnToUpdate = 'views' + qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS + break - case REQUEST_VIDEO_EVENT_TYPES.LIKES: - columnToUpdate = 'likes' - qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES - break + case REQUEST_VIDEO_EVENT_TYPES.LIKES: + columnToUpdate = 'likes' + qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES + break - case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: - columnToUpdate = 'dislikes' - qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES - break + case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: + columnToUpdate = 'dislikes' + qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES + break - default: - return callback(new Error('Unknown video event type.')) - } + default: + throw new Error('Unknown video event type.') + } - const query = {} - query[columnToUpdate] = eventData.count + const query = {} + query[columnToUpdate] = eventData.count - videoInstance.increment(query, options).asCallback(function (err) { - return callback(err, t, videoInstance, qaduType) + return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType })) }) - }, - - function sendQaduToFriends (t, videoInstance, qaduType, callback) { - const qadusParams = [ - { - videoId: videoInstance.id, - type: qaduType - } - ] - - quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) { - return callback(err, t) + .then(({ videoInstance, qaduType }) => { + const qadusParams = [ + { + videoId: videoInstance.id, + type: qaduType + } + ] + + return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) }) - }, - - commitTransaction - - ], function (err: Error, t: Sequelize.Transaction) { - if (err) { - logger.debug('Cannot process a video event.', { error: err }) - return rollbackTransaction(err, t, finalCallback) - } - - logger.info('Remote video event processed for video %s.', eventData.remoteId) - return finalCallback(null) + }) + .then(() => logger.info('Remote video event processed for video %s.', eventData.remoteId)) + .catch(err => { + logger.debug('Cannot process a video event.', { error: err }) + throw err }) } -function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { +function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance) { const options = { arguments: [ videoData, fromPod ], errorMessage: 'Cannot update quick and dirty the remote video with many retries.' } - retryTransactionWrapper(quickAndDirtyUpdateVideo, options, finalCallback) + return retryTransactionWrapper(quickAndDirtyUpdateVideo, options) } -function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { +function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance) { let videoName - waterfall([ - 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 } + return db.sequelize.transaction(t => { + return fetchRemoteVideo(fromPod.host, videoData.remoteId) + .then(videoInstance => { + const options = { transaction: t } - videoName = videoInstance.name + videoName = videoInstance.name - if (videoData.views) { - videoInstance.set('views', videoData.views) - } + if (videoData.views) { + videoInstance.set('views', videoData.views) + } - if (videoData.likes) { - videoInstance.set('likes', videoData.likes) - } + if (videoData.likes) { + videoInstance.set('likes', videoData.likes) + } - if (videoData.dislikes) { - videoInstance.set('dislikes', videoData.dislikes) - } + if (videoData.dislikes) { + videoInstance.set('dislikes', videoData.dislikes) + } - videoInstance.save(options).asCallback(function (err) { - return callback(err, t) + return videoInstance.save(options) }) - }, - - commitTransaction - - ], function (err: Error, t: Sequelize.Transaction) { - if (err) { - logger.debug('Cannot quick and dirty update the remote video.', { error: err }) - return rollbackTransaction(err, t, finalCallback) - } - - logger.info('Remote video %s quick and dirty updated', videoName) - return finalCallback(null) }) + .then(() => logger.info('Remote video %s quick and dirty updated', videoName)) + .catch(err => logger.debug('Cannot quick and dirty update the remote video.', { error: err })) } // Handle retries on fail -function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { +function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance) { const options = { arguments: [ videoToCreateData, fromPod ], errorMessage: 'Cannot insert the remote video with many retries.' } - retryTransactionWrapper(addRemoteVideo, options, finalCallback) + return retryTransactionWrapper(addRemoteVideo, options) } -function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { +function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance) { logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) - waterfall([ - - startSerializableTransaction, - - function assertRemoteIdAndHostUnique (t, callback) { - db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) { - if (err) return callback(err) + return db.sequelize.transaction(t => { + return db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId) + .then(video => { + if (video) throw new Error('RemoteId and host pair is not unique.') - if (video) return callback(new Error('RemoteId and host pair is not unique.')) - - return callback(null, t) + return undefined }) - }, - - function findOrCreateAuthor (t, callback) { - const name = videoToCreateData.author - const podId = fromPod.id - // This author is from another pod so we do not associate a user - const userId = null + .then(() => { + const name = videoToCreateData.author + const podId = fromPod.id + // This author is from another pod so we do not associate a user + const userId = null - db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { - return callback(err, t, authorInstance) + return db.Author.findOrCreateAuthor(name, podId, userId, t) }) - }, + .then(author => { + const tags = videoToCreateData.tags - function findOrCreateTags (t, author, callback) { - const tags = videoToCreateData.tags - - db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { - return callback(err, t, author, tagInstances) + return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances })) }) - }, - - function createVideoObject (t, author, tagInstances, callback) { - const videoData = { - name: videoToCreateData.name, - remoteId: videoToCreateData.remoteId, - extname: videoToCreateData.extname, - infoHash: videoToCreateData.infoHash, - category: videoToCreateData.category, - licence: videoToCreateData.licence, - language: videoToCreateData.language, - nsfw: videoToCreateData.nsfw, - description: videoToCreateData.description, - authorId: author.id, - duration: videoToCreateData.duration, - createdAt: videoToCreateData.createdAt, - // FIXME: updatedAt does not seems to be considered by Sequelize - updatedAt: videoToCreateData.updatedAt, - views: videoToCreateData.views, - likes: videoToCreateData.likes, - dislikes: videoToCreateData.dislikes - } - - const video = db.Video.build(videoData) - - return callback(null, t, tagInstances, video) - }, - - function generateThumbnail (t, tagInstances, video, callback) { - db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) { - if (err) { - logger.error('Cannot generate thumbnail from data.', { error: err }) - return callback(err) + .then(({ author, tagInstances }) => { + const videoData = { + name: videoToCreateData.name, + remoteId: videoToCreateData.remoteId, + extname: videoToCreateData.extname, + infoHash: videoToCreateData.infoHash, + category: videoToCreateData.category, + licence: videoToCreateData.licence, + language: videoToCreateData.language, + nsfw: videoToCreateData.nsfw, + description: videoToCreateData.description, + authorId: author.id, + duration: videoToCreateData.duration, + createdAt: videoToCreateData.createdAt, + // FIXME: updatedAt does not seems to be considered by Sequelize + updatedAt: videoToCreateData.updatedAt, + views: videoToCreateData.views, + likes: videoToCreateData.likes, + dislikes: videoToCreateData.dislikes } - return callback(err, t, tagInstances, video) + const video = db.Video.build(videoData) + return { tagInstances, video } }) - }, - - function insertVideoIntoDB (t, tagInstances, video, callback) { - const options = { - transaction: t - } - - video.save(options).asCallback(function (err, videoCreated) { - return callback(err, t, tagInstances, videoCreated) + .then(({ tagInstances, video }) => { + return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video })) }) - }, - - function associateTagsToVideo (t, tagInstances, video, callback) { - const options = { - transaction: t - } + .then(({ tagInstances, video }) => { + const options = { + transaction: t + } - video.setTags(tagInstances, options).asCallback(function (err) { - return callback(err, t) + return video.save(options).then(videoCreated => ({ tagInstances, videoCreated })) }) - }, - - commitTransaction - - ], function (err: Error, t: Sequelize.Transaction) { - if (err) { - // This is just a debug because we will retry the insert - logger.debug('Cannot insert the remote video.', { error: err }) - return rollbackTransaction(err, t, finalCallback) - } + .then(({ tagInstances, videoCreated }) => { + const options = { + transaction: t + } - logger.info('Remote video %s inserted.', videoToCreateData.name) - return finalCallback(null) + return videoCreated.setTags(tagInstances, options) + }) + }) + .then(() => logger.info('Remote video %s inserted.', videoToCreateData.name)) + .catch(err => { + logger.debug('Cannot insert the remote video.', { error: err }) + throw err }) } // Handle retries on fail -function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { +function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance) { const options = { arguments: [ videoAttributesToUpdate, fromPod ], errorMessage: 'Cannot update the remote video with many retries' } - retryTransactionWrapper(updateRemoteVideo, options, finalCallback) + return retryTransactionWrapper(updateRemoteVideo, options) } -function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { +function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance) { logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) - waterfall([ + return db.sequelize.transaction(t => { + return fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId) + .then(videoInstance => { + const tags = videoAttributesToUpdate.tags - startSerializableTransaction, - - function findVideo (t, callback) { - fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) { - return callback(err, t, videoInstance) + return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances })) }) - }, - - function findOrCreateTags (t, videoInstance, callback) { - const tags = videoAttributesToUpdate.tags - - db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { - return callback(err, t, videoInstance, tagInstances) - }) - }, - - function updateVideoIntoDB (t, videoInstance, tagInstances, callback) { - const options = { transaction: t } - - videoInstance.set('name', videoAttributesToUpdate.name) - videoInstance.set('category', videoAttributesToUpdate.category) - videoInstance.set('licence', videoAttributesToUpdate.licence) - videoInstance.set('language', videoAttributesToUpdate.language) - videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) - videoInstance.set('description', videoAttributesToUpdate.description) - videoInstance.set('infoHash', videoAttributesToUpdate.infoHash) - videoInstance.set('duration', videoAttributesToUpdate.duration) - videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) - videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) - videoInstance.set('extname', videoAttributesToUpdate.extname) - videoInstance.set('views', videoAttributesToUpdate.views) - videoInstance.set('likes', videoAttributesToUpdate.likes) - videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) - - videoInstance.save(options).asCallback(function (err) { - return callback(err, t, videoInstance, tagInstances) + .then(({ videoInstance, tagInstances }) => { + const options = { transaction: t } + + videoInstance.set('name', videoAttributesToUpdate.name) + videoInstance.set('category', videoAttributesToUpdate.category) + videoInstance.set('licence', videoAttributesToUpdate.licence) + videoInstance.set('language', videoAttributesToUpdate.language) + videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) + videoInstance.set('description', videoAttributesToUpdate.description) + videoInstance.set('infoHash', videoAttributesToUpdate.infoHash) + videoInstance.set('duration', videoAttributesToUpdate.duration) + videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) + videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) + videoInstance.set('extname', videoAttributesToUpdate.extname) + videoInstance.set('views', videoAttributesToUpdate.views) + videoInstance.set('likes', videoAttributesToUpdate.likes) + videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) + + return videoInstance.save(options).then(() => ({ videoInstance, tagInstances })) }) - }, - - function associateTagsToVideo (t, videoInstance, tagInstances, callback) { - const options = { transaction: t } + .then(({ videoInstance, tagInstances }) => { + const options = { transaction: t } - videoInstance.setTags(tagInstances, options).asCallback(function (err) { - return callback(err, t) + return videoInstance.setTags(tagInstances, options) }) - }, - - commitTransaction - - ], function (err: Error, t: Sequelize.Transaction) { - if (err) { - // This is just a debug because we will retry the insert - logger.debug('Cannot update the remote video.', { error: err }) - return rollbackTransaction(err, t, finalCallback) - } - - logger.info('Remote video %s updated', videoAttributesToUpdate.name) - return finalCallback(null) + }) + .then(() => logger.info('Remote video %s updated', videoAttributesToUpdate.name)) + .catch(err => { + // This is just a debug because we will retry the insert + logger.debug('Cannot update the remote video.', { error: err }) + throw err }) } -function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance, callback: (err: Error) => void) { +function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance) { // We need the instance because we have to remove some other stuffs (thumbnail etc) - 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(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) + return fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId) + .then(video => { + logger.debug('Removing remote video %s.', video.remoteId) + return video.destroy() + }) + .catch(err => { + logger.debug('Could not fetch remote video.', { host: fromPod.host, remoteId: videoToRemoveData.remoteId, error: err }) }) - }) } -function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance, callback: (err: Error) => void) { - 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 }) - // Do not return the error, continue the process - return callback(null) - } - - logger.debug('Reporting remote abuse for video %s.', video.id) - - const videoAbuseData = { - reporterUsername: reportData.reporterUsername, - reason: reportData.reportReason, - reporterPodId: fromPod.id, - videoId: video.id - } +function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance) { + return fetchOwnedVideo(reportData.videoRemoteId) + .then(video => { + logger.debug('Reporting remote abuse for video %s.', video.id) - db.VideoAbuse.create(videoAbuseData).asCallback(function (err) { - if (err) { - logger.error('Cannot create remote abuse video.', { error: err }) + const videoAbuseData = { + reporterUsername: reportData.reporterUsername, + reason: reportData.reportReason, + reporterPodId: fromPod.id, + videoId: video.id } - return callback(null) + return db.VideoAbuse.create(videoAbuseData) }) - }) + .catch(err => logger.error('Cannot create remote abuse video.', { error: err })) } -function fetchOwnedVideo (id: string, callback: (err: Error, video?: VideoInstance) => void) { - db.Video.load(id, function (err, video) { - if (err || !video) { - if (!err) err = new Error('video not found') +function fetchOwnedVideo (id: string) { + return db.Video.load(id) + .then(video => { + if (!video) throw new Error('Video not found') + return video + }) + .catch(err => { logger.error('Cannot load owned video from id.', { error: err, id }) - return callback(err) - } - - return callback(null, video) - }) + throw err + }) } -function fetchRemoteVideo (podHost: string, remoteId: string, callback: (err: Error, video?: VideoInstance) => void) { - db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) { - if (err || !video) { - if (!err) err = new Error('video not found') +function fetchRemoteVideo (podHost: string, remoteId: string) { + return db.Video.loadByHostAndRemoteId(podHost, remoteId) + .then(video => { + if (!video) throw new Error('Video not found') + return video + }) + .catch(err => { logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) - return callback(err) - } - - return callback(null, video) - }) + throw err + }) } diff --git a/server/controllers/api/request-schedulers.ts b/server/controllers/api/request-schedulers.ts index 8dd849007..2a934a512 100644 --- a/server/controllers/api/request-schedulers.ts +++ b/server/controllers/api/request-schedulers.ts @@ -1,5 +1,5 @@ import * as express from 'express' -import { parallel } from 'async' +import * as Promise from 'bluebird' import { AbstractRequestScheduler, @@ -27,33 +27,27 @@ export { // --------------------------------------------------------------------------- function getRequestSchedulersStats (req: express.Request, res: express.Response, next: express.NextFunction) { - parallel({ + Promise.props({ requestScheduler: buildRequestSchedulerStats(getRequestScheduler()), requestVideoQaduScheduler: buildRequestSchedulerStats(getRequestVideoQaduScheduler()), requestVideoEventScheduler: buildRequestSchedulerStats(getRequestVideoEventScheduler()) - }, function (err, result) { - if (err) return next(err) - - return res.json(result) }) + .then(result => res.json(result)) + .catch(err => next(err)) } // --------------------------------------------------------------------------- -function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler) { - return function (callback) { - requestScheduler.remainingRequestsCount(function (err, count) { - if (err) return callback(err) - - const result: RequestSchedulerStatsAttributes = { - totalRequests: count, - requestsLimitPods: requestScheduler.limitPods, - requestsLimitPerPod: requestScheduler.limitPerPod, - remainingMilliSeconds: requestScheduler.remainingMilliSeconds(), - milliSecondsInterval: requestScheduler.requestInterval - } - - return callback(null, result) - }) - } +function buildRequestSchedulerStats (requestScheduler: AbstractRequestScheduler) { + return requestScheduler.remainingRequestsCount().then(count => { + const result: RequestSchedulerStatsAttributes = { + totalRequests: count, + requestsLimitPods: requestScheduler.limitPods, + requestsLimitPerPod: requestScheduler.limitPerPod, + remainingMilliSeconds: requestScheduler.remainingMilliSeconds(), + milliSecondsInterval: requestScheduler.requestInterval + } + + return result + }) } diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index ce15353ef..6e0bb474a 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts @@ -1,8 +1,7 @@ import * as express from 'express' -import { waterfall } from 'async' import { database as db } from '../../initializers/database' -import { CONFIG, USER_ROLES } from '../../initializers' +import { USER_ROLES } from '../../initializers' import { logger, getFormatedObjects } from '../../helpers' import { authenticate, @@ -87,78 +86,61 @@ function createUser (req: express.Request, res: express.Response, next: express. role: USER_ROLES.USER }) - user.save().asCallback(function (err) { - if (err) return next(err) - - return res.type('json').status(204).end() - }) + user.save() + .then(() => res.type('json').status(204).end()) + .catch(err => next(err)) } function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { - db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { - if (err) return next(err) - - return res.json(user.toFormatedJSON()) - }) + db.User.loadByUsername(res.locals.oauth.token.user.username) + .then(user => res.json(user.toFormatedJSON())) + .catch(err => next(err)) } function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { const videoId = '' + req.params.videoId const userId = +res.locals.oauth.token.User.id - db.UserVideoRate.load(userId, videoId, null, function (err, ratingObj) { - if (err) return next(err) - - const rating = ratingObj ? ratingObj.type : 'none' - - const json: FormatedUserVideoRate = { - videoId, - rating - } - res.json(json) - }) + db.UserVideoRate.load(userId, videoId, null) + .then(ratingObj => { + const rating = ratingObj ? ratingObj.type : 'none' + const json: FormatedUserVideoRate = { + videoId, + rating + } + res.json(json) + }) + .catch(err => next(err)) } function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { - db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { - if (err) return next(err) - - res.json(getFormatedObjects(usersList, usersTotal)) - }) + db.User.listForApi(req.query.start, req.query.count, req.query.sort) + .then(resultList => { + res.json(getFormatedObjects(resultList.data, resultList.total)) + }) + .catch(err => next(err)) } function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { - waterfall([ - function loadUser (callback) { - db.User.loadById(req.params.id, callback) - }, - - function deleteUser (user, callback) { - user.destroy().asCallback(callback) - } - ], function andFinally (err) { - if (err) { + db.User.loadById(req.params.id) + .then(user => user.destroy()) + .then(() => res.sendStatus(204)) + .catch(err => { logger.error('Errors when removed the user.', { error: err }) return next(err) - } - - return res.sendStatus(204) - }) + }) } function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { - db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { - if (err) return next(err) - - if (req.body.password) user.password = req.body.password - if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW + db.User.loadByUsername(res.locals.oauth.token.user.username) + .then(user => { + if (req.body.password) user.password = req.body.password + if (req.body.displayNSFW !== undefined) user.displayNSFW = req.body.displayNSFW - user.save().asCallback(function (err) { - if (err) return next(err) - - return res.sendStatus(204) + return user.save() }) - }) + .then(() => res.sendStatus(204)) + .catch(err => next(err)) } function success (req: express.Request, res: express.Response, next: express.NextFunction) { diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 78e8e8b3d..fcbd5465f 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts @@ -1,16 +1,11 @@ import * as express from 'express' -import * as Sequelize from 'sequelize' -import { waterfall } from 'async' import { database as db } from '../../../initializers/database' import * as friends from '../../../lib/friends' import { logger, getFormatedObjects, - retryTransactionWrapper, - startSerializableTransaction, - commitTransaction, - rollbackTransaction + retryTransactionWrapper } from '../../../helpers' import { authenticate, @@ -21,6 +16,7 @@ import { setVideoAbusesSort, setPagination } from '../../../middlewares' +import { VideoInstance } from '../../../models' const abuseVideoRouter = express.Router() @@ -48,11 +44,9 @@ export { // --------------------------------------------------------------------------- function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { - db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) { - if (err) return next(err) - - res.json(getFormatedObjects(abusesList, abusesTotal)) - }) + db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort) + .then(result => res.json(getFormatedObjects(result.data, result.total))) + .catch(err => next(err)) } function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { @@ -61,14 +55,12 @@ function reportVideoAbuseRetryWrapper (req: express.Request, res: express.Respon errorMessage: 'Cannot report abuse to the video with many retries.' } - retryTransactionWrapper(reportVideoAbuse, options, function (err) { - if (err) return next(err) - - return res.type('json').status(204).end() - }) + retryTransactionWrapper(reportVideoAbuse, options) + .then(() => res.type('json').status(204).end()) + .catch(err => next(err)) } -function reportVideoAbuse (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { +function reportVideoAbuse (req: express.Request, res: express.Response) { const videoInstance = res.locals.video const reporterUsername = res.locals.oauth.token.User.username @@ -79,40 +71,26 @@ function reportVideoAbuse (req: express.Request, res: express.Response, finalCal reporterPodId: null // This is our pod that reported this abuse } - waterfall([ - - startSerializableTransaction, - - function createAbuse (t, callback) { - db.VideoAbuse.create(abuse).asCallback(function (err, abuse) { - return callback(err, t, abuse) - }) - }, - - function sendToFriendsIfNeeded (t, abuse, callback) { - // We send the information to the destination pod - if (videoInstance.isOwned() === false) { - const reportData = { - reporterUsername, - reportReason: abuse.reason, - videoRemoteId: videoInstance.remoteId + return db.sequelize.transaction(t => { + return db.VideoAbuse.create(abuse, { transaction: t }) + .then(abuse => { + // We send the information to the destination pod + if (videoInstance.isOwned() === false) { + const reportData = { + reporterUsername, + reportReason: abuse.reason, + videoRemoteId: videoInstance.remoteId + } + + return friends.reportAbuseVideoToFriend(reportData, videoInstance, t).then(() => videoInstance) } - friends.reportAbuseVideoToFriend(reportData, videoInstance) - } - - return callback(null, t) - }, - - commitTransaction - - ], function andFinally (err: Error, t: Sequelize.Transaction) { - if (err) { - logger.debug('Cannot update the video.', { error: err }) - return rollbackTransaction(err, t, finalCallback) - } - - logger.info('Abuse report for video %s created.', videoInstance.name) - return finalCallback(null) + return videoInstance + }) + }) + .then((videoInstance: VideoInstance) => logger.info('Abuse report for video %s created.', videoInstance.name)) + .catch(err => { + logger.debug('Cannot update the video.', { error: err }) + throw err }) } diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 4b42fc2d7..e4be6f0f9 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts @@ -32,12 +32,10 @@ function addVideoToBlacklist (req: express.Request, res: express.Response, next: videoId: videoInstance.id } - db.BlacklistedVideo.create(toCreate).asCallback(function (err) { - if (err) { + db.BlacklistedVideo.create(toCreate) + .then(() => res.type('json').status(204).end()) + .catch(err => { logger.error('Errors when blacklisting video ', { error: err }) return next(err) - } - - return res.type('json').status(204).end() - }) + }) } diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 5e8cf2d25..ed1f21d66 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -1,9 +1,7 @@ import * as express from 'express' -import * as Sequelize from 'sequelize' -import * as fs from 'fs' +import * as Promise from 'bluebird' import * as multer from 'multer' import * as path from 'path' -import { waterfall } from 'async' import { database as db } from '../../../initializers/database' import { @@ -35,13 +33,12 @@ import { } from '../../../middlewares' import { logger, - commitTransaction, retryTransactionWrapper, - rollbackTransaction, - startSerializableTransaction, generateRandomString, - getFormatedObjects + getFormatedObjects, + renamePromise } from '../../../helpers' +import { TagInstance } from '../../../models' import { abuseVideoRouter } from './abuse' import { blacklistRouter } from './blacklist' @@ -60,10 +57,15 @@ const storage = multer.diskStorage({ if (file.mimetype === 'video/webm') extension = 'webm' else if (file.mimetype === 'video/mp4') extension = 'mp4' else if (file.mimetype === 'video/ogg') extension = 'ogv' - generateRandomString(16, function (err, randomString) { - const fieldname = err ? undefined : randomString - cb(null, fieldname + '.' + extension) - }) + generateRandomString(16) + .then(randomString => { + const filename = randomString + cb(null, filename + '.' + extension) + }) + .catch(err => { + logger.error('Cannot generate random string for file name.', { error: err }) + throw err + }) } }) @@ -144,125 +146,97 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next errorMessage: 'Cannot insert the video with many retries.' } - retryTransactionWrapper(addVideo, options, function (err) { - if (err) return next(err) - - // TODO : include Location of the new video -> 201 - return res.type('json').status(204).end() - }) + retryTransactionWrapper(addVideo, options) + .then(() => { + // TODO : include Location of the new video -> 201 + res.type('json').status(204).end() + }) + .catch(err => next(err)) } -function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File, finalCallback: (err: Error) => void) { +function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File) { const videoInfos = req.body - waterfall([ - - startSerializableTransaction, + return db.sequelize.transaction(t => { + const user = res.locals.oauth.token.User - function findOrCreateAuthor (t, callback) { - const user = res.locals.oauth.token.User + const name = user.username + // null because it is OUR pod + const podId = null + const userId = user.id - const name = user.username - // null because it is OUR pod - const podId = null - const userId = user.id + return db.Author.findOrCreateAuthor(name, podId, userId, t) + .then(author => { + const tags = videoInfos.tags + if (!tags) return { author, tagInstances: undefined } - db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { - return callback(err, t, authorInstance) + return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances })) }) - }, - - function findOrCreateTags (t, author, callback) { - const tags = videoInfos.tags - - db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { - return callback(err, t, author, tagInstances) + .then(({ author, tagInstances }) => { + const videoData = { + name: videoInfos.name, + remoteId: null, + extname: path.extname(videoFile.filename), + category: videoInfos.category, + licence: videoInfos.licence, + language: videoInfos.language, + nsfw: videoInfos.nsfw, + description: videoInfos.description, + duration: videoFile['duration'], // duration was added by a previous middleware + authorId: author.id + } + + const video = db.Video.build(videoData) + return { author, tagInstances, video } }) - }, - - function createVideoObject (t, author, tagInstances, callback) { - const videoData = { - name: videoInfos.name, - remoteId: null, - extname: path.extname(videoFile.filename), - category: videoInfos.category, - licence: videoInfos.licence, - language: videoInfos.language, - nsfw: videoInfos.nsfw, - description: videoInfos.description, - duration: videoFile['duration'], // duration was added by a previous middleware - authorId: author.id - } - - const video = db.Video.build(videoData) - - return callback(null, t, author, tagInstances, video) - }, - - // Set the videoname the same as the id - function renameVideoFile (t, author, tagInstances, video, callback) { - const videoDir = CONFIG.STORAGE.VIDEOS_DIR - const source = path.join(videoDir, videoFile.filename) - const destination = path.join(videoDir, video.getVideoFilename()) - - fs.rename(source, destination, function (err) { - if (err) return callback(err) - - // This is important in case if there is another attempt - videoFile.filename = video.getVideoFilename() - return callback(null, t, author, tagInstances, video) + .then(({ author, tagInstances, video }) => { + const videoDir = CONFIG.STORAGE.VIDEOS_DIR + const source = path.join(videoDir, videoFile.filename) + const destination = path.join(videoDir, video.getVideoFilename()) + + return renamePromise(source, destination) + .then(() => { + // This is important in case if there is another attempt in the retry process + videoFile.filename = video.getVideoFilename() + return { author, tagInstances, video } + }) }) - }, - - function insertVideoIntoDB (t, author, tagInstances, video, callback) { - const options = { transaction: t } - - // Add tags association - video.save(options).asCallback(function (err, videoCreated) { - if (err) return callback(err) + .then(({ author, tagInstances, video }) => { + const options = { transaction: t } - // Do not forget to add Author informations to the created video - videoCreated.Author = author + return video.save(options) + .then(videoCreated => { + // Do not forget to add Author informations to the created video + videoCreated.Author = author - return callback(err, t, tagInstances, videoCreated) + return { tagInstances, video: videoCreated } + }) }) - }, - - function associateTagsToVideo (t, tagInstances, video, callback) { - const options = { transaction: t } + .then(({ tagInstances, video }) => { + if (!tagInstances) return video - video.setTags(tagInstances, options).asCallback(function (err) { - video.Tags = tagInstances - - return callback(err, t, video) + const options = { transaction: t } + return video.setTags(tagInstances, options) + .then(() => { + video.Tags = tagInstances + return video + }) }) - }, - - function sendToFriends (t, video, callback) { - // Let transcoding job send the video to friends because the videofile extension might change - if (CONFIG.TRANSCODING.ENABLED === true) return callback(null, t) - - video.toAddRemoteJSON(function (err, remoteVideo) { - if (err) return callback(err) - - // Now we'll add the video's meta data to our friends - addVideoToFriends(remoteVideo, t, function (err) { - return callback(err, t) - }) + .then(video => { + // Let transcoding job send the video to friends because the videofile extension might change + if (CONFIG.TRANSCODING.ENABLED === true) return undefined + + return video.toAddRemoteJSON() + .then(remoteVideo => { + // Now we'll add the video's meta data to our friends + return addVideoToFriends(remoteVideo, t) + }) }) - }, - - commitTransaction - - ], function andFinally (err: Error, t: Sequelize.Transaction) { - if (err) { - // This is just a debug because we will retry the insert - logger.debug('Cannot insert the video.', { error: err }) - return rollbackTransaction(err, t, finalCallback) - } - - logger.info('Video with name %s created.', videoInfos.name) - return finalCallback(null) + }) + .then(() => logger.info('Video with name %s created.', videoInfos.name)) + .catch((err: Error) => { + logger.debug('Cannot insert the video.', { error: err.stack }) + throw err }) } @@ -272,92 +246,75 @@ function updateVideoRetryWrapper (req: express.Request, res: express.Response, n errorMessage: 'Cannot update the video with many retries.' } - retryTransactionWrapper(updateVideo, options, function (err) { - if (err) return next(err) - - // TODO : include Location of the new video -> 201 - return res.type('json').status(204).end() - }) + retryTransactionWrapper(updateVideo, options) + .then(() => { + // TODO : include Location of the new video -> 201 + return res.type('json').status(204).end() + }) + .catch(err => next(err)) } -function updateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { +function updateVideo (req: express.Request, res: express.Response) { const videoInstance = res.locals.video const videoFieldsSave = videoInstance.toJSON() const videoInfosToUpdate = req.body - waterfall([ - - startSerializableTransaction, - - function findOrCreateTags (t, callback) { - if (videoInfosToUpdate.tags) { - db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) { - return callback(err, t, tagInstances) - }) - } else { - return callback(null, t, null) - } - }, - - function updateVideoIntoDB (t, tagInstances, callback) { - const options = { - transaction: t - } - - if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name) - if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category) - if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence) - if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language) - if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw) - if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description) - - videoInstance.save(options).asCallback(function (err) { - return callback(err, t, tagInstances) - }) - }, - - function associateTagsToVideo (t, tagInstances, callback) { - if (tagInstances) { - const options = { transaction: t } - - videoInstance.setTags(tagInstances, options).asCallback(function (err) { - videoInstance.Tags = tagInstances + return db.sequelize.transaction(t => { + let tagsPromise: Promise + if (!videoInfosToUpdate.tags) { + tagsPromise = Promise.resolve(null) + } else { + tagsPromise = db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t) + } - return callback(err, t) - }) - } else { - return callback(null, t) - } - }, + return tagsPromise + .then(tagInstances => { + const options = { + transaction: t + } - function sendToFriends (t, callback) { - const json = videoInstance.toUpdateRemoteJSON() + if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name) + if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category) + if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence) + if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language) + if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw) + if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description) - // Now we'll update the video's meta data to our friends - updateVideoToFriends(json, t, function (err) { - return callback(err, t) + return videoInstance.save(options).then(() => tagInstances) }) - }, - - commitTransaction + .then(tagInstances => { + if (!tagInstances) return - ], function andFinally (err: Error, t: Sequelize.Transaction) { - if (err) { - logger.debug('Cannot update the video.', { error: err }) + const options = { transaction: t } + return videoInstance.setTags(tagInstances, options) + .then(() => { + videoInstance.Tags = tagInstances - // Force fields we want to update - // If the transaction is retried, sequelize will think the object has not changed - // So it will skip the SQL request, even if the last one was ROLLBACKed! - Object.keys(videoFieldsSave).forEach(function (key) { - const value = videoFieldsSave[key] - videoInstance.set(key, value) + return + }) }) + .then(() => { + const json = videoInstance.toUpdateRemoteJSON() - return rollbackTransaction(err, t, finalCallback) - } - + // Now we'll update the video's meta data to our friends + return updateVideoToFriends(json, t) + }) + }) + .then(() => { logger.info('Video with name %s updated.', videoInstance.name) - return finalCallback(null) + }) + .catch(err => { + logger.debug('Cannot update the video.', { error: err }) + + // Force fields we want to update + // If the transaction is retried, sequelize will think the object has not changed + // So it will skip the SQL request, even if the last one was ROLLBACKed! + Object.keys(videoFieldsSave).forEach(function (key) { + const value = videoFieldsSave[key] + videoInstance.set(key, value) + }) + + throw err }) } @@ -366,20 +323,17 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne if (videoInstance.isOwned()) { // The increment is done directly in the database, not using the instance value - videoInstance.increment('views').asCallback(function (err) { - if (err) { - logger.error('Cannot add view to video %d.', videoInstance.id) - return - } - - // FIXME: make a real view system - // For example, only add a view when a user watch a video during 30s etc - const qaduParams = { - videoId: videoInstance.id, - type: REQUEST_VIDEO_QADU_TYPES.VIEWS - } - quickAndDirtyUpdateVideoToFriends(qaduParams) - }) + videoInstance.increment('views') + .then(() => { + // FIXME: make a real view system + // For example, only add a view when a user watch a video during 30s etc + const qaduParams = { + videoId: videoInstance.id, + type: REQUEST_VIDEO_QADU_TYPES.VIEWS + } + return quickAndDirtyUpdateVideoToFriends(qaduParams) + }) + .catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, { error: err })) } else { // Just send the event to our friends const eventParams = { @@ -394,33 +348,24 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne } function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { - db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { - if (err) return next(err) - - res.json(getFormatedObjects(videosList, videosTotal)) - }) + db.Video.listForApi(req.query.start, req.query.count, req.query.sort) + .then(result => res.json(getFormatedObjects(result.data, result.total))) + .catch(err => next(err)) } function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) { const videoInstance = res.locals.video - videoInstance.destroy().asCallback(function (err) { - if (err) { + videoInstance.destroy() + .then(() => res.type('json').status(204).end()) + .catch(err => { logger.error('Errors when removed the video.', { error: err }) return next(err) - } - - return res.type('json').status(204).end() - }) + }) } function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { - db.Video.searchAndPopulateAuthorAndPodAndTags( - req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, - function (err, videosList, videosTotal) { - if (err) return next(err) - - res.json(getFormatedObjects(videosList, videosTotal)) - } - ) + db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort) + .then(result => res.json(getFormatedObjects(result.data, result.total))) + .catch(err => next(err)) } diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts index afdd099f8..3d119d98b 100644 --- a/server/controllers/api/videos/rate.ts +++ b/server/controllers/api/videos/rate.ts @@ -1,14 +1,9 @@ import * as express from 'express' -import * as Sequelize from 'sequelize' -import { waterfall } from 'async' import { database as db } from '../../../initializers/database' import { logger, - retryTransactionWrapper, - startSerializableTransaction, - commitTransaction, - rollbackTransaction + retryTransactionWrapper } from '../../../helpers' import { VIDEO_RATE_TYPES, @@ -46,137 +41,109 @@ function rateVideoRetryWrapper (req: express.Request, res: express.Response, nex errorMessage: 'Cannot update the user video rate.' } - retryTransactionWrapper(rateVideo, options, function (err) { - if (err) return next(err) - - return res.type('json').status(204).end() - }) + retryTransactionWrapper(rateVideo, options) + .then(() => res.type('json').status(204).end()) + .catch(err => next(err)) } -function rateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { +function rateVideo (req: express.Request, res: express.Response) { const rateType = req.body.rating const videoInstance = res.locals.video const userInstance = res.locals.oauth.token.User - waterfall([ - startSerializableTransaction, - - function findPreviousRate (t, callback) { - db.UserVideoRate.load(userInstance.id, videoInstance.id, t, function (err, previousRate) { - return callback(err, t, previousRate) - }) - }, + return db.sequelize.transaction(t => { + return db.UserVideoRate.load(userInstance.id, videoInstance.id, t) + .then(previousRate => { + const options = { transaction: t } - function insertUserRateIntoDB (t, previousRate, callback) { - const options = { transaction: t } + let likesToIncrement = 0 + let dislikesToIncrement = 0 - let likesToIncrement = 0 - let dislikesToIncrement = 0 + if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++ + else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ - if (rateType === VIDEO_RATE_TYPES.LIKE) likesToIncrement++ - else if (rateType === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement++ + // There was a previous rate, update it + if (previousRate) { + // We will remove the previous rate, so we will need to remove it from the video attribute + if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement-- + else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement-- - // There was a previous rate, update it - if (previousRate) { - // We will remove the previous rate, so we will need to remove it from the video attribute - if (previousRate.type === VIDEO_RATE_TYPES.LIKE) likesToIncrement-- - else if (previousRate.type === VIDEO_RATE_TYPES.DISLIKE) dislikesToIncrement-- + previousRate.type = rateType - previousRate.type = rateType + return previousRate.save(options).then(() => ({ t, likesToIncrement, dislikesToIncrement })) + } else { // There was not a previous rate, insert a new one + const query = { + userId: userInstance.id, + videoId: videoInstance.id, + type: rateType + } - previousRate.save(options).asCallback(function (err) { - return callback(err, t, likesToIncrement, dislikesToIncrement) - }) - } else { // There was not a previous rate, insert a new one - const query = { - userId: userInstance.id, - videoId: videoInstance.id, - type: rateType + return db.UserVideoRate.create(query, options).then(() => ({ likesToIncrement, dislikesToIncrement })) } - - db.UserVideoRate.create(query, options).asCallback(function (err) { - return callback(err, t, likesToIncrement, dislikesToIncrement) - }) - } - }, - - function updateVideoAttributeDB (t, likesToIncrement, dislikesToIncrement, callback) { - const options = { transaction: t } - const incrementQuery = { - likes: likesToIncrement, - dislikes: dislikesToIncrement - } - - // Even if we do not own the video we increment the attributes - // It is usefull for the user to have a feedback - videoInstance.increment(incrementQuery, options).asCallback(function (err) { - return callback(err, t, likesToIncrement, dislikesToIncrement) - }) - }, - - function sendEventsToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) { - // No need for an event type, we own the video - if (videoInstance.isOwned()) return callback(null, t, likesToIncrement, dislikesToIncrement) - - const eventsParams = [] - - if (likesToIncrement !== 0) { - eventsParams.push({ - videoId: videoInstance.id, - type: REQUEST_VIDEO_EVENT_TYPES.LIKES, - count: likesToIncrement - }) - } - - if (dislikesToIncrement !== 0) { - eventsParams.push({ - videoId: videoInstance.id, - type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES, - count: dislikesToIncrement - }) - } - - addEventsToRemoteVideo(eventsParams, t, function (err) { - return callback(err, t, likesToIncrement, dislikesToIncrement) }) - }, - - function sendQaduToFriendsIfNeeded (t, likesToIncrement, dislikesToIncrement, callback) { - // We do not own the video, there is no need to send a quick and dirty update to friends - // Our rate was already sent by the addEvent function - if (videoInstance.isOwned() === false) return callback(null, t) - - const qadusParams = [] - - if (likesToIncrement !== 0) { - qadusParams.push({ - videoId: videoInstance.id, - type: REQUEST_VIDEO_QADU_TYPES.LIKES - }) - } - - if (dislikesToIncrement !== 0) { - qadusParams.push({ - videoId: videoInstance.id, - type: REQUEST_VIDEO_QADU_TYPES.DISLIKES - }) - } - - quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) { - return callback(err, t) + .then(({ likesToIncrement, dislikesToIncrement }) => { + const options = { transaction: t } + const incrementQuery = { + likes: likesToIncrement, + dislikes: dislikesToIncrement + } + + // Even if we do not own the video we increment the attributes + // It is usefull for the user to have a feedback + return videoInstance.increment(incrementQuery, options).then(() => ({ likesToIncrement, dislikesToIncrement })) }) - }, + .then(({ likesToIncrement, dislikesToIncrement }) => { + // No need for an event type, we own the video + if (videoInstance.isOwned()) return { likesToIncrement, dislikesToIncrement } + + const eventsParams = [] + + if (likesToIncrement !== 0) { + eventsParams.push({ + videoId: videoInstance.id, + type: REQUEST_VIDEO_EVENT_TYPES.LIKES, + count: likesToIncrement + }) + } + + if (dislikesToIncrement !== 0) { + eventsParams.push({ + videoId: videoInstance.id, + type: REQUEST_VIDEO_EVENT_TYPES.DISLIKES, + count: dislikesToIncrement + }) + } - commitTransaction + return addEventsToRemoteVideo(eventsParams, t).then(() => ({ likesToIncrement, dislikesToIncrement })) + }) + .then(({ likesToIncrement, dislikesToIncrement }) => { + // We do not own the video, there is no need to send a quick and dirty update to friends + // Our rate was already sent by the addEvent function + if (videoInstance.isOwned() === false) return undefined + + const qadusParams = [] + + if (likesToIncrement !== 0) { + qadusParams.push({ + videoId: videoInstance.id, + type: REQUEST_VIDEO_QADU_TYPES.LIKES + }) + } - ], function (err: Error, t: Sequelize.Transaction) { - if (err) { - // This is just a debug because we will retry the insert - logger.debug('Cannot add the user video rate.', { error: err }) - return rollbackTransaction(err, t, finalCallback) - } + if (dislikesToIncrement !== 0) { + qadusParams.push({ + videoId: videoInstance.id, + type: REQUEST_VIDEO_QADU_TYPES.DISLIKES + }) + } - logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username) - return finalCallback(null) + return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) + }) + }) + .then(() => logger.info('User video rate for video %s of user %s updated.', videoInstance.name, userInstance.username)) + .catch(err => { + // This is just a debug because we will retry the insert + logger.debug('Cannot add the user video rate.', { error: err }) + throw err }) } diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 503eff750..e4d69eae7 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts @@ -1,8 +1,7 @@ -import { parallel } from 'async' import * as express from 'express' -import * as fs from 'fs' import { join } from 'path' import * as validator from 'validator' +import * as Promise from 'bluebird' import { database as db } from '../initializers/database' import { @@ -11,7 +10,7 @@ import { STATIC_PATHS, STATIC_MAX_AGE } from '../initializers' -import { root } from '../helpers' +import { root, readFileBufferPromise } from '../helpers' import { VideoInstance } from '../models' const clientsRouter = express.Router() @@ -95,19 +94,15 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex // Let Angular application handle errors if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath) - parallel({ - file: function (callback) { - fs.readFile(indexPath, callback) - }, + Promise.all([ + readFileBufferPromise(indexPath), + db.Video.loadAndPopulateAuthorAndPodAndTags(videoId) + ]) + .then(([ file, video ]) => { + file = file as Buffer + video = video as VideoInstance - video: function (callback) { - db.Video.loadAndPopulateAuthorAndPodAndTags(videoId, callback) - } - }, function (err: Error, result: { file: Buffer, video: VideoInstance }) { - if (err) return next(err) - - const html = result.file.toString() - const video = result.video + const html = file.toString() // Let Angular application handle errors if (!video) return res.sendFile(indexPath) @@ -115,4 +110,5 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex const htmlStringPageWithTags = addOpenGraphTags(html, video) res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) }) + .catch(err => next(err)) } -- cgit v1.2.3