]>
Commit | Line | Data |
---|---|---|
9f10b292 C |
1 | 'use strict' |
2 | ||
1a42c9e2 C |
3 | const each = require('async/each') |
4 | const eachLimit = require('async/eachLimit') | |
5 | const eachSeries = require('async/eachSeries') | |
f0f5567b | 6 | const fs = require('fs') |
aaf61f38 | 7 | const mongoose = require('mongoose') |
f0f5567b | 8 | const request = require('request') |
2c49ca42 | 9 | const urlUtil = require('url') |
1a42c9e2 | 10 | const waterfall = require('async/waterfall') |
f0f5567b C |
11 | |
12 | const constants = require('../initializers/constants') | |
13 | const logger = require('../helpers/logger') | |
f0f5567b | 14 | const requests = require('../helpers/requests') |
f0f5567b | 15 | |
a3ee6fa2 | 16 | const Pod = mongoose.model('Pod') |
00057e85 | 17 | const Request = mongoose.model('Request') |
aaf61f38 | 18 | const Video = mongoose.model('Video') |
f0f5567b | 19 | |
a3ee6fa2 | 20 | const friends = { |
c4403b29 C |
21 | addVideoToFriends, |
22 | hasFriends, | |
23 | getMyCertificate, | |
24 | makeFriends, | |
25 | quitFriends, | |
26 | removeVideoToFriends, | |
27 | sendOwnedVideosToPod | |
9f10b292 C |
28 | } |
29 | ||
30 | function addVideoToFriends (video) { | |
00057e85 | 31 | createRequest('add', video) |
9f10b292 C |
32 | } |
33 | ||
34 | function hasFriends (callback) { | |
a3ee6fa2 | 35 | Pod.countAll(function (err, count) { |
9f10b292 C |
36 | if (err) return callback(err) |
37 | ||
bc503c2a C |
38 | const hasFriends = (count !== 0) |
39 | callback(null, hasFriends) | |
9f10b292 C |
40 | }) |
41 | } | |
42 | ||
cbe2f7c3 | 43 | function getMyCertificate (callback) { |
e861452f | 44 | fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub', 'utf8', callback) |
cbe2f7c3 C |
45 | } |
46 | ||
1e2564d3 | 47 | function makeFriends (urls, callback) { |
bc503c2a | 48 | const podsScore = {} |
9f10b292 C |
49 | |
50 | logger.info('Make friends!') | |
cbe2f7c3 | 51 | getMyCertificate(function (err, cert) { |
9f10b292 C |
52 | if (err) { |
53 | logger.error('Cannot read public cert.') | |
54 | return callback(err) | |
55 | } | |
c173e565 | 56 | |
1a42c9e2 | 57 | eachSeries(urls, function (url, callbackEach) { |
bc503c2a | 58 | computeForeignPodsList(url, podsScore, callbackEach) |
89d1d8ba | 59 | }, function (err) { |
c173e565 C |
60 | if (err) return callback(err) |
61 | ||
bc503c2a C |
62 | logger.debug('Pods scores computed.', { podsScore: podsScore }) |
63 | const podsList = computeWinningPods(urls, podsScore) | |
64 | logger.debug('Pods that we keep.', { podsToKeep: podsList }) | |
9f10b292 | 65 | |
bc503c2a | 66 | makeRequestsToWinningPods(cert, podsList, callback) |
c173e565 | 67 | }) |
9f10b292 | 68 | }) |
9f10b292 C |
69 | } |
70 | ||
71 | function quitFriends (callback) { | |
72 | // Stop pool requests | |
00057e85 | 73 | Request.deactivate() |
9f10b292 | 74 | // Flush pool requests |
00057e85 | 75 | Request.flush() |
9f10b292 | 76 | |
1a42c9e2 | 77 | waterfall([ |
e7ea2817 | 78 | function getPodsList (callbackAsync) { |
a3ee6fa2 | 79 | return Pod.list(callbackAsync) |
e7ea2817 C |
80 | }, |
81 | ||
82 | function announceIQuitMyFriends (pods, callbackAsync) { | |
528a9efa | 83 | const requestParams = { |
e7ea2817 C |
84 | method: 'POST', |
85 | path: '/api/' + constants.API_VERSION + '/pods/remove', | |
528a9efa | 86 | sign: true |
c173e565 C |
87 | } |
88 | ||
e7ea2817 | 89 | // Announce we quit them |
528a9efa C |
90 | // We don't care if the request fails |
91 | // The other pod will exclude us automatically after a while | |
1a42c9e2 | 92 | eachLimit(pods, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) { |
528a9efa C |
93 | requestParams.toPod = pod |
94 | requests.makeSecureRequest(requestParams, callbackEach) | |
95 | }, function (err) { | |
96 | if (err) { | |
97 | logger.error('Some errors while quitting friends.', { err: err }) | |
98 | // Don't stop the process | |
99 | } | |
100 | ||
80a6c9e7 | 101 | return callbackAsync(null, pods) |
e7ea2817 C |
102 | }) |
103 | }, | |
c173e565 | 104 | |
80a6c9e7 C |
105 | function removePodsFromDB (pods, callbackAsync) { |
106 | each(pods, function (pod, callbackEach) { | |
107 | pod.remove(callbackEach) | |
aaf61f38 | 108 | }, callbackAsync) |
e7ea2817 C |
109 | } |
110 | ], function (err) { | |
111 | // Don't forget to re activate the scheduler, even if there was an error | |
00057e85 | 112 | Request.activate() |
e7ea2817 C |
113 | |
114 | if (err) return callback(err) | |
115 | ||
116 | logger.info('Removed all remote videos.') | |
117 | return callback(null) | |
9f10b292 C |
118 | }) |
119 | } | |
c173e565 | 120 | |
00057e85 C |
121 | function removeVideoToFriends (videoParams) { |
122 | createRequest('remove', videoParams) | |
528a9efa C |
123 | } |
124 | ||
125 | function sendOwnedVideosToPod (podId) { | |
aaf61f38 | 126 | Video.listOwned(function (err, videosList) { |
528a9efa C |
127 | if (err) { |
128 | logger.error('Cannot get the list of videos we own.') | |
129 | return | |
130 | } | |
131 | ||
132 | videosList.forEach(function (video) { | |
aaf61f38 | 133 | video.toRemoteJSON(function (err, remoteVideo) { |
528a9efa C |
134 | if (err) { |
135 | logger.error('Cannot convert video to remote.', { error: err }) | |
136 | // Don't break the process | |
137 | return | |
138 | } | |
139 | ||
00057e85 | 140 | createRequest('add', remoteVideo, [ podId ]) |
528a9efa C |
141 | }) |
142 | }) | |
143 | }) | |
9f10b292 | 144 | } |
c173e565 | 145 | |
9f10b292 | 146 | // --------------------------------------------------------------------------- |
c173e565 | 147 | |
a3ee6fa2 | 148 | module.exports = friends |
c173e565 | 149 | |
9f10b292 | 150 | // --------------------------------------------------------------------------- |
c173e565 | 151 | |
bc503c2a | 152 | function computeForeignPodsList (url, podsScore, callback) { |
bc503c2a | 153 | getForeignPodsList(url, function (err, foreignPodsList) { |
89d1d8ba | 154 | if (err) return callback(err) |
528a9efa C |
155 | |
156 | if (!foreignPodsList) foreignPodsList = [] | |
157 | ||
158 | // Let's give 1 point to the pod we ask the friends list | |
159 | foreignPodsList.push({ url: url }) | |
89d1d8ba | 160 | |
bc503c2a | 161 | foreignPodsList.forEach(function (foreignPod) { |
528a9efa | 162 | const foreignPodUrl = foreignPod.url |
89d1d8ba | 163 | |
528a9efa C |
164 | if (podsScore[foreignPodUrl]) podsScore[foreignPodUrl]++ |
165 | else podsScore[foreignPodUrl] = 1 | |
89d1d8ba | 166 | }) |
cbe2f7c3 C |
167 | |
168 | callback() | |
89d1d8ba C |
169 | }) |
170 | } | |
171 | ||
bc503c2a | 172 | function computeWinningPods (urls, podsScore) { |
89d1d8ba C |
173 | // Build the list of pods to add |
174 | // Only add a pod if it exists in more than a half base pods | |
bc503c2a C |
175 | const podsList = [] |
176 | const baseScore = urls.length / 2 | |
2c49ca42 C |
177 | Object.keys(podsScore).forEach(function (podUrl) { |
178 | // If the pod is not me and with a good score we add it | |
179 | if (isMe(podUrl) === false && podsScore[podUrl] > baseScore) { | |
180 | podsList.push({ url: podUrl }) | |
181 | } | |
89d1d8ba | 182 | }) |
e7ea2817 | 183 | |
bc503c2a | 184 | return podsList |
89d1d8ba C |
185 | } |
186 | ||
9f10b292 | 187 | function getForeignPodsList (url, callback) { |
f0f5567b | 188 | const path = '/api/' + constants.API_VERSION + '/pods' |
c173e565 | 189 | |
9f10b292 C |
190 | request.get(url + path, function (err, response, body) { |
191 | if (err) return callback(err) | |
8425cb89 | 192 | |
39f87cb2 C |
193 | try { |
194 | const json = JSON.parse(body) | |
195 | return callback(null, json) | |
196 | } catch (err) { | |
197 | return callback(err) | |
198 | } | |
9f10b292 C |
199 | }) |
200 | } | |
89d1d8ba | 201 | |
bc503c2a | 202 | function makeRequestsToWinningPods (cert, podsList, callback) { |
89d1d8ba | 203 | // Stop pool requests |
00057e85 | 204 | Request.deactivate() |
89d1d8ba | 205 | // Flush pool requests |
00057e85 | 206 | Request.forceSend() |
89d1d8ba | 207 | |
1a42c9e2 | 208 | eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) { |
528a9efa C |
209 | const params = { |
210 | url: pod.url + '/api/' + constants.API_VERSION + '/pods/', | |
211 | method: 'POST', | |
212 | json: { | |
e861452f | 213 | url: constants.CONFIG.WEBSERVER.URL, |
528a9efa C |
214 | publicKey: cert |
215 | } | |
89d1d8ba C |
216 | } |
217 | ||
528a9efa C |
218 | requests.makeRetryRequest(params, function (err, res, body) { |
219 | if (err) { | |
220 | logger.error('Error with adding %s pod.', pod.url, { error: err }) | |
221 | // Don't break the process | |
222 | return callbackEach() | |
223 | } | |
89d1d8ba | 224 | |
528a9efa | 225 | if (res.statusCode === 200) { |
a3ee6fa2 C |
226 | const podObj = new Pod({ url: pod.url, publicKey: body.cert }) |
227 | podObj.save(function (err, podCreated) { | |
228 | if (err) { | |
229 | logger.error('Cannot add friend %s pod.', pod.url, { error: err }) | |
230 | return callbackEach() | |
231 | } | |
89d1d8ba | 232 | |
528a9efa C |
233 | // Add our videos to the request scheduler |
234 | sendOwnedVideosToPod(podCreated._id) | |
89d1d8ba | 235 | |
528a9efa C |
236 | return callbackEach() |
237 | }) | |
238 | } else { | |
239 | logger.error('Status not 200 for %s pod.', pod.url) | |
240 | return callbackEach() | |
89d1d8ba | 241 | } |
528a9efa C |
242 | }) |
243 | }, function endRequests () { | |
244 | // Final callback, we've ended all the requests | |
245 | // Now we made new friends, we can re activate the pool of requests | |
00057e85 | 246 | Request.activate() |
528a9efa C |
247 | |
248 | logger.debug('makeRequestsToWinningPods finished.') | |
249 | return callback() | |
89d1d8ba C |
250 | }) |
251 | } | |
00057e85 C |
252 | |
253 | function createRequest (type, data, to) { | |
254 | const req = new Request({ | |
255 | request: { | |
256 | type: type, | |
257 | data: data | |
258 | } | |
259 | }) | |
260 | ||
261 | if (to) { | |
262 | req.to = to | |
263 | } | |
264 | ||
265 | req.save(function (err) { | |
266 | if (err) logger.error('Cannot save the request.', { error: err }) | |
267 | }) | |
268 | } | |
2c49ca42 C |
269 | |
270 | function isMe (url) { | |
271 | const parsedUrl = urlUtil.parse(url) | |
272 | ||
273 | const hostname = parsedUrl.hostname | |
274 | const port = parseInt(parsedUrl.port) | |
275 | ||
3737bbaf | 276 | const myHostname = constants.CONFIG.WEBSERVER.HOSTNAME |
2c49ca42 C |
277 | const myPort = constants.CONFIG.WEBSERVER.PORT |
278 | ||
279 | return hostname === myHostname && port === myPort | |
280 | } |