]> 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,
45 Pod as FormatedPod
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
99fdec46
C
245function getRequestScheduler () {
246 return requestScheduler
247}
248
249function getRequestVideoQaduScheduler () {
250 return requestVideoQaduScheduler
251}
252
253function getRequestVideoEventScheduler () {
254 return requestVideoEventScheduler
255}
256
9f10b292 257// ---------------------------------------------------------------------------
c173e565 258
65fcc311
C
259export {
260 activateSchedulers,
261 addVideoToFriends,
262 updateVideoToFriends,
263 reportAbuseVideoToFriend,
264 quickAndDirtyUpdateVideoToFriends,
265 quickAndDirtyUpdatesVideoToFriends,
266 addEventToRemoteVideo,
267 addEventsToRemoteVideo,
268 hasFriends,
269 makeFriends,
270 quitFriends,
271 removeVideoToFriends,
272 sendOwnedVideosToPod,
273 getRequestScheduler,
274 getRequestVideoQaduScheduler,
f981dae8
C
275 getRequestVideoEventScheduler,
276 fetchRemotePreview
65fcc311 277}
c173e565 278
9f10b292 279// ---------------------------------------------------------------------------
c173e565 280
6fcd19ba
C
281function computeForeignPodsList (host: string, podsScore: { [ host: string ]: number }) {
282 // TODO: type res
e6d4b0ff
C
283 return getForeignPodsList(host).then(res => {
284 const foreignPodsList: { host: string }[] = res.data
528a9efa
C
285
286 // Let's give 1 point to the pod we ask the friends list
49abbbbe 287 foreignPodsList.push({ host })
89d1d8ba 288
6fcd19ba 289 foreignPodsList.forEach(foreignPod => {
49abbbbe 290 const foreignPodHost = foreignPod.host
89d1d8ba 291
49abbbbe
C
292 if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++
293 else podsScore[foreignPodHost] = 1
89d1d8ba 294 })
cbe2f7c3 295
6fcd19ba 296 return undefined
89d1d8ba
C
297 })
298}
299
69818c93 300function computeWinningPods (hosts: string[], podsScore: { [ host: string ]: number }) {
89d1d8ba
C
301 // Build the list of pods to add
302 // Only add a pod if it exists in more than a half base pods
bc503c2a 303 const podsList = []
49abbbbe 304 const baseScore = hosts.length / 2
1e4b0080 305
6fcd19ba 306 Object.keys(podsScore).forEach(podHost => {
2c49ca42 307 // If the pod is not me and with a good score we add it
49abbbbe
C
308 if (isMe(podHost) === false && podsScore[podHost] > baseScore) {
309 podsList.push({ host: podHost })
2c49ca42 310 }
89d1d8ba 311 })
e7ea2817 312
bc503c2a 313 return podsList
89d1d8ba
C
314}
315
6fcd19ba 316function getForeignPodsList (host: string) {
e6d4b0ff 317 return new Promise< ResultList<FormatedPod> >((res, rej) => {
6fcd19ba 318 const path = '/api/' + API_VERSION + '/pods'
c173e565 319
075f16ca 320 request.get(REMOTE_SCHEME.HTTP + '://' + host + path, (err, response, body) => {
6fcd19ba 321 if (err) return rej(err)
8425cb89 322
6fcd19ba
C
323 try {
324 const json = JSON.parse(body)
325 return res(json)
326 } catch (err) {
327 return rej(err)
328 }
329 })
9f10b292
C
330 })
331}
89d1d8ba 332
6fcd19ba 333function makeRequestsToWinningPods (cert: string, podsList: PodInstance[]) {
89d1d8ba 334 // Stop pool requests
c1a7ab7f 335 requestScheduler.deactivate()
89d1d8ba 336 // Flush pool requests
c1a7ab7f 337 requestScheduler.forceSend()
89d1d8ba 338
6fcd19ba 339 return Promise.map(podsList, pod => {
528a9efa 340 const params = {
65fcc311 341 url: REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + API_VERSION + '/pods/',
69818c93 342 method: 'POST' as 'POST',
528a9efa 343 json: {
65fcc311
C
344 host: CONFIG.WEBSERVER.HOST,
345 email: CONFIG.ADMIN.EMAIL,
528a9efa
C
346 publicKey: cert
347 }
89d1d8ba
C
348 }
349
6fcd19ba
C
350 return makeRetryRequest(params)
351 .then(({ response, body }) => {
352 body = body as { cert: string, email: string }
353
354 if (response.statusCode === 200) {
355 const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert, email: body.email })
356 return podObj.save()
357 .then(podCreated => {
358
359 // Add our videos to the request scheduler
360 sendOwnedVideosToPod(podCreated.id)
361 })
362 .catch(err => {
ad0997ad 363 logger.error('Cannot add friend %s pod.', pod.host, err)
6fcd19ba
C
364 })
365 } else {
366 logger.error('Status not 200 for %s pod.', pod.host)
367 }
368 })
369 .catch(err => {
370 logger.error('Error with adding %s pod.', pod.host, { error: err.stack })
528a9efa 371 // Don't break the process
6fcd19ba
C
372 })
373 }, { concurrency: REQUESTS_IN_PARALLEL })
374 .then(() => logger.debug('makeRequestsToWinningPods finished.'))
375 .finally(() => {
528a9efa
C
376 // Final callback, we've ended all the requests
377 // Now we made new friends, we can re activate the pool of requests
c1a7ab7f 378 requestScheduler.activate()
89d1d8ba
C
379 })
380}
00057e85 381
55fa55a9 382// Wrapper that populate "toIds" argument with all our friends if it is not specified
69818c93
C
383type CreateRequestOptions = {
384 type: string
ee9e7b61 385 endpoint: RequestEndpoint
69818c93
C
386 data: Object
387 toIds?: number[]
388 transaction: Sequelize.Transaction
389}
6fcd19ba
C
390function createRequest (options: CreateRequestOptions) {
391 if (options.toIds !== undefined) return requestScheduler.createRequest(options as RequestSchedulerOptions)
feb4bdfd 392
55fa55a9 393 // If the "toIds" pods is not specified, we send the request to all our friends
6fcd19ba 394 return db.Pod.listAllIds(options.transaction).then(podIds => {
ed04d94f 395 const newOptions = Object.assign(options, { toIds: podIds })
6fcd19ba 396 return requestScheduler.createRequest(newOptions)
00057e85
C
397 })
398}
2c49ca42 399
6fcd19ba
C
400function createVideoQaduRequest (options: RequestVideoQaduSchedulerOptions) {
401 return requestVideoQaduScheduler.createRequest(options)
9e167724
C
402}
403
6fcd19ba
C
404function createVideoEventRequest (options: RequestVideoEventSchedulerOptions) {
405 return requestVideoEventScheduler.createRequest(options)
e4c87ec2
C
406}
407
69818c93 408function isMe (host: string) {
65fcc311 409 return host === CONFIG.WEBSERVER.HOST
2c49ca42 410}