]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/friends.js
Update standard -> 10
[github/Chocobozzz/PeerTube.git] / server / lib / friends.js
1 'use strict'
2
3 const each = require('async/each')
4 const eachLimit = require('async/eachLimit')
5 const eachSeries = require('async/eachSeries')
6 const series = require('async/series')
7 const request = require('request')
8 const waterfall = require('async/waterfall')
9
10 const constants = require('../initializers/constants')
11 const db = require('../initializers/database')
12 const logger = require('../helpers/logger')
13 const peertubeCrypto = require('../helpers/peertube-crypto')
14 const requests = require('../helpers/requests')
15 const utils = require('../helpers/utils')
16 const RequestScheduler = require('./request-scheduler')
17 const RequestVideoQaduScheduler = require('./request-video-qadu-scheduler')
18 const RequestVideoEventScheduler = require('./request-video-event-scheduler')
19
20 const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS]
21
22 const requestScheduler = new RequestScheduler()
23 const requestVideoQaduScheduler = new RequestVideoQaduScheduler()
24 const requestVideoEventScheduler = new RequestVideoEventScheduler()
25
26 const friends = {
27 activate,
28 addVideoToFriends,
29 updateVideoToFriends,
30 reportAbuseVideoToFriend,
31 quickAndDirtyUpdateVideoToFriends,
32 quickAndDirtyUpdatesVideoToFriends,
33 addEventToRemoteVideo,
34 addEventsToRemoteVideo,
35 hasFriends,
36 makeFriends,
37 quitFriends,
38 removeVideoToFriends,
39 sendOwnedVideosToPod,
40 getRequestScheduler,
41 getRequestVideoQaduScheduler,
42 getRequestVideoEventScheduler
43 }
44
45 function activate () {
46 requestScheduler.activate()
47 requestVideoQaduScheduler.activate()
48 requestVideoEventScheduler.activate()
49 }
50
51 function addVideoToFriends (videoData, transaction, callback) {
52 const options = {
53 type: ENDPOINT_ACTIONS.ADD,
54 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
55 data: videoData,
56 transaction
57 }
58 createRequest(options, callback)
59 }
60
61 function updateVideoToFriends (videoData, transaction, callback) {
62 const options = {
63 type: ENDPOINT_ACTIONS.UPDATE,
64 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
65 data: videoData,
66 transaction
67 }
68 createRequest(options, callback)
69 }
70
71 function removeVideoToFriends (videoParams) {
72 const options = {
73 type: ENDPOINT_ACTIONS.REMOVE,
74 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
75 data: videoParams
76 }
77 createRequest(options)
78 }
79
80 function reportAbuseVideoToFriend (reportData, video) {
81 const options = {
82 type: ENDPOINT_ACTIONS.REPORT_ABUSE,
83 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
84 data: reportData,
85 toIds: [ video.Author.podId ]
86 }
87 createRequest(options)
88 }
89
90 function quickAndDirtyUpdateVideoToFriends (qaduParams, transaction, callback) {
91 const options = {
92 videoId: qaduParams.videoId,
93 type: qaduParams.type,
94 transaction
95 }
96 return createVideoQaduRequest(options, callback)
97 }
98
99 function quickAndDirtyUpdatesVideoToFriends (qadusParams, transaction, finalCallback) {
100 const tasks = []
101
102 qadusParams.forEach(function (qaduParams) {
103 const fun = function (callback) {
104 quickAndDirtyUpdateVideoToFriends(qaduParams, transaction, callback)
105 }
106
107 tasks.push(fun)
108 })
109
110 series(tasks, finalCallback)
111 }
112
113 function addEventToRemoteVideo (eventParams, transaction, callback) {
114 const options = {
115 videoId: eventParams.videoId,
116 type: eventParams.type,
117 transaction
118 }
119 createVideoEventRequest(options, callback)
120 }
121
122 function addEventsToRemoteVideo (eventsParams, transaction, finalCallback) {
123 const tasks = []
124
125 eventsParams.forEach(function (eventParams) {
126 const fun = function (callback) {
127 addEventToRemoteVideo(eventParams, transaction, callback)
128 }
129
130 tasks.push(fun)
131 })
132
133 series(tasks, finalCallback)
134 }
135
136 function hasFriends (callback) {
137 db.Pod.countAll(function (err, count) {
138 if (err) return callback(err)
139
140 const hasFriends = (count !== 0)
141 callback(null, hasFriends)
142 })
143 }
144
145 function makeFriends (hosts, callback) {
146 const podsScore = {}
147
148 logger.info('Make friends!')
149 peertubeCrypto.getMyPublicCert(function (err, cert) {
150 if (err) {
151 logger.error('Cannot read public cert.')
152 return callback(err)
153 }
154
155 eachSeries(hosts, function (host, callbackEach) {
156 computeForeignPodsList(host, podsScore, callbackEach)
157 }, function (err) {
158 if (err) return callback(err)
159
160 logger.debug('Pods scores computed.', { podsScore: podsScore })
161 const podsList = computeWinningPods(hosts, podsScore)
162 logger.debug('Pods that we keep.', { podsToKeep: podsList })
163
164 makeRequestsToWinningPods(cert, podsList, callback)
165 })
166 })
167 }
168
169 function quitFriends (callback) {
170 // Stop pool requests
171 requestScheduler.deactivate()
172
173 waterfall([
174 function flushRequests (callbackAsync) {
175 requestScheduler.flush(err => callbackAsync(err))
176 },
177
178 function flushVideoQaduRequests (callbackAsync) {
179 requestVideoQaduScheduler.flush(err => callbackAsync(err))
180 },
181
182 function getPodsList (callbackAsync) {
183 return db.Pod.list(callbackAsync)
184 },
185
186 function announceIQuitMyFriends (pods, callbackAsync) {
187 const requestParams = {
188 method: 'POST',
189 path: '/api/' + constants.API_VERSION + '/remote/pods/remove',
190 sign: true
191 }
192
193 // Announce we quit them
194 // We don't care if the request fails
195 // The other pod will exclude us automatically after a while
196 eachLimit(pods, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
197 requestParams.toPod = pod
198 requests.makeSecureRequest(requestParams, callbackEach)
199 }, function (err) {
200 if (err) {
201 logger.error('Some errors while quitting friends.', { err: err })
202 // Don't stop the process
203 }
204
205 return callbackAsync(null, pods)
206 })
207 },
208
209 function removePodsFromDB (pods, callbackAsync) {
210 each(pods, function (pod, callbackEach) {
211 pod.destroy().asCallback(callbackEach)
212 }, callbackAsync)
213 }
214 ], function (err) {
215 // Don't forget to re activate the scheduler, even if there was an error
216 requestScheduler.activate()
217
218 if (err) return callback(err)
219
220 logger.info('Removed all remote videos.')
221 return callback(null)
222 })
223 }
224
225 function sendOwnedVideosToPod (podId) {
226 db.Video.listOwnedAndPopulateAuthorAndTags(function (err, videosList) {
227 if (err) {
228 logger.error('Cannot get the list of videos we own.')
229 return
230 }
231
232 videosList.forEach(function (video) {
233 video.toAddRemoteJSON(function (err, remoteVideo) {
234 if (err) {
235 logger.error('Cannot convert video to remote.', { error: err })
236 // Don't break the process
237 return
238 }
239
240 const options = {
241 type: 'add',
242 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
243 data: remoteVideo,
244 toIds: [ podId ]
245 }
246 createRequest(options)
247 })
248 })
249 })
250 }
251
252 function getRequestScheduler () {
253 return requestScheduler
254 }
255
256 function getRequestVideoQaduScheduler () {
257 return requestVideoQaduScheduler
258 }
259
260 function getRequestVideoEventScheduler () {
261 return requestVideoEventScheduler
262 }
263
264 // ---------------------------------------------------------------------------
265
266 module.exports = friends
267
268 // ---------------------------------------------------------------------------
269
270 function computeForeignPodsList (host, podsScore, callback) {
271 getForeignPodsList(host, function (err, res) {
272 if (err) return callback(err)
273
274 const foreignPodsList = res.data
275
276 // Let's give 1 point to the pod we ask the friends list
277 foreignPodsList.push({ host })
278
279 foreignPodsList.forEach(function (foreignPod) {
280 const foreignPodHost = foreignPod.host
281
282 if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++
283 else podsScore[foreignPodHost] = 1
284 })
285
286 return callback()
287 })
288 }
289
290 function computeWinningPods (hosts, podsScore) {
291 // Build the list of pods to add
292 // Only add a pod if it exists in more than a half base pods
293 const podsList = []
294 const baseScore = hosts.length / 2
295
296 Object.keys(podsScore).forEach(function (podHost) {
297 // If the pod is not me and with a good score we add it
298 if (isMe(podHost) === false && podsScore[podHost] > baseScore) {
299 podsList.push({ host: podHost })
300 }
301 })
302
303 return podsList
304 }
305
306 function getForeignPodsList (host, callback) {
307 const path = '/api/' + constants.API_VERSION + '/pods'
308
309 request.get(constants.REMOTE_SCHEME.HTTP + '://' + host + path, function (err, response, body) {
310 if (err) return callback(err)
311
312 try {
313 const json = JSON.parse(body)
314 return callback(null, json)
315 } catch (err) {
316 return callback(err)
317 }
318 })
319 }
320
321 function makeRequestsToWinningPods (cert, podsList, callback) {
322 // Stop pool requests
323 requestScheduler.deactivate()
324 // Flush pool requests
325 requestScheduler.forceSend()
326
327 eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
328 const params = {
329 url: constants.REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + constants.API_VERSION + '/pods/',
330 method: 'POST',
331 json: {
332 host: constants.CONFIG.WEBSERVER.HOST,
333 email: constants.CONFIG.ADMIN.EMAIL,
334 publicKey: cert
335 }
336 }
337
338 requests.makeRetryRequest(params, function (err, res, body) {
339 if (err) {
340 logger.error('Error with adding %s pod.', pod.host, { error: err })
341 // Don't break the process
342 return callbackEach()
343 }
344
345 if (res.statusCode === 200) {
346 const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert, email: body.email })
347 podObj.save().asCallback(function (err, podCreated) {
348 if (err) {
349 logger.error('Cannot add friend %s pod.', pod.host, { error: err })
350 return callbackEach()
351 }
352
353 // Add our videos to the request scheduler
354 sendOwnedVideosToPod(podCreated.id)
355
356 return callbackEach()
357 })
358 } else {
359 logger.error('Status not 200 for %s pod.', pod.host)
360 return callbackEach()
361 }
362 })
363 }, function endRequests () {
364 // Final callback, we've ended all the requests
365 // Now we made new friends, we can re activate the pool of requests
366 requestScheduler.activate()
367
368 logger.debug('makeRequestsToWinningPods finished.')
369 return callback()
370 })
371 }
372
373 // Wrapper that populate "toIds" argument with all our friends if it is not specified
374 // { type, endpoint, data, toIds, transaction }
375 function createRequest (options, callback) {
376 if (!callback) callback = function () {}
377 if (options.toIds) return requestScheduler.createRequest(options, callback)
378
379 // If the "toIds" pods is not specified, we send the request to all our friends
380 db.Pod.listAllIds(options.transaction, function (err, podIds) {
381 if (err) {
382 logger.error('Cannot get pod ids', { error: err })
383 return
384 }
385
386 const newOptions = Object.assign(options, { toIds: podIds })
387 return requestScheduler.createRequest(newOptions, callback)
388 })
389 }
390
391 function createVideoQaduRequest (options, callback) {
392 if (!callback) callback = utils.createEmptyCallback()
393
394 requestVideoQaduScheduler.createRequest(options, callback)
395 }
396
397 function createVideoEventRequest (options, callback) {
398 if (!callback) callback = utils.createEmptyCallback()
399
400 requestVideoEventScheduler.createRequest(options, callback)
401 }
402
403 function isMe (host) {
404 return host === constants.CONFIG.WEBSERVER.HOST
405 }