]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/controllers/api/remote/videos.js
Server: always check commit result
[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 middlewares = require('../../../middlewares')
9 const secureMiddleware = middlewares.secure
10 const videosValidators = middlewares.validators.remote.videos
11 const signatureValidators = middlewares.validators.remote.signature
12 const logger = require('../../../helpers/logger')
13 const utils = require('../../../helpers/utils')
14
15 const router = express.Router()
16
17 router.post('/',
18 signatureValidators.signature,
19 secureMiddleware.checkSignature,
20 videosValidators.remoteVideos,
21 remoteVideos
22 )
23
24 // ---------------------------------------------------------------------------
25
26 module.exports = router
27
28 // ---------------------------------------------------------------------------
29
30 function remoteVideos (req, res, next) {
31 const requests = req.body.data
32 const fromPod = res.locals.secure.pod
33
34 // We need to process in the same order to keep consistency
35 // TODO: optimization
36 eachSeries(requests, function (request, callbackEach) {
37 const data = request.data
38
39 switch (request.type) {
40 case 'add':
41 addRemoteVideoRetryWrapper(data, fromPod, callbackEach)
42 break
43
44 case 'update':
45 updateRemoteVideoRetryWrapper(data, fromPod, callbackEach)
46 break
47
48 case 'remove':
49 removeRemoteVideo(data, fromPod, callbackEach)
50 break
51
52 case 'report-abuse':
53 reportAbuseRemoteVideo(data, fromPod, callbackEach)
54 break
55
56 default:
57 logger.error('Unkown remote request type %s.', request.type)
58 }
59 }, function (err) {
60 if (err) logger.error('Error managing remote videos.', { error: err })
61 })
62
63 // We don't need to keep the other pod waiting
64 return res.type('json').status(204).end()
65 }
66
67 // Handle retries on fail
68 function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) {
69 utils.transactionRetryer(
70 function (callback) {
71 return addRemoteVideo(videoToCreateData, fromPod, callback)
72 },
73 function (err) {
74 if (err) {
75 logger.error('Cannot insert the remote video with many retries.', { error: err })
76 return finalCallback(err)
77 }
78
79 return finalCallback()
80 }
81 )
82 }
83
84 function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
85 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
86
87 waterfall([
88
89 function startTransaction (callback) {
90 db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
91 return callback(err, t)
92 })
93 },
94
95 function findOrCreateAuthor (t, callback) {
96 const name = videoToCreateData.author
97 const podId = fromPod.id
98 // This author is from another pod so we do not associate a user
99 const userId = null
100
101 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
102 return callback(err, t, authorInstance)
103 })
104 },
105
106 function findOrCreateTags (t, author, callback) {
107 const tags = videoToCreateData.tags
108
109 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
110 return callback(err, t, author, tagInstances)
111 })
112 },
113
114 function createVideoObject (t, author, tagInstances, callback) {
115 const videoData = {
116 name: videoToCreateData.name,
117 remoteId: videoToCreateData.remoteId,
118 extname: videoToCreateData.extname,
119 infoHash: videoToCreateData.infoHash,
120 description: videoToCreateData.description,
121 authorId: author.id,
122 duration: videoToCreateData.duration,
123 createdAt: videoToCreateData.createdAt,
124 // FIXME: updatedAt does not seems to be considered by Sequelize
125 updatedAt: videoToCreateData.updatedAt
126 }
127
128 const video = db.Video.build(videoData)
129
130 return callback(null, t, tagInstances, video)
131 },
132
133 function generateThumbnail (t, tagInstances, video, callback) {
134 db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
135 if (err) {
136 logger.error('Cannot generate thumbnail from data.', { error: err })
137 return callback(err)
138 }
139
140 return callback(err, t, tagInstances, video)
141 })
142 },
143
144 function insertVideoIntoDB (t, tagInstances, video, callback) {
145 const options = {
146 transaction: t
147 }
148
149 video.save(options).asCallback(function (err, videoCreated) {
150 return callback(err, t, tagInstances, videoCreated)
151 })
152 },
153
154 function associateTagsToVideo (t, tagInstances, video, callback) {
155 const options = { transaction: t }
156
157 video.setTags(tagInstances, options).asCallback(function (err) {
158 return callback(err, t)
159 })
160 }
161
162 ], function (err, t) {
163 if (err) {
164 // This is just a debug because we will retry the insert
165 logger.debug('Cannot insert the remote video.', { error: err })
166
167 // Abort transaction?
168 if (t) t.rollback()
169
170 return finalCallback(err)
171 }
172
173 // Commit transaction
174 t.commit().asCallback(function (err) {
175 if (err) return finalCallback(err)
176
177 logger.info('Remote video %s inserted.', videoToCreateData.videoToCreateData.name)
178 return finalCallback(null)
179 })
180 })
181 }
182
183 // Handle retries on fail
184 function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) {
185 utils.transactionRetryer(
186 function (callback) {
187 return updateRemoteVideo(videoAttributesToUpdate, fromPod, callback)
188 },
189 function (err) {
190 if (err) {
191 logger.error('Cannot update the remote video with many retries.', { error: err })
192 return finalCallback(err)
193 }
194
195 return finalCallback()
196 }
197 )
198 }
199
200 function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
201 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
202
203 waterfall([
204
205 function startTransaction (callback) {
206 db.sequelize.transaction().asCallback(function (err, t) {
207 return callback(err, t)
208 })
209 },
210
211 function findVideo (t, callback) {
212 fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
213 return callback(err, t, videoInstance)
214 })
215 },
216
217 function findOrCreateTags (t, videoInstance, callback) {
218 const tags = videoAttributesToUpdate.tags
219
220 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
221 return callback(err, t, videoInstance, tagInstances)
222 })
223 },
224
225 function updateVideoIntoDB (t, videoInstance, tagInstances, callback) {
226 const options = { transaction: t }
227
228 videoInstance.set('name', videoAttributesToUpdate.name)
229 videoInstance.set('description', videoAttributesToUpdate.description)
230 videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
231 videoInstance.set('duration', videoAttributesToUpdate.duration)
232 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
233 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
234 videoInstance.set('extname', videoAttributesToUpdate.extname)
235
236 videoInstance.save(options).asCallback(function (err) {
237 return callback(err, t, videoInstance, tagInstances)
238 })
239 },
240
241 function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
242 const options = { transaction: t }
243
244 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
245 return callback(err, t)
246 })
247 }
248
249 ], function (err, t) {
250 if (err) {
251 // This is just a debug because we will retry the insert
252 logger.debug('Cannot update the remote video.', { error: err })
253
254 // Abort transaction?
255 if (t) t.rollback()
256
257 return finalCallback(err)
258 }
259
260 // Commit transaction
261 t.commit().asCallback(function (err) {
262 if (err) return finalCallback(err)
263
264 logger.info('Remote video %s updated', videoAttributesToUpdate.name)
265 return finalCallback(null)
266 })
267 })
268 }
269
270 function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
271 // We need the instance because we have to remove some other stuffs (thumbnail etc)
272 fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
273 if (err) return callback(err)
274
275 logger.debug('Removing remote video %s.', video.remoteId)
276 video.destroy().asCallback(callback)
277 })
278 }
279
280 function reportAbuseRemoteVideo (reportData, fromPod, callback) {
281 db.Video.load(reportData.videoRemoteId, function (err, video) {
282 if (err || !video) {
283 if (!err) err = new Error('video not found')
284
285 logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId })
286 return callback(err)
287 }
288
289 logger.debug('Reporting remote abuse for video %s.', video.id)
290
291 const videoAbuseData = {
292 reporterUsername: reportData.reporterUsername,
293 reason: reportData.reportReason,
294 reporterPodId: fromPod.id,
295 videoId: video.id
296 }
297
298 db.VideoAbuse.create(videoAbuseData).asCallback(callback)
299 })
300 }
301
302 function fetchVideo (podHost, remoteId, callback) {
303 db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
304 if (err || !video) {
305 if (!err) err = new Error('video not found')
306
307 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
308 return callback(err)
309 }
310
311 return callback(null, video)
312 })
313 }