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