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