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