aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/videos/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api/videos/index.ts')
-rw-r--r--server/controllers/api/videos/index.ts381
1 files changed, 163 insertions, 218 deletions
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 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Sequelize from 'sequelize' 2import * as Promise from 'bluebird'
3import * as fs from 'fs'
4import * as multer from 'multer' 3import * as multer from 'multer'
5import * as path from 'path' 4import * as path from 'path'
6import { waterfall } from 'async'
7 5
8import { database as db } from '../../../initializers/database' 6import { database as db } from '../../../initializers/database'
9import { 7import {
@@ -35,13 +33,12 @@ import {
35} from '../../../middlewares' 33} from '../../../middlewares'
36import { 34import {
37 logger, 35 logger,
38 commitTransaction,
39 retryTransactionWrapper, 36 retryTransactionWrapper,
40 rollbackTransaction,
41 startSerializableTransaction,
42 generateRandomString, 37 generateRandomString,
43 getFormatedObjects 38 getFormatedObjects,
39 renamePromise
44} from '../../../helpers' 40} from '../../../helpers'
41import { TagInstance } from '../../../models'
45 42
46import { abuseVideoRouter } from './abuse' 43import { abuseVideoRouter } from './abuse'
47import { blacklistRouter } from './blacklist' 44import { blacklistRouter } from './blacklist'
@@ -60,10 +57,15 @@ const storage = multer.diskStorage({
60 if (file.mimetype === 'video/webm') extension = 'webm' 57 if (file.mimetype === 'video/webm') extension = 'webm'
61 else if (file.mimetype === 'video/mp4') extension = 'mp4' 58 else if (file.mimetype === 'video/mp4') extension = 'mp4'
62 else if (file.mimetype === 'video/ogg') extension = 'ogv' 59 else if (file.mimetype === 'video/ogg') extension = 'ogv'
63 generateRandomString(16, function (err, randomString) { 60 generateRandomString(16)
64 const fieldname = err ? undefined : randomString 61 .then(randomString => {
65 cb(null, fieldname + '.' + extension) 62 const filename = randomString
66 }) 63 cb(null, filename + '.' + extension)
64 })
65 .catch(err => {
66 logger.error('Cannot generate random string for file name.', { error: err })
67 throw err
68 })
67 } 69 }
68}) 70})
69 71
@@ -144,125 +146,97 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next
144 errorMessage: 'Cannot insert the video with many retries.' 146 errorMessage: 'Cannot insert the video with many retries.'
145 } 147 }
146 148
147 retryTransactionWrapper(addVideo, options, function (err) { 149 retryTransactionWrapper(addVideo, options)
148 if (err) return next(err) 150 .then(() => {
149 151 // TODO : include Location of the new video -> 201
150 // TODO : include Location of the new video -> 201 152 res.type('json').status(204).end()
151 return res.type('json').status(204).end() 153 })
152 }) 154 .catch(err => next(err))
153} 155}
154 156
155function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File, finalCallback: (err: Error) => void) { 157function addVideo (req: express.Request, res: express.Response, videoFile: Express.Multer.File) {
156 const videoInfos = req.body 158 const videoInfos = req.body
157 159
158 waterfall([ 160 return db.sequelize.transaction(t => {
159 161 const user = res.locals.oauth.token.User
160 startSerializableTransaction,
161 162
162 function findOrCreateAuthor (t, callback) { 163 const name = user.username
163 const user = res.locals.oauth.token.User 164 // null because it is OUR pod
165 const podId = null
166 const userId = user.id
164 167
165 const name = user.username 168 return db.Author.findOrCreateAuthor(name, podId, userId, t)
166 // null because it is OUR pod 169 .then(author => {
167 const podId = null 170 const tags = videoInfos.tags
168 const userId = user.id 171 if (!tags) return { author, tagInstances: undefined }
169 172
170 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { 173 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
171 return callback(err, t, authorInstance)
172 }) 174 })
173 }, 175 .then(({ author, tagInstances }) => {
174 176 const videoData = {
175 function findOrCreateTags (t, author, callback) { 177 name: videoInfos.name,
176 const tags = videoInfos.tags 178 remoteId: null,
177 179 extname: path.extname(videoFile.filename),
178 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { 180 category: videoInfos.category,
179 return callback(err, t, author, tagInstances) 181 licence: videoInfos.licence,
182 language: videoInfos.language,
183 nsfw: videoInfos.nsfw,
184 description: videoInfos.description,
185 duration: videoFile['duration'], // duration was added by a previous middleware
186 authorId: author.id
187 }
188
189 const video = db.Video.build(videoData)
190 return { author, tagInstances, video }
180 }) 191 })
181 }, 192 .then(({ author, tagInstances, video }) => {
182 193 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
183 function createVideoObject (t, author, tagInstances, callback) { 194 const source = path.join(videoDir, videoFile.filename)
184 const videoData = { 195 const destination = path.join(videoDir, video.getVideoFilename())
185 name: videoInfos.name, 196
186 remoteId: null, 197 return renamePromise(source, destination)
187 extname: path.extname(videoFile.filename), 198 .then(() => {
188 category: videoInfos.category, 199 // This is important in case if there is another attempt in the retry process
189 licence: videoInfos.licence, 200 videoFile.filename = video.getVideoFilename()
190 language: videoInfos.language, 201 return { author, tagInstances, video }
191 nsfw: videoInfos.nsfw, 202 })
192 description: videoInfos.description,
193 duration: videoFile['duration'], // duration was added by a previous middleware
194 authorId: author.id
195 }
196
197 const video = db.Video.build(videoData)
198
199 return callback(null, t, author, tagInstances, video)
200 },
201
202 // Set the videoname the same as the id
203 function renameVideoFile (t, author, tagInstances, video, callback) {
204 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
205 const source = path.join(videoDir, videoFile.filename)
206 const destination = path.join(videoDir, video.getVideoFilename())
207
208 fs.rename(source, destination, function (err) {
209 if (err) return callback(err)
210
211 // This is important in case if there is another attempt
212 videoFile.filename = video.getVideoFilename()
213 return callback(null, t, author, tagInstances, video)
214 }) 203 })
215 }, 204 .then(({ author, tagInstances, video }) => {
216 205 const options = { transaction: t }
217 function insertVideoIntoDB (t, author, tagInstances, video, callback) {
218 const options = { transaction: t }
219
220 // Add tags association
221 video.save(options).asCallback(function (err, videoCreated) {
222 if (err) return callback(err)
223 206
224 // Do not forget to add Author informations to the created video 207 return video.save(options)
225 videoCreated.Author = author 208 .then(videoCreated => {
209 // Do not forget to add Author informations to the created video
210 videoCreated.Author = author
226 211
227 return callback(err, t, tagInstances, videoCreated) 212 return { tagInstances, video: videoCreated }
213 })
228 }) 214 })
229 }, 215 .then(({ tagInstances, video }) => {
230 216 if (!tagInstances) return video
231 function associateTagsToVideo (t, tagInstances, video, callback) {
232 const options = { transaction: t }
233 217
234 video.setTags(tagInstances, options).asCallback(function (err) { 218 const options = { transaction: t }
235 video.Tags = tagInstances 219 return video.setTags(tagInstances, options)
236 220 .then(() => {
237 return callback(err, t, video) 221 video.Tags = tagInstances
222 return video
223 })
238 }) 224 })
239 }, 225 .then(video => {
240 226 // Let transcoding job send the video to friends because the videofile extension might change
241 function sendToFriends (t, video, callback) { 227 if (CONFIG.TRANSCODING.ENABLED === true) return undefined
242 // Let transcoding job send the video to friends because the videofile extension might change 228
243 if (CONFIG.TRANSCODING.ENABLED === true) return callback(null, t) 229 return video.toAddRemoteJSON()
244 230 .then(remoteVideo => {
245 video.toAddRemoteJSON(function (err, remoteVideo) { 231 // Now we'll add the video's meta data to our friends
246 if (err) return callback(err) 232 return addVideoToFriends(remoteVideo, t)
247 233 })
248 // Now we'll add the video's meta data to our friends
249 addVideoToFriends(remoteVideo, t, function (err) {
250 return callback(err, t)
251 })
252 }) 234 })
253 }, 235 })
254 236 .then(() => logger.info('Video with name %s created.', videoInfos.name))
255 commitTransaction 237 .catch((err: Error) => {
256 238 logger.debug('Cannot insert the video.', { error: err.stack })
257 ], function andFinally (err: Error, t: Sequelize.Transaction) { 239 throw err
258 if (err) {
259 // This is just a debug because we will retry the insert
260 logger.debug('Cannot insert the video.', { error: err })
261 return rollbackTransaction(err, t, finalCallback)
262 }
263
264 logger.info('Video with name %s created.', videoInfos.name)
265 return finalCallback(null)
266 }) 240 })
267} 241}
268 242
@@ -272,92 +246,75 @@ function updateVideoRetryWrapper (req: express.Request, res: express.Response, n
272 errorMessage: 'Cannot update the video with many retries.' 246 errorMessage: 'Cannot update the video with many retries.'
273 } 247 }
274 248
275 retryTransactionWrapper(updateVideo, options, function (err) { 249 retryTransactionWrapper(updateVideo, options)
276 if (err) return next(err) 250 .then(() => {
277 251 // TODO : include Location of the new video -> 201
278 // TODO : include Location of the new video -> 201 252 return res.type('json').status(204).end()
279 return res.type('json').status(204).end() 253 })
280 }) 254 .catch(err => next(err))
281} 255}
282 256
283function updateVideo (req: express.Request, res: express.Response, finalCallback: (err: Error) => void) { 257function updateVideo (req: express.Request, res: express.Response) {
284 const videoInstance = res.locals.video 258 const videoInstance = res.locals.video
285 const videoFieldsSave = videoInstance.toJSON() 259 const videoFieldsSave = videoInstance.toJSON()
286 const videoInfosToUpdate = req.body 260 const videoInfosToUpdate = req.body
287 261
288 waterfall([ 262 return db.sequelize.transaction(t => {
289 263 let tagsPromise: Promise<TagInstance[]>
290 startSerializableTransaction, 264 if (!videoInfosToUpdate.tags) {
291 265 tagsPromise = Promise.resolve(null)
292 function findOrCreateTags (t, callback) { 266 } else {
293 if (videoInfosToUpdate.tags) { 267 tagsPromise = db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t)
294 db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) { 268 }
295 return callback(err, t, tagInstances)
296 })
297 } else {
298 return callback(null, t, null)
299 }
300 },
301
302 function updateVideoIntoDB (t, tagInstances, callback) {
303 const options = {
304 transaction: t
305 }
306
307 if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name)
308 if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category)
309 if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence)
310 if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language)
311 if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw)
312 if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description)
313
314 videoInstance.save(options).asCallback(function (err) {
315 return callback(err, t, tagInstances)
316 })
317 },
318
319 function associateTagsToVideo (t, tagInstances, callback) {
320 if (tagInstances) {
321 const options = { transaction: t }
322
323 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
324 videoInstance.Tags = tagInstances
325 269
326 return callback(err, t) 270 return tagsPromise
327 }) 271 .then(tagInstances => {
328 } else { 272 const options = {
329 return callback(null, t) 273 transaction: t
330 } 274 }
331 },
332 275
333 function sendToFriends (t, callback) { 276 if (videoInfosToUpdate.name !== undefined) videoInstance.set('name', videoInfosToUpdate.name)
334 const json = videoInstance.toUpdateRemoteJSON() 277 if (videoInfosToUpdate.category !== undefined) videoInstance.set('category', videoInfosToUpdate.category)
278 if (videoInfosToUpdate.licence !== undefined) videoInstance.set('licence', videoInfosToUpdate.licence)
279 if (videoInfosToUpdate.language !== undefined) videoInstance.set('language', videoInfosToUpdate.language)
280 if (videoInfosToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfosToUpdate.nsfw)
281 if (videoInfosToUpdate.description !== undefined) videoInstance.set('description', videoInfosToUpdate.description)
335 282
336 // Now we'll update the video's meta data to our friends 283 return videoInstance.save(options).then(() => tagInstances)
337 updateVideoToFriends(json, t, function (err) {
338 return callback(err, t)
339 }) 284 })
340 }, 285 .then(tagInstances => {
341 286 if (!tagInstances) return
342 commitTransaction
343 287
344 ], function andFinally (err: Error, t: Sequelize.Transaction) { 288 const options = { transaction: t }
345 if (err) { 289 return videoInstance.setTags(tagInstances, options)
346 logger.debug('Cannot update the video.', { error: err }) 290 .then(() => {
291 videoInstance.Tags = tagInstances
347 292
348 // Force fields we want to update 293 return
349 // If the transaction is retried, sequelize will think the object has not changed 294 })
350 // So it will skip the SQL request, even if the last one was ROLLBACKed!
351 Object.keys(videoFieldsSave).forEach(function (key) {
352 const value = videoFieldsSave[key]
353 videoInstance.set(key, value)
354 }) 295 })
296 .then(() => {
297 const json = videoInstance.toUpdateRemoteJSON()
355 298
356 return rollbackTransaction(err, t, finalCallback) 299 // Now we'll update the video's meta data to our friends
357 } 300 return updateVideoToFriends(json, t)
358 301 })
302 })
303 .then(() => {
359 logger.info('Video with name %s updated.', videoInstance.name) 304 logger.info('Video with name %s updated.', videoInstance.name)
360 return finalCallback(null) 305 })
306 .catch(err => {
307 logger.debug('Cannot update the video.', { error: err })
308
309 // Force fields we want to update
310 // If the transaction is retried, sequelize will think the object has not changed
311 // So it will skip the SQL request, even if the last one was ROLLBACKed!
312 Object.keys(videoFieldsSave).forEach(function (key) {
313 const value = videoFieldsSave[key]
314 videoInstance.set(key, value)
315 })
316
317 throw err
361 }) 318 })
362} 319}
363 320
@@ -366,20 +323,17 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne
366 323
367 if (videoInstance.isOwned()) { 324 if (videoInstance.isOwned()) {
368 // The increment is done directly in the database, not using the instance value 325 // The increment is done directly in the database, not using the instance value
369 videoInstance.increment('views').asCallback(function (err) { 326 videoInstance.increment('views')
370 if (err) { 327 .then(() => {
371 logger.error('Cannot add view to video %d.', videoInstance.id) 328 // FIXME: make a real view system
372 return 329 // For example, only add a view when a user watch a video during 30s etc
373 } 330 const qaduParams = {
374 331 videoId: videoInstance.id,
375 // FIXME: make a real view system 332 type: REQUEST_VIDEO_QADU_TYPES.VIEWS
376 // For example, only add a view when a user watch a video during 30s etc 333 }
377 const qaduParams = { 334 return quickAndDirtyUpdateVideoToFriends(qaduParams)
378 videoId: videoInstance.id, 335 })
379 type: REQUEST_VIDEO_QADU_TYPES.VIEWS 336 .catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, { error: err }))
380 }
381 quickAndDirtyUpdateVideoToFriends(qaduParams)
382 })
383 } else { 337 } else {
384 // Just send the event to our friends 338 // Just send the event to our friends
385 const eventParams = { 339 const eventParams = {
@@ -394,33 +348,24 @@ function getVideo (req: express.Request, res: express.Response, next: express.Ne
394} 348}
395 349
396function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 350function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
397 db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { 351 db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
398 if (err) return next(err) 352 .then(result => res.json(getFormatedObjects(result.data, result.total)))
399 353 .catch(err => next(err))
400 res.json(getFormatedObjects(videosList, videosTotal))
401 })
402} 354}
403 355
404function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) { 356function removeVideo (req: express.Request, res: express.Response, next: express.NextFunction) {
405 const videoInstance = res.locals.video 357 const videoInstance = res.locals.video
406 358
407 videoInstance.destroy().asCallback(function (err) { 359 videoInstance.destroy()
408 if (err) { 360 .then(() => res.type('json').status(204).end())
361 .catch(err => {
409 logger.error('Errors when removed the video.', { error: err }) 362 logger.error('Errors when removed the video.', { error: err })
410 return next(err) 363 return next(err)
411 } 364 })
412
413 return res.type('json').status(204).end()
414 })
415} 365}
416 366
417function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 367function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
418 db.Video.searchAndPopulateAuthorAndPodAndTags( 368 db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort)
419 req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, 369 .then(result => res.json(getFormatedObjects(result.data, result.total)))
420 function (err, videosList, videosTotal) { 370 .catch(err => next(err))
421 if (err) return next(err)
422
423 res.json(getFormatedObjects(videosList, videosTotal))
424 }
425 )
426} 371}