]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/controllers/api/remote/videos.js
Server: make a basic "quick and dirty update" for videos
[github/Chocobozzz/PeerTube.git] / server / controllers / api / remote / videos.js
1 'use strict'
2
3 const eachSeries = require('async/eachSeries')
4 const express = require('express')
5 const waterfall = require('async/waterfall')
6
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')
15
16 const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS]
17
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
24
25 const router = express.Router()
26
27 router.post('/',
28 signatureValidators.signature,
29 secureMiddleware.checkSignature,
30 videosValidators.remoteVideos,
31 remoteVideos
32 )
33
34 router.post('/qadu',
35 signatureValidators.signature,
36 secureMiddleware.checkSignature,
37 videosValidators.remoteQaduVideos,
38 remoteVideosQadu
39 )
40
41 // ---------------------------------------------------------------------------
42
43 module.exports = router
44
45 // ---------------------------------------------------------------------------
46
47 function remoteVideos (req, res, next) {
48 const requests = req.body.data
49 const fromPod = res.locals.secure.pod
50
51 // We need to process in the same order to keep consistency
52 // TODO: optimization
53 eachSeries(requests, function (request, callbackEach) {
54 const data = request.data
55
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)
61 }
62
63 fun.call(this, data, fromPod, callbackEach)
64 }, function (err) {
65 if (err) logger.error('Error managing remote videos.', { error: err })
66 })
67
68 // We don't need to keep the other pod waiting
69 return res.type('json').status(204).end()
70 }
71
72 function remoteVideosQadu (req, res, next) {
73 const requests = req.body.data
74 const fromPod = res.locals.secure.pod
75
76 eachSeries(requests, function (request, callbackEach) {
77 const videoData = request.data
78
79 quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod, callbackEach)
80 }, function (err) {
81 if (err) logger.error('Error managing remote videos.', { error: err })
82 })
83
84 return res.type('json').status(204).end()
85 }
86
87 function quickAndDirtyUpdateVideoRetryWrapper (videoData, fromPod, finalCallback) {
88 const options = {
89 arguments: [ videoData, fromPod ],
90 errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
91 }
92
93 databaseUtils.retryTransactionWrapper(quickAndDirtyUpdateVideo, options, finalCallback)
94 }
95
96 function quickAndDirtyUpdateVideo (videoData, fromPod, finalCallback) {
97 waterfall([
98 databaseUtils.startSerializableTransaction,
99
100 function findVideo (t, callback) {
101 fetchVideo(fromPod.host, videoData.remoteId, function (err, videoInstance) {
102 return callback(err, t, videoInstance)
103 })
104 },
105
106 function updateVideoIntoDB (t, videoInstance, callback) {
107 const options = { transaction: t }
108
109 if (videoData.views) {
110 videoInstance.set('views', videoData.views)
111 }
112
113 if (videoData.likes) {
114 videoInstance.set('likes', videoData.likes)
115 }
116
117 if (videoData.dislikes) {
118 videoInstance.set('dislikes', videoData.dislikes)
119 }
120
121 videoInstance.save(options).asCallback(function (err) {
122 return callback(err, t)
123 })
124 },
125
126 databaseUtils.commitTransaction
127
128 ], function (err, t) {
129 if (err) {
130 logger.debug('Cannot quick and dirty update the remote video.', { error: err })
131 return databaseUtils.rollbackTransaction(err, t, finalCallback)
132 }
133
134 logger.info('Remote video %s quick and dirty updated', videoData.name)
135 return finalCallback(null)
136 })
137 }
138
139 // Handle retries on fail
140 function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) {
141 const options = {
142 arguments: [ videoToCreateData, fromPod ],
143 errorMessage: 'Cannot insert the remote video with many retries.'
144 }
145
146 databaseUtils.retryTransactionWrapper(addRemoteVideo, options, finalCallback)
147 }
148
149 function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
150 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
151
152 waterfall([
153
154 databaseUtils.startSerializableTransaction,
155
156 function assertRemoteIdAndHostUnique (t, callback) {
157 db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) {
158 if (err) return callback(err)
159
160 if (video) return callback(new Error('RemoteId and host pair is not unique.'))
161
162 return callback(null, t)
163 })
164 },
165
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
170 const userId = null
171
172 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
173 return callback(err, t, authorInstance)
174 })
175 },
176
177 function findOrCreateTags (t, author, callback) {
178 const tags = videoToCreateData.tags
179
180 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
181 return callback(err, t, author, tagInstances)
182 })
183 },
184
185 function createVideoObject (t, author, tagInstances, callback) {
186 const videoData = {
187 name: videoToCreateData.name,
188 remoteId: videoToCreateData.remoteId,
189 extname: videoToCreateData.extname,
190 infoHash: videoToCreateData.infoHash,
191 description: videoToCreateData.description,
192 authorId: author.id,
193 duration: videoToCreateData.duration,
194 createdAt: videoToCreateData.createdAt,
195 // FIXME: updatedAt does not seems to be considered by Sequelize
196 updatedAt: videoToCreateData.updatedAt
197 }
198
199 const video = db.Video.build(videoData)
200
201 return callback(null, t, tagInstances, video)
202 },
203
204 function generateThumbnail (t, tagInstances, video, callback) {
205 db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
206 if (err) {
207 logger.error('Cannot generate thumbnail from data.', { error: err })
208 return callback(err)
209 }
210
211 return callback(err, t, tagInstances, video)
212 })
213 },
214
215 function insertVideoIntoDB (t, tagInstances, video, callback) {
216 const options = {
217 transaction: t
218 }
219
220 video.save(options).asCallback(function (err, videoCreated) {
221 return callback(err, t, tagInstances, videoCreated)
222 })
223 },
224
225 function associateTagsToVideo (t, tagInstances, video, callback) {
226 const options = {
227 transaction: t
228 }
229
230 video.setTags(tagInstances, options).asCallback(function (err) {
231 return callback(err, t)
232 })
233 },
234
235 databaseUtils.commitTransaction
236
237 ], function (err, t) {
238 if (err) {
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)
242 }
243
244 logger.info('Remote video %s inserted.', videoToCreateData.name)
245 return finalCallback(null)
246 })
247 }
248
249 // Handle retries on fail
250 function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) {
251 const options = {
252 arguments: [ videoAttributesToUpdate, fromPod ],
253 errorMessage: 'Cannot update the remote video with many retries'
254 }
255
256 databaseUtils.retryTransactionWrapper(updateRemoteVideo, options, finalCallback)
257 }
258
259 function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
260 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
261
262 waterfall([
263
264 databaseUtils.startSerializableTransaction,
265
266 function findVideo (t, callback) {
267 fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
268 return callback(err, t, videoInstance)
269 })
270 },
271
272 function findOrCreateTags (t, videoInstance, callback) {
273 const tags = videoAttributesToUpdate.tags
274
275 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
276 return callback(err, t, videoInstance, tagInstances)
277 })
278 },
279
280 function updateVideoIntoDB (t, videoInstance, tagInstances, callback) {
281 const options = { transaction: t }
282
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)
290
291 videoInstance.save(options).asCallback(function (err) {
292 return callback(err, t, videoInstance, tagInstances)
293 })
294 },
295
296 function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
297 const options = { transaction: t }
298
299 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
300 return callback(err, t)
301 })
302 },
303
304 databaseUtils.commitTransaction
305
306 ], function (err, t) {
307 if (err) {
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)
311 }
312
313 logger.info('Remote video %s updated', videoAttributesToUpdate.name)
314 return finalCallback(null)
315 })
316 }
317
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)
323
324 logger.debug('Removing remote video %s.', video.remoteId)
325 video.destroy().asCallback(function (err) {
326 // Do not return the error, continue the process
327 if (err) {
328 logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err })
329 }
330
331 return callback(null)
332 })
333 })
334 }
335
336 function reportAbuseRemoteVideo (reportData, fromPod, callback) {
337 db.Video.load(reportData.videoRemoteId, function (err, video) {
338 if (err || !video) {
339 if (!err) err = new Error('video not found')
340
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)
344 }
345
346 logger.debug('Reporting remote abuse for video %s.', video.id)
347
348 const videoAbuseData = {
349 reporterUsername: reportData.reporterUsername,
350 reason: reportData.reportReason,
351 reporterPodId: fromPod.id,
352 videoId: video.id
353 }
354
355 db.VideoAbuse.create(videoAbuseData).asCallback(function (err) {
356 if (err) {
357 logger.error('Cannot create remote abuse video.', { error: err })
358 }
359
360 return callback(null)
361 })
362 })
363 }
364
365 function fetchVideo (podHost, remoteId, callback) {
366 db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
367 if (err || !video) {
368 if (!err) err = new Error('video not found')
369
370 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
371 return callback(err)
372 }
373
374 return callback(null, video)
375 })
376 }