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