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