diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2016-06-24 17:42:51 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2016-06-24 17:42:51 +0200 |
commit | aaf61f3810e6d57c5130af959bd2860df32775e7 (patch) | |
tree | 3871b20359aa56995a4b8974ae5cb6b911dd73f5 | |
parent | 07d932038745e773359fa87bac6be70523f593ee (diff) | |
download | PeerTube-aaf61f3810e6d57c5130af959bd2860df32775e7.tar.gz PeerTube-aaf61f3810e6d57c5130af959bd2860df32775e7.tar.zst PeerTube-aaf61f3810e6d57c5130af959bd2860df32775e7.zip |
Video model refractoring -> use mongoose api
-rw-r--r-- | server.js | 5 | ||||
-rw-r--r-- | server/controllers/api/v1/pods.js | 17 | ||||
-rw-r--r-- | server/controllers/api/v1/remote.js | 45 | ||||
-rw-r--r-- | server/controllers/api/v1/videos.js | 124 | ||||
-rw-r--r-- | server/helpers/customValidators.js | 11 | ||||
-rw-r--r-- | server/initializers/constants.js | 3 | ||||
-rw-r--r-- | server/initializers/database.js | 3 | ||||
-rw-r--r-- | server/lib/friends.js | 21 | ||||
-rw-r--r-- | server/lib/requestsScheduler.js | 18 | ||||
-rw-r--r-- | server/lib/videos.js | 199 | ||||
-rw-r--r-- | server/middlewares/reqValidators/remote.js | 2 | ||||
-rw-r--r-- | server/middlewares/reqValidators/videos.js | 20 | ||||
-rw-r--r-- | server/models/video.js | 314 | ||||
-rw-r--r-- | server/models/videos.js | 162 | ||||
-rw-r--r-- | server/tests/api/multiplePods.js | 2 |
15 files changed, 412 insertions, 534 deletions
@@ -31,7 +31,6 @@ const logger = require('./server/helpers/logger') | |||
31 | const poolRequests = require('./server/lib/requestsScheduler') | 31 | const poolRequests = require('./server/lib/requestsScheduler') |
32 | const routes = require('./server/controllers') | 32 | const routes = require('./server/controllers') |
33 | const utils = require('./server/helpers/utils') | 33 | const utils = require('./server/helpers/utils') |
34 | const videos = require('./server/lib/videos') | ||
35 | const webtorrent = require('./server/lib/webtorrent') | 34 | const webtorrent = require('./server/lib/webtorrent') |
36 | 35 | ||
37 | // Get configurations | 36 | // Get configurations |
@@ -138,11 +137,11 @@ installer.installApplication(function (err) { | |||
138 | // Activate the pool requests | 137 | // Activate the pool requests |
139 | poolRequests.activate() | 138 | poolRequests.activate() |
140 | 139 | ||
141 | videos.seedAllExisting(function () { | 140 | // videos.seedAllExisting(function () { |
142 | logger.info('Seeded all the videos') | 141 | logger.info('Seeded all the videos') |
143 | logger.info('Server listening on port %d', port) | 142 | logger.info('Server listening on port %d', port) |
144 | app.emit('ready') | 143 | app.emit('ready') |
145 | }) | 144 | // }) |
146 | }) | 145 | }) |
147 | }) | 146 | }) |
148 | }) | 147 | }) |
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 | ||
3 | const async = require('async') | 3 | const async = require('async') |
4 | const express = require('express') | 4 | const express = require('express') |
5 | const mongoose = require('mongoose') | ||
5 | 6 | ||
6 | const logger = require('../../../helpers/logger') | 7 | const logger = require('../../../helpers/logger') |
7 | const friends = require('../../../lib/friends') | 8 | const friends = require('../../../lib/friends') |
@@ -10,10 +11,9 @@ const Pods = require('../../../models/pods') | |||
10 | const oAuth2 = middlewares.oauth2 | 11 | const oAuth2 = middlewares.oauth2 |
11 | const reqValidator = middlewares.reqValidators.pods | 12 | const reqValidator = middlewares.reqValidators.pods |
12 | const signatureValidator = middlewares.reqValidators.remote.signature | 13 | const signatureValidator = middlewares.reqValidators.remote.signature |
13 | const videos = require('../../../lib/videos') | ||
14 | const Videos = require('../../../models/videos') | ||
15 | 14 | ||
16 | const router = express.Router() | 15 | const router = express.Router() |
16 | const Video = mongoose.model('Video') | ||
17 | 17 | ||
18 | router.get('/', listPodsUrl) | 18 | router.get('/', listPodsUrl) |
19 | router.post('/', reqValidator.podsAdd, addPods) | 19 | router.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 | ||
3 | const async = require('async') | 3 | const async = require('async') |
4 | const express = require('express') | 4 | const express = require('express') |
5 | const mongoose = require('mongoose') | ||
5 | 6 | ||
6 | const middlewares = require('../../../middlewares') | 7 | const middlewares = require('../../../middlewares') |
7 | const secureMiddleware = middlewares.secure | 8 | const secureMiddleware = middlewares.secure |
8 | const reqValidator = middlewares.reqValidators.remote | 9 | const reqValidator = middlewares.reqValidators.remote |
9 | const logger = require('../../../helpers/logger') | 10 | const logger = require('../../../helpers/logger') |
10 | const Videos = require('../../../models/videos') | ||
11 | const videos = require('../../../lib/videos') | ||
12 | 11 | ||
13 | const router = express.Router() | 12 | const router = express.Router() |
13 | const Video = mongoose.model('Video') | ||
14 | 14 | ||
15 | router.post('/videos', | 15 | router.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 | ||
49 | function addRemoteVideo (videoToCreate, callback) { | 51 | function 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 | ||
60 | function removeRemoteVideo (videoToRemove, fromUrl, callback) { | 59 | function 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 @@ | |||
3 | const async = require('async') | 3 | const async = require('async') |
4 | const config = require('config') | 4 | const config = require('config') |
5 | const express = require('express') | 5 | const express = require('express') |
6 | const mongoose = require('mongoose') | ||
6 | const multer = require('multer') | 7 | const multer = require('multer') |
7 | 8 | ||
8 | const constants = require('../../../initializers/constants') | ||
9 | const logger = require('../../../helpers/logger') | 9 | const logger = require('../../../helpers/logger') |
10 | const friends = require('../../../lib/friends') | 10 | const friends = require('../../../lib/friends') |
11 | const middlewares = require('../../../middlewares') | 11 | const middlewares = require('../../../middlewares') |
@@ -18,12 +18,10 @@ const reqValidatorVideos = reqValidator.videos | |||
18 | const search = middlewares.search | 18 | const search = middlewares.search |
19 | const sort = middlewares.sort | 19 | const sort = middlewares.sort |
20 | const utils = require('../../../helpers/utils') | 20 | const utils = require('../../../helpers/utils') |
21 | const Videos = require('../../../models/videos') // model | ||
22 | const videos = require('../../../lib/videos') | ||
23 | const webtorrent = require('../../../lib/webtorrent') | ||
24 | 21 | ||
25 | const router = express.Router() | 22 | const router = express.Router() |
26 | const uploads = config.get('storage.uploads') | 23 | const uploads = config.get('storage.uploads') |
24 | const Video = mongoose.model('Video') | ||
27 | 25 | ||
28 | // multer configuration | 26 | // multer configuration |
29 | const storage = multer.diskStorage({ | 27 | const 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 | ||
159 | function getVideo (req, res, next) { | 132 | function 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 | ||
172 | function listVideos (req, res, next) { | 144 | function 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 | ||
230 | function searchVideos (req, res, next) { | 188 | function 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 | ||
241 | function getFormatedVideo (videoObj) { | 199 | function 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 | |||
259 | function 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 | ||
273 | function 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 | ||
23 | function exists (value) { | 24 | function 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 | ||
99 | function isVideoThumbnailValid (value) { | 100 | function isVideoThumbnailValid (value) { |
101 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL) | ||
102 | } | ||
103 | |||
104 | function 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 | ||
6 | const logger = require('../helpers/logger') | 6 | const logger = require('../helpers/logger') |
7 | 7 | ||
8 | // Bootstrap models | ||
9 | require('../models/video') | ||
10 | |||
8 | const dbname = 'peertube' + config.get('database.suffix') | 11 | const dbname = 'peertube' + config.get('database.suffix') |
9 | const host = config.get('database.host') | 12 | const host = config.get('database.host') |
10 | const port = config.get('database.port') | 13 | const 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 @@ | |||
3 | const async = require('async') | 3 | const async = require('async') |
4 | const config = require('config') | 4 | const config = require('config') |
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const mongoose = require('mongoose') | ||
6 | const request = require('request') | 7 | const request = require('request') |
7 | 8 | ||
8 | const constants = require('../initializers/constants') | 9 | const constants = require('../initializers/constants') |
@@ -11,12 +12,11 @@ const peertubeCrypto = require('../helpers/peertubeCrypto') | |||
11 | const Pods = require('../models/pods') | 12 | const Pods = require('../models/pods') |
12 | const requestsScheduler = require('../lib/requestsScheduler') | 13 | const requestsScheduler = require('../lib/requestsScheduler') |
13 | const requests = require('../helpers/requests') | 14 | const requests = require('../helpers/requests') |
14 | const videos = require('../lib/videos') | ||
15 | const Videos = require('../models/videos') | ||
16 | 15 | ||
17 | const http = config.get('webserver.https') ? 'https' : 'http' | 16 | const http = config.get('webserver.https') ? 'https' : 'http' |
18 | const host = config.get('webserver.host') | 17 | const host = config.get('webserver.host') |
19 | const port = config.get('webserver.port') | 18 | const port = config.get('webserver.port') |
19 | const Video = mongoose.model('Video') | ||
20 | 20 | ||
21 | const pods = { | 21 | const 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 | ||
148 | function sendOwnedVideosToPod (podId) { | 143 | function 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 | ||
3 | const async = require('async') | 3 | const async = require('async') |
4 | const map = require('lodash/map') | 4 | const map = require('lodash/map') |
5 | const mongoose = require('mongoose') | ||
5 | 6 | ||
6 | const constants = require('../initializers/constants') | 7 | const constants = require('../initializers/constants') |
7 | const logger = require('../helpers/logger') | 8 | const logger = require('../helpers/logger') |
8 | const Pods = require('../models/pods') | 9 | const Pods = require('../models/pods') |
9 | const Requests = require('../models/requests') | 10 | const Requests = require('../models/requests') |
10 | const requests = require('../helpers/requests') | 11 | const requests = require('../helpers/requests') |
11 | const videos = require('../lib/videos') | 12 | |
12 | const Videos = require('../models/videos') | 13 | const Video = mongoose.model('Video') |
13 | 14 | ||
14 | let timer = null | 15 | let 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 | |||
3 | const async = require('async') | ||
4 | const config = require('config') | ||
5 | const ffmpeg = require('fluent-ffmpeg') | ||
6 | const fs = require('fs') | ||
7 | const map = require('lodash/map') | ||
8 | const pathUtils = require('path') | ||
9 | |||
10 | const constants = require('../initializers/constants') | ||
11 | const logger = require('../helpers/logger') | ||
12 | const utils = require('../helpers/utils') | ||
13 | const Videos = require('../models/videos') | ||
14 | const webtorrent = require('../lib/webtorrent') | ||
15 | |||
16 | const uploadDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads')) | ||
17 | const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails')) | ||
18 | |||
19 | const 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 | |||
31 | function 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 | |||
54 | function 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 | |||
63 | function 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 | |||
71 | function 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 | |||
81 | function 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...) | ||
97 | function 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 | |||
118 | function 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 | |||
126 | function 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 | |||
136 | function 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 | |||
158 | module.exports = videos | ||
159 | |||
160 | // --------------------------------------------------------------------------- | ||
161 | |||
162 | function 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 | |||
197 | function 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 | ||
3 | const mongoose = require('mongoose') | ||
4 | |||
3 | const checkErrors = require('./utils').checkErrors | 5 | const checkErrors = require('./utils').checkErrors |
4 | const constants = require('../../initializers/constants') | 6 | const constants = require('../../initializers/constants') |
5 | const customValidators = require('../../helpers/customValidators') | 7 | const customValidators = require('../../helpers/customValidators') |
6 | const logger = require('../../helpers/logger') | 8 | const logger = require('../../helpers/logger') |
7 | const videos = require('../../lib/videos') | 9 | |
8 | const Videos = require('../../models/videos') | 10 | const Video = mongoose.model('Video') |
9 | 11 | ||
10 | const reqValidatorsVideos = { | 12 | const 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 | |||
3 | const async = require('async') | ||
4 | const config = require('config') | ||
5 | const ffmpeg = require('fluent-ffmpeg') | ||
6 | const fs = require('fs') | ||
7 | const pathUtils = require('path') | ||
8 | const mongoose = require('mongoose') | ||
9 | |||
10 | const constants = require('../initializers/constants') | ||
11 | const customValidators = require('../helpers/customValidators') | ||
12 | const logger = require('../helpers/logger') | ||
13 | const utils = require('../helpers/utils') | ||
14 | const webtorrent = require('../lib/webtorrent') | ||
15 | |||
16 | const http = config.get('webserver.https') === true ? 'https' : 'http' | ||
17 | const host = config.get('webserver.host') | ||
18 | const port = config.get('webserver.port') | ||
19 | const uploadsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.uploads')) | ||
20 | const thumbnailsDir = pathUtils.join(__dirname, '..', '..', config.get('storage.thumbnails')) | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | // TODO: add indexes on searchable columns | ||
25 | const 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 | |||
41 | VideoSchema.path('name').validate(customValidators.isVideoNameValid) | ||
42 | VideoSchema.path('description').validate(customValidators.isVideoDescriptionValid) | ||
43 | VideoSchema.path('magnetUri').validate(customValidators.isVideoMagnetUriValid) | ||
44 | VideoSchema.path('podUrl').validate(customValidators.isVideoPodUrlValid) | ||
45 | VideoSchema.path('author').validate(customValidators.isVideoAuthorValid) | ||
46 | VideoSchema.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 | ||
49 | VideoSchema.path('thumbnail').validate(function (value) { | ||
50 | return customValidators.isVideoThumbnailValid(value) || customValidators.isVideoThumbnail64Valid(value) | ||
51 | }) | ||
52 | VideoSchema.path('tags').validate(customValidators.isVideoTagsValid) | ||
53 | |||
54 | VideoSchema.methods = { | ||
55 | isOwned: isOwned, | ||
56 | toFormatedJSON: toFormatedJSON, | ||
57 | toRemoteJSON: toRemoteJSON | ||
58 | } | ||
59 | |||
60 | VideoSchema.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 | |||
72 | VideoSchema.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 | |||
96 | VideoSchema.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 | |||
132 | mongoose.model('Video', VideoSchema) | ||
133 | |||
134 | // ------------------------------ METHODS ------------------------------ | ||
135 | |||
136 | function isOwned () { | ||
137 | return this.namePath !== null | ||
138 | } | ||
139 | |||
140 | function 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 | |||
158 | function 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 | |||
187 | function 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 | |||
195 | function list (start, count, sort, callback) { | ||
196 | const query = {} | ||
197 | return findWithCount.call(this, query, start, count, sort, callback) | ||
198 | } | ||
199 | |||
200 | function listByUrlAndMagnet (fromUrl, magnetUri, callback) { | ||
201 | this.find({ podUrl: fromUrl, magnetUri: magnetUri }, callback) | ||
202 | } | ||
203 | |||
204 | function listByUrls (fromUrls, callback) { | ||
205 | this.find({ podUrl: { $in: fromUrls } }, callback) | ||
206 | } | ||
207 | |||
208 | function listOwned (callback) { | ||
209 | // If namePath is not null this is *our* video | ||
210 | this.find({ namePath: { $ne: null } }, callback) | ||
211 | } | ||
212 | |||
213 | function listRemotes (callback) { | ||
214 | this.find({ namePath: null }, callback) | ||
215 | } | ||
216 | |||
217 | function load (id, callback) { | ||
218 | this.findById(id, callback) | ||
219 | } | ||
220 | |||
221 | function 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 | ||
234 | function seedAllExisting () { | ||
235 | |||
236 | } | ||
237 | |||
238 | // --------------------------------------------------------------------------- | ||
239 | |||
240 | function 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 | |||
259 | function removeThumbnail (video, callback) { | ||
260 | fs.unlink(thumbnailsDir + video.thumbnail, callback) | ||
261 | } | ||
262 | |||
263 | function 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 | ||
268 | function 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 | |||
277 | function 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 | |||
292 | function 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 | |||
302 | function 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 | |||
3 | const async = require('async') | ||
4 | const config = require('config') | ||
5 | const mongoose = require('mongoose') | ||
6 | |||
7 | const logger = require('../helpers/logger') | ||
8 | |||
9 | const http = config.get('webserver.https') === true ? 'https' : 'http' | ||
10 | const host = config.get('webserver.host') | ||
11 | const port = config.get('webserver.port') | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | // TODO: add indexes on searchable columns | ||
16 | const 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 | }) | ||
31 | const VideosDB = mongoose.model('videos', videosSchema) | ||
32 | |||
33 | // --------------------------------------------------------------------------- | ||
34 | |||
35 | const 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 | |||
50 | function 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 | |||
66 | function 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 | |||
75 | function 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 | |||
86 | function list (start, count, sort, callback) { | ||
87 | const query = {} | ||
88 | return findWithCount(query, start, count, sort, callback) | ||
89 | } | ||
90 | |||
91 | function listFromUrl (fromUrl, callback) { | ||
92 | VideosDB.find({ podUrl: fromUrl }, callback) | ||
93 | } | ||
94 | |||
95 | function listFromUrls (fromUrls, callback) { | ||
96 | VideosDB.find({ podUrl: { $in: fromUrls } }, callback) | ||
97 | } | ||
98 | |||
99 | function listFromUrlAndMagnets (fromUrl, magnets, callback) { | ||
100 | VideosDB.find({ podUrl: fromUrl, magnetUri: { $in: magnets } }, callback) | ||
101 | } | ||
102 | |||
103 | function listFromRemotes (callback) { | ||
104 | VideosDB.find({ namePath: null }, callback) | ||
105 | } | ||
106 | |||
107 | function 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 | ||
120 | function 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 | ||
125 | function removeByIds (ids, callback) { | ||
126 | VideosDB.remove({ _id: { $in: ids } }, callback) | ||
127 | } | ||
128 | |||
129 | function 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 | |||
143 | module.exports = Videos | ||
144 | |||
145 | // --------------------------------------------------------------------------- | ||
146 | |||
147 | function 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 | } |