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