aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api')
-rw-r--r--server/controllers/api/remote/videos.js56
-rw-r--r--server/controllers/api/videos.js114
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
10const videosValidators = middlewares.validators.remote.videos 10const videosValidators = middlewares.validators.remote.videos
11const signatureValidators = middlewares.validators.remote.signature 11const signatureValidators = middlewares.validators.remote.signature
12const logger = require('../../../helpers/logger') 12const logger = require('../../../helpers/logger')
13const utils = require('../../../helpers/utils')
13 14
14const router = express.Router() 15const 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
68function 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
66function addRemoteVideo (videoToCreateData, fromPod, finalCallback) { 84function 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
181function 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
160function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) { 197function 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)
75router.post('/', 75router.post('/',
76 oAuth.authenticate, 76 oAuth.authenticate,
77 reqFiles, 77 reqFiles,
78 validatorsVideos.videosAdd, 78 validatorsVideos.videosAdd,
79 addVideo 79 addVideoRetryWrapper
80) 80)
81router.get('/:id', 81router.get('/:id',
82 validatorsVideos.videosGet, 82 validatorsVideos.videosGet,
@@ -103,19 +103,37 @@ module.exports = router
103 103
104// --------------------------------------------------------------------------- 104// ---------------------------------------------------------------------------
105 105
106function 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
108function 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
125function 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
218function updateVideo (req, res, next) { 242function 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
259function 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