]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Video model refractoring -> use mongoose api
authorChocobozzz <florian.bigard@gmail.com>
Fri, 24 Jun 2016 15:42:51 +0000 (17:42 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Fri, 24 Jun 2016 15:42:51 +0000 (17:42 +0200)
15 files changed:
server.js
server/controllers/api/v1/pods.js
server/controllers/api/v1/remote.js
server/controllers/api/v1/videos.js
server/helpers/customValidators.js
server/initializers/constants.js
server/initializers/database.js
server/lib/friends.js
server/lib/requestsScheduler.js
server/lib/videos.js [deleted file]
server/middlewares/reqValidators/remote.js
server/middlewares/reqValidators/videos.js
server/models/video.js [new file with mode: 0644]
server/models/videos.js [deleted file]
server/tests/api/multiplePods.js

index 4c8e8cfd39d03b18614e57fb56a1c1d143582871..63aeb7145959348f3e406cae86cfa70132616e58 100644 (file)
--- a/server.js
+++ b/server.js
@@ -31,7 +31,6 @@ const logger = require('./server/helpers/logger')
 const poolRequests = require('./server/lib/requestsScheduler')
 const routes = require('./server/controllers')
 const utils = require('./server/helpers/utils')
-const videos = require('./server/lib/videos')
 const webtorrent = require('./server/lib/webtorrent')
 
 // Get configurations
@@ -138,11 +137,11 @@ installer.installApplication(function (err) {
       // Activate the pool requests
       poolRequests.activate()
 
-      videos.seedAllExisting(function () {
+      // videos.seedAllExisting(function () {
         logger.info('Seeded all the videos')
         logger.info('Server listening on port %d', port)
         app.emit('ready')
-      })
+      // })
     })
   })
 })
index 881b2090d2599e7c49305cc03bf84733555e4943..9dd9197b3be46e76306770b33875026a598bb3dd 100644 (file)
@@ -2,6 +2,7 @@
 
 const async = require('async')
 const express = require('express')
+const mongoose = require('mongoose')
 
 const logger = require('../../../helpers/logger')
 const friends = require('../../../lib/friends')
@@ -10,10 +11,9 @@ const Pods = require('../../../models/pods')
 const oAuth2 = middlewares.oauth2
 const reqValidator = middlewares.reqValidators.pods
 const signatureValidator = middlewares.reqValidators.remote.signature
-const videos = require('../../../lib/videos')
-const Videos = require('../../../models/videos')
 
 const router = express.Router()
+const Video = mongoose.model('Video')
 
 router.get('/', listPodsUrl)
 router.post('/', reqValidator.podsAdd, addPods)
@@ -86,7 +86,7 @@ function removePods (req, res, next) {
     },
 
     function (callback) {
-      Videos.listFromUrl(url, function (err, videosList) {
+      Video.listByUrls([ url ], function (err, videosList) {
         if (err) {
           logger.error('Cannot list videos from url.', { error: err })
           return callback(err)
@@ -97,14 +97,9 @@ function removePods (req, res, next) {
     },
 
     function removeTheRemoteVideos (videosList, callback) {
-      videos.removeRemoteVideos(videosList, function (err) {
-        if (err) {
-          logger.error('Cannot remove remote videos.', { error: err })
-          return callback(err)
-        }
-
-        return callback(null)
-      })
+      async.each(videosList, function (video, callbackEach) {
+        video.remove(callbackEach)
+      }, callback)
     }
   ], function (err) {
     if (err) return next(err)
index ced8470d74dd960ef9fe69ea88ea2bbd90214b47..2d71c605d9852e4623b92bf17b7d4a361baf2895 100644 (file)
@@ -2,15 +2,15 @@
 
 const async = require('async')
 const express = require('express')
+const mongoose = require('mongoose')
 
 const middlewares = require('../../../middlewares')
 const secureMiddleware = middlewares.secure
 const reqValidator = middlewares.reqValidators.remote
 const logger = require('../../../helpers/logger')
-const Videos = require('../../../models/videos')
-const videos = require('../../../lib/videos')
 
 const router = express.Router()
+const Video = mongoose.model('Video')
 
 router.post('/videos',
   reqValidator.signature,
@@ -33,48 +33,39 @@ function remoteVideos (req, res, next) {
   // We need to process in the same order to keep consistency
   // TODO: optimization
   async.eachSeries(requests, function (request, callbackEach) {
-    const video = request.data
+    const videoData = request.data
 
     if (request.type === 'add') {
-      addRemoteVideo(video, callbackEach)
+      addRemoteVideo(videoData, callbackEach)
     } else if (request.type === 'remove') {
-      removeRemoteVideo(video, fromUrl, callbackEach)
+      removeRemoteVideo(videoData, fromUrl, callbackEach)
     }
+  }, function (err) {
+    if (err) logger.error('Error managing remote videos.', { error: err })
   })
 
   // We don't need to keep the other pod waiting
   return res.type('json').status(204).end()
 }
 
-function addRemoteVideo (videoToCreate, callback) {
-  videos.createRemoteVideos([ videoToCreate ], function (err, remoteVideos) {
-    if (err) {
-      logger.error('Cannot create remote videos.', { error: err })
-      // Don't break the process
-    }
+function addRemoteVideo (videoToCreateData, callback) {
+  // Mongoose pre hook will automatically create the thumbnail on disk
+  videoToCreateData.thumbnail = videoToCreateData.thumbnailBase64
 
-    return callback()
-  })
+  const video = new Video(videoToCreateData)
+  video.save(callback)
 }
 
-function removeRemoteVideo (videoToRemove, fromUrl, callback) {
-  const magnetUris = [ videoToRemove.magnetUri ]
-
+function removeRemoteVideo (videoToRemoveData, fromUrl, callback) {
   // We need the list because we have to remove some other stuffs (thumbnail etc)
-  Videos.listFromUrlAndMagnets(fromUrl, magnetUris, function (err, videosList) {
+  Video.listByUrlAndMagnet(fromUrl, videoToRemoveData.magnetUri, function (err, videosList) {
     if (err) {
       logger.error('Cannot list videos from url and magnets.', { error: err })
-      // Don't break the process
-      return callback()
+      return callback(err)
     }
 
-    videos.removeRemoteVideos(videosList, function (err) {
-      if (err) {
-        logger.error('Cannot remove remote videos.', { error: err })
-        // Don't break the process
-      }
-
-      return callback()
-    })
+    async.each(videosList, function (video, callbackEach) {
+      video.remove(callbackEach)
+    }, callback)
   })
 }
index 2edb3112273d2e63eb65fb98ef026b56a96b1157..83734b35e7581f55481a1a009ae07acbe4c747ea 100644 (file)
@@ -3,9 +3,9 @@
 const async = require('async')
 const config = require('config')
 const express = require('express')
+const mongoose = require('mongoose')
 const multer = require('multer')
 
-const constants = require('../../../initializers/constants')
 const logger = require('../../../helpers/logger')
 const friends = require('../../../lib/friends')
 const middlewares = require('../../../middlewares')
@@ -18,12 +18,10 @@ const reqValidatorVideos = reqValidator.videos
 const search = middlewares.search
 const sort = middlewares.sort
 const utils = require('../../../helpers/utils')
-const Videos = require('../../../models/videos') // model
-const videos = require('../../../lib/videos')
-const webtorrent = require('../../../lib/webtorrent')
 
 const router = express.Router()
 const uploads = config.get('storage.uploads')
+const Video = mongoose.model('Video')
 
 // multer configuration
 const storage = multer.diskStorage({
@@ -88,55 +86,27 @@ function addVideo (req, res, next) {
   const videoInfos = req.body
 
   async.waterfall([
-    function seedTheVideo (callback) {
-      videos.seed(videoFile.path, callback)
-    },
-
-    function createThumbnail (torrent, callback) {
-      videos.createVideoThumbnail(videoFile.path, function (err, thumbnailName) {
-        if (err) {
-          // TODO: unseed the video
-          logger.error('Cannot make a thumbnail of the video file.')
-          return callback(err)
-        }
-
-        callback(null, torrent, thumbnailName)
-      })
-    },
 
-    function insertIntoDB (torrent, thumbnailName, callback) {
+    function insertIntoDB (callback) {
       const videoData = {
         name: videoInfos.name,
         namePath: videoFile.filename,
         description: videoInfos.description,
-        magnetUri: torrent.magnetURI,
         author: res.locals.oauth.token.user.username,
         duration: videoFile.duration,
-        thumbnail: thumbnailName,
         tags: videoInfos.tags
       }
 
-      Videos.add(videoData, function (err, insertedVideo) {
-        if (err) {
-          // TODO unseed the video
-          // TODO remove thumbnail
-          logger.error('Cannot insert this video in the database.')
-          return callback(err)
-        }
-
-        return callback(null, insertedVideo)
+      const video = new Video(videoData)
+      video.save(function (err, video) {
+        // Assert there are only one argument sent to the next function (video)
+        return callback(err, video)
       })
     },
 
-    function sendToFriends (insertedVideo, callback) {
-      videos.convertVideoToRemote(insertedVideo, function (err, remoteVideo) {
-        if (err) {
-          // TODO unseed the video
-          // TODO remove thumbnail
-          // TODO delete from DB
-          logger.error('Cannot convert video to remote.')
-          return callback(err)
-        }
+    function sendToFriends (video, callback) {
+      video.toRemoteJSON(function (err, remoteVideo) {
+        if (err) return callback(err)
 
         // Now we'll add the video's meta data to our friends
         friends.addVideoToFriends(remoteVideo)
@@ -147,6 +117,9 @@ function addVideo (req, res, next) {
 
   ], function andFinally (err) {
     if (err) {
+      // TODO unseed the video
+      // TODO remove thumbnail
+      // TODO delete from DB
       logger.error('Cannot insert the video.')
       return next(err)
     }
@@ -157,23 +130,22 @@ function addVideo (req, res, next) {
 }
 
 function getVideo (req, res, next) {
-  Videos.get(req.params.id, function (err, videoObj) {
+  Video.load(req.params.id, function (err, video) {
     if (err) return next(err)
 
-    const state = videos.getVideoState(videoObj)
-    if (state.exist === false) {
+    if (!video) {
       return res.type('json').status(204).end()
     }
 
-    res.json(getFormatedVideo(videoObj))
+    res.json(video.toFormatedJSON())
   })
 }
 
 function listVideos (req, res, next) {
-  Videos.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, totalVideos) {
+  Video.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
     if (err) return next(err)
 
-    res.json(getFormatedVideos(videosList, totalVideos))
+    res.json(getFormatedVideos(videosList, videosTotal))
   })
 }
 
@@ -182,31 +154,17 @@ function removeVideo (req, res, next) {
 
   async.waterfall([
     function getVideo (callback) {
-      Videos.get(videoId, callback)
-    },
-
-    function removeVideoTorrent (video, callback) {
-      removeTorrent(video.magnetUri, function () {
-        return callback(null, video)
-      })
+      Video.load(videoId, callback)
     },
 
     function removeFromDB (video, callback) {
-      Videos.removeOwned(req.params.id, function (err) {
+      video.remove(function (err) {
         if (err) return callback(err)
 
         return callback(null, video)
       })
     },
 
-    function removeVideoData (video, callback) {
-      videos.removeVideosDataFromDisk([ video ], function (err) {
-        if (err) logger.error('Cannot remove video data from disk.', { video: video })
-
-        return callback(null, video)
-      })
-    },
-
     function sendInformationToFriends (video, callback) {
       const params = {
         name: video.name,
@@ -228,53 +186,25 @@ function removeVideo (req, res, next) {
 }
 
 function searchVideos (req, res, next) {
-  Videos.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
-  function (err, videosList, totalVideos) {
+  Video.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
+  function (err, videosList, videosTotal) {
     if (err) return next(err)
 
-    res.json(getFormatedVideos(videosList, totalVideos))
+    res.json(getFormatedVideos(videosList, videosTotal))
   })
 }
 
 // ---------------------------------------------------------------------------
 
-function getFormatedVideo (videoObj) {
-  const formatedVideo = {
-    id: videoObj._id,
-    name: videoObj.name,
-    description: videoObj.description,
-    podUrl: videoObj.podUrl.replace(/^https?:\/\//, ''),
-    isLocal: videos.getVideoState(videoObj).owned,
-    magnetUri: videoObj.magnetUri,
-    author: videoObj.author,
-    duration: videoObj.duration,
-    tags: videoObj.tags,
-    thumbnailPath: constants.THUMBNAILS_STATIC_PATH + '/' + videoObj.thumbnail,
-    createdDate: videoObj.createdDate
-  }
-
-  return formatedVideo
-}
-
-function getFormatedVideos (videosObj, totalVideos) {
+function getFormatedVideos (videos, videosTotal) {
   const formatedVideos = []
 
-  videosObj.forEach(function (videoObj) {
-    formatedVideos.push(getFormatedVideo(videoObj))
+  videos.forEach(function (video) {
+    formatedVideos.push(video.toFormatedJSON())
   })
 
   return {
-    total: totalVideos,
+    total: videosTotal,
     data: formatedVideos
   }
 }
-
-// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
-function removeTorrent (magnetUri, callback) {
-  try {
-    webtorrent.remove(magnetUri, callback)
-  } catch (err) {
-    logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
-    return callback(null)
-  }
-}
index a6cf680e509ed3be62f3a85cd522b2c9a5a939f0..4d6139a3d4d0b3ce98ded0b037d537d25cb51340 100644 (file)
@@ -17,7 +17,8 @@ const customValidators = {
   isVideoNameValid: isVideoNameValid,
   isVideoPodUrlValid: isVideoPodUrlValid,
   isVideoTagsValid: isVideoTagsValid,
-  isVideoThumbnailValid: isVideoThumbnailValid
+  isVideoThumbnailValid: isVideoThumbnailValid,
+  isVideoThumbnail64Valid: isVideoThumbnail64Valid
 }
 
 function exists (value) {
@@ -37,7 +38,7 @@ function isEachRemoteVideosValid (requests) {
       isVideoNameValid(video.name) &&
       isVideoPodUrlValid(video.podUrl) &&
       isVideoTagsValid(video.tags) &&
-      isVideoThumbnailValid(video.thumbnailBase64)
+      isVideoThumbnail64Valid(video.thumbnailBase64)
     ) ||
     (
       isRequestTypeRemoveValid(request.type) &&
@@ -97,8 +98,12 @@ function isVideoTagsValid (tags) {
 }
 
 function isVideoThumbnailValid (value) {
+  return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL)
+}
+
+function isVideoThumbnail64Valid (value) {
   return validator.isBase64(value) &&
-         validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL)
+         validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL64)
 }
 
 // ---------------------------------------------------------------------------
index caeb340cffffaa7d002979b8a1feb715c032ab23..1f9876c4b94ef5fc3ed1270f1c123b97679fe317 100644 (file)
@@ -48,7 +48,8 @@ const VIDEOS_CONSTRAINTS_FIELDS = {
   AUTHOR: { min: 3, max: 20 }, // Length
   TAGS: { min: 1, max: 3 }, // Number of total tags
   TAG: { min: 2, max: 10 }, // Length
-  THUMBNAIL: { min: 0, max: 20000 } // Bytes
+  THUMBNAIL: { min: 2, max: 30 },
+  THUMBNAIL64: { min: 0, max: 20000 } // Bytes
 }
 
 // Special constants for a test instance
index 830cc7dd8e455287e51f5af0be7cd0000745380c..5932a978bc0a05a43e0e9218b76b7e44cd869338 100644 (file)
@@ -5,6 +5,9 @@ const mongoose = require('mongoose')
 
 const logger = require('../helpers/logger')
 
+// Bootstrap models
+require('../models/video')
+
 const dbname = 'peertube' + config.get('database.suffix')
 const host = config.get('database.host')
 const port = config.get('database.port')
index d81a603ad5267e4007cd139c6e54923ade9a8e36..91cd69f8611c34d308207efcae361c0f5e734007 100644 (file)
@@ -3,6 +3,7 @@
 const async = require('async')
 const config = require('config')
 const fs = require('fs')
+const mongoose = require('mongoose')
 const request = require('request')
 
 const constants = require('../initializers/constants')
@@ -11,12 +12,11 @@ const peertubeCrypto = require('../helpers/peertubeCrypto')
 const Pods = require('../models/pods')
 const requestsScheduler = require('../lib/requestsScheduler')
 const requests = require('../helpers/requests')
-const videos = require('../lib/videos')
-const Videos = require('../models/videos')
 
 const http = config.get('webserver.https') ? 'https' : 'http'
 const host = config.get('webserver.host')
 const port = config.get('webserver.port')
+const Video = mongoose.model('Video')
 
 const pods = {
   addVideoToFriends: addVideoToFriends,
@@ -117,18 +117,13 @@ function quitFriends (callback) {
     function listRemoteVideos (callbackAsync) {
       logger.info('Broke friends, so sad :(')
 
-      Videos.listFromRemotes(callbackAsync)
+      Video.listRemotes(callbackAsync)
     },
 
     function removeTheRemoteVideos (videosList, callbackAsync) {
-      videos.removeRemoteVideos(videosList, function (err) {
-        if (err) {
-          logger.error('Cannot remove remote videos.', { error: err })
-          return callbackAsync(err)
-        }
-
-        return callbackAsync(null)
-      })
+      async.each(videosList, function (video, callbackEach) {
+        video.remove(callbackEach)
+      }, callbackAsync)
     }
   ], function (err) {
     // Don't forget to re activate the scheduler, even if there was an error
@@ -146,14 +141,14 @@ function removeVideoToFriends (video) {
 }
 
 function sendOwnedVideosToPod (podId) {
-  Videos.listOwned(function (err, videosList) {
+  Video.listOwned(function (err, videosList) {
     if (err) {
       logger.error('Cannot get the list of videos we own.')
       return
     }
 
     videosList.forEach(function (video) {
-      videos.convertVideoToRemote(video, function (err, remoteVideo) {
+      video.toRemoteJSON(function (err, remoteVideo) {
         if (err) {
           logger.error('Cannot convert video to remote.', { error: err })
           // Don't break the process
index ac75e5b93646983ba2e74c7d4ea54aa47445fa15..b192d8299c0866c89258c985e1d0c733c7e90b67 100644 (file)
@@ -2,14 +2,15 @@
 
 const async = require('async')
 const map = require('lodash/map')
+const mongoose = require('mongoose')
 
 const constants = require('../initializers/constants')
 const logger = require('../helpers/logger')
 const Pods = require('../models/pods')
 const Requests = require('../models/requests')
 const requests = require('../helpers/requests')
-const videos = require('../lib/videos')
-const Videos = require('../models/videos')
+
+const Video = mongoose.model('Video')
 
 let timer = null
 
@@ -210,7 +211,7 @@ function removeBadPods () {
       const urls = map(pods, 'url')
       const ids = map(pods, '_id')
 
-      Videos.listFromUrls(urls, function (err, videosList) {
+      Video.listByUrls(urls, function (err, videosList) {
         if (err) {
           logger.error('Cannot list videos urls.', { error: err, urls: urls })
           return callback(null, ids, [])
@@ -224,9 +225,14 @@ function removeBadPods () {
       // We don't have to remove pods, skip
       if (typeof podIds === 'function') return podIds(null)
 
-      // Remove the remote videos
-      videos.removeRemoteVideos(videosList, function (err) {
-        if (err) logger.error('Cannot remove remote videos.', { error: err })
+      async.each(videosList, function (video, callbackEach) {
+        video.remove(callbackEach)
+      }, function (err) {
+        if (err) {
+          // Don't stop the process
+          logger.error('Error while removing videos of bad pods.', { error: err })
+          return
+        }
 
         return callback(null, podIds)
       })
diff --git a/server/lib/videos.js b/server/lib/videos.js
deleted file mode 100644 (file)
index a74c77d..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-'use strict'
-
-const async = require('async')
-const config = require('config')
-const ffmpeg = require('fluent-ffmpeg')
-const fs = require('fs')
-const map = require('lodash/map')
-const pathUtils = require('path')
-
-const constants = require('../initializers/constants')
-const logger = require('../helpers/logger')
-const utils = require('../helpers/utils')
-const Videos = require('../models/videos')
-const webtorrent = require('../lib/webtorrent')
-
-const uploadDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads'))
-const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails'))
-
-const videos = {
-  convertVideoToRemote: convertVideoToRemote,
-  createRemoteVideos: createRemoteVideos,
-  getVideoDuration: getVideoDuration,
-  getVideoState: getVideoState,
-  createVideoThumbnail: createVideoThumbnail,
-  removeVideosDataFromDisk: removeVideosDataFromDisk,
-  removeRemoteVideos: removeRemoteVideos,
-  seed: seed,
-  seedAllExisting: seedAllExisting
-}
-
-function convertVideoToRemote (video, callback) {
-  fs.readFile(thumbnailsDir + video.thumbnail, function (err, thumbnailData) {
-    if (err) {
-      logger.error('Cannot read the thumbnail of the video')
-      return callback(err)
-    }
-
-    const remoteVideo = {
-      name: video.name,
-      description: video.description,
-      magnetUri: video.magnetUri,
-      author: video.author,
-      duration: video.duration,
-      thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
-      tags: video.tags,
-      createdDate: video.createdDate,
-      podUrl: video.podUrl
-    }
-
-    return callback(null, remoteVideo)
-  })
-}
-
-function createRemoteVideos (videos, callback) {
-  // Create the remote videos from the new pod
-  createRemoteVideoObjects(videos, function (err, remoteVideos) {
-    if (err) return callback(err)
-
-    Videos.addRemotes(remoteVideos, callback)
-  })
-}
-
-function getVideoDuration (videoPath, callback) {
-  ffmpeg.ffprobe(videoPath, function (err, metadata) {
-    if (err) return callback(err)
-
-    return callback(null, Math.floor(metadata.format.duration))
-  })
-}
-
-function getVideoState (video) {
-  const exist = (video !== null)
-  let owned = false
-  if (exist === true) {
-    owned = (video.namePath !== null)
-  }
-
-  return { exist: exist, owned: owned }
-}
-
-function createVideoThumbnail (videoPath, callback) {
-  const filename = pathUtils.basename(videoPath) + '.jpg'
-  ffmpeg(videoPath)
-    .on('error', callback)
-    .on('end', function () {
-      callback(null, filename)
-    })
-    .thumbnail({
-      count: 1,
-      folder: thumbnailsDir,
-      size: constants.THUMBNAILS_SIZE,
-      filename: filename
-    })
-}
-
-// Remove video datas from disk (video file, thumbnail...)
-function removeVideosDataFromDisk (videos, callback) {
-  async.each(videos, function (video, callbackEach) {
-    fs.unlink(thumbnailsDir + video.thumbnail, function (err) {
-      if (err) logger.error('Cannot remove the video thumbnail')
-
-      if (getVideoState(video).owned === true) {
-        fs.unlink(uploadDir + video.namePath, function (err) {
-          if (err) {
-            logger.error('Cannot remove this video file.')
-            return callbackEach(err)
-          }
-
-          callbackEach(null)
-        })
-      } else {
-        callbackEach(null)
-      }
-    })
-  }, callback)
-}
-
-function removeRemoteVideos (videos, callback) {
-  Videos.removeByIds(map(videos, '_id'), function (err) {
-    if (err) return callback(err)
-
-    removeVideosDataFromDisk(videos, callback)
-  })
-}
-
-function seed (path, callback) {
-  logger.info('Seeding %s...', path)
-
-  webtorrent.seed(path, function (torrent) {
-    logger.info('%s seeded (%s).', path, torrent.magnetURI)
-
-    return callback(null, torrent)
-  })
-}
-
-function seedAllExisting (callback) {
-  Videos.listOwned(function (err, videosList) {
-    if (err) {
-      logger.error('Cannot get list of the videos to seed.')
-      return callback(err)
-    }
-
-    async.each(videosList, function (video, callbackEach) {
-      seed(uploadDir + video.namePath, function (err) {
-        if (err) {
-          logger.error('Cannot seed this video.')
-          return callback(err)
-        }
-
-        callbackEach(null)
-      })
-    }, callback)
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = videos
-
-// ---------------------------------------------------------------------------
-
-function createRemoteVideoObjects (videos, callback) {
-  const remoteVideos = []
-
-  async.each(videos, function (video, callbackEach) {
-    // Creating the thumbnail for this remote video
-    utils.generateRandomString(16, function (err, randomString) {
-      if (err) return callbackEach(err)
-
-      const thumbnailName = randomString + '.jpg'
-      createThumbnailFromBase64(thumbnailName, video.thumbnailBase64, function (err) {
-        if (err) return callbackEach(err)
-
-        const params = {
-          name: video.name,
-          description: video.description,
-          magnetUri: video.magnetUri,
-          podUrl: video.podUrl,
-          duration: video.duration,
-          thumbnail: thumbnailName,
-          tags: video.tags,
-          author: video.author
-        }
-        remoteVideos.push(params)
-
-        callbackEach(null)
-      })
-    })
-  },
-  function (err) {
-    if (err) return callback(err)
-
-    callback(null, remoteVideos)
-  })
-}
-
-function createThumbnailFromBase64 (thumbnailName, data, callback) {
-  fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, callback)
-}
index a23673d89aee0bf8a169c4de3cb153465d0fb240..dd8ee5f6e346a36bba077db81b3cbe2a090f3e6a 100644 (file)
@@ -22,7 +22,7 @@ function remoteVideos (req, res, next) {
   req.checkBody('data').isArray()
   req.checkBody('data').isEachRemoteVideosValid()
 
-  logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body })
+  logger.debug('Checking remoteVideos parameters', { parameters: req.body })
 
   checkErrors(req, res, next)
 }
index f31fd93a21c798c7a431f73526d4fbd6427a9ef1..452fbc859e0686873ac740f722590f222a726a95 100644 (file)
@@ -1,11 +1,13 @@
 'use strict'
 
+const mongoose = require('mongoose')
+
 const checkErrors = require('./utils').checkErrors
 const constants = require('../../initializers/constants')
 const customValidators = require('../../helpers/customValidators')
 const logger = require('../../helpers/logger')
-const videos = require('../../lib/videos')
-const Videos = require('../../models/videos')
+
+const Video = mongoose.model('Video')
 
 const reqValidatorsVideos = {
   videosAdd: videosAdd,
@@ -26,7 +28,7 @@ function videosAdd (req, res, next) {
   checkErrors(req, res, function () {
     const videoFile = req.files.videofile[0]
 
-    videos.getVideoDuration(videoFile.path, function (err, duration) {
+    Video.getDurationFromFile(videoFile.path, function (err, duration) {
       if (err) {
         return res.status(400).send('Cannot retrieve metadata of the file.')
       }
@@ -47,14 +49,13 @@ function videosGet (req, res, next) {
   logger.debug('Checking videosGet parameters', { parameters: req.params })
 
   checkErrors(req, res, function () {
-    Videos.get(req.params.id, function (err, video) {
+    Video.load(req.params.id, function (err, video) {
       if (err) {
         logger.error('Error in videosGet request validator.', { error: err })
         return res.sendStatus(500)
       }
 
-      const state = videos.getVideoState(video)
-      if (state.exist === false) return res.status(404).send('Video not found')
+      if (!video) return res.status(404).send('Video not found')
 
       next()
     })
@@ -67,15 +68,14 @@ function videosRemove (req, res, next) {
   logger.debug('Checking videosRemove parameters', { parameters: req.params })
 
   checkErrors(req, res, function () {
-    Videos.get(req.params.id, function (err, video) {
+    Video.load(req.params.id, function (err, video) {
       if (err) {
         logger.error('Error in videosRemove request validator.', { error: err })
         return res.sendStatus(500)
       }
 
-      const state = videos.getVideoState(video)
-      if (state.exist === false) return res.status(404).send('Video not found')
-      else if (state.owned === false) return res.status(403).send('Cannot remove video of another pod')
+      if (!video) return res.status(404).send('Video not found')
+      else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod')
 
       next()
     })
diff --git a/server/models/video.js b/server/models/video.js
new file mode 100644 (file)
index 0000000..8b14e9b
--- /dev/null
@@ -0,0 +1,314 @@
+'use strict'
+
+const async = require('async')
+const config = require('config')
+const ffmpeg = require('fluent-ffmpeg')
+const fs = require('fs')
+const pathUtils = require('path')
+const mongoose = require('mongoose')
+
+const constants = require('../initializers/constants')
+const customValidators = require('../helpers/customValidators')
+const logger = require('../helpers/logger')
+const utils = require('../helpers/utils')
+const webtorrent = require('../lib/webtorrent')
+
+const http = config.get('webserver.https') === true ? 'https' : 'http'
+const host = config.get('webserver.host')
+const port = config.get('webserver.port')
+const uploadsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads'))
+const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails'))
+
+// ---------------------------------------------------------------------------
+
+// TODO: add indexes on searchable columns
+const VideoSchema = mongoose.Schema({
+  name: String,
+  namePath: String,
+  description: String,
+  magnetUri: String,
+  podUrl: String,
+  author: String,
+  duration: Number,
+  thumbnail: String,
+  tags: [ String ],
+  createdDate: {
+    type: Date,
+    default: Date.now
+  }
+})
+
+VideoSchema.path('name').validate(customValidators.isVideoNameValid)
+VideoSchema.path('description').validate(customValidators.isVideoDescriptionValid)
+VideoSchema.path('magnetUri').validate(customValidators.isVideoMagnetUriValid)
+VideoSchema.path('podUrl').validate(customValidators.isVideoPodUrlValid)
+VideoSchema.path('author').validate(customValidators.isVideoAuthorValid)
+VideoSchema.path('duration').validate(customValidators.isVideoDurationValid)
+// The tumbnail can be the path or the data in base 64
+// The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
+VideoSchema.path('thumbnail').validate(function (value) {
+  return customValidators.isVideoThumbnailValid(value) || customValidators.isVideoThumbnail64Valid(value)
+})
+VideoSchema.path('tags').validate(customValidators.isVideoTagsValid)
+
+VideoSchema.methods = {
+  isOwned: isOwned,
+  toFormatedJSON: toFormatedJSON,
+  toRemoteJSON: toRemoteJSON
+}
+
+VideoSchema.statics = {
+  getDurationFromFile: getDurationFromFile,
+  list: list,
+  listByUrlAndMagnet: listByUrlAndMagnet,
+  listByUrls: listByUrls,
+  listOwned: listOwned,
+  listRemotes: listRemotes,
+  load: load,
+  search: search,
+  seedAllExisting: seedAllExisting
+}
+
+VideoSchema.pre('remove', function (next) {
+  const video = this
+  const tasks = []
+
+  tasks.push(
+    function (callback) {
+      removeThumbnail(video, callback)
+    }
+  )
+
+  if (video.isOwned()) {
+    tasks.push(
+      function (callback) {
+        removeFile(video, callback)
+      },
+      function (callback) {
+        removeTorrent(video, callback)
+      }
+    )
+  }
+
+  async.parallel(tasks, next)
+})
+
+VideoSchema.pre('save', function (next) {
+  const video = this
+  const tasks = []
+
+  if (video.isOwned()) {
+    const videoPath = pathUtils.join(uploadsDir, video.namePath)
+    this.podUrl = http + '://' + host + ':' + port
+
+    tasks.push(
+      function (callback) {
+        seed(videoPath, callback)
+      },
+      function (callback) {
+        createThumbnail(videoPath, callback)
+      }
+    )
+
+    async.parallel(tasks, function (err, results) {
+      if (err) return next(err)
+
+      video.magnetUri = results[0].magnetURI
+      video.thumbnail = results[1]
+
+      return next()
+    })
+  } else {
+    generateThumbnailFromBase64(video.thumbnail, function (err, thumbnailName) {
+      if (err) return next(err)
+
+      video.thumbnail = thumbnailName
+
+      return next()
+    })
+  }
+})
+
+mongoose.model('Video', VideoSchema)
+
+// ------------------------------ METHODS ------------------------------
+
+function isOwned () {
+  return this.namePath !== null
+}
+
+function toFormatedJSON () {
+  const json = {
+    id: this._id,
+    name: this.name,
+    description: this.description,
+    podUrl: this.podUrl.replace(/^https?:\/\//, ''),
+    isLocal: this.isOwned(),
+    magnetUri: this.magnetUri,
+    author: this.author,
+    duration: this.duration,
+    tags: this.tags,
+    thumbnailPath: constants.THUMBNAILS_STATIC_PATH + '/' + this.thumbnail,
+    createdDate: this.createdDate
+  }
+
+  return json
+}
+
+function toRemoteJSON (callback) {
+  const self = this
+
+  // Convert thumbnail to base64
+  fs.readFile(pathUtils.join(thumbnailsDir, this.thumbnail), function (err, thumbnailData) {
+    if (err) {
+      logger.error('Cannot read the thumbnail of the video')
+      return callback(err)
+    }
+
+    const remoteVideo = {
+      name: self.name,
+      description: self.description,
+      magnetUri: self.magnetUri,
+      namePath: null,
+      author: self.author,
+      duration: self.duration,
+      thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
+      tags: self.tags,
+      createdDate: self.createdDate,
+      podUrl: self.podUrl
+    }
+
+    return callback(null, remoteVideo)
+  })
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function getDurationFromFile (videoPath, callback) {
+  ffmpeg.ffprobe(videoPath, function (err, metadata) {
+    if (err) return callback(err)
+
+    return callback(null, Math.floor(metadata.format.duration))
+  })
+}
+
+function list (start, count, sort, callback) {
+  const query = {}
+  return findWithCount.call(this, query, start, count, sort, callback)
+}
+
+function listByUrlAndMagnet (fromUrl, magnetUri, callback) {
+  this.find({ podUrl: fromUrl, magnetUri: magnetUri }, callback)
+}
+
+function listByUrls (fromUrls, callback) {
+  this.find({ podUrl: { $in: fromUrls } }, callback)
+}
+
+function listOwned (callback) {
+  // If namePath is not null this is *our* video
+  this.find({ namePath: { $ne: null } }, callback)
+}
+
+function listRemotes (callback) {
+  this.find({ namePath: null }, callback)
+}
+
+function load (id, callback) {
+  this.findById(id, callback)
+}
+
+function search (value, field, start, count, sort, callback) {
+  const query = {}
+  // Make an exact search with the magnet
+  if (field === 'magnetUri' || field === 'tags') {
+    query[field] = value
+  } else {
+    query[field] = new RegExp(value)
+  }
+
+  findWithCount.call(this, query, start, count, sort, callback)
+}
+
+// TODO
+function seedAllExisting () {
+
+}
+
+// ---------------------------------------------------------------------------
+
+function findWithCount (query, start, count, sort, callback) {
+  const self = this
+
+  async.parallel([
+    function (asyncCallback) {
+      self.find(query).skip(start).limit(start + count).sort(sort).exec(asyncCallback)
+    },
+    function (asyncCallback) {
+      self.count(query, asyncCallback)
+    }
+  ], function (err, results) {
+    if (err) return callback(err)
+
+    const videos = results[0]
+    const totalVideos = results[1]
+    return callback(null, videos, totalVideos)
+  })
+}
+
+function removeThumbnail (video, callback) {
+  fs.unlink(thumbnailsDir + video.thumbnail, callback)
+}
+
+function removeFile (video, callback) {
+  fs.unlink(uploadsDir + video.namePath, callback)
+}
+
+// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
+function removeTorrent (video, callback) {
+  try {
+    webtorrent.remove(video.magnetUri, callback)
+  } catch (err) {
+    logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
+    return callback(null)
+  }
+}
+
+function createThumbnail (videoPath, callback) {
+  const filename = pathUtils.basename(videoPath) + '.jpg'
+  ffmpeg(videoPath)
+    .on('error', callback)
+    .on('end', function () {
+      callback(null, filename)
+    })
+    .thumbnail({
+      count: 1,
+      folder: thumbnailsDir,
+      size: constants.THUMBNAILS_SIZE,
+      filename: filename
+    })
+}
+
+function seed (path, callback) {
+  logger.info('Seeding %s...', path)
+
+  webtorrent.seed(path, function (torrent) {
+    logger.info('%s seeded (%s).', path, torrent.magnetURI)
+
+    return callback(null, torrent)
+  })
+}
+
+function generateThumbnailFromBase64 (data, callback) {
+  // Creating the thumbnail for this remote video
+  utils.generateRandomString(16, function (err, randomString) {
+    if (err) return callback(err)
+
+    const thumbnailName = randomString + '.jpg'
+    fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, function (err) {
+      if (err) return callback(err)
+
+      return callback(null, thumbnailName)
+    })
+  })
+}
diff --git a/server/models/videos.js b/server/models/videos.js
deleted file mode 100644 (file)
index c177b41..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-'use strict'
-
-const async = require('async')
-const config = require('config')
-const mongoose = require('mongoose')
-
-const logger = require('../helpers/logger')
-
-const http = config.get('webserver.https') === true ? 'https' : 'http'
-const host = config.get('webserver.host')
-const port = config.get('webserver.port')
-
-// ---------------------------------------------------------------------------
-
-// TODO: add indexes on searchable columns
-const videosSchema = mongoose.Schema({
-  name: String,
-  namePath: String,
-  description: String,
-  magnetUri: String,
-  podUrl: String,
-  author: String,
-  duration: Number,
-  thumbnail: String,
-  tags: [ String ],
-  createdDate: {
-    type: Date,
-    default: Date.now
-  }
-})
-const VideosDB = mongoose.model('videos', videosSchema)
-
-// ---------------------------------------------------------------------------
-
-const Videos = {
-  add: add,
-  addRemotes: addRemotes,
-  get: get,
-  list: list,
-  listFromUrl: listFromUrl,
-  listFromUrls: listFromUrls,
-  listFromUrlAndMagnets: listFromUrlAndMagnets,
-  listFromRemotes: listFromRemotes,
-  listOwned: listOwned,
-  removeOwned: removeOwned,
-  removeByIds: removeByIds,
-  search: search
-}
-
-function add (video, callback) {
-  logger.info('Adding %s video to database.', video.name)
-
-  const params = video
-  params.podUrl = http + '://' + host + ':' + port
-
-  VideosDB.create(params, function (err, insertedVideo) {
-    if (err) {
-      logger.error('Cannot insert this video into database.')
-      return callback(err)
-    }
-
-    callback(null, insertedVideo)
-  })
-}
-
-function addRemotes (videos, callback) {
-  videos.forEach(function (video) {
-    // Ensure they are remote videos
-    video.namePath = null
-  })
-
-  VideosDB.create(videos, callback)
-}
-
-function get (id, callback) {
-  VideosDB.findById(id, function (err, video) {
-    if (err) {
-      logger.error('Cannot get this video.')
-      return callback(err)
-    }
-
-    return callback(null, video)
-  })
-}
-
-function list (start, count, sort, callback) {
-  const query = {}
-  return findWithCount(query, start, count, sort, callback)
-}
-
-function listFromUrl (fromUrl, callback) {
-  VideosDB.find({ podUrl: fromUrl }, callback)
-}
-
-function listFromUrls (fromUrls, callback) {
-  VideosDB.find({ podUrl: { $in: fromUrls } }, callback)
-}
-
-function listFromUrlAndMagnets (fromUrl, magnets, callback) {
-  VideosDB.find({ podUrl: fromUrl, magnetUri: { $in: magnets } }, callback)
-}
-
-function listFromRemotes (callback) {
-  VideosDB.find({ namePath: null }, callback)
-}
-
-function listOwned (callback) {
-  // If namePath is not null this is *our* video
-  VideosDB.find({ namePath: { $ne: null } }, function (err, videosList) {
-    if (err) {
-      logger.error('Cannot get the list of owned videos.')
-      return callback(err)
-    }
-
-    return callback(null, videosList)
-  })
-}
-
-// Return the video in the callback
-function removeOwned (id, callback) {
-  VideosDB.findByIdAndRemove(id, callback)
-}
-
-// Use the magnet Uri because the _id field is not the same on different servers
-function removeByIds (ids, callback) {
-  VideosDB.remove({ _id: { $in: ids } }, callback)
-}
-
-function search (value, field, start, count, sort, callback) {
-  const query = {}
-  // Make an exact search with the magnet
-  if (field === 'magnetUri' || field === 'tags') {
-    query[field] = value
-  } else {
-    query[field] = new RegExp(value)
-  }
-
-  findWithCount(query, start, count, sort, callback)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = Videos
-
-// ---------------------------------------------------------------------------
-
-function findWithCount (query, start, count, sort, callback) {
-  async.parallel([
-    function (asyncCallback) {
-      VideosDB.find(query).skip(start).limit(start + count).sort(sort).exec(asyncCallback)
-    },
-    function (asyncCallback) {
-      VideosDB.count(query, asyncCallback)
-    }
-  ], function (err, results) {
-    if (err) return callback(err)
-
-    const videos = results[0]
-    const totalVideos = results[1]
-    return callback(null, videos, totalVideos)
-  })
-}
index 2a1bc64e601b2381f7c4492cdb9d59a1a516d6a9..52dfda137fb06e8ab52c35fe17bccc04fddd39ae 100644 (file)
@@ -414,7 +414,7 @@ describe('Test multiple pods', function () {
 
     // Keep the logs if the test failed
     if (this.ok) {
-      utils.flushTests(done)
+      // utils.flushTests(done)
     } else {
       done()
     }