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