]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - 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
CommitLineData
528a9efa
C
1'use strict'
2
1a42c9e2 3const eachSeries = require('async/eachSeries')
528a9efa 4const express = require('express')
feb4bdfd 5const waterfall = require('async/waterfall')
528a9efa 6
a6fd2b30 7const db = require('../../../initializers/database')
62f4ef41 8const constants = require('../../../initializers/constants')
a6fd2b30 9const middlewares = require('../../../middlewares')
528a9efa 10const secureMiddleware = middlewares.secure
55fa55a9
C
11const videosValidators = middlewares.validators.remote.videos
12const signatureValidators = middlewares.validators.remote.signature
a6fd2b30 13const logger = require('../../../helpers/logger')
4df023f2 14const databaseUtils = require('../../../helpers/database-utils')
528a9efa 15
62f4ef41
C
16const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS]
17
18// Functions to call when processing a remote request
19const functionsHash = {}
20functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
21functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
22functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
23functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideo
24
528a9efa
C
25const router = express.Router()
26
a6fd2b30 27router.post('/',
55fa55a9 28 signatureValidators.signature,
0eb78d53 29 secureMiddleware.checkSignature,
55fa55a9 30 videosValidators.remoteVideos,
528a9efa
C
31 remoteVideos
32)
33
9e167724
C
34router.post('/qadu',
35 signatureValidators.signature,
36 secureMiddleware.checkSignature,
37 videosValidators.remoteQaduVideos,
38 remoteVideosQadu
39)
40
528a9efa
C
41// ---------------------------------------------------------------------------
42
43module.exports = router
44
45// ---------------------------------------------------------------------------
46
47function remoteVideos (req, res, next) {
48 const requests = req.body.data
4ff0d862 49 const fromPod = res.locals.secure.pod
528a9efa
C
50
51 // We need to process in the same order to keep consistency
52 // TODO: optimization
1a42c9e2 53 eachSeries(requests, function (request, callbackEach) {
55fa55a9 54 const data = request.data
528a9efa 55
62f4ef41
C
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)
528a9efa 61 }
62f4ef41
C
62
63 fun.call(this, data, fromPod, callbackEach)
aaf61f38
C
64 }, function (err) {
65 if (err) logger.error('Error managing remote videos.', { error: err })
528a9efa
C
66 })
67
68 // We don't need to keep the other pod waiting
69 return res.type('json').status(204).end()
70}
71
9e167724
C
72function 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
87function 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
96function 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
ed04d94f
C
139// Handle retries on fail
140function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) {
d6a5b018
C
141 const options = {
142 arguments: [ videoToCreateData, fromPod ],
143 errorMessage: 'Cannot insert the remote video with many retries.'
144 }
ed04d94f 145
4df023f2 146 databaseUtils.retryTransactionWrapper(addRemoteVideo, options, finalCallback)
ed04d94f
C
147}
148
4ff0d862 149function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
ed04d94f 150 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
6666aad4 151
feb4bdfd
C
152 waterfall([
153
4df023f2 154 databaseUtils.startSerializableTransaction,
7920c273 155
cddadde8
C
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
4ff0d862
C
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
feb4bdfd 171
4ff0d862
C
172 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
173 return callback(err, t, authorInstance)
feb4bdfd
C
174 })
175 },
176
7920c273
C
177 function findOrCreateTags (t, author, callback) {
178 const tags = videoToCreateData.tags
7920c273 179
4ff0d862 180 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
7920c273
C
181 return callback(err, t, author, tagInstances)
182 })
183 },
184
185 function createVideoObject (t, author, tagInstances, callback) {
feb4bdfd
C
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,
124648d7 193 duration: videoToCreateData.duration,
79066fdf 194 createdAt: videoToCreateData.createdAt,
ed04d94f 195 // FIXME: updatedAt does not seems to be considered by Sequelize
79066fdf 196 updatedAt: videoToCreateData.updatedAt
feb4bdfd
C
197 }
198
199 const video = db.Video.build(videoData)
200
7920c273 201 return callback(null, t, tagInstances, video)
feb4bdfd
C
202 },
203
7920c273 204 function generateThumbnail (t, tagInstances, video, callback) {
4d324488 205 db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
feb4bdfd 206 if (err) {
4d324488 207 logger.error('Cannot generate thumbnail from data.', { error: err })
feb4bdfd
C
208 return callback(err)
209 }
210
7920c273 211 return callback(err, t, tagInstances, video)
feb4bdfd
C
212 })
213 },
214
7920c273
C
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) {
62f4ef41
C
226 const options = {
227 transaction: t
228 }
7920c273
C
229
230 video.setTags(tagInstances, options).asCallback(function (err) {
231 return callback(err, t)
232 })
4145c1c6
C
233 },
234
235 databaseUtils.commitTransaction
c77fa067 236
7920c273
C
237 ], function (err, t) {
238 if (err) {
ed04d94f
C
239 // This is just a debug because we will retry the insert
240 logger.debug('Cannot insert the remote video.', { error: err })
4145c1c6 241 return databaseUtils.rollbackTransaction(err, t, finalCallback)
7920c273
C
242 }
243
4145c1c6
C
244 logger.info('Remote video %s inserted.', videoToCreateData.name)
245 return finalCallback(null)
7920c273 246 })
528a9efa
C
247}
248
ed04d94f
C
249// Handle retries on fail
250function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) {
d6a5b018 251 const options = {
fbc22d79 252 arguments: [ videoAttributesToUpdate, fromPod ],
d6a5b018
C
253 errorMessage: 'Cannot update the remote video with many retries'
254 }
ed04d94f 255
fbc22d79 256 databaseUtils.retryTransactionWrapper(updateRemoteVideo, options, finalCallback)
ed04d94f
C
257}
258
3d118fb5 259function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
ed04d94f 260 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
feb4bdfd 261
3d118fb5
C
262 waterfall([
263
4df023f2 264 databaseUtils.startSerializableTransaction,
3d118fb5
C
265
266 function findVideo (t, callback) {
55fa55a9
C
267 fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
268 return callback(err, t, videoInstance)
3d118fb5
C
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)
79066fdf 288 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
3d118fb5
C
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 })
4145c1c6
C
302 },
303
304 databaseUtils.commitTransaction
528a9efa 305
3d118fb5
C
306 ], function (err, t) {
307 if (err) {
ed04d94f
C
308 // This is just a debug because we will retry the insert
309 logger.debug('Cannot update the remote video.', { error: err })
4145c1c6 310 return databaseUtils.rollbackTransaction(err, t, finalCallback)
6666aad4
C
311 }
312
4145c1c6
C
313 logger.info('Remote video %s updated', videoAttributesToUpdate.name)
314 return finalCallback(null)
3d118fb5
C
315 })
316}
317
318function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
319 // We need the instance because we have to remove some other stuffs (thumbnail etc)
55fa55a9 320 fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
d8cc063e
C
321 // Do not return the error, continue the process
322 if (err) return callback(null)
55fa55a9
C
323
324 logger.debug('Removing remote video %s.', video.remoteId)
d8cc063e
C
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 })
55fa55a9
C
333 })
334}
335
336function reportAbuseRemoteVideo (reportData, fromPod, callback) {
337 db.Video.load(reportData.videoRemoteId, function (err, video) {
3d118fb5 338 if (err || !video) {
55fa55a9
C
339 if (!err) err = new Error('video not found')
340
ed04d94f 341 logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId })
d8cc063e
C
342 // Do not return the error, continue the process
343 return callback(null)
3d118fb5 344 }
6666aad4 345
55fa55a9
C
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
d8cc063e
C
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 })
55fa55a9
C
362 })
363}
364
365function 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
ed04d94f 370 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
55fa55a9
C
371 return callback(err)
372 }
373
374 return callback(null, video)
528a9efa
C
375 })
376}