aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/v1/pods.js17
-rw-r--r--server/controllers/api/v1/remote.js45
-rw-r--r--server/controllers/api/v1/videos.js124
-rw-r--r--server/helpers/customValidators.js11
-rw-r--r--server/initializers/constants.js3
-rw-r--r--server/initializers/database.js3
-rw-r--r--server/lib/friends.js21
-rw-r--r--server/lib/requestsScheduler.js18
-rw-r--r--server/lib/videos.js199
-rw-r--r--server/middlewares/reqValidators/remote.js2
-rw-r--r--server/middlewares/reqValidators/videos.js20
-rw-r--r--server/models/video.js314
-rw-r--r--server/models/videos.js162
-rw-r--r--server/tests/api/multiplePods.js2
14 files changed, 410 insertions, 531 deletions
diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js
index 881b2090d..9dd9197b3 100644
--- a/server/controllers/api/v1/pods.js
+++ b/server/controllers/api/v1/pods.js
@@ -2,6 +2,7 @@
2 2
3const async = require('async') 3const async = require('async')
4const express = require('express') 4const express = require('express')
5const mongoose = require('mongoose')
5 6
6const logger = require('../../../helpers/logger') 7const logger = require('../../../helpers/logger')
7const friends = require('../../../lib/friends') 8const friends = require('../../../lib/friends')
@@ -10,10 +11,9 @@ const Pods = require('../../../models/pods')
10const oAuth2 = middlewares.oauth2 11const oAuth2 = middlewares.oauth2
11const reqValidator = middlewares.reqValidators.pods 12const reqValidator = middlewares.reqValidators.pods
12const signatureValidator = middlewares.reqValidators.remote.signature 13const signatureValidator = middlewares.reqValidators.remote.signature
13const videos = require('../../../lib/videos')
14const Videos = require('../../../models/videos')
15 14
16const router = express.Router() 15const router = express.Router()
16const Video = mongoose.model('Video')
17 17
18router.get('/', listPodsUrl) 18router.get('/', listPodsUrl)
19router.post('/', reqValidator.podsAdd, addPods) 19router.post('/', reqValidator.podsAdd, addPods)
@@ -86,7 +86,7 @@ function removePods (req, res, next) {
86 }, 86 },
87 87
88 function (callback) { 88 function (callback) {
89 Videos.listFromUrl(url, function (err, videosList) { 89 Video.listByUrls([ url ], function (err, videosList) {
90 if (err) { 90 if (err) {
91 logger.error('Cannot list videos from url.', { error: err }) 91 logger.error('Cannot list videos from url.', { error: err })
92 return callback(err) 92 return callback(err)
@@ -97,14 +97,9 @@ function removePods (req, res, next) {
97 }, 97 },
98 98
99 function removeTheRemoteVideos (videosList, callback) { 99 function removeTheRemoteVideos (videosList, callback) {
100 videos.removeRemoteVideos(videosList, function (err) { 100 async.each(videosList, function (video, callbackEach) {
101 if (err) { 101 video.remove(callbackEach)
102 logger.error('Cannot remove remote videos.', { error: err }) 102 }, callback)
103 return callback(err)
104 }
105
106 return callback(null)
107 })
108 } 103 }
109 ], function (err) { 104 ], function (err) {
110 if (err) return next(err) 105 if (err) return next(err)
diff --git a/server/controllers/api/v1/remote.js b/server/controllers/api/v1/remote.js
index ced8470d7..2d71c605d 100644
--- a/server/controllers/api/v1/remote.js
+++ b/server/controllers/api/v1/remote.js
@@ -2,15 +2,15 @@
2 2
3const async = require('async') 3const async = require('async')
4const express = require('express') 4const express = require('express')
5const mongoose = require('mongoose')
5 6
6const middlewares = require('../../../middlewares') 7const middlewares = require('../../../middlewares')
7const secureMiddleware = middlewares.secure 8const secureMiddleware = middlewares.secure
8const reqValidator = middlewares.reqValidators.remote 9const reqValidator = middlewares.reqValidators.remote
9const logger = require('../../../helpers/logger') 10const logger = require('../../../helpers/logger')
10const Videos = require('../../../models/videos')
11const videos = require('../../../lib/videos')
12 11
13const router = express.Router() 12const router = express.Router()
13const Video = mongoose.model('Video')
14 14
15router.post('/videos', 15router.post('/videos',
16 reqValidator.signature, 16 reqValidator.signature,
@@ -33,48 +33,39 @@ function remoteVideos (req, res, next) {
33 // We need to process in the same order to keep consistency 33 // We need to process in the same order to keep consistency
34 // TODO: optimization 34 // TODO: optimization
35 async.eachSeries(requests, function (request, callbackEach) { 35 async.eachSeries(requests, function (request, callbackEach) {
36 const video = request.data 36 const videoData = request.data
37 37
38 if (request.type === 'add') { 38 if (request.type === 'add') {
39 addRemoteVideo(video, callbackEach) 39 addRemoteVideo(videoData, callbackEach)
40 } else if (request.type === 'remove') { 40 } else if (request.type === 'remove') {
41 removeRemoteVideo(video, fromUrl, callbackEach) 41 removeRemoteVideo(videoData, fromUrl, callbackEach)
42 } 42 }
43 }, function (err) {
44 if (err) logger.error('Error managing remote videos.', { error: err })
43 }) 45 })
44 46
45 // We don't need to keep the other pod waiting 47 // We don't need to keep the other pod waiting
46 return res.type('json').status(204).end() 48 return res.type('json').status(204).end()
47} 49}
48 50
49function addRemoteVideo (videoToCreate, callback) { 51function addRemoteVideo (videoToCreateData, callback) {
50 videos.createRemoteVideos([ videoToCreate ], function (err, remoteVideos) { 52 // Mongoose pre hook will automatically create the thumbnail on disk
51 if (err) { 53 videoToCreateData.thumbnail = videoToCreateData.thumbnailBase64
52 logger.error('Cannot create remote videos.', { error: err })
53 // Don't break the process
54 }
55 54
56 return callback() 55 const video = new Video(videoToCreateData)
57 }) 56 video.save(callback)
58} 57}
59 58
60function removeRemoteVideo (videoToRemove, fromUrl, callback) { 59function removeRemoteVideo (videoToRemoveData, fromUrl, callback) {
61 const magnetUris = [ videoToRemove.magnetUri ]
62
63 // We need the list because we have to remove some other stuffs (thumbnail etc) 60 // We need the list because we have to remove some other stuffs (thumbnail etc)
64 Videos.listFromUrlAndMagnets(fromUrl, magnetUris, function (err, videosList) { 61 Video.listByUrlAndMagnet(fromUrl, videoToRemoveData.magnetUri, function (err, videosList) {
65 if (err) { 62 if (err) {
66 logger.error('Cannot list videos from url and magnets.', { error: err }) 63 logger.error('Cannot list videos from url and magnets.', { error: err })
67 // Don't break the process 64 return callback(err)
68 return callback()
69 } 65 }
70 66
71 videos.removeRemoteVideos(videosList, function (err) { 67 async.each(videosList, function (video, callbackEach) {
72 if (err) { 68 video.remove(callbackEach)
73 logger.error('Cannot remove remote videos.', { error: err }) 69 }, callback)
74 // Don't break the process
75 }
76
77 return callback()
78 })
79 }) 70 })
80} 71}
diff --git a/server/controllers/api/v1/videos.js b/server/controllers/api/v1/videos.js
index 2edb31122..83734b35e 100644
--- a/server/controllers/api/v1/videos.js
+++ b/server/controllers/api/v1/videos.js
@@ -3,9 +3,9 @@
3const async = require('async') 3const async = require('async')
4const config = require('config') 4const config = require('config')
5const express = require('express') 5const express = require('express')
6const mongoose = require('mongoose')
6const multer = require('multer') 7const multer = require('multer')
7 8
8const constants = require('../../../initializers/constants')
9const logger = require('../../../helpers/logger') 9const logger = require('../../../helpers/logger')
10const friends = require('../../../lib/friends') 10const friends = require('../../../lib/friends')
11const middlewares = require('../../../middlewares') 11const middlewares = require('../../../middlewares')
@@ -18,12 +18,10 @@ const reqValidatorVideos = reqValidator.videos
18const search = middlewares.search 18const search = middlewares.search
19const sort = middlewares.sort 19const sort = middlewares.sort
20const utils = require('../../../helpers/utils') 20const utils = require('../../../helpers/utils')
21const Videos = require('../../../models/videos') // model
22const videos = require('../../../lib/videos')
23const webtorrent = require('../../../lib/webtorrent')
24 21
25const router = express.Router() 22const router = express.Router()
26const uploads = config.get('storage.uploads') 23const uploads = config.get('storage.uploads')
24const Video = mongoose.model('Video')
27 25
28// multer configuration 26// multer configuration
29const storage = multer.diskStorage({ 27const storage = multer.diskStorage({
@@ -88,55 +86,27 @@ function addVideo (req, res, next) {
88 const videoInfos = req.body 86 const videoInfos = req.body
89 87
90 async.waterfall([ 88 async.waterfall([
91 function seedTheVideo (callback) {
92 videos.seed(videoFile.path, callback)
93 },
94
95 function createThumbnail (torrent, callback) {
96 videos.createVideoThumbnail(videoFile.path, function (err, thumbnailName) {
97 if (err) {
98 // TODO: unseed the video
99 logger.error('Cannot make a thumbnail of the video file.')
100 return callback(err)
101 }
102
103 callback(null, torrent, thumbnailName)
104 })
105 },
106 89
107 function insertIntoDB (torrent, thumbnailName, callback) { 90 function insertIntoDB (callback) {
108 const videoData = { 91 const videoData = {
109 name: videoInfos.name, 92 name: videoInfos.name,
110 namePath: videoFile.filename, 93 namePath: videoFile.filename,
111 description: videoInfos.description, 94 description: videoInfos.description,
112 magnetUri: torrent.magnetURI,
113 author: res.locals.oauth.token.user.username, 95 author: res.locals.oauth.token.user.username,
114 duration: videoFile.duration, 96 duration: videoFile.duration,
115 thumbnail: thumbnailName,
116 tags: videoInfos.tags 97 tags: videoInfos.tags
117 } 98 }
118 99
119 Videos.add(videoData, function (err, insertedVideo) { 100 const video = new Video(videoData)
120 if (err) { 101 video.save(function (err, video) {
121 // TODO unseed the video 102 // Assert there are only one argument sent to the next function (video)
122 // TODO remove thumbnail 103 return callback(err, video)
123 logger.error('Cannot insert this video in the database.')
124 return callback(err)
125 }
126
127 return callback(null, insertedVideo)
128 }) 104 })
129 }, 105 },
130 106
131 function sendToFriends (insertedVideo, callback) { 107 function sendToFriends (video, callback) {
132 videos.convertVideoToRemote(insertedVideo, function (err, remoteVideo) { 108 video.toRemoteJSON(function (err, remoteVideo) {
133 if (err) { 109 if (err) return callback(err)
134 // TODO unseed the video
135 // TODO remove thumbnail
136 // TODO delete from DB
137 logger.error('Cannot convert video to remote.')
138 return callback(err)
139 }
140 110
141 // Now we'll add the video's meta data to our friends 111 // Now we'll add the video's meta data to our friends
142 friends.addVideoToFriends(remoteVideo) 112 friends.addVideoToFriends(remoteVideo)
@@ -147,6 +117,9 @@ function addVideo (req, res, next) {
147 117
148 ], function andFinally (err) { 118 ], function andFinally (err) {
149 if (err) { 119 if (err) {
120 // TODO unseed the video
121 // TODO remove thumbnail
122 // TODO delete from DB
150 logger.error('Cannot insert the video.') 123 logger.error('Cannot insert the video.')
151 return next(err) 124 return next(err)
152 } 125 }
@@ -157,23 +130,22 @@ function addVideo (req, res, next) {
157} 130}
158 131
159function getVideo (req, res, next) { 132function getVideo (req, res, next) {
160 Videos.get(req.params.id, function (err, videoObj) { 133 Video.load(req.params.id, function (err, video) {
161 if (err) return next(err) 134 if (err) return next(err)
162 135
163 const state = videos.getVideoState(videoObj) 136 if (!video) {
164 if (state.exist === false) {
165 return res.type('json').status(204).end() 137 return res.type('json').status(204).end()
166 } 138 }
167 139
168 res.json(getFormatedVideo(videoObj)) 140 res.json(video.toFormatedJSON())
169 }) 141 })
170} 142}
171 143
172function listVideos (req, res, next) { 144function listVideos (req, res, next) {
173 Videos.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, totalVideos) { 145 Video.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
174 if (err) return next(err) 146 if (err) return next(err)
175 147
176 res.json(getFormatedVideos(videosList, totalVideos)) 148 res.json(getFormatedVideos(videosList, videosTotal))
177 }) 149 })
178} 150}
179 151
@@ -182,31 +154,17 @@ function removeVideo (req, res, next) {
182 154
183 async.waterfall([ 155 async.waterfall([
184 function getVideo (callback) { 156 function getVideo (callback) {
185 Videos.get(videoId, callback) 157 Video.load(videoId, callback)
186 },
187
188 function removeVideoTorrent (video, callback) {
189 removeTorrent(video.magnetUri, function () {
190 return callback(null, video)
191 })
192 }, 158 },
193 159
194 function removeFromDB (video, callback) { 160 function removeFromDB (video, callback) {
195 Videos.removeOwned(req.params.id, function (err) { 161 video.remove(function (err) {
196 if (err) return callback(err) 162 if (err) return callback(err)
197 163
198 return callback(null, video) 164 return callback(null, video)
199 }) 165 })
200 }, 166 },
201 167
202 function removeVideoData (video, callback) {
203 videos.removeVideosDataFromDisk([ video ], function (err) {
204 if (err) logger.error('Cannot remove video data from disk.', { video: video })
205
206 return callback(null, video)
207 })
208 },
209
210 function sendInformationToFriends (video, callback) { 168 function sendInformationToFriends (video, callback) {
211 const params = { 169 const params = {
212 name: video.name, 170 name: video.name,
@@ -228,53 +186,25 @@ function removeVideo (req, res, next) {
228} 186}
229 187
230function searchVideos (req, res, next) { 188function searchVideos (req, res, next) {
231 Videos.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, 189 Video.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
232 function (err, videosList, totalVideos) { 190 function (err, videosList, videosTotal) {
233 if (err) return next(err) 191 if (err) return next(err)
234 192
235 res.json(getFormatedVideos(videosList, totalVideos)) 193 res.json(getFormatedVideos(videosList, videosTotal))
236 }) 194 })
237} 195}
238 196
239// --------------------------------------------------------------------------- 197// ---------------------------------------------------------------------------
240 198
241function getFormatedVideo (videoObj) { 199function getFormatedVideos (videos, videosTotal) {
242 const formatedVideo = {
243 id: videoObj._id,
244 name: videoObj.name,
245 description: videoObj.description,
246 podUrl: videoObj.podUrl.replace(/^https?:\/\//, ''),
247 isLocal: videos.getVideoState(videoObj).owned,
248 magnetUri: videoObj.magnetUri,
249 author: videoObj.author,
250 duration: videoObj.duration,
251 tags: videoObj.tags,
252 thumbnailPath: constants.THUMBNAILS_STATIC_PATH + '/' + videoObj.thumbnail,
253 createdDate: videoObj.createdDate
254 }
255
256 return formatedVideo
257}
258
259function getFormatedVideos (videosObj, totalVideos) {
260 const formatedVideos = [] 200 const formatedVideos = []
261 201
262 videosObj.forEach(function (videoObj) { 202 videos.forEach(function (video) {
263 formatedVideos.push(getFormatedVideo(videoObj)) 203 formatedVideos.push(video.toFormatedJSON())
264 }) 204 })
265 205
266 return { 206 return {
267 total: totalVideos, 207 total: videosTotal,
268 data: formatedVideos 208 data: formatedVideos
269 } 209 }
270} 210}
271
272// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
273function removeTorrent (magnetUri, callback) {
274 try {
275 webtorrent.remove(magnetUri, callback)
276 } catch (err) {
277 logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
278 return callback(null)
279 }
280}
diff --git a/server/helpers/customValidators.js b/server/helpers/customValidators.js
index a6cf680e5..4d6139a3d 100644
--- a/server/helpers/customValidators.js
+++ b/server/helpers/customValidators.js
@@ -17,7 +17,8 @@ const customValidators = {
17 isVideoNameValid: isVideoNameValid, 17 isVideoNameValid: isVideoNameValid,
18 isVideoPodUrlValid: isVideoPodUrlValid, 18 isVideoPodUrlValid: isVideoPodUrlValid,
19 isVideoTagsValid: isVideoTagsValid, 19 isVideoTagsValid: isVideoTagsValid,
20 isVideoThumbnailValid: isVideoThumbnailValid 20 isVideoThumbnailValid: isVideoThumbnailValid,
21 isVideoThumbnail64Valid: isVideoThumbnail64Valid
21} 22}
22 23
23function exists (value) { 24function exists (value) {
@@ -37,7 +38,7 @@ function isEachRemoteVideosValid (requests) {
37 isVideoNameValid(video.name) && 38 isVideoNameValid(video.name) &&
38 isVideoPodUrlValid(video.podUrl) && 39 isVideoPodUrlValid(video.podUrl) &&
39 isVideoTagsValid(video.tags) && 40 isVideoTagsValid(video.tags) &&
40 isVideoThumbnailValid(video.thumbnailBase64) 41 isVideoThumbnail64Valid(video.thumbnailBase64)
41 ) || 42 ) ||
42 ( 43 (
43 isRequestTypeRemoveValid(request.type) && 44 isRequestTypeRemoveValid(request.type) &&
@@ -97,8 +98,12 @@ function isVideoTagsValid (tags) {
97} 98}
98 99
99function isVideoThumbnailValid (value) { 100function isVideoThumbnailValid (value) {
101 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL)
102}
103
104function isVideoThumbnail64Valid (value) {
100 return validator.isBase64(value) && 105 return validator.isBase64(value) &&
101 validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL) 106 validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL64)
102} 107}
103 108
104// --------------------------------------------------------------------------- 109// ---------------------------------------------------------------------------
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
index caeb340cf..1f9876c4b 100644
--- a/server/initializers/constants.js
+++ b/server/initializers/constants.js
@@ -48,7 +48,8 @@ const VIDEOS_CONSTRAINTS_FIELDS = {
48 AUTHOR: { min: 3, max: 20 }, // Length 48 AUTHOR: { min: 3, max: 20 }, // Length
49 TAGS: { min: 1, max: 3 }, // Number of total tags 49 TAGS: { min: 1, max: 3 }, // Number of total tags
50 TAG: { min: 2, max: 10 }, // Length 50 TAG: { min: 2, max: 10 }, // Length
51 THUMBNAIL: { min: 0, max: 20000 } // Bytes 51 THUMBNAIL: { min: 2, max: 30 },
52 THUMBNAIL64: { min: 0, max: 20000 } // Bytes
52} 53}
53 54
54// Special constants for a test instance 55// Special constants for a test instance
diff --git a/server/initializers/database.js b/server/initializers/database.js
index 830cc7dd8..5932a978b 100644
--- a/server/initializers/database.js
+++ b/server/initializers/database.js
@@ -5,6 +5,9 @@ const mongoose = require('mongoose')
5 5
6const logger = require('../helpers/logger') 6const logger = require('../helpers/logger')
7 7
8// Bootstrap models
9require('../models/video')
10
8const dbname = 'peertube' + config.get('database.suffix') 11const dbname = 'peertube' + config.get('database.suffix')
9const host = config.get('database.host') 12const host = config.get('database.host')
10const port = config.get('database.port') 13const port = config.get('database.port')
diff --git a/server/lib/friends.js b/server/lib/friends.js
index d81a603ad..91cd69f86 100644
--- a/server/lib/friends.js
+++ b/server/lib/friends.js
@@ -3,6 +3,7 @@
3const async = require('async') 3const async = require('async')
4const config = require('config') 4const config = require('config')
5const fs = require('fs') 5const fs = require('fs')
6const mongoose = require('mongoose')
6const request = require('request') 7const request = require('request')
7 8
8const constants = require('../initializers/constants') 9const constants = require('../initializers/constants')
@@ -11,12 +12,11 @@ const peertubeCrypto = require('../helpers/peertubeCrypto')
11const Pods = require('../models/pods') 12const Pods = require('../models/pods')
12const requestsScheduler = require('../lib/requestsScheduler') 13const requestsScheduler = require('../lib/requestsScheduler')
13const requests = require('../helpers/requests') 14const requests = require('../helpers/requests')
14const videos = require('../lib/videos')
15const Videos = require('../models/videos')
16 15
17const http = config.get('webserver.https') ? 'https' : 'http' 16const http = config.get('webserver.https') ? 'https' : 'http'
18const host = config.get('webserver.host') 17const host = config.get('webserver.host')
19const port = config.get('webserver.port') 18const port = config.get('webserver.port')
19const Video = mongoose.model('Video')
20 20
21const pods = { 21const pods = {
22 addVideoToFriends: addVideoToFriends, 22 addVideoToFriends: addVideoToFriends,
@@ -117,18 +117,13 @@ function quitFriends (callback) {
117 function listRemoteVideos (callbackAsync) { 117 function listRemoteVideos (callbackAsync) {
118 logger.info('Broke friends, so sad :(') 118 logger.info('Broke friends, so sad :(')
119 119
120 Videos.listFromRemotes(callbackAsync) 120 Video.listRemotes(callbackAsync)
121 }, 121 },
122 122
123 function removeTheRemoteVideos (videosList, callbackAsync) { 123 function removeTheRemoteVideos (videosList, callbackAsync) {
124 videos.removeRemoteVideos(videosList, function (err) { 124 async.each(videosList, function (video, callbackEach) {
125 if (err) { 125 video.remove(callbackEach)
126 logger.error('Cannot remove remote videos.', { error: err }) 126 }, callbackAsync)
127 return callbackAsync(err)
128 }
129
130 return callbackAsync(null)
131 })
132 } 127 }
133 ], function (err) { 128 ], function (err) {
134 // Don't forget to re activate the scheduler, even if there was an error 129 // Don't forget to re activate the scheduler, even if there was an error
@@ -146,14 +141,14 @@ function removeVideoToFriends (video) {
146} 141}
147 142
148function sendOwnedVideosToPod (podId) { 143function sendOwnedVideosToPod (podId) {
149 Videos.listOwned(function (err, videosList) { 144 Video.listOwned(function (err, videosList) {
150 if (err) { 145 if (err) {
151 logger.error('Cannot get the list of videos we own.') 146 logger.error('Cannot get the list of videos we own.')
152 return 147 return
153 } 148 }
154 149
155 videosList.forEach(function (video) { 150 videosList.forEach(function (video) {
156 videos.convertVideoToRemote(video, function (err, remoteVideo) { 151 video.toRemoteJSON(function (err, remoteVideo) {
157 if (err) { 152 if (err) {
158 logger.error('Cannot convert video to remote.', { error: err }) 153 logger.error('Cannot convert video to remote.', { error: err })
159 // Don't break the process 154 // Don't break the process
diff --git a/server/lib/requestsScheduler.js b/server/lib/requestsScheduler.js
index ac75e5b93..b192d8299 100644
--- a/server/lib/requestsScheduler.js
+++ b/server/lib/requestsScheduler.js
@@ -2,14 +2,15 @@
2 2
3const async = require('async') 3const async = require('async')
4const map = require('lodash/map') 4const map = require('lodash/map')
5const mongoose = require('mongoose')
5 6
6const constants = require('../initializers/constants') 7const constants = require('../initializers/constants')
7const logger = require('../helpers/logger') 8const logger = require('../helpers/logger')
8const Pods = require('../models/pods') 9const Pods = require('../models/pods')
9const Requests = require('../models/requests') 10const Requests = require('../models/requests')
10const requests = require('../helpers/requests') 11const requests = require('../helpers/requests')
11const videos = require('../lib/videos') 12
12const Videos = require('../models/videos') 13const Video = mongoose.model('Video')
13 14
14let timer = null 15let timer = null
15 16
@@ -210,7 +211,7 @@ function removeBadPods () {
210 const urls = map(pods, 'url') 211 const urls = map(pods, 'url')
211 const ids = map(pods, '_id') 212 const ids = map(pods, '_id')
212 213
213 Videos.listFromUrls(urls, function (err, videosList) { 214 Video.listByUrls(urls, function (err, videosList) {
214 if (err) { 215 if (err) {
215 logger.error('Cannot list videos urls.', { error: err, urls: urls }) 216 logger.error('Cannot list videos urls.', { error: err, urls: urls })
216 return callback(null, ids, []) 217 return callback(null, ids, [])
@@ -224,9 +225,14 @@ function removeBadPods () {
224 // We don't have to remove pods, skip 225 // We don't have to remove pods, skip
225 if (typeof podIds === 'function') return podIds(null) 226 if (typeof podIds === 'function') return podIds(null)
226 227
227 // Remove the remote videos 228 async.each(videosList, function (video, callbackEach) {
228 videos.removeRemoteVideos(videosList, function (err) { 229 video.remove(callbackEach)
229 if (err) logger.error('Cannot remove remote videos.', { error: err }) 230 }, function (err) {
231 if (err) {
232 // Don't stop the process
233 logger.error('Error while removing videos of bad pods.', { error: err })
234 return
235 }
230 236
231 return callback(null, podIds) 237 return callback(null, podIds)
232 }) 238 })
diff --git a/server/lib/videos.js b/server/lib/videos.js
deleted file mode 100644
index a74c77dc4..000000000
--- a/server/lib/videos.js
+++ /dev/null
@@ -1,199 +0,0 @@
1'use strict'
2
3const async = require('async')
4const config = require('config')
5const ffmpeg = require('fluent-ffmpeg')
6const fs = require('fs')
7const map = require('lodash/map')
8const pathUtils = require('path')
9
10const constants = require('../initializers/constants')
11const logger = require('../helpers/logger')
12const utils = require('../helpers/utils')
13const Videos = require('../models/videos')
14const webtorrent = require('../lib/webtorrent')
15
16const uploadDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads'))
17const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails'))
18
19const videos = {
20 convertVideoToRemote: convertVideoToRemote,
21 createRemoteVideos: createRemoteVideos,
22 getVideoDuration: getVideoDuration,
23 getVideoState: getVideoState,
24 createVideoThumbnail: createVideoThumbnail,
25 removeVideosDataFromDisk: removeVideosDataFromDisk,
26 removeRemoteVideos: removeRemoteVideos,
27 seed: seed,
28 seedAllExisting: seedAllExisting
29}
30
31function convertVideoToRemote (video, callback) {
32 fs.readFile(thumbnailsDir + video.thumbnail, function (err, thumbnailData) {
33 if (err) {
34 logger.error('Cannot read the thumbnail of the video')
35 return callback(err)
36 }
37
38 const remoteVideo = {
39 name: video.name,
40 description: video.description,
41 magnetUri: video.magnetUri,
42 author: video.author,
43 duration: video.duration,
44 thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
45 tags: video.tags,
46 createdDate: video.createdDate,
47 podUrl: video.podUrl
48 }
49
50 return callback(null, remoteVideo)
51 })
52}
53
54function createRemoteVideos (videos, callback) {
55 // Create the remote videos from the new pod
56 createRemoteVideoObjects(videos, function (err, remoteVideos) {
57 if (err) return callback(err)
58
59 Videos.addRemotes(remoteVideos, callback)
60 })
61}
62
63function getVideoDuration (videoPath, callback) {
64 ffmpeg.ffprobe(videoPath, function (err, metadata) {
65 if (err) return callback(err)
66
67 return callback(null, Math.floor(metadata.format.duration))
68 })
69}
70
71function getVideoState (video) {
72 const exist = (video !== null)
73 let owned = false
74 if (exist === true) {
75 owned = (video.namePath !== null)
76 }
77
78 return { exist: exist, owned: owned }
79}
80
81function createVideoThumbnail (videoPath, callback) {
82 const filename = pathUtils.basename(videoPath) + '.jpg'
83 ffmpeg(videoPath)
84 .on('error', callback)
85 .on('end', function () {
86 callback(null, filename)
87 })
88 .thumbnail({
89 count: 1,
90 folder: thumbnailsDir,
91 size: constants.THUMBNAILS_SIZE,
92 filename: filename
93 })
94}
95
96// Remove video datas from disk (video file, thumbnail...)
97function removeVideosDataFromDisk (videos, callback) {
98 async.each(videos, function (video, callbackEach) {
99 fs.unlink(thumbnailsDir + video.thumbnail, function (err) {
100 if (err) logger.error('Cannot remove the video thumbnail')
101
102 if (getVideoState(video).owned === true) {
103 fs.unlink(uploadDir + video.namePath, function (err) {
104 if (err) {
105 logger.error('Cannot remove this video file.')
106 return callbackEach(err)
107 }
108
109 callbackEach(null)
110 })
111 } else {
112 callbackEach(null)
113 }
114 })
115 }, callback)
116}
117
118function removeRemoteVideos (videos, callback) {
119 Videos.removeByIds(map(videos, '_id'), function (err) {
120 if (err) return callback(err)
121
122 removeVideosDataFromDisk(videos, callback)
123 })
124}
125
126function seed (path, callback) {
127 logger.info('Seeding %s...', path)
128
129 webtorrent.seed(path, function (torrent) {
130 logger.info('%s seeded (%s).', path, torrent.magnetURI)
131
132 return callback(null, torrent)
133 })
134}
135
136function seedAllExisting (callback) {
137 Videos.listOwned(function (err, videosList) {
138 if (err) {
139 logger.error('Cannot get list of the videos to seed.')
140 return callback(err)
141 }
142
143 async.each(videosList, function (video, callbackEach) {
144 seed(uploadDir + video.namePath, function (err) {
145 if (err) {
146 logger.error('Cannot seed this video.')
147 return callback(err)
148 }
149
150 callbackEach(null)
151 })
152 }, callback)
153 })
154}
155
156// ---------------------------------------------------------------------------
157
158module.exports = videos
159
160// ---------------------------------------------------------------------------
161
162function createRemoteVideoObjects (videos, callback) {
163 const remoteVideos = []
164
165 async.each(videos, function (video, callbackEach) {
166 // Creating the thumbnail for this remote video
167 utils.generateRandomString(16, function (err, randomString) {
168 if (err) return callbackEach(err)
169
170 const thumbnailName = randomString + '.jpg'
171 createThumbnailFromBase64(thumbnailName, video.thumbnailBase64, function (err) {
172 if (err) return callbackEach(err)
173
174 const params = {
175 name: video.name,
176 description: video.description,
177 magnetUri: video.magnetUri,
178 podUrl: video.podUrl,
179 duration: video.duration,
180 thumbnail: thumbnailName,
181 tags: video.tags,
182 author: video.author
183 }
184 remoteVideos.push(params)
185
186 callbackEach(null)
187 })
188 })
189 },
190 function (err) {
191 if (err) return callback(err)
192
193 callback(null, remoteVideos)
194 })
195}
196
197function createThumbnailFromBase64 (thumbnailName, data, callback) {
198 fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, callback)
199}
diff --git a/server/middlewares/reqValidators/remote.js b/server/middlewares/reqValidators/remote.js
index a23673d89..dd8ee5f6e 100644
--- a/server/middlewares/reqValidators/remote.js
+++ b/server/middlewares/reqValidators/remote.js
@@ -22,7 +22,7 @@ function remoteVideos (req, res, next) {
22 req.checkBody('data').isArray() 22 req.checkBody('data').isArray()
23 req.checkBody('data').isEachRemoteVideosValid() 23 req.checkBody('data').isEachRemoteVideosValid()
24 24
25 logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body }) 25 logger.debug('Checking remoteVideos parameters', { parameters: req.body })
26 26
27 checkErrors(req, res, next) 27 checkErrors(req, res, next)
28} 28}
diff --git a/server/middlewares/reqValidators/videos.js b/server/middlewares/reqValidators/videos.js
index f31fd93a2..452fbc859 100644
--- a/server/middlewares/reqValidators/videos.js
+++ b/server/middlewares/reqValidators/videos.js
@@ -1,11 +1,13 @@
1'use strict' 1'use strict'
2 2
3const mongoose = require('mongoose')
4
3const checkErrors = require('./utils').checkErrors 5const checkErrors = require('./utils').checkErrors
4const constants = require('../../initializers/constants') 6const constants = require('../../initializers/constants')
5const customValidators = require('../../helpers/customValidators') 7const customValidators = require('../../helpers/customValidators')
6const logger = require('../../helpers/logger') 8const logger = require('../../helpers/logger')
7const videos = require('../../lib/videos') 9
8const Videos = require('../../models/videos') 10const Video = mongoose.model('Video')
9 11
10const reqValidatorsVideos = { 12const reqValidatorsVideos = {
11 videosAdd: videosAdd, 13 videosAdd: videosAdd,
@@ -26,7 +28,7 @@ function videosAdd (req, res, next) {
26 checkErrors(req, res, function () { 28 checkErrors(req, res, function () {
27 const videoFile = req.files.videofile[0] 29 const videoFile = req.files.videofile[0]
28 30
29 videos.getVideoDuration(videoFile.path, function (err, duration) { 31 Video.getDurationFromFile(videoFile.path, function (err, duration) {
30 if (err) { 32 if (err) {
31 return res.status(400).send('Cannot retrieve metadata of the file.') 33 return res.status(400).send('Cannot retrieve metadata of the file.')
32 } 34 }
@@ -47,14 +49,13 @@ function videosGet (req, res, next) {
47 logger.debug('Checking videosGet parameters', { parameters: req.params }) 49 logger.debug('Checking videosGet parameters', { parameters: req.params })
48 50
49 checkErrors(req, res, function () { 51 checkErrors(req, res, function () {
50 Videos.get(req.params.id, function (err, video) { 52 Video.load(req.params.id, function (err, video) {
51 if (err) { 53 if (err) {
52 logger.error('Error in videosGet request validator.', { error: err }) 54 logger.error('Error in videosGet request validator.', { error: err })
53 return res.sendStatus(500) 55 return res.sendStatus(500)
54 } 56 }
55 57
56 const state = videos.getVideoState(video) 58 if (!video) return res.status(404).send('Video not found')
57 if (state.exist === false) return res.status(404).send('Video not found')
58 59
59 next() 60 next()
60 }) 61 })
@@ -67,15 +68,14 @@ function videosRemove (req, res, next) {
67 logger.debug('Checking videosRemove parameters', { parameters: req.params }) 68 logger.debug('Checking videosRemove parameters', { parameters: req.params })
68 69
69 checkErrors(req, res, function () { 70 checkErrors(req, res, function () {
70 Videos.get(req.params.id, function (err, video) { 71 Video.load(req.params.id, function (err, video) {
71 if (err) { 72 if (err) {
72 logger.error('Error in videosRemove request validator.', { error: err }) 73 logger.error('Error in videosRemove request validator.', { error: err })
73 return res.sendStatus(500) 74 return res.sendStatus(500)
74 } 75 }
75 76
76 const state = videos.getVideoState(video) 77 if (!video) return res.status(404).send('Video not found')
77 if (state.exist === false) return res.status(404).send('Video not found') 78 else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod')
78 else if (state.owned === false) return res.status(403).send('Cannot remove video of another pod')
79 79
80 next() 80 next()
81 }) 81 })
diff --git a/server/models/video.js b/server/models/video.js
new file mode 100644
index 000000000..8b14e9b35
--- /dev/null
+++ b/server/models/video.js
@@ -0,0 +1,314 @@
1'use strict'
2
3const async = require('async')
4const config = require('config')
5const ffmpeg = require('fluent-ffmpeg')
6const fs = require('fs')
7const pathUtils = require('path')
8const mongoose = require('mongoose')
9
10const constants = require('../initializers/constants')
11const customValidators = require('../helpers/customValidators')
12const logger = require('../helpers/logger')
13const utils = require('../helpers/utils')
14const webtorrent = require('../lib/webtorrent')
15
16const http = config.get('webserver.https') === true ? 'https' : 'http'
17const host = config.get('webserver.host')
18const port = config.get('webserver.port')
19const uploadsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads'))
20const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails'))
21
22// ---------------------------------------------------------------------------
23
24// TODO: add indexes on searchable columns
25const VideoSchema = mongoose.Schema({
26 name: String,
27 namePath: String,
28 description: String,
29 magnetUri: String,
30 podUrl: String,
31 author: String,
32 duration: Number,
33 thumbnail: String,
34 tags: [ String ],
35 createdDate: {
36 type: Date,
37 default: Date.now
38 }
39})
40
41VideoSchema.path('name').validate(customValidators.isVideoNameValid)
42VideoSchema.path('description').validate(customValidators.isVideoDescriptionValid)
43VideoSchema.path('magnetUri').validate(customValidators.isVideoMagnetUriValid)
44VideoSchema.path('podUrl').validate(customValidators.isVideoPodUrlValid)
45VideoSchema.path('author').validate(customValidators.isVideoAuthorValid)
46VideoSchema.path('duration').validate(customValidators.isVideoDurationValid)
47// The tumbnail can be the path or the data in base 64
48// The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
49VideoSchema.path('thumbnail').validate(function (value) {
50 return customValidators.isVideoThumbnailValid(value) || customValidators.isVideoThumbnail64Valid(value)
51})
52VideoSchema.path('tags').validate(customValidators.isVideoTagsValid)
53
54VideoSchema.methods = {
55 isOwned: isOwned,
56 toFormatedJSON: toFormatedJSON,
57 toRemoteJSON: toRemoteJSON
58}
59
60VideoSchema.statics = {
61 getDurationFromFile: getDurationFromFile,
62 list: list,
63 listByUrlAndMagnet: listByUrlAndMagnet,
64 listByUrls: listByUrls,
65 listOwned: listOwned,
66 listRemotes: listRemotes,
67 load: load,
68 search: search,
69 seedAllExisting: seedAllExisting
70}
71
72VideoSchema.pre('remove', function (next) {
73 const video = this
74 const tasks = []
75
76 tasks.push(
77 function (callback) {
78 removeThumbnail(video, callback)
79 }
80 )
81
82 if (video.isOwned()) {
83 tasks.push(
84 function (callback) {
85 removeFile(video, callback)
86 },
87 function (callback) {
88 removeTorrent(video, callback)
89 }
90 )
91 }
92
93 async.parallel(tasks, next)
94})
95
96VideoSchema.pre('save', function (next) {
97 const video = this
98 const tasks = []
99
100 if (video.isOwned()) {
101 const videoPath = pathUtils.join(uploadsDir, video.namePath)
102 this.podUrl = http + '://' + host + ':' + port
103
104 tasks.push(
105 function (callback) {
106 seed(videoPath, callback)
107 },
108 function (callback) {
109 createThumbnail(videoPath, callback)
110 }
111 )
112
113 async.parallel(tasks, function (err, results) {
114 if (err) return next(err)
115
116 video.magnetUri = results[0].magnetURI
117 video.thumbnail = results[1]
118
119 return next()
120 })
121 } else {
122 generateThumbnailFromBase64(video.thumbnail, function (err, thumbnailName) {
123 if (err) return next(err)
124
125 video.thumbnail = thumbnailName
126
127 return next()
128 })
129 }
130})
131
132mongoose.model('Video', VideoSchema)
133
134// ------------------------------ METHODS ------------------------------
135
136function isOwned () {
137 return this.namePath !== null
138}
139
140function toFormatedJSON () {
141 const json = {
142 id: this._id,
143 name: this.name,
144 description: this.description,
145 podUrl: this.podUrl.replace(/^https?:\/\//, ''),
146 isLocal: this.isOwned(),
147 magnetUri: this.magnetUri,
148 author: this.author,
149 duration: this.duration,
150 tags: this.tags,
151 thumbnailPath: constants.THUMBNAILS_STATIC_PATH + '/' + this.thumbnail,
152 createdDate: this.createdDate
153 }
154
155 return json
156}
157
158function toRemoteJSON (callback) {
159 const self = this
160
161 // Convert thumbnail to base64
162 fs.readFile(pathUtils.join(thumbnailsDir, this.thumbnail), function (err, thumbnailData) {
163 if (err) {
164 logger.error('Cannot read the thumbnail of the video')
165 return callback(err)
166 }
167
168 const remoteVideo = {
169 name: self.name,
170 description: self.description,
171 magnetUri: self.magnetUri,
172 namePath: null,
173 author: self.author,
174 duration: self.duration,
175 thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
176 tags: self.tags,
177 createdDate: self.createdDate,
178 podUrl: self.podUrl
179 }
180
181 return callback(null, remoteVideo)
182 })
183}
184
185// ------------------------------ STATICS ------------------------------
186
187function getDurationFromFile (videoPath, callback) {
188 ffmpeg.ffprobe(videoPath, function (err, metadata) {
189 if (err) return callback(err)
190
191 return callback(null, Math.floor(metadata.format.duration))
192 })
193}
194
195function list (start, count, sort, callback) {
196 const query = {}
197 return findWithCount.call(this, query, start, count, sort, callback)
198}
199
200function listByUrlAndMagnet (fromUrl, magnetUri, callback) {
201 this.find({ podUrl: fromUrl, magnetUri: magnetUri }, callback)
202}
203
204function listByUrls (fromUrls, callback) {
205 this.find({ podUrl: { $in: fromUrls } }, callback)
206}
207
208function listOwned (callback) {
209 // If namePath is not null this is *our* video
210 this.find({ namePath: { $ne: null } }, callback)
211}
212
213function listRemotes (callback) {
214 this.find({ namePath: null }, callback)
215}
216
217function load (id, callback) {
218 this.findById(id, callback)
219}
220
221function search (value, field, start, count, sort, callback) {
222 const query = {}
223 // Make an exact search with the magnet
224 if (field === 'magnetUri' || field === 'tags') {
225 query[field] = value
226 } else {
227 query[field] = new RegExp(value)
228 }
229
230 findWithCount.call(this, query, start, count, sort, callback)
231}
232
233// TODO
234function seedAllExisting () {
235
236}
237
238// ---------------------------------------------------------------------------
239
240function findWithCount (query, start, count, sort, callback) {
241 const self = this
242
243 async.parallel([
244 function (asyncCallback) {
245 self.find(query).skip(start).limit(start + count).sort(sort).exec(asyncCallback)
246 },
247 function (asyncCallback) {
248 self.count(query, asyncCallback)
249 }
250 ], function (err, results) {
251 if (err) return callback(err)
252
253 const videos = results[0]
254 const totalVideos = results[1]
255 return callback(null, videos, totalVideos)
256 })
257}
258
259function removeThumbnail (video, callback) {
260 fs.unlink(thumbnailsDir + video.thumbnail, callback)
261}
262
263function removeFile (video, callback) {
264 fs.unlink(uploadsDir + video.namePath, callback)
265}
266
267// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
268function removeTorrent (video, callback) {
269 try {
270 webtorrent.remove(video.magnetUri, callback)
271 } catch (err) {
272 logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
273 return callback(null)
274 }
275}
276
277function createThumbnail (videoPath, callback) {
278 const filename = pathUtils.basename(videoPath) + '.jpg'
279 ffmpeg(videoPath)
280 .on('error', callback)
281 .on('end', function () {
282 callback(null, filename)
283 })
284 .thumbnail({
285 count: 1,
286 folder: thumbnailsDir,
287 size: constants.THUMBNAILS_SIZE,
288 filename: filename
289 })
290}
291
292function seed (path, callback) {
293 logger.info('Seeding %s...', path)
294
295 webtorrent.seed(path, function (torrent) {
296 logger.info('%s seeded (%s).', path, torrent.magnetURI)
297
298 return callback(null, torrent)
299 })
300}
301
302function generateThumbnailFromBase64 (data, callback) {
303 // Creating the thumbnail for this remote video
304 utils.generateRandomString(16, function (err, randomString) {
305 if (err) return callback(err)
306
307 const thumbnailName = randomString + '.jpg'
308 fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, function (err) {
309 if (err) return callback(err)
310
311 return callback(null, thumbnailName)
312 })
313 })
314}
diff --git a/server/models/videos.js b/server/models/videos.js
deleted file mode 100644
index c177b414c..000000000
--- a/server/models/videos.js
+++ /dev/null
@@ -1,162 +0,0 @@
1'use strict'
2
3const async = require('async')
4const config = require('config')
5const mongoose = require('mongoose')
6
7const logger = require('../helpers/logger')
8
9const http = config.get('webserver.https') === true ? 'https' : 'http'
10const host = config.get('webserver.host')
11const port = config.get('webserver.port')
12
13// ---------------------------------------------------------------------------
14
15// TODO: add indexes on searchable columns
16const videosSchema = mongoose.Schema({
17 name: String,
18 namePath: String,
19 description: String,
20 magnetUri: String,
21 podUrl: String,
22 author: String,
23 duration: Number,
24 thumbnail: String,
25 tags: [ String ],
26 createdDate: {
27 type: Date,
28 default: Date.now
29 }
30})
31const VideosDB = mongoose.model('videos', videosSchema)
32
33// ---------------------------------------------------------------------------
34
35const Videos = {
36 add: add,
37 addRemotes: addRemotes,
38 get: get,
39 list: list,
40 listFromUrl: listFromUrl,
41 listFromUrls: listFromUrls,
42 listFromUrlAndMagnets: listFromUrlAndMagnets,
43 listFromRemotes: listFromRemotes,
44 listOwned: listOwned,
45 removeOwned: removeOwned,
46 removeByIds: removeByIds,
47 search: search
48}
49
50function add (video, callback) {
51 logger.info('Adding %s video to database.', video.name)
52
53 const params = video
54 params.podUrl = http + '://' + host + ':' + port
55
56 VideosDB.create(params, function (err, insertedVideo) {
57 if (err) {
58 logger.error('Cannot insert this video into database.')
59 return callback(err)
60 }
61
62 callback(null, insertedVideo)
63 })
64}
65
66function addRemotes (videos, callback) {
67 videos.forEach(function (video) {
68 // Ensure they are remote videos
69 video.namePath = null
70 })
71
72 VideosDB.create(videos, callback)
73}
74
75function get (id, callback) {
76 VideosDB.findById(id, function (err, video) {
77 if (err) {
78 logger.error('Cannot get this video.')
79 return callback(err)
80 }
81
82 return callback(null, video)
83 })
84}
85
86function list (start, count, sort, callback) {
87 const query = {}
88 return findWithCount(query, start, count, sort, callback)
89}
90
91function listFromUrl (fromUrl, callback) {
92 VideosDB.find({ podUrl: fromUrl }, callback)
93}
94
95function listFromUrls (fromUrls, callback) {
96 VideosDB.find({ podUrl: { $in: fromUrls } }, callback)
97}
98
99function listFromUrlAndMagnets (fromUrl, magnets, callback) {
100 VideosDB.find({ podUrl: fromUrl, magnetUri: { $in: magnets } }, callback)
101}
102
103function listFromRemotes (callback) {
104 VideosDB.find({ namePath: null }, callback)
105}
106
107function listOwned (callback) {
108 // If namePath is not null this is *our* video
109 VideosDB.find({ namePath: { $ne: null } }, function (err, videosList) {
110 if (err) {
111 logger.error('Cannot get the list of owned videos.')
112 return callback(err)
113 }
114
115 return callback(null, videosList)
116 })
117}
118
119// Return the video in the callback
120function removeOwned (id, callback) {
121 VideosDB.findByIdAndRemove(id, callback)
122}
123
124// Use the magnet Uri because the _id field is not the same on different servers
125function removeByIds (ids, callback) {
126 VideosDB.remove({ _id: { $in: ids } }, callback)
127}
128
129function search (value, field, start, count, sort, callback) {
130 const query = {}
131 // Make an exact search with the magnet
132 if (field === 'magnetUri' || field === 'tags') {
133 query[field] = value
134 } else {
135 query[field] = new RegExp(value)
136 }
137
138 findWithCount(query, start, count, sort, callback)
139}
140
141// ---------------------------------------------------------------------------
142
143module.exports = Videos
144
145// ---------------------------------------------------------------------------
146
147function findWithCount (query, start, count, sort, callback) {
148 async.parallel([
149 function (asyncCallback) {
150 VideosDB.find(query).skip(start).limit(start + count).sort(sort).exec(asyncCallback)
151 },
152 function (asyncCallback) {
153 VideosDB.count(query, asyncCallback)
154 }
155 ], function (err, results) {
156 if (err) return callback(err)
157
158 const videos = results[0]
159 const totalVideos = results[1]
160 return callback(null, videos, totalVideos)
161 })
162}
diff --git a/server/tests/api/multiplePods.js b/server/tests/api/multiplePods.js
index 2a1bc64e6..52dfda137 100644
--- a/server/tests/api/multiplePods.js
+++ b/server/tests/api/multiplePods.js
@@ -414,7 +414,7 @@ describe('Test multiple pods', function () {
414 414
415 // Keep the logs if the test failed 415 // Keep the logs if the test failed
416 if (this.ok) { 416 if (this.ok) {
417 utils.flushTests(done) 417 // utils.flushTests(done)
418 } else { 418 } else {
419 done() 419 done()
420 } 420 }