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