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