diff options
Diffstat (limited to 'server/controllers/api/remote/videos.ts')
-rw-r--r-- | server/controllers/api/remote/videos.ts | 528 |
1 files changed, 204 insertions, 324 deletions
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts index d9cc08fb4..ebe4eca36 100644 --- a/server/controllers/api/remote/videos.ts +++ b/server/controllers/api/remote/videos.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Sequelize from 'sequelize' | 2 | import * as Promise from 'bluebird' |
3 | import { eachSeries, waterfall } from 'async' | ||
4 | 3 | ||
5 | import { database as db } from '../../../initializers/database' | 4 | import { database as db } from '../../../initializers/database' |
6 | import { | 5 | import { |
@@ -16,20 +15,14 @@ import { | |||
16 | remoteQaduVideosValidator, | 15 | remoteQaduVideosValidator, |
17 | remoteEventsVideosValidator | 16 | remoteEventsVideosValidator |
18 | } from '../../../middlewares' | 17 | } from '../../../middlewares' |
19 | import { | 18 | import { logger, retryTransactionWrapper } from '../../../helpers' |
20 | logger, | ||
21 | commitTransaction, | ||
22 | retryTransactionWrapper, | ||
23 | rollbackTransaction, | ||
24 | startSerializableTransaction | ||
25 | } from '../../../helpers' | ||
26 | import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' | 19 | import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' |
27 | import { PodInstance, VideoInstance } from '../../../models' | 20 | import { PodInstance, VideoInstance } from '../../../models' |
28 | 21 | ||
29 | const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] | 22 | const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] |
30 | 23 | ||
31 | // Functions to call when processing a remote request | 24 | // Functions to call when processing a remote request |
32 | const functionsHash = {} | 25 | const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {} |
33 | functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper | 26 | functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper |
34 | functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper | 27 | functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper |
35 | functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo | 28 | functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo |
@@ -72,20 +65,19 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres | |||
72 | 65 | ||
73 | // We need to process in the same order to keep consistency | 66 | // We need to process in the same order to keep consistency |
74 | // TODO: optimization | 67 | // TODO: optimization |
75 | eachSeries(requests, function (request: any, callbackEach) { | 68 | Promise.mapSeries(requests, (request: any) => { |
76 | const data = request.data | 69 | const data = request.data |
77 | 70 | ||
78 | // Get the function we need to call in order to process the request | 71 | // Get the function we need to call in order to process the request |
79 | const fun = functionsHash[request.type] | 72 | const fun = functionsHash[request.type] |
80 | if (fun === undefined) { | 73 | if (fun === undefined) { |
81 | logger.error('Unkown remote request type %s.', request.type) | 74 | logger.error('Unkown remote request type %s.', request.type) |
82 | return callbackEach(null) | 75 | return |
83 | } | 76 | } |
84 | 77 | ||
85 | fun.call(this, data, fromPod, callbackEach) | 78 | return fun.call(this, data, fromPod) |
86 | }, function (err) { | ||
87 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
88 | }) | 79 | }) |
80 | .catch(err => logger.error('Error managing remote videos.', { error: err })) | ||
89 | 81 | ||
90 | // We don't need to keep the other pod waiting | 82 | // We don't need to keep the other pod waiting |
91 | return res.type('json').status(204).end() | 83 | return res.type('json').status(204).end() |
@@ -95,13 +87,12 @@ function remoteVideosQadu (req: express.Request, res: express.Response, next: ex | |||
95 | const requests = req.body.data | 87 | const requests = req.body.data |
96 | const fromPod = res.locals.secure.pod | 88 | const fromPod = res.locals.secure.pod |
97 | 89 | ||
98 | eachSeries(requests, function (request: any, callbackEach) { | 90 | Promise.mapSeries(requests, (request: any) => { |
99 | const videoData = request.data | 91 | const videoData = request.data |
100 | 92 | ||
101 | quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod, callbackEach) | 93 | return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod) |
102 | }, function (err) { | ||
103 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
104 | }) | 94 | }) |
95 | .catch(err => logger.error('Error managing remote videos.', { error: err })) | ||
105 | 96 | ||
106 | return res.type('json').status(204).end() | 97 | return res.type('json').status(204).end() |
107 | } | 98 | } |
@@ -110,414 +101,303 @@ function remoteVideosEvents (req: express.Request, res: express.Response, next: | |||
110 | const requests = req.body.data | 101 | const requests = req.body.data |
111 | const fromPod = res.locals.secure.pod | 102 | const fromPod = res.locals.secure.pod |
112 | 103 | ||
113 | eachSeries(requests, function (request: any, callbackEach) { | 104 | Promise.mapSeries(requests, (request: any) => { |
114 | const eventData = request.data | 105 | const eventData = request.data |
115 | 106 | ||
116 | processVideosEventsRetryWrapper(eventData, fromPod, callbackEach) | 107 | return processVideosEventsRetryWrapper(eventData, fromPod) |
117 | }, function (err) { | ||
118 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
119 | }) | 108 | }) |
109 | .catch(err => logger.error('Error managing remote videos.', { error: err })) | ||
120 | 110 | ||
121 | return res.type('json').status(204).end() | 111 | return res.type('json').status(204).end() |
122 | } | 112 | } |
123 | 113 | ||
124 | function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 114 | function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance) { |
125 | const options = { | 115 | const options = { |
126 | arguments: [ eventData, fromPod ], | 116 | arguments: [ eventData, fromPod ], |
127 | errorMessage: 'Cannot process videos events with many retries.' | 117 | errorMessage: 'Cannot process videos events with many retries.' |
128 | } | 118 | } |
129 | 119 | ||
130 | retryTransactionWrapper(processVideosEvents, options, finalCallback) | 120 | return retryTransactionWrapper(processVideosEvents, options) |
131 | } | 121 | } |
132 | 122 | ||
133 | function processVideosEvents (eventData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 123 | function processVideosEvents (eventData: any, fromPod: PodInstance) { |
134 | waterfall([ | ||
135 | startSerializableTransaction, | ||
136 | |||
137 | function findVideo (t, callback) { | ||
138 | fetchOwnedVideo(eventData.remoteId, function (err, videoInstance) { | ||
139 | return callback(err, t, videoInstance) | ||
140 | }) | ||
141 | }, | ||
142 | 124 | ||
143 | function updateVideoIntoDB (t, videoInstance, callback) { | 125 | return db.sequelize.transaction(t => { |
144 | const options = { transaction: t } | 126 | return fetchOwnedVideo(eventData.remoteId) |
127 | .then(videoInstance => { | ||
128 | const options = { transaction: t } | ||
145 | 129 | ||
146 | let columnToUpdate | 130 | let columnToUpdate |
147 | let qaduType | 131 | let qaduType |
148 | 132 | ||
149 | switch (eventData.eventType) { | 133 | switch (eventData.eventType) { |
150 | case REQUEST_VIDEO_EVENT_TYPES.VIEWS: | 134 | case REQUEST_VIDEO_EVENT_TYPES.VIEWS: |
151 | columnToUpdate = 'views' | 135 | columnToUpdate = 'views' |
152 | qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS | 136 | qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS |
153 | break | 137 | break |
154 | 138 | ||
155 | case REQUEST_VIDEO_EVENT_TYPES.LIKES: | 139 | case REQUEST_VIDEO_EVENT_TYPES.LIKES: |
156 | columnToUpdate = 'likes' | 140 | columnToUpdate = 'likes' |
157 | qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES | 141 | qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES |
158 | break | 142 | break |
159 | 143 | ||
160 | case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: | 144 | case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: |
161 | columnToUpdate = 'dislikes' | 145 | columnToUpdate = 'dislikes' |
162 | qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES | 146 | qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES |
163 | break | 147 | break |
164 | 148 | ||
165 | default: | 149 | default: |
166 | return callback(new Error('Unknown video event type.')) | 150 | throw new Error('Unknown video event type.') |
167 | } | 151 | } |
168 | 152 | ||
169 | const query = {} | 153 | const query = {} |
170 | query[columnToUpdate] = eventData.count | 154 | query[columnToUpdate] = eventData.count |
171 | 155 | ||
172 | videoInstance.increment(query, options).asCallback(function (err) { | 156 | return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType })) |
173 | return callback(err, t, videoInstance, qaduType) | ||
174 | }) | 157 | }) |
175 | }, | 158 | .then(({ videoInstance, qaduType }) => { |
176 | 159 | const qadusParams = [ | |
177 | function sendQaduToFriends (t, videoInstance, qaduType, callback) { | 160 | { |
178 | const qadusParams = [ | 161 | videoId: videoInstance.id, |
179 | { | 162 | type: qaduType |
180 | videoId: videoInstance.id, | 163 | } |
181 | type: qaduType | 164 | ] |
182 | } | 165 | |
183 | ] | 166 | return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) |
184 | |||
185 | quickAndDirtyUpdatesVideoToFriends(qadusParams, t, function (err) { | ||
186 | return callback(err, t) | ||
187 | }) | 167 | }) |
188 | }, | 168 | }) |
189 | 169 | .then(() => logger.info('Remote video event processed for video %s.', eventData.remoteId)) | |
190 | commitTransaction | 170 | .catch(err => { |
191 | 171 | logger.debug('Cannot process a video event.', { error: err }) | |
192 | ], function (err: Error, t: Sequelize.Transaction) { | 172 | throw err |
193 | if (err) { | ||
194 | logger.debug('Cannot process a video event.', { error: err }) | ||
195 | return rollbackTransaction(err, t, finalCallback) | ||
196 | } | ||
197 | |||
198 | logger.info('Remote video event processed for video %s.', eventData.remoteId) | ||
199 | return finalCallback(null) | ||
200 | }) | 173 | }) |
201 | } | 174 | } |
202 | 175 | ||
203 | function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 176 | function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance) { |
204 | const options = { | 177 | const options = { |
205 | arguments: [ videoData, fromPod ], | 178 | arguments: [ videoData, fromPod ], |
206 | errorMessage: 'Cannot update quick and dirty the remote video with many retries.' | 179 | errorMessage: 'Cannot update quick and dirty the remote video with many retries.' |
207 | } | 180 | } |
208 | 181 | ||
209 | retryTransactionWrapper(quickAndDirtyUpdateVideo, options, finalCallback) | 182 | return retryTransactionWrapper(quickAndDirtyUpdateVideo, options) |
210 | } | 183 | } |
211 | 184 | ||
212 | function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 185 | function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance) { |
213 | let videoName | 186 | let videoName |
214 | 187 | ||
215 | waterfall([ | 188 | return db.sequelize.transaction(t => { |
216 | startSerializableTransaction, | 189 | return fetchRemoteVideo(fromPod.host, videoData.remoteId) |
217 | 190 | .then(videoInstance => { | |
218 | function findVideo (t, callback) { | 191 | const options = { transaction: t } |
219 | fetchRemoteVideo(fromPod.host, videoData.remoteId, function (err, videoInstance) { | ||
220 | return callback(err, t, videoInstance) | ||
221 | }) | ||
222 | }, | ||
223 | |||
224 | function updateVideoIntoDB (t, videoInstance, callback) { | ||
225 | const options = { transaction: t } | ||
226 | 192 | ||
227 | videoName = videoInstance.name | 193 | videoName = videoInstance.name |
228 | 194 | ||
229 | if (videoData.views) { | 195 | if (videoData.views) { |
230 | videoInstance.set('views', videoData.views) | 196 | videoInstance.set('views', videoData.views) |
231 | } | 197 | } |
232 | 198 | ||
233 | if (videoData.likes) { | 199 | if (videoData.likes) { |
234 | videoInstance.set('likes', videoData.likes) | 200 | videoInstance.set('likes', videoData.likes) |
235 | } | 201 | } |
236 | 202 | ||
237 | if (videoData.dislikes) { | 203 | if (videoData.dislikes) { |
238 | videoInstance.set('dislikes', videoData.dislikes) | 204 | videoInstance.set('dislikes', videoData.dislikes) |
239 | } | 205 | } |
240 | 206 | ||
241 | videoInstance.save(options).asCallback(function (err) { | 207 | return videoInstance.save(options) |
242 | return callback(err, t) | ||
243 | }) | 208 | }) |
244 | }, | ||
245 | |||
246 | commitTransaction | ||
247 | |||
248 | ], function (err: Error, t: Sequelize.Transaction) { | ||
249 | if (err) { | ||
250 | logger.debug('Cannot quick and dirty update the remote video.', { error: err }) | ||
251 | return rollbackTransaction(err, t, finalCallback) | ||
252 | } | ||
253 | |||
254 | logger.info('Remote video %s quick and dirty updated', videoName) | ||
255 | return finalCallback(null) | ||
256 | }) | 209 | }) |
210 | .then(() => logger.info('Remote video %s quick and dirty updated', videoName)) | ||
211 | .catch(err => logger.debug('Cannot quick and dirty update the remote video.', { error: err })) | ||
257 | } | 212 | } |
258 | 213 | ||
259 | // Handle retries on fail | 214 | // Handle retries on fail |
260 | function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 215 | function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance) { |
261 | const options = { | 216 | const options = { |
262 | arguments: [ videoToCreateData, fromPod ], | 217 | arguments: [ videoToCreateData, fromPod ], |
263 | errorMessage: 'Cannot insert the remote video with many retries.' | 218 | errorMessage: 'Cannot insert the remote video with many retries.' |
264 | } | 219 | } |
265 | 220 | ||
266 | retryTransactionWrapper(addRemoteVideo, options, finalCallback) | 221 | return retryTransactionWrapper(addRemoteVideo, options) |
267 | } | 222 | } |
268 | 223 | ||
269 | function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 224 | function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance) { |
270 | logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) | 225 | logger.debug('Adding remote video "%s".', videoToCreateData.remoteId) |
271 | 226 | ||
272 | waterfall([ | 227 | return db.sequelize.transaction(t => { |
273 | 228 | return db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId) | |
274 | startSerializableTransaction, | 229 | .then(video => { |
275 | 230 | if (video) throw new Error('RemoteId and host pair is not unique.') | |
276 | function assertRemoteIdAndHostUnique (t, callback) { | ||
277 | db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) { | ||
278 | if (err) return callback(err) | ||
279 | 231 | ||
280 | if (video) return callback(new Error('RemoteId and host pair is not unique.')) | 232 | return undefined |
281 | |||
282 | return callback(null, t) | ||
283 | }) | 233 | }) |
284 | }, | 234 | .then(() => { |
285 | 235 | const name = videoToCreateData.author | |
286 | function findOrCreateAuthor (t, callback) { | 236 | const podId = fromPod.id |
287 | const name = videoToCreateData.author | 237 | // This author is from another pod so we do not associate a user |
288 | const podId = fromPod.id | 238 | const userId = null |
289 | // This author is from another pod so we do not associate a user | ||
290 | const userId = null | ||
291 | 239 | ||
292 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { | 240 | return db.Author.findOrCreateAuthor(name, podId, userId, t) |
293 | return callback(err, t, authorInstance) | ||
294 | }) | 241 | }) |
295 | }, | 242 | .then(author => { |
243 | const tags = videoToCreateData.tags | ||
296 | 244 | ||
297 | function findOrCreateTags (t, author, callback) { | 245 | return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances })) |
298 | const tags = videoToCreateData.tags | ||
299 | |||
300 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | ||
301 | return callback(err, t, author, tagInstances) | ||
302 | }) | 246 | }) |
303 | }, | 247 | .then(({ author, tagInstances }) => { |
304 | 248 | const videoData = { | |
305 | function createVideoObject (t, author, tagInstances, callback) { | 249 | name: videoToCreateData.name, |
306 | const videoData = { | 250 | remoteId: videoToCreateData.remoteId, |
307 | name: videoToCreateData.name, | 251 | extname: videoToCreateData.extname, |
308 | remoteId: videoToCreateData.remoteId, | 252 | infoHash: videoToCreateData.infoHash, |
309 | extname: videoToCreateData.extname, | 253 | category: videoToCreateData.category, |
310 | infoHash: videoToCreateData.infoHash, | 254 | licence: videoToCreateData.licence, |
311 | category: videoToCreateData.category, | 255 | language: videoToCreateData.language, |
312 | licence: videoToCreateData.licence, | 256 | nsfw: videoToCreateData.nsfw, |
313 | language: videoToCreateData.language, | 257 | description: videoToCreateData.description, |
314 | nsfw: videoToCreateData.nsfw, | 258 | authorId: author.id, |
315 | description: videoToCreateData.description, | 259 | duration: videoToCreateData.duration, |
316 | authorId: author.id, | 260 | createdAt: videoToCreateData.createdAt, |
317 | duration: videoToCreateData.duration, | 261 | // FIXME: updatedAt does not seems to be considered by Sequelize |
318 | createdAt: videoToCreateData.createdAt, | 262 | updatedAt: videoToCreateData.updatedAt, |
319 | // FIXME: updatedAt does not seems to be considered by Sequelize | 263 | views: videoToCreateData.views, |
320 | updatedAt: videoToCreateData.updatedAt, | 264 | likes: videoToCreateData.likes, |
321 | views: videoToCreateData.views, | 265 | dislikes: videoToCreateData.dislikes |
322 | likes: videoToCreateData.likes, | ||
323 | dislikes: videoToCreateData.dislikes | ||
324 | } | ||
325 | |||
326 | const video = db.Video.build(videoData) | ||
327 | |||
328 | return callback(null, t, tagInstances, video) | ||
329 | }, | ||
330 | |||
331 | function generateThumbnail (t, tagInstances, video, callback) { | ||
332 | db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) { | ||
333 | if (err) { | ||
334 | logger.error('Cannot generate thumbnail from data.', { error: err }) | ||
335 | return callback(err) | ||
336 | } | 266 | } |
337 | 267 | ||
338 | return callback(err, t, tagInstances, video) | 268 | const video = db.Video.build(videoData) |
269 | return { tagInstances, video } | ||
339 | }) | 270 | }) |
340 | }, | 271 | .then(({ tagInstances, video }) => { |
341 | 272 | return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video })) | |
342 | function insertVideoIntoDB (t, tagInstances, video, callback) { | ||
343 | const options = { | ||
344 | transaction: t | ||
345 | } | ||
346 | |||
347 | video.save(options).asCallback(function (err, videoCreated) { | ||
348 | return callback(err, t, tagInstances, videoCreated) | ||
349 | }) | 273 | }) |
350 | }, | 274 | .then(({ tagInstances, video }) => { |
351 | 275 | const options = { | |
352 | function associateTagsToVideo (t, tagInstances, video, callback) { | 276 | transaction: t |
353 | const options = { | 277 | } |
354 | transaction: t | ||
355 | } | ||
356 | 278 | ||
357 | video.setTags(tagInstances, options).asCallback(function (err) { | 279 | return video.save(options).then(videoCreated => ({ tagInstances, videoCreated })) |
358 | return callback(err, t) | ||
359 | }) | 280 | }) |
360 | }, | 281 | .then(({ tagInstances, videoCreated }) => { |
361 | 282 | const options = { | |
362 | commitTransaction | 283 | transaction: t |
363 | 284 | } | |
364 | ], function (err: Error, t: Sequelize.Transaction) { | ||
365 | if (err) { | ||
366 | // This is just a debug because we will retry the insert | ||
367 | logger.debug('Cannot insert the remote video.', { error: err }) | ||
368 | return rollbackTransaction(err, t, finalCallback) | ||
369 | } | ||
370 | 285 | ||
371 | logger.info('Remote video %s inserted.', videoToCreateData.name) | 286 | return videoCreated.setTags(tagInstances, options) |
372 | return finalCallback(null) | 287 | }) |
288 | }) | ||
289 | .then(() => logger.info('Remote video %s inserted.', videoToCreateData.name)) | ||
290 | .catch(err => { | ||
291 | logger.debug('Cannot insert the remote video.', { error: err }) | ||
292 | throw err | ||
373 | }) | 293 | }) |
374 | } | 294 | } |
375 | 295 | ||
376 | // Handle retries on fail | 296 | // Handle retries on fail |
377 | function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 297 | function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance) { |
378 | const options = { | 298 | const options = { |
379 | arguments: [ videoAttributesToUpdate, fromPod ], | 299 | arguments: [ videoAttributesToUpdate, fromPod ], |
380 | errorMessage: 'Cannot update the remote video with many retries' | 300 | errorMessage: 'Cannot update the remote video with many retries' |
381 | } | 301 | } |
382 | 302 | ||
383 | retryTransactionWrapper(updateRemoteVideo, options, finalCallback) | 303 | return retryTransactionWrapper(updateRemoteVideo, options) |
384 | } | 304 | } |
385 | 305 | ||
386 | function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance, finalCallback: (err: Error) => void) { | 306 | function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance) { |
387 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) | 307 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId) |
388 | 308 | ||
389 | waterfall([ | 309 | return db.sequelize.transaction(t => { |
310 | return fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId) | ||
311 | .then(videoInstance => { | ||
312 | const tags = videoAttributesToUpdate.tags | ||
390 | 313 | ||
391 | startSerializableTransaction, | 314 | return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances })) |
392 | |||
393 | function findVideo (t, callback) { | ||
394 | fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) { | ||
395 | return callback(err, t, videoInstance) | ||
396 | }) | 315 | }) |
397 | }, | 316 | .then(({ videoInstance, tagInstances }) => { |
398 | 317 | const options = { transaction: t } | |
399 | function findOrCreateTags (t, videoInstance, callback) { | 318 | |
400 | const tags = videoAttributesToUpdate.tags | 319 | videoInstance.set('name', videoAttributesToUpdate.name) |
401 | 320 | videoInstance.set('category', videoAttributesToUpdate.category) | |
402 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | 321 | videoInstance.set('licence', videoAttributesToUpdate.licence) |
403 | return callback(err, t, videoInstance, tagInstances) | 322 | videoInstance.set('language', videoAttributesToUpdate.language) |
404 | }) | 323 | videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) |
405 | }, | 324 | videoInstance.set('description', videoAttributesToUpdate.description) |
406 | 325 | videoInstance.set('infoHash', videoAttributesToUpdate.infoHash) | |
407 | function updateVideoIntoDB (t, videoInstance, tagInstances, callback) { | 326 | videoInstance.set('duration', videoAttributesToUpdate.duration) |
408 | const options = { transaction: t } | 327 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) |
409 | 328 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) | |
410 | videoInstance.set('name', videoAttributesToUpdate.name) | 329 | videoInstance.set('extname', videoAttributesToUpdate.extname) |
411 | videoInstance.set('category', videoAttributesToUpdate.category) | 330 | videoInstance.set('views', videoAttributesToUpdate.views) |
412 | videoInstance.set('licence', videoAttributesToUpdate.licence) | 331 | videoInstance.set('likes', videoAttributesToUpdate.likes) |
413 | videoInstance.set('language', videoAttributesToUpdate.language) | 332 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) |
414 | videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) | 333 | |
415 | videoInstance.set('description', videoAttributesToUpdate.description) | 334 | return videoInstance.save(options).then(() => ({ videoInstance, tagInstances })) |
416 | videoInstance.set('infoHash', videoAttributesToUpdate.infoHash) | ||
417 | videoInstance.set('duration', videoAttributesToUpdate.duration) | ||
418 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) | ||
419 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) | ||
420 | videoInstance.set('extname', videoAttributesToUpdate.extname) | ||
421 | videoInstance.set('views', videoAttributesToUpdate.views) | ||
422 | videoInstance.set('likes', videoAttributesToUpdate.likes) | ||
423 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) | ||
424 | |||
425 | videoInstance.save(options).asCallback(function (err) { | ||
426 | return callback(err, t, videoInstance, tagInstances) | ||
427 | }) | 335 | }) |
428 | }, | 336 | .then(({ videoInstance, tagInstances }) => { |
429 | 337 | const options = { transaction: t } | |
430 | function associateTagsToVideo (t, videoInstance, tagInstances, callback) { | ||
431 | const options = { transaction: t } | ||
432 | 338 | ||
433 | videoInstance.setTags(tagInstances, options).asCallback(function (err) { | 339 | return videoInstance.setTags(tagInstances, options) |
434 | return callback(err, t) | ||
435 | }) | 340 | }) |
436 | }, | 341 | }) |
437 | 342 | .then(() => logger.info('Remote video %s updated', videoAttributesToUpdate.name)) | |
438 | commitTransaction | 343 | .catch(err => { |
439 | 344 | // This is just a debug because we will retry the insert | |
440 | ], function (err: Error, t: Sequelize.Transaction) { | 345 | logger.debug('Cannot update the remote video.', { error: err }) |
441 | if (err) { | 346 | throw err |
442 | // This is just a debug because we will retry the insert | ||
443 | logger.debug('Cannot update the remote video.', { error: err }) | ||
444 | return rollbackTransaction(err, t, finalCallback) | ||
445 | } | ||
446 | |||
447 | logger.info('Remote video %s updated', videoAttributesToUpdate.name) | ||
448 | return finalCallback(null) | ||
449 | }) | 347 | }) |
450 | } | 348 | } |
451 | 349 | ||
452 | function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance, callback: (err: Error) => void) { | 350 | function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance) { |
453 | // We need the instance because we have to remove some other stuffs (thumbnail etc) | 351 | // We need the instance because we have to remove some other stuffs (thumbnail etc) |
454 | fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) { | 352 | return fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId) |
455 | // Do not return the error, continue the process | 353 | .then(video => { |
456 | if (err) return callback(null) | 354 | logger.debug('Removing remote video %s.', video.remoteId) |
457 | 355 | return video.destroy() | |
458 | logger.debug('Removing remote video %s.', video.remoteId) | 356 | }) |
459 | video.destroy().asCallback(function (err) { | 357 | .catch(err => { |
460 | // Do not return the error, continue the process | 358 | logger.debug('Could not fetch remote video.', { host: fromPod.host, remoteId: videoToRemoveData.remoteId, error: err }) |
461 | if (err) { | ||
462 | logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err }) | ||
463 | } | ||
464 | |||
465 | return callback(null) | ||
466 | }) | 359 | }) |
467 | }) | ||
468 | } | 360 | } |
469 | 361 | ||
470 | function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance, callback: (err: Error) => void) { | 362 | function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance) { |
471 | fetchOwnedVideo(reportData.videoRemoteId, function (err, video) { | 363 | return fetchOwnedVideo(reportData.videoRemoteId) |
472 | if (err || !video) { | 364 | .then(video => { |
473 | if (!err) err = new Error('video not found') | 365 | logger.debug('Reporting remote abuse for video %s.', video.id) |
474 | |||
475 | logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId }) | ||
476 | // Do not return the error, continue the process | ||
477 | return callback(null) | ||
478 | } | ||
479 | |||
480 | logger.debug('Reporting remote abuse for video %s.', video.id) | ||
481 | |||
482 | const videoAbuseData = { | ||
483 | reporterUsername: reportData.reporterUsername, | ||
484 | reason: reportData.reportReason, | ||
485 | reporterPodId: fromPod.id, | ||
486 | videoId: video.id | ||
487 | } | ||
488 | 366 | ||
489 | db.VideoAbuse.create(videoAbuseData).asCallback(function (err) { | 367 | const videoAbuseData = { |
490 | if (err) { | 368 | reporterUsername: reportData.reporterUsername, |
491 | logger.error('Cannot create remote abuse video.', { error: err }) | 369 | reason: reportData.reportReason, |
370 | reporterPodId: fromPod.id, | ||
371 | videoId: video.id | ||
492 | } | 372 | } |
493 | 373 | ||
494 | return callback(null) | 374 | return db.VideoAbuse.create(videoAbuseData) |
495 | }) | 375 | }) |
496 | }) | 376 | .catch(err => logger.error('Cannot create remote abuse video.', { error: err })) |
497 | } | 377 | } |
498 | 378 | ||
499 | function fetchOwnedVideo (id: string, callback: (err: Error, video?: VideoInstance) => void) { | 379 | function fetchOwnedVideo (id: string) { |
500 | db.Video.load(id, function (err, video) { | 380 | return db.Video.load(id) |
501 | if (err || !video) { | 381 | .then(video => { |
502 | if (!err) err = new Error('video not found') | 382 | if (!video) throw new Error('Video not found') |
503 | 383 | ||
384 | return video | ||
385 | }) | ||
386 | .catch(err => { | ||
504 | logger.error('Cannot load owned video from id.', { error: err, id }) | 387 | logger.error('Cannot load owned video from id.', { error: err, id }) |
505 | return callback(err) | 388 | throw err |
506 | } | 389 | }) |
507 | |||
508 | return callback(null, video) | ||
509 | }) | ||
510 | } | 390 | } |
511 | 391 | ||
512 | function fetchRemoteVideo (podHost: string, remoteId: string, callback: (err: Error, video?: VideoInstance) => void) { | 392 | function fetchRemoteVideo (podHost: string, remoteId: string) { |
513 | db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) { | 393 | return db.Video.loadByHostAndRemoteId(podHost, remoteId) |
514 | if (err || !video) { | 394 | .then(video => { |
515 | if (!err) err = new Error('video not found') | 395 | if (!video) throw new Error('Video not found') |
516 | 396 | ||
397 | return video | ||
398 | }) | ||
399 | .catch(err => { | ||
517 | logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) | 400 | logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId }) |
518 | return callback(err) | 401 | throw err |
519 | } | 402 | }) |
520 | |||
521 | return callback(null, video) | ||
522 | }) | ||
523 | } | 403 | } |