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