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