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