]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/friends.js
Use async waterfall for better readability in friends lib
[github/Chocobozzz/PeerTube.git] / server / lib / friends.js
1 'use strict'
2
3 const async = require('async')
4 const config = require('config')
5 const fs = require('fs')
6 const request = require('request')
7
8 const constants = require('../initializers/constants')
9 const logger = require('../helpers/logger')
10 const peertubeCrypto = require('../helpers/peertubeCrypto')
11 const Pods = require('../models/pods')
12 const requestsScheduler = require('../lib/requestsScheduler')
13 const requests = require('../helpers/requests')
14 const videos = require('../lib/videos')
15 const Videos = require('../models/videos')
16
17 const http = config.get('webserver.https') ? 'https' : 'http'
18 const host = config.get('webserver.host')
19 const port = config.get('webserver.port')
20
21 const pods = {
22 addVideoToFriends: addVideoToFriends,
23 hasFriends: hasFriends,
24 getMyCertificate: getMyCertificate,
25 makeFriends: makeFriends,
26 quitFriends: quitFriends,
27 removeVideoToFriends: removeVideoToFriends
28 }
29
30 function addVideoToFriends (video) {
31 // To avoid duplicates
32 const id = video.name + video.magnetUri
33 // ensure namePath is null
34 video.namePath = null
35 requestsScheduler.addRequest(id, 'add', video)
36 }
37
38 function hasFriends (callback) {
39 Pods.count(function (err, count) {
40 if (err) return callback(err)
41
42 const hasFriends = (count !== 0)
43 callback(null, hasFriends)
44 })
45 }
46
47 function getMyCertificate (callback) {
48 fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', callback)
49 }
50
51 function makeFriends (callback) {
52 const podsScore = {}
53
54 logger.info('Make friends!')
55 getMyCertificate(function (err, cert) {
56 if (err) {
57 logger.error('Cannot read public cert.')
58 return callback(err)
59 }
60
61 const urls = config.get('network.friends')
62
63 async.each(urls, function (url, callbackEach) {
64 computeForeignPodsList(url, podsScore, callbackEach)
65 }, function (err) {
66 if (err) return callback(err)
67
68 logger.debug('Pods scores computed.', { podsScore: podsScore })
69 const podsList = computeWinningPods(urls, podsScore)
70 logger.debug('Pods that we keep.', { podsToKeep: podsList })
71
72 makeRequestsToWinningPods(cert, podsList, callback)
73 })
74 })
75 }
76
77 function quitFriends (callback) {
78 // Stop pool requests
79 requestsScheduler.deactivate()
80 // Flush pool requests
81 requestsScheduler.forceSend()
82
83 async.waterfall([
84 function getPodsList (callbackAsync) {
85 return Pods.list(callbackAsync)
86 },
87
88 function announceIQuitMyFriends (pods, callbackAsync) {
89 const request = {
90 method: 'POST',
91 path: '/api/' + constants.API_VERSION + '/pods/remove',
92 sign: true,
93 encrypt: true,
94 data: {
95 url: 'me' // Fake data
96 }
97 }
98
99 // Announce we quit them
100 requests.makeMultipleRetryRequest(request, pods, function (err) {
101 return callbackAsync(err)
102 })
103 },
104
105 function removePodsFromDB (callbackAsync) {
106 Pods.removeAll(function (err) {
107 return callbackAsync(err)
108 })
109 },
110
111 function listRemoteVideos (callbackAsync) {
112 logger.info('Broke friends, so sad :(')
113
114 Videos.listFromRemotes(callbackAsync)
115 },
116
117 function removeTheRemoteVideos (videosList, callbackAsync) {
118 videos.removeRemoteVideos(videosList, function (err) {
119 if (err) {
120 logger.error('Cannot remove remote videos.', { error: err })
121 return callbackAsync(err)
122 }
123
124 return callbackAsync(null)
125 })
126 }
127 ], function (err) {
128 // Don't forget to re activate the scheduler, even if there was an error
129 requestsScheduler.activate()
130
131 if (err) return callback(err)
132
133 logger.info('Removed all remote videos.')
134 return callback(null)
135 })
136 }
137
138 function removeVideoToFriends (video) {
139 // To avoid duplicates
140 const id = video.name + video.magnetUri
141 requestsScheduler.addRequest(id, 'remove', video)
142 }
143
144 // ---------------------------------------------------------------------------
145
146 module.exports = pods
147
148 // ---------------------------------------------------------------------------
149
150 function computeForeignPodsList (url, podsScore, callback) {
151 // Let's give 1 point to the pod we ask the friends list
152 podsScore[url] = 1
153
154 getForeignPodsList(url, function (err, foreignPodsList) {
155 if (err) return callback(err)
156 if (foreignPodsList.length === 0) return callback()
157
158 foreignPodsList.forEach(function (foreignPod) {
159 const foreignUrl = foreignPod.url
160
161 if (podsScore[foreignUrl]) podsScore[foreignUrl]++
162 else podsScore[foreignUrl] = 1
163 })
164
165 callback()
166 })
167 }
168
169 function computeWinningPods (urls, podsScore) {
170 // Build the list of pods to add
171 // Only add a pod if it exists in more than a half base pods
172 const podsList = []
173 const baseScore = urls.length / 2
174 Object.keys(podsScore).forEach(function (pod) {
175 if (podsScore[pod] > baseScore) podsList.push({ url: pod })
176 })
177
178 return podsList
179 }
180
181 function getForeignPodsList (url, callback) {
182 const path = '/api/' + constants.API_VERSION + '/pods'
183
184 request.get(url + path, function (err, response, body) {
185 if (err) return callback(err)
186
187 callback(null, JSON.parse(body))
188 })
189 }
190
191 function makeRequestsToWinningPods (cert, podsList, callback) {
192 // Stop pool requests
193 requestsScheduler.deactivate()
194 // Flush pool requests
195 requestsScheduler.forceSend()
196
197 // Get the list of our videos to send to our new friends
198 Videos.listOwned(function (err, videosList) {
199 if (err) {
200 logger.error('Cannot get the list of videos we own.')
201 return callback(err)
202 }
203
204 const data = {
205 url: http + '://' + host + ':' + port,
206 publicKey: cert,
207 videos: videosList
208 }
209
210 requests.makeMultipleRetryRequest(
211 { method: 'POST', path: '/api/' + constants.API_VERSION + '/pods/', data: data },
212
213 podsList,
214
215 // Callback called after each request
216 function eachRequest (err, response, body, url, pod, callbackEachRequest) {
217 // We add the pod if it responded correctly with its public certificate
218 if (!err && response.statusCode === 200) {
219 Pods.add({ url: pod.url, publicKey: body.cert, score: constants.FRIEND_BASE_SCORE }, function (err) {
220 if (err) {
221 logger.error('Error with adding %s pod.', pod.url, { error: err })
222 return callbackEachRequest()
223 }
224
225 videos.createRemoteVideos(body.videos, function (err) {
226 if (err) {
227 logger.error('Error with adding videos of pod.', pod.url, { error: err })
228 return callbackEachRequest()
229 }
230
231 logger.debug('Adding remote videos from %s.', pod.url, { videos: body.videos })
232 return callbackEachRequest()
233 })
234 })
235 } else {
236 logger.error('Error with adding %s pod.', pod.url, { error: err || new Error('Status not 200') })
237 return callbackEachRequest()
238 }
239 },
240
241 // Final callback, we've ended all the requests
242 function endRequests (err) {
243 // Now we made new friends, we can re activate the pool of requests
244 requestsScheduler.activate()
245
246 if (err) {
247 logger.error('There was some errors when we wanted to make friends.')
248 return callback(err)
249 }
250
251 logger.debug('makeRequestsToWinningPods finished.')
252 return callback(null)
253 }
254 )
255 })
256 }