Video model refractoring -> use mongoose api

This commit is contained in:
Chocobozzz 2016-06-24 17:42:51 +02:00
parent 07d9320387
commit aaf61f3810
15 changed files with 412 additions and 534 deletions

View File

@ -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')
})
// })
})
})
})

View 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)

View 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)
})
}

View 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)
}
}

View 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)
}
// ---------------------------------------------------------------------------

View 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

View 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')

View 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

View 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)
})

View File

@ -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)
}

View 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)
}

View 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()
})

314
server/models/video.js Normal file
View File

@ -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)
})
})
}

View File

@ -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)
})
}

View 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()
}