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