diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-01-06 23:24:47 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-01-06 23:46:36 +0100 |
commit | ed04d94f6d7132055f97a2f757b85c03c5f2a0b6 (patch) | |
tree | 2f1f8c6f6e46b9ab18ba009403f80b14af61af68 /server/controllers | |
parent | bb0b243c92577872a5f4d98f707e078082af4d2a (diff) | |
download | PeerTube-ed04d94f6d7132055f97a2f757b85c03c5f2a0b6.tar.gz PeerTube-ed04d94f6d7132055f97a2f757b85c03c5f2a0b6.tar.zst PeerTube-ed04d94f6d7132055f97a2f757b85c03c5f2a0b6.zip |
Server: try to have a better video integrity
Diffstat (limited to 'server/controllers')
-rw-r--r-- | server/controllers/api/remote/videos.js | 56 | ||||
-rw-r--r-- | server/controllers/api/videos.js | 114 |
2 files changed, 124 insertions, 46 deletions
diff --git a/server/controllers/api/remote/videos.js b/server/controllers/api/remote/videos.js index d02da4463..6d768eae8 100644 --- a/server/controllers/api/remote/videos.js +++ b/server/controllers/api/remote/videos.js | |||
@@ -10,6 +10,7 @@ const secureMiddleware = middlewares.secure | |||
10 | const videosValidators = middlewares.validators.remote.videos | 10 | const videosValidators = middlewares.validators.remote.videos |
11 | const signatureValidators = middlewares.validators.remote.signature | 11 | const signatureValidators = middlewares.validators.remote.signature |
12 | const logger = require('../../../helpers/logger') | 12 | const logger = require('../../../helpers/logger') |
13 | const utils = require('../../../helpers/utils') | ||
13 | 14 | ||
14 | const router = express.Router() | 15 | const router = express.Router() |
15 | 16 | ||
@@ -37,11 +38,11 @@ function remoteVideos (req, res, next) { | |||
37 | 38 | ||
38 | switch (request.type) { | 39 | switch (request.type) { |
39 | case 'add': | 40 | case 'add': |
40 | addRemoteVideo(data, fromPod, callbackEach) | 41 | addRemoteVideoRetryWrapper(data, fromPod, callbackEach) |
41 | break | 42 | break |
42 | 43 | ||
43 | case 'update': | 44 | case 'update': |
44 | updateRemoteVideo(data, fromPod, callbackEach) | 45 | updateRemoteVideoRetryWrapper(data, fromPod, callbackEach) |
45 | break | 46 | break |
46 | 47 | ||
47 | case 'remove': | 48 | case 'remove': |
@@ -63,13 +64,30 @@ function remoteVideos (req, res, next) { | |||
63 | return res.type('json').status(204).end() | 64 | return res.type('json').status(204).end() |
64 | } | 65 | } |
65 | 66 | ||
67 | // Handle retries on fail | ||
68 | function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) { | ||
69 | utils.transactionRetryer( | ||
70 | function (callback) { | ||
71 | return addRemoteVideo(videoToCreateData, fromPod, callback) | ||
72 | }, | ||
73 | function (err) { | ||
74 | if (err) { | ||
75 | logger.error('Cannot insert the remote video with many retries.', { error: err }) | ||
76 | return finalCallback(err) | ||
77 | } | ||
78 | |||
79 | return finalCallback() | ||
80 | } | ||
81 | ) | ||
82 | } | ||
83 | |||
66 | function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { | 84 | function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { |
67 | logger.debug('Adding remote video "%s".', videoToCreateData.name) | 85 | logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) |
68 | 86 | ||
69 | waterfall([ | 87 | waterfall([ |
70 | 88 | ||
71 | function startTransaction (callback) { | 89 | function startTransaction (callback) { |
72 | db.sequelize.transaction().asCallback(function (err, t) { | 90 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { |
73 | return callback(err, t) | 91 | return callback(err, t) |
74 | }) | 92 | }) |
75 | }, | 93 | }, |
@@ -103,6 +121,7 @@ function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { | |||
103 | authorId: author.id, | 121 | authorId: author.id, |
104 | duration: videoToCreateData.duration, | 122 | duration: videoToCreateData.duration, |
105 | createdAt: videoToCreateData.createdAt, | 123 | createdAt: videoToCreateData.createdAt, |
124 | // FIXME: updatedAt does not seems to be considered by Sequelize | ||
106 | updatedAt: videoToCreateData.updatedAt | 125 | updatedAt: videoToCreateData.updatedAt |
107 | } | 126 | } |
108 | 127 | ||
@@ -142,7 +161,8 @@ function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { | |||
142 | 161 | ||
143 | ], function (err, t) { | 162 | ], function (err, t) { |
144 | if (err) { | 163 | if (err) { |
145 | logger.error('Cannot insert the remote video.') | 164 | // This is just a debug because we will retry the insert |
165 | logger.debug('Cannot insert the remote video.', { error: err }) | ||
146 | 166 | ||
147 | // Abort transaction? | 167 | // Abort transaction? |
148 | if (t) t.rollback() | 168 | if (t) t.rollback() |
@@ -157,8 +177,25 @@ function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { | |||
157 | }) | 177 | }) |
158 | } | 178 | } |
159 | 179 | ||
180 | // Handle retries on fail | ||
181 | function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) { | ||
182 | utils.transactionRetryer( | ||
183 | function (callback) { | ||
184 | return updateRemoteVideo(videoAttributesToUpdate, fromPod, callback) | ||
185 | }, | ||
186 | function (err) { | ||
187 | if (err) { | ||
188 | logger.error('Cannot update the remote video with many retries.', { error: err }) | ||
189 | return finalCallback(err) | ||
190 | } | ||
191 | |||
192 | return finalCallback() | ||
193 | } | ||
194 | ) | ||
195 | } | ||
196 | |||
160 | function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { | 197 | function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { |
161 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.name) | 198 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) |
162 | 199 | ||
163 | waterfall([ | 200 | waterfall([ |
164 | 201 | ||
@@ -208,7 +245,8 @@ function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { | |||
208 | 245 | ||
209 | ], function (err, t) { | 246 | ], function (err, t) { |
210 | if (err) { | 247 | if (err) { |
211 | logger.error('Cannot update the remote video.') | 248 | // This is just a debug because we will retry the insert |
249 | logger.debug('Cannot update the remote video.', { error: err }) | ||
212 | 250 | ||
213 | // Abort transaction? | 251 | // Abort transaction? |
214 | if (t) t.rollback() | 252 | if (t) t.rollback() |
@@ -238,7 +276,7 @@ function reportAbuseRemoteVideo (reportData, fromPod, callback) { | |||
238 | if (err || !video) { | 276 | if (err || !video) { |
239 | if (!err) err = new Error('video not found') | 277 | if (!err) err = new Error('video not found') |
240 | 278 | ||
241 | logger.error('Cannot load video from host and remote id.', { error: err }) | 279 | logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId }) |
242 | return callback(err) | 280 | return callback(err) |
243 | } | 281 | } |
244 | 282 | ||
@@ -260,7 +298,7 @@ function fetchVideo (podHost, remoteId, callback) { | |||
260 | if (err || !video) { | 298 | if (err || !video) { |
261 | if (!err) err = new Error('video not found') | 299 | if (!err) err = new Error('video not found') |
262 | 300 | ||
263 | logger.error('Cannot load video from host and remote id.', { error: err }) | 301 | logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) |
264 | return callback(err) | 302 | return callback(err) |
265 | } | 303 | } |
266 | 304 | ||
diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js index 6829804ec..4d45c11c0 100644 --- a/server/controllers/api/videos.js +++ b/server/controllers/api/videos.js | |||
@@ -70,13 +70,13 @@ router.put('/:id', | |||
70 | oAuth.authenticate, | 70 | oAuth.authenticate, |
71 | reqFiles, | 71 | reqFiles, |
72 | validatorsVideos.videosUpdate, | 72 | validatorsVideos.videosUpdate, |
73 | updateVideo | 73 | updateVideoRetryWrapper |
74 | ) | 74 | ) |
75 | router.post('/', | 75 | router.post('/', |
76 | oAuth.authenticate, | 76 | oAuth.authenticate, |
77 | reqFiles, | 77 | reqFiles, |
78 | validatorsVideos.videosAdd, | 78 | validatorsVideos.videosAdd, |
79 | addVideo | 79 | addVideoRetryWrapper |
80 | ) | 80 | ) |
81 | router.get('/:id', | 81 | router.get('/:id', |
82 | validatorsVideos.videosGet, | 82 | validatorsVideos.videosGet, |
@@ -103,19 +103,37 @@ module.exports = router | |||
103 | 103 | ||
104 | // --------------------------------------------------------------------------- | 104 | // --------------------------------------------------------------------------- |
105 | 105 | ||
106 | function addVideo (req, res, next) { | 106 | // Wrapper to video add that retry the function if there is a database error |
107 | const videoFile = req.files.videofile[0] | 107 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail |
108 | function addVideoRetryWrapper (req, res, next) { | ||
109 | utils.transactionRetryer( | ||
110 | function (callback) { | ||
111 | return addVideo(req, res, req.files.videofile[0], callback) | ||
112 | }, | ||
113 | function (err) { | ||
114 | if (err) { | ||
115 | logger.error('Cannot insert the video with many retries.', { error: err }) | ||
116 | return next(err) | ||
117 | } | ||
118 | |||
119 | // TODO : include Location of the new video -> 201 | ||
120 | return res.type('json').status(204).end() | ||
121 | } | ||
122 | ) | ||
123 | } | ||
124 | |||
125 | function addVideo (req, res, videoFile, callback) { | ||
108 | const videoInfos = req.body | 126 | const videoInfos = req.body |
109 | 127 | ||
110 | waterfall([ | 128 | waterfall([ |
111 | 129 | ||
112 | function startTransaction (callback) { | 130 | function startTransaction (callbackWaterfall) { |
113 | db.sequelize.transaction().asCallback(function (err, t) { | 131 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { |
114 | return callback(err, t) | 132 | return callbackWaterfall(err, t) |
115 | }) | 133 | }) |
116 | }, | 134 | }, |
117 | 135 | ||
118 | function findOrCreateAuthor (t, callback) { | 136 | function findOrCreateAuthor (t, callbackWaterfall) { |
119 | const user = res.locals.oauth.token.User | 137 | const user = res.locals.oauth.token.User |
120 | 138 | ||
121 | const name = user.username | 139 | const name = user.username |
@@ -124,19 +142,19 @@ function addVideo (req, res, next) { | |||
124 | const userId = user.id | 142 | const userId = user.id |
125 | 143 | ||
126 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { | 144 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { |
127 | return callback(err, t, authorInstance) | 145 | return callbackWaterfall(err, t, authorInstance) |
128 | }) | 146 | }) |
129 | }, | 147 | }, |
130 | 148 | ||
131 | function findOrCreateTags (t, author, callback) { | 149 | function findOrCreateTags (t, author, callbackWaterfall) { |
132 | const tags = videoInfos.tags | 150 | const tags = videoInfos.tags |
133 | 151 | ||
134 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | 152 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { |
135 | return callback(err, t, author, tagInstances) | 153 | return callbackWaterfall(err, t, author, tagInstances) |
136 | }) | 154 | }) |
137 | }, | 155 | }, |
138 | 156 | ||
139 | function createVideoObject (t, author, tagInstances, callback) { | 157 | function createVideoObject (t, author, tagInstances, callbackWaterfall) { |
140 | const videoData = { | 158 | const videoData = { |
141 | name: videoInfos.name, | 159 | name: videoInfos.name, |
142 | remoteId: null, | 160 | remoteId: null, |
@@ -148,74 +166,97 @@ function addVideo (req, res, next) { | |||
148 | 166 | ||
149 | const video = db.Video.build(videoData) | 167 | const video = db.Video.build(videoData) |
150 | 168 | ||
151 | return callback(null, t, author, tagInstances, video) | 169 | return callbackWaterfall(null, t, author, tagInstances, video) |
152 | }, | 170 | }, |
153 | 171 | ||
154 | // Set the videoname the same as the id | 172 | // Set the videoname the same as the id |
155 | function renameVideoFile (t, author, tagInstances, video, callback) { | 173 | function renameVideoFile (t, author, tagInstances, video, callbackWaterfall) { |
156 | const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR | 174 | const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR |
157 | const source = path.join(videoDir, videoFile.filename) | 175 | const source = path.join(videoDir, videoFile.filename) |
158 | const destination = path.join(videoDir, video.getVideoFilename()) | 176 | const destination = path.join(videoDir, video.getVideoFilename()) |
159 | 177 | ||
160 | fs.rename(source, destination, function (err) { | 178 | fs.rename(source, destination, function (err) { |
161 | return callback(err, t, author, tagInstances, video) | 179 | if (err) return callbackWaterfall(err) |
180 | |||
181 | // This is important in case if there is another attempt | ||
182 | videoFile.filename = video.getVideoFilename() | ||
183 | return callbackWaterfall(null, t, author, tagInstances, video) | ||
162 | }) | 184 | }) |
163 | }, | 185 | }, |
164 | 186 | ||
165 | function insertVideoIntoDB (t, author, tagInstances, video, callback) { | 187 | function insertVideoIntoDB (t, author, tagInstances, video, callbackWaterfall) { |
166 | const options = { transaction: t } | 188 | const options = { transaction: t } |
167 | 189 | ||
168 | // Add tags association | 190 | // Add tags association |
169 | video.save(options).asCallback(function (err, videoCreated) { | 191 | video.save(options).asCallback(function (err, videoCreated) { |
170 | if (err) return callback(err) | 192 | if (err) return callbackWaterfall(err) |
171 | 193 | ||
172 | // Do not forget to add Author informations to the created video | 194 | // Do not forget to add Author informations to the created video |
173 | videoCreated.Author = author | 195 | videoCreated.Author = author |
174 | 196 | ||
175 | return callback(err, t, tagInstances, videoCreated) | 197 | return callbackWaterfall(err, t, tagInstances, videoCreated) |
176 | }) | 198 | }) |
177 | }, | 199 | }, |
178 | 200 | ||
179 | function associateTagsToVideo (t, tagInstances, video, callback) { | 201 | function associateTagsToVideo (t, tagInstances, video, callbackWaterfall) { |
180 | const options = { transaction: t } | 202 | const options = { transaction: t } |
181 | 203 | ||
182 | video.setTags(tagInstances, options).asCallback(function (err) { | 204 | video.setTags(tagInstances, options).asCallback(function (err) { |
183 | video.Tags = tagInstances | 205 | video.Tags = tagInstances |
184 | 206 | ||
185 | return callback(err, t, video) | 207 | return callbackWaterfall(err, t, video) |
186 | }) | 208 | }) |
187 | }, | 209 | }, |
188 | 210 | ||
189 | function sendToFriends (t, video, callback) { | 211 | function sendToFriends (t, video, callbackWaterfall) { |
190 | video.toAddRemoteJSON(function (err, remoteVideo) { | 212 | video.toAddRemoteJSON(function (err, remoteVideo) { |
191 | if (err) return callback(err) | 213 | if (err) return callbackWaterfall(err) |
192 | 214 | ||
193 | // Now we'll add the video's meta data to our friends | 215 | // Now we'll add the video's meta data to our friends |
194 | friends.addVideoToFriends(remoteVideo) | 216 | friends.addVideoToFriends(remoteVideo, t, function (err) { |
195 | 217 | return callbackWaterfall(err, t) | |
196 | return callback(null, t) | 218 | }) |
197 | }) | 219 | }) |
198 | } | 220 | } |
199 | 221 | ||
200 | ], function andFinally (err, t) { | 222 | ], function andFinally (err, t) { |
201 | if (err) { | 223 | if (err) { |
202 | logger.error('Cannot insert the video.') | 224 | // This is just a debug because we will retry the insert |
225 | logger.debug('Cannot insert the video.', { error: err }) | ||
203 | 226 | ||
204 | // Abort transaction? | 227 | // Abort transaction? |
205 | if (t) t.rollback() | 228 | if (t) t.rollback() |
206 | 229 | ||
207 | return next(err) | 230 | return callback(err) |
208 | } | 231 | } |
209 | 232 | ||
210 | // Commit transaction | 233 | // Commit transaction |
211 | t.commit() | 234 | t.commit() |
212 | 235 | ||
213 | // TODO : include Location of the new video -> 201 | 236 | logger.info('Video with name %s created.', videoInfos.name) |
214 | return res.type('json').status(204).end() | 237 | |
238 | return callback(null) | ||
215 | }) | 239 | }) |
216 | } | 240 | } |
217 | 241 | ||
218 | function updateVideo (req, res, next) { | 242 | function updateVideoRetryWrapper (req, res, next) { |
243 | utils.transactionRetryer( | ||
244 | function (callback) { | ||
245 | return updateVideo(req, res, callback) | ||
246 | }, | ||
247 | function (err) { | ||
248 | if (err) { | ||
249 | logger.error('Cannot update the video with many retries.', { error: err }) | ||
250 | return next(err) | ||
251 | } | ||
252 | |||
253 | // TODO : include Location of the new video -> 201 | ||
254 | return res.type('json').status(204).end() | ||
255 | } | ||
256 | ) | ||
257 | } | ||
258 | |||
259 | function updateVideo (req, res, finalCallback) { | ||
219 | const videoInstance = res.locals.video | 260 | const videoInstance = res.locals.video |
220 | const videoInfosToUpdate = req.body | 261 | const videoInfosToUpdate = req.body |
221 | 262 | ||
@@ -267,26 +308,25 @@ function updateVideo (req, res, next) { | |||
267 | const json = videoInstance.toUpdateRemoteJSON() | 308 | const json = videoInstance.toUpdateRemoteJSON() |
268 | 309 | ||
269 | // Now we'll update the video's meta data to our friends | 310 | // Now we'll update the video's meta data to our friends |
270 | friends.updateVideoToFriends(json) | 311 | friends.updateVideoToFriends(json, t, function (err) { |
271 | 312 | return callback(err, t) | |
272 | return callback(null, t) | 313 | }) |
273 | } | 314 | } |
274 | 315 | ||
275 | ], function andFinally (err, t) { | 316 | ], function andFinally (err, t) { |
276 | if (err) { | 317 | if (err) { |
277 | logger.error('Cannot insert the video.') | 318 | logger.debug('Cannot update the video.', { error: err }) |
278 | 319 | ||
279 | // Abort transaction? | 320 | // Abort transaction? |
280 | if (t) t.rollback() | 321 | if (t) t.rollback() |
281 | 322 | ||
282 | return next(err) | 323 | return finalCallback(err) |
283 | } | 324 | } |
284 | 325 | ||
285 | // Commit transaction | 326 | // Commit transaction |
286 | t.commit() | 327 | t.commit() |
287 | 328 | ||
288 | // TODO : include Location of the new video -> 201 | 329 | return finalCallback(null) |
289 | return res.type('json').status(204).end() | ||
290 | }) | 330 | }) |
291 | } | 331 | } |
292 | 332 | ||