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.ts354
1 files changed, 146 insertions, 208 deletions
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index ec855ee8e..7ebbf4d6e 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -1,5 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Promise from 'bluebird'
3import * as multer from 'multer' 2import * as multer from 'multer'
4import { extname, join } from 'path' 3import { extname, join } from 'path'
5 4
@@ -30,7 +29,8 @@ import {
30 videosSearchValidator, 29 videosSearchValidator,
31 videosAddValidator, 30 videosAddValidator,
32 videosGetValidator, 31 videosGetValidator,
33 videosRemoveValidator 32 videosRemoveValidator,
33 asyncMiddleware
34} from '../../../middlewares' 34} from '../../../middlewares'
35import { 35import {
36 logger, 36 logger,
@@ -38,7 +38,8 @@ import {
38 generateRandomString, 38 generateRandomString,
39 getFormattedObjects, 39 getFormattedObjects,
40 renamePromise, 40 renamePromise,
41 getVideoFileHeight 41 getVideoFileHeight,
42 resetSequelizeInstance
42} from '../../../helpers' 43} from '../../../helpers'
43import { TagInstance, VideoInstance } from '../../../models' 44import { TagInstance, VideoInstance } from '../../../models'
44import { VideoCreate, VideoUpdate } from '../../../../shared' 45import { VideoCreate, VideoUpdate } from '../../../../shared'
@@ -88,18 +89,18 @@ videosRouter.get('/',
88 videosSortValidator, 89 videosSortValidator,
89 setVideosSort, 90 setVideosSort,
90 setPagination, 91 setPagination,
91 listVideos 92 asyncMiddleware(listVideos)
92) 93)
93videosRouter.put('/:id', 94videosRouter.put('/:id',
94 authenticate, 95 authenticate,
95 videosUpdateValidator, 96 videosUpdateValidator,
96 updateVideoRetryWrapper 97 asyncMiddleware(updateVideoRetryWrapper)
97) 98)
98videosRouter.post('/upload', 99videosRouter.post('/upload',
99 authenticate, 100 authenticate,
100 reqFiles, 101 reqFiles,
101 videosAddValidator, 102 videosAddValidator,
102 addVideoRetryWrapper 103 asyncMiddleware(addVideoRetryWrapper)
103) 104)
104videosRouter.get('/:id', 105videosRouter.get('/:id',
105 videosGetValidator, 106 videosGetValidator,
@@ -109,7 +110,7 @@ videosRouter.get('/:id',
109videosRouter.delete('/:id', 110videosRouter.delete('/:id',
110 authenticate, 111 authenticate,
111 videosRemoveValidator, 112 videosRemoveValidator,
112 removeVideoRetryWrapper 113 asyncMiddleware(removeVideoRetryWrapper)
113) 114)
114 115
115videosRouter.get('/search/:value', 116videosRouter.get('/search/:value',
@@ -119,7 +120,7 @@ videosRouter.get('/search/:value',
119 setVideosSort, 120 setVideosSort,
120 setPagination, 121 setPagination,
121 setVideosSearch, 122 setVideosSearch,
122 searchVideos 123 asyncMiddleware(searchVideos)
123) 124)
124 125
125// --------------------------------------------------------------------------- 126// ---------------------------------------------------------------------------
@@ -144,220 +145,157 @@ function listVideoLanguages (req: express.Request, res: express.Response) {
144 145
145// Wrapper to video add that retry the function if there is a database error 146// Wrapper to video add that retry the function if there is a database error
146// We need this because we run the transaction in SERIALIZABLE isolation that can fail 147// We need this because we run the transaction in SERIALIZABLE isolation that can fail
147function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 148async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
148 const options = { 149 const options = {
149 arguments: [ req, res, req.files['videofile'][0] ], 150 arguments: [ req, res, req.files['videofile'][0] ],
150 errorMessage: 'Cannot insert the video with many retries.' 151 errorMessage: 'Cannot insert the video with many retries.'
151 } 152 }
152 153
153 retryTransactionWrapper(addVideo, options) 154 await retryTransactionWrapper(addVideo, options)
154 .then(() => { 155
155 // TODO : include Location of the new video -> 201 156 // TODO : include Location of the new video -> 201
156 res.type('json').status(204).end() 157 res.type('json').status(204).end()
157 })
158 .catch(err => next(err))
159} 158}
160 159
161function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { 160async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
162 const videoInfo: VideoCreate = req.body 161 const videoInfo: VideoCreate = req.body
163 let videoUUID = '' 162 let videoUUID = ''
164 163
165 return db.sequelize.transaction(t => { 164 await db.sequelize.transaction(async t => {
166 let p: Promise<TagInstance[]> 165 const sequelizeOptions = { transaction: t }
167 166
168 if (!videoInfo.tags) p = Promise.resolve(undefined) 167 const videoData = {
169 else p = db.Tag.findOrCreateTags(videoInfo.tags, t) 168 name: videoInfo.name,
170 169 remote: false,
171 return p 170 extname: extname(videoPhysicalFile.filename),
172 .then(tagInstances => { 171 category: videoInfo.category,
173 const videoData = { 172 licence: videoInfo.licence,
174 name: videoInfo.name, 173 language: videoInfo.language,
175 remote: false, 174 nsfw: videoInfo.nsfw,
176 extname: extname(videoPhysicalFile.filename), 175 description: videoInfo.description,
177 category: videoInfo.category, 176 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
178 licence: videoInfo.licence, 177 channelId: res.locals.videoChannel.id
179 language: videoInfo.language, 178 }
180 nsfw: videoInfo.nsfw, 179 const video = db.Video.build(videoData)
181 description: videoInfo.description,
182 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
183 channelId: res.locals.videoChannel.id
184 }
185 180
186 const video = db.Video.build(videoData) 181 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
187 return { tagInstances, video } 182 const videoFileHeight = await getVideoFileHeight(videoFilePath)
188 })
189 .then(({ tagInstances, video }) => {
190 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
191 return getVideoFileHeight(videoFilePath)
192 .then(height => ({ tagInstances, video, videoFileHeight: height }))
193 })
194 .then(({ tagInstances, video, videoFileHeight }) => {
195 const videoFileData = {
196 extname: extname(videoPhysicalFile.filename),
197 resolution: videoFileHeight,
198 size: videoPhysicalFile.size
199 }
200 183
201 const videoFile = db.VideoFile.build(videoFileData) 184 const videoFileData = {
202 return { tagInstances, video, videoFile } 185 extname: extname(videoPhysicalFile.filename),
203 }) 186 resolution: videoFileHeight,
204 .then(({ tagInstances, video, videoFile }) => { 187 size: videoPhysicalFile.size
205 const videoDir = CONFIG.STORAGE.VIDEOS_DIR 188 }
206 const source = join(videoDir, videoPhysicalFile.filename) 189 const videoFile = db.VideoFile.build(videoFileData)
207 const destination = join(videoDir, video.getVideoFilename(videoFile)) 190 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
208 191 const source = join(videoDir, videoPhysicalFile.filename)
209 return renamePromise(source, destination) 192 const destination = join(videoDir, video.getVideoFilename(videoFile))
210 .then(() => { 193
211 // This is important in case if there is another attempt in the retry process 194 await renamePromise(source, destination)
212 videoPhysicalFile.filename = video.getVideoFilename(videoFile) 195 // This is important in case if there is another attempt in the retry process
213 return { tagInstances, video, videoFile } 196 videoPhysicalFile.filename = video.getVideoFilename(videoFile)
214 }) 197
215 }) 198 const tasks = []
216 .then(({ tagInstances, video, videoFile }) => { 199
217 const tasks = [] 200 tasks.push(
218 201 video.createTorrentAndSetInfoHash(videoFile),
219 tasks.push( 202 video.createThumbnail(videoFile),
220 video.createTorrentAndSetInfoHash(videoFile), 203 video.createPreview(videoFile)
221 video.createThumbnail(videoFile), 204 )
222 video.createPreview(videoFile) 205
223 ) 206 if (CONFIG.TRANSCODING.ENABLED === true) {
224 207 // Put uuid because we don't have id auto incremented for now
225 if (CONFIG.TRANSCODING.ENABLED === true) { 208 const dataInput = {
226 // Put uuid because we don't have id auto incremented for now 209 videoUUID: video.uuid
227 const dataInput = { 210 }
228 videoUUID: video.uuid 211
229 } 212 tasks.push(
230 213 JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput)
231 tasks.push( 214 )
232 JobScheduler.Instance.createJob(t, 'videoFileOptimizer', dataInput) 215 }
233 ) 216 await Promise.all(tasks)
234 }
235 217
236 return Promise.all(tasks).then(() => ({ tagInstances, video, videoFile })) 218 const videoCreated = await video.save(sequelizeOptions)
237 }) 219 // Do not forget to add video channel information to the created video
238 .then(({ tagInstances, video, videoFile }) => { 220 videoCreated.VideoChannel = res.locals.videoChannel
239 const options = { transaction: t } 221 videoUUID = videoCreated.uuid
240 222
241 return video.save(options) 223 videoFile.videoId = video.id
242 .then(videoCreated => {
243 // Do not forget to add video channel information to the created video
244 videoCreated.VideoChannel = res.locals.videoChannel
245 videoUUID = videoCreated.uuid
246 224
247 return { tagInstances, video: videoCreated, videoFile } 225 await videoFile.save(sequelizeOptions)
248 }) 226 video.VideoFiles = [videoFile]
249 })
250 .then(({ tagInstances, video, videoFile }) => {
251 const options = { transaction: t }
252 videoFile.videoId = video.id
253 227
254 return videoFile.save(options) 228 if (videoInfo.tags) {
255 .then(() => video.VideoFiles = [ videoFile ]) 229 const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t)
256 .then(() => ({ tagInstances, video })) 230
257 }) 231 await video.setTags(tagInstances, sequelizeOptions)
258 .then(({ tagInstances, video }) => { 232 video.Tags = tagInstances
259 if (!tagInstances) return video 233 }
260 234
261 const options = { transaction: t } 235 // Let transcoding job send the video to friends because the video file extension might change
262 return video.setTags(tagInstances, options) 236 if (CONFIG.TRANSCODING.ENABLED === true) return undefined
263 .then(() => { 237
264 video.Tags = tagInstances 238 const remoteVideo = await video.toAddRemoteJSON()
265 return video 239 // Now we'll add the video's meta data to our friends
266 }) 240 return addVideoToFriends(remoteVideo, t)
267 })
268 .then(video => {
269 // Let transcoding job send the video to friends because the video file extension might change
270 if (CONFIG.TRANSCODING.ENABLED === true) return undefined
271
272 return video.toAddRemoteJSON()
273 .then(remoteVideo => {
274 // Now we'll add the video's meta data to our friends
275 return addVideoToFriends(remoteVideo, t)
276 })
277 })
278 })
279 .then(() => logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID))
280 .catch((err: Error) => {
281 logger.debug('Cannot insert the video.', err)
282 throw err
283 }) 241 })
242
243 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID)
284} 244}
285 245
286function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 246async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
287 const options = { 247 const options = {
288 arguments: [ req, res ], 248 arguments: [ req, res ],
289 errorMessage: 'Cannot update the video with many retries.' 249 errorMessage: 'Cannot update the video with many retries.'
290 } 250 }
291 251
292 retryTransactionWrapper(updateVideo, options) 252 await retryTransactionWrapper(updateVideo, options)
293 .then(() => { 253
294 return res.type('json').status(204).end() 254 return res.type('json').status(204).end()
295 })
296 .catch(err => next(err))
297} 255}
298 256
299function updateVideo (req: express.Request, res: express.Response) { 257async function updateVideo (req: express.Request, res: express.Response) {
300 const videoInstance = res.locals.video 258 const videoInstance = res.locals.video
301 const videoFieldsSave = videoInstance.toJSON() 259 const videoFieldsSave = videoInstance.toJSON()
302 const videoInfoToUpdate: VideoUpdate = req.body 260 const videoInfoToUpdate: VideoUpdate = req.body
303 261
304 return db.sequelize.transaction(t => { 262 try {
305 let tagsPromise: Promise<TagInstance[]> 263 await db.sequelize.transaction(async t => {
306 if (!videoInfoToUpdate.tags) { 264 const sequelizeOptions = {
307 tagsPromise = Promise.resolve(null) 265 transaction: t
308 } else { 266 }
309 tagsPromise = db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
310 }
311 267
312 return tagsPromise 268 if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
313 .then(tagInstances => { 269 if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
314 const options = { 270 if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
315 transaction: t 271 if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
316 } 272 if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
273 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
317 274
318 if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name) 275 await videoInstance.save(sequelizeOptions)
319 if (videoInfoToUpdate.category !== undefined) videoInstance.set('category', videoInfoToUpdate.category)
320 if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
321 if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
322 if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
323 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
324 276
325 return videoInstance.save(options).then(() => tagInstances) 277 if (videoInfoToUpdate.tags) {
326 }) 278 const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t)
327 .then(tagInstances => {
328 if (!tagInstances) return
329 279
330 const options = { transaction: t } 280 await videoInstance.setTags(tagInstances, sequelizeOptions)
331 return videoInstance.setTags(tagInstances, options) 281 videoInstance.Tags = tagInstances
332 .then(() => { 282 }
333 videoInstance.Tags = tagInstances
334 283
335 return 284 const json = videoInstance.toUpdateRemoteJSON()
336 })
337 })
338 .then(() => {
339 const json = videoInstance.toUpdateRemoteJSON()
340 285
341 // Now we'll update the video's meta data to our friends 286 // Now we'll update the video's meta data to our friends
342 return updateVideoToFriends(json, t) 287 return updateVideoToFriends(json, t)
343 }) 288 })
344 })
345 .then(() => {
346 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
347 })
348 .catch(err => {
349 logger.debug('Cannot update the video.', err)
350 289
290 logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
291 } catch (err) {
351 // Force fields we want to update 292 // Force fields we want to update
352 // If the transaction is retried, sequelize will think the object has not changed 293 // If the transaction is retried, sequelize will think the object has not changed
353 // So it will skip the SQL request, even if the last one was ROLLBACKed! 294 // So it will skip the SQL request, even if the last one was ROLLBACKed!
354 Object.keys(videoFieldsSave).forEach(key => { 295 resetSequelizeInstance(videoInstance, videoFieldsSave)
355 const value = videoFieldsSave[key]
356 videoInstance.set(key, value)
357 })
358 296
359 throw err 297 throw err
360 }) 298 }
361} 299}
362 300
363function getVideo (req: express.Request, res: express.Response) { 301function getVideo (req: express.Request, res: express.Response) {
@@ -365,17 +303,17 @@ function getVideo (req: express.Request, res: express.Response) {
365 303
366 if (videoInstance.isOwned()) { 304 if (videoInstance.isOwned()) {
367 // The increment is done directly in the database, not using the instance value 305 // The increment is done directly in the database, not using the instance value
306 // FIXME: make a real view system
307 // For example, only add a view when a user watch a video during 30s etc
368 videoInstance.increment('views') 308 videoInstance.increment('views')
369 .then(() => { 309 .then(() => {
370 // FIXME: make a real view system
371 // For example, only add a view when a user watch a video during 30s etc
372 const qaduParams = { 310 const qaduParams = {
373 videoId: videoInstance.id, 311 videoId: videoInstance.id,
374 type: REQUEST_VIDEO_QADU_TYPES.VIEWS 312 type: REQUEST_VIDEO_QADU_TYPES.VIEWS
375 } 313 }
376 return quickAndDirtyUpdateVideoToFriends(qaduParams) 314 return quickAndDirtyUpdateVideoToFriends(qaduParams)
377 }) 315 })
378 .catch(err => logger.error('Cannot add view to video %d.', videoInstance.id, err)) 316 .catch(err => logger.error('Cannot add view to video %s.', videoInstance.uuid, err))
379 } else { 317 } else {
380 // Just send the event to our friends 318 // Just send the event to our friends
381 const eventParams = { 319 const eventParams = {
@@ -383,48 +321,48 @@ function getVideo (req: express.Request, res: express.Response) {
383 type: REQUEST_VIDEO_EVENT_TYPES.VIEWS 321 type: REQUEST_VIDEO_EVENT_TYPES.VIEWS
384 } 322 }
385 addEventToRemoteVideo(eventParams) 323 addEventToRemoteVideo(eventParams)
324 .catch(err => logger.error('Cannot add event to remote video %s.', videoInstance.uuid, err))
386 } 325 }
387 326
388 // Do not wait the view system 327 // Do not wait the view system
389 res.json(videoInstance.toFormattedDetailsJSON()) 328 return res.json(videoInstance.toFormattedDetailsJSON())
390} 329}
391 330
392function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 331async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
393 db.Video.listForApi(req.query.start, req.query.count, req.query.sort) 332 const resultList = await db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
394 .then(result => res.json(getFormattedObjects(result.data, result.total))) 333
395 .catch(err => next(err)) 334 return res.json(getFormattedObjects(resultList.data, resultList.total))
396} 335}
397 336
398function removeVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 337async function removeVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
399 const options = { 338 const options = {
400 arguments: [ req, res ], 339 arguments: [ req, res ],
401 errorMessage: 'Cannot remove the video with many retries.' 340 errorMessage: 'Cannot remove the video with many retries.'
402 } 341 }
403 342
404 retryTransactionWrapper(removeVideo, options) 343 await retryTransactionWrapper(removeVideo, options)
405 .then(() => { 344
406 return res.type('json').status(204).end() 345 return res.type('json').status(204).end()
407 })
408 .catch(err => next(err))
409} 346}
410 347
411function removeVideo (req: express.Request, res: express.Response) { 348async function removeVideo (req: express.Request, res: express.Response) {
412 const videoInstance: VideoInstance = res.locals.video 349 const videoInstance: VideoInstance = res.locals.video
413 350
414 return db.sequelize.transaction(t => { 351 await db.sequelize.transaction(async t => {
415 return videoInstance.destroy({ transaction: t }) 352 await videoInstance.destroy({ transaction: t })
416 })
417 .then(() => {
418 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
419 })
420 .catch(err => {
421 logger.error('Errors when removed the video.', err)
422 throw err
423 }) 353 })
354
355 logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid)
424} 356}
425 357
426function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 358async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
427 db.Video.searchAndPopulateAuthorAndPodAndTags(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort) 359 const resultList = await db.Video.searchAndPopulateAuthorAndPodAndTags(
428 .then(result => res.json(getFormattedObjects(result.data, result.total))) 360 req.params.value,
429 .catch(err => next(err)) 361 req.query.field,
362 req.query.start,
363 req.query.count,
364 req.query.sort
365 )
366
367 return res.json(getFormattedObjects(resultList.data, resultList.total))
430} 368}