3 const eachSeries
= require('async/eachSeries')
4 const express
= require('express')
5 const waterfall
= require('async/waterfall')
7 const db
= require('../../../initializers/database')
8 const constants
= require('../../../initializers/constants')
9 const middlewares
= require('../../../middlewares')
10 const secureMiddleware
= middlewares
.secure
11 const videosValidators
= middlewares
.validators
.remote
.videos
12 const signatureValidators
= middlewares
.validators
.remote
.signature
13 const logger
= require('../../../helpers/logger')
14 const databaseUtils
= require('../../../helpers/database-utils')
16 const ENDPOINT_ACTIONS
= constants
.REQUEST_ENDPOINT_ACTIONS
[constants
.REQUEST_ENDPOINTS
.VIDEOS
]
18 // Functions to call when processing a remote request
19 const functionsHash
= {}
20 functionsHash
[ENDPOINT_ACTIONS
.ADD
] = addRemoteVideoRetryWrapper
21 functionsHash
[ENDPOINT_ACTIONS
.UPDATE
] = updateRemoteVideoRetryWrapper
22 functionsHash
[ENDPOINT_ACTIONS
.REMOVE
] = removeRemoteVideo
23 functionsHash
[ENDPOINT_ACTIONS
.REPORT_ABUSE
] = reportAbuseRemoteVideo
25 const router
= express
.Router()
28 signatureValidators
.signature
,
29 secureMiddleware
.checkSignature
,
30 videosValidators
.remoteVideos
,
35 signatureValidators
.signature
,
36 secureMiddleware
.checkSignature
,
37 videosValidators
.remoteQaduVideos
,
41 // ---------------------------------------------------------------------------
43 module
.exports
= router
45 // ---------------------------------------------------------------------------
47 function remoteVideos (req
, res
, next
) {
48 const requests
= req
.body
.data
49 const fromPod
= res
.locals
.secure
.pod
51 // We need to process in the same order to keep consistency
53 eachSeries(requests
, function (request
, callbackEach
) {
54 const data
= request
.data
56 // Get the function we need to call in order to process the request
57 const fun
= functionsHash
[request
.type
]
58 if (fun
=== undefined) {
59 logger
.error('Unkown remote request type %s.', request
.type
)
60 return callbackEach(null)
63 fun
.call(this, data
, fromPod
, callbackEach
)
65 if (err
) logger
.error('Error managing remote videos.', { error: err
})
68 // We don't need to keep the other pod waiting
69 return res
.type('json').status(204).end()
72 function remoteVideosQadu (req
, res
, next
) {
73 const requests
= req
.body
.data
74 const fromPod
= res
.locals
.secure
.pod
76 eachSeries(requests
, function (request
, callbackEach
) {
77 const videoData
= request
.data
79 quickAndDirtyUpdateVideoRetryWrapper(videoData
, fromPod
, callbackEach
)
81 if (err
) logger
.error('Error managing remote videos.', { error: err
})
84 return res
.type('json').status(204).end()
87 function quickAndDirtyUpdateVideoRetryWrapper (videoData
, fromPod
, finalCallback
) {
89 arguments: [ videoData
, fromPod
],
90 errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
93 databaseUtils
.retryTransactionWrapper(quickAndDirtyUpdateVideo
, options
, finalCallback
)
96 function quickAndDirtyUpdateVideo (videoData
, fromPod
, finalCallback
) {
98 databaseUtils
.startSerializableTransaction
,
100 function findVideo (t
, callback
) {
101 fetchVideo(fromPod
.host
, videoData
.remoteId
, function (err
, videoInstance
) {
102 return callback(err
, t
, videoInstance
)
106 function updateVideoIntoDB (t
, videoInstance
, callback
) {
107 const options
= { transaction: t
}
109 if (videoData
.views
) {
110 videoInstance
.set('views', videoData
.views
)
113 if (videoData
.likes
) {
114 videoInstance
.set('likes', videoData
.likes
)
117 if (videoData
.dislikes
) {
118 videoInstance
.set('dislikes', videoData
.dislikes
)
121 videoInstance
.save(options
).asCallback(function (err
) {
122 return callback(err
, t
)
126 databaseUtils
.commitTransaction
128 ], function (err
, t
) {
130 logger
.debug('Cannot quick and dirty update the remote video.', { error: err
})
131 return databaseUtils
.rollbackTransaction(err
, t
, finalCallback
)
134 logger
.info('Remote video %s quick and dirty updated', videoData
.name
)
135 return finalCallback(null)
139 // Handle retries on fail
140 function addRemoteVideoRetryWrapper (videoToCreateData
, fromPod
, finalCallback
) {
142 arguments: [ videoToCreateData
, fromPod
],
143 errorMessage: 'Cannot insert the remote video with many retries.'
146 databaseUtils
.retryTransactionWrapper(addRemoteVideo
, options
, finalCallback
)
149 function addRemoteVideo (videoToCreateData
, fromPod
, finalCallback
) {
150 logger
.debug('Adding remote video "%s".', videoToCreateData
.remoteId
)
154 databaseUtils
.startSerializableTransaction
,
156 function assertRemoteIdAndHostUnique (t
, callback
) {
157 db
.Video
.loadByHostAndRemoteId(fromPod
.host
, videoToCreateData
.remoteId
, function (err
, video
) {
158 if (err
) return callback(err
)
160 if (video
) return callback(new Error('RemoteId and host pair is not unique.'))
162 return callback(null, t
)
166 function findOrCreateAuthor (t
, callback
) {
167 const name
= videoToCreateData
.author
168 const podId
= fromPod
.id
169 // This author is from another pod so we do not associate a user
172 db
.Author
.findOrCreateAuthor(name
, podId
, userId
, t
, function (err
, authorInstance
) {
173 return callback(err
, t
, authorInstance
)
177 function findOrCreateTags (t
, author
, callback
) {
178 const tags
= videoToCreateData
.tags
180 db
.Tag
.findOrCreateTags(tags
, t
, function (err
, tagInstances
) {
181 return callback(err
, t
, author
, tagInstances
)
185 function createVideoObject (t
, author
, tagInstances
, callback
) {
187 name: videoToCreateData
.name
,
188 remoteId: videoToCreateData
.remoteId
,
189 extname: videoToCreateData
.extname
,
190 infoHash: videoToCreateData
.infoHash
,
191 description: videoToCreateData
.description
,
193 duration: videoToCreateData
.duration
,
194 createdAt: videoToCreateData
.createdAt
,
195 // FIXME: updatedAt does not seems to be considered by Sequelize
196 updatedAt: videoToCreateData
.updatedAt
199 const video
= db
.Video
.build(videoData
)
201 return callback(null, t
, tagInstances
, video
)
204 function generateThumbnail (t
, tagInstances
, video
, callback
) {
205 db
.Video
.generateThumbnailFromData(video
, videoToCreateData
.thumbnailData
, function (err
) {
207 logger
.error('Cannot generate thumbnail from data.', { error: err
})
211 return callback(err
, t
, tagInstances
, video
)
215 function insertVideoIntoDB (t
, tagInstances
, video
, callback
) {
220 video
.save(options
).asCallback(function (err
, videoCreated
) {
221 return callback(err
, t
, tagInstances
, videoCreated
)
225 function associateTagsToVideo (t
, tagInstances
, video
, callback
) {
230 video
.setTags(tagInstances
, options
).asCallback(function (err
) {
231 return callback(err
, t
)
235 databaseUtils
.commitTransaction
237 ], function (err
, t
) {
239 // This is just a debug because we will retry the insert
240 logger
.debug('Cannot insert the remote video.', { error: err
})
241 return databaseUtils
.rollbackTransaction(err
, t
, finalCallback
)
244 logger
.info('Remote video %s inserted.', videoToCreateData
.name
)
245 return finalCallback(null)
249 // Handle retries on fail
250 function updateRemoteVideoRetryWrapper (videoAttributesToUpdate
, fromPod
, finalCallback
) {
252 arguments: [ videoAttributesToUpdate
, fromPod
],
253 errorMessage: 'Cannot update the remote video with many retries'
256 databaseUtils
.retryTransactionWrapper(updateRemoteVideo
, options
, finalCallback
)
259 function updateRemoteVideo (videoAttributesToUpdate
, fromPod
, finalCallback
) {
260 logger
.debug('Updating remote video "%s".', videoAttributesToUpdate
.remoteId
)
264 databaseUtils
.startSerializableTransaction
,
266 function findVideo (t
, callback
) {
267 fetchVideo(fromPod
.host
, videoAttributesToUpdate
.remoteId
, function (err
, videoInstance
) {
268 return callback(err
, t
, videoInstance
)
272 function findOrCreateTags (t
, videoInstance
, callback
) {
273 const tags
= videoAttributesToUpdate
.tags
275 db
.Tag
.findOrCreateTags(tags
, t
, function (err
, tagInstances
) {
276 return callback(err
, t
, videoInstance
, tagInstances
)
280 function updateVideoIntoDB (t
, videoInstance
, tagInstances
, callback
) {
281 const options
= { transaction: t
}
283 videoInstance
.set('name', videoAttributesToUpdate
.name
)
284 videoInstance
.set('description', videoAttributesToUpdate
.description
)
285 videoInstance
.set('infoHash', videoAttributesToUpdate
.infoHash
)
286 videoInstance
.set('duration', videoAttributesToUpdate
.duration
)
287 videoInstance
.set('createdAt', videoAttributesToUpdate
.createdAt
)
288 videoInstance
.set('updatedAt', videoAttributesToUpdate
.updatedAt
)
289 videoInstance
.set('extname', videoAttributesToUpdate
.extname
)
291 videoInstance
.save(options
).asCallback(function (err
) {
292 return callback(err
, t
, videoInstance
, tagInstances
)
296 function associateTagsToVideo (t
, videoInstance
, tagInstances
, callback
) {
297 const options
= { transaction: t
}
299 videoInstance
.setTags(tagInstances
, options
).asCallback(function (err
) {
300 return callback(err
, t
)
304 databaseUtils
.commitTransaction
306 ], function (err
, t
) {
308 // This is just a debug because we will retry the insert
309 logger
.debug('Cannot update the remote video.', { error: err
})
310 return databaseUtils
.rollbackTransaction(err
, t
, finalCallback
)
313 logger
.info('Remote video %s updated', videoAttributesToUpdate
.name
)
314 return finalCallback(null)
318 function removeRemoteVideo (videoToRemoveData
, fromPod
, callback
) {
319 // We need the instance because we have to remove some other stuffs (thumbnail etc)
320 fetchVideo(fromPod
.host
, videoToRemoveData
.remoteId
, function (err
, video
) {
321 // Do not return the error, continue the process
322 if (err
) return callback(null)
324 logger
.debug('Removing remote video %s.', video
.remoteId
)
325 video
.destroy().asCallback(function (err
) {
326 // Do not return the error, continue the process
328 logger
.error('Cannot remove remote video with id %s.', videoToRemoveData
.remoteId
, { error: err
})
331 return callback(null)
336 function reportAbuseRemoteVideo (reportData
, fromPod
, callback
) {
337 db
.Video
.load(reportData
.videoRemoteId
, function (err
, video
) {
339 if (!err
) err
= new Error('video not found')
341 logger
.error('Cannot load video from id.', { error: err
, id: reportData
.videoRemoteId
})
342 // Do not return the error, continue the process
343 return callback(null)
346 logger
.debug('Reporting remote abuse for video %s.', video
.id
)
348 const videoAbuseData
= {
349 reporterUsername: reportData
.reporterUsername
,
350 reason: reportData
.reportReason
,
351 reporterPodId: fromPod
.id
,
355 db
.VideoAbuse
.create(videoAbuseData
).asCallback(function (err
) {
357 logger
.error('Cannot create remote abuse video.', { error: err
})
360 return callback(null)
365 function fetchVideo (podHost
, remoteId
, callback
) {
366 db
.Video
.loadByHostAndRemoteId(podHost
, remoteId
, function (err
, video
) {
368 if (!err
) err
= new Error('video not found')
370 logger
.error('Cannot load video from host and remote id.', { error: err
, podHost
, remoteId
})
374 return callback(null, video
)