]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/friends.ts
Add pod list endpoint with pagination, sort...
[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
91f6f169 83function removeVideoToFriends (videoParams: RemoteVideoRemoveData, transaction: Sequelize.Transaction) {
ed04d94f 84 const options = {
da691c46 85 type: ENDPOINT_ACTIONS.REMOVE,
65fcc311 86 endpoint: REQUEST_ENDPOINTS.VIDEOS,
69818c93 87 data: videoParams,
91f6f169 88 transaction
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
d592e0a9 193 return pods
e7ea2817 194 })
6fcd19ba
C
195 })
196 .then(pods => {
197 const tasks = []
198 pods.forEach(pod => tasks.push(pod.destroy()))
e7ea2817 199
6fcd19ba
C
200 return Promise.all(pods)
201 })
202 .then(() => {
203 logger.info('Removed all remote videos.')
204 // Don't forget to re activate the scheduler, even if there was an error
205 return requestScheduler.activate()
206 })
207 .finally(() => requestScheduler.activate())
9f10b292 208}
c173e565 209
69818c93 210function sendOwnedVideosToPod (podId: number) {
6fcd19ba
C
211 db.Video.listOwnedAndPopulateAuthorAndTags()
212 .then(videosList => {
213 const tasks = []
214 videosList.forEach(video => {
215 const promise = video.toAddRemoteJSON()
216 .then(remoteVideo => {
217 const options = {
218 type: 'add',
219 endpoint: REQUEST_ENDPOINTS.VIDEOS,
220 data: remoteVideo,
221 toIds: [ podId ],
222 transaction: null
223 }
224 return createRequest(options)
225 })
226 .catch(err => {
ad0997ad 227 logger.error('Cannot convert video to remote.', err)
6fcd19ba
C
228 // Don't break the process
229 return undefined
230 })
231
232 tasks.push(promise)
528a9efa 233 })
6fcd19ba
C
234
235 return Promise.all(tasks)
528a9efa 236 })
9f10b292 237}
c173e565 238
f981dae8
C
239function fetchRemotePreview (pod: PodInstance, video: VideoInstance) {
240 const host = video.Author.Pod.host
241 const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName())
242
243 return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
244}
245
d5f5a670
GS
246function removeFriend (pod: PodInstance) {
247 const requestParams = {
248 method: 'POST' as 'POST',
249 path: '/api/' + API_VERSION + '/remote/pods/remove',
250 toPod: pod
251 }
252
253 return makeSecureRequest(requestParams)
254 .then(() => pod.destroy())
255 .then(() => {
256 logger.info('Removed friend.')
257 })
258 .catch(err => {
259 logger.error('Some errors while quitting friend %s (id: %d).', pod.host, pod.id, err)
260 })
261}
262
99fdec46
C
263function getRequestScheduler () {
264 return requestScheduler
265}
266
267function getRequestVideoQaduScheduler () {
268 return requestVideoQaduScheduler
269}
270
271function getRequestVideoEventScheduler () {
272 return requestVideoEventScheduler
273}
274
9f10b292 275// ---------------------------------------------------------------------------
c173e565 276
65fcc311
C
277export {
278 activateSchedulers,
279 addVideoToFriends,
280 updateVideoToFriends,
281 reportAbuseVideoToFriend,
282 quickAndDirtyUpdateVideoToFriends,
283 quickAndDirtyUpdatesVideoToFriends,
284 addEventToRemoteVideo,
285 addEventsToRemoteVideo,
286 hasFriends,
287 makeFriends,
288 quitFriends,
d5f5a670 289 removeFriend,
65fcc311
C
290 removeVideoToFriends,
291 sendOwnedVideosToPod,
292 getRequestScheduler,
293 getRequestVideoQaduScheduler,
f981dae8
C
294 getRequestVideoEventScheduler,
295 fetchRemotePreview
65fcc311 296}
c173e565 297
9f10b292 298// ---------------------------------------------------------------------------
c173e565 299
6fcd19ba
C
300function computeForeignPodsList (host: string, podsScore: { [ host: string ]: number }) {
301 // TODO: type res
e6d4b0ff
C
302 return getForeignPodsList(host).then(res => {
303 const foreignPodsList: { host: string }[] = res.data
528a9efa
C
304
305 // Let's give 1 point to the pod we ask the friends list
49abbbbe 306 foreignPodsList.push({ host })
89d1d8ba 307
6fcd19ba 308 foreignPodsList.forEach(foreignPod => {
49abbbbe 309 const foreignPodHost = foreignPod.host
89d1d8ba 310
49abbbbe
C
311 if (podsScore[foreignPodHost]) podsScore[foreignPodHost]++
312 else podsScore[foreignPodHost] = 1
89d1d8ba 313 })
cbe2f7c3 314
6fcd19ba 315 return undefined
89d1d8ba
C
316 })
317}
318
69818c93 319function computeWinningPods (hosts: string[], podsScore: { [ host: string ]: number }) {
89d1d8ba
C
320 // Build the list of pods to add
321 // Only add a pod if it exists in more than a half base pods
bc503c2a 322 const podsList = []
49abbbbe 323 const baseScore = hosts.length / 2
1e4b0080 324
6fcd19ba 325 Object.keys(podsScore).forEach(podHost => {
2c49ca42 326 // If the pod is not me and with a good score we add it
49abbbbe
C
327 if (isMe(podHost) === false && podsScore[podHost] > baseScore) {
328 podsList.push({ host: podHost })
2c49ca42 329 }
89d1d8ba 330 })
e7ea2817 331
bc503c2a 332 return podsList
89d1d8ba
C
333}
334
6fcd19ba 335function getForeignPodsList (host: string) {
0aef76c4 336 return new Promise< ResultList<FormattedPod> >((res, rej) => {
8a02bd04 337 const path = '/api/' + API_VERSION + '/remote/pods/list'
c173e565 338
8a02bd04 339 request.post(REMOTE_SCHEME.HTTP + '://' + host + path, (err, response, body) => {
6fcd19ba 340 if (err) return rej(err)
8425cb89 341
6fcd19ba
C
342 try {
343 const json = JSON.parse(body)
344 return res(json)
345 } catch (err) {
346 return rej(err)
347 }
348 })
9f10b292
C
349 })
350}
89d1d8ba 351
6fcd19ba 352function makeRequestsToWinningPods (cert: string, podsList: PodInstance[]) {
89d1d8ba 353 // Stop pool requests
c1a7ab7f 354 requestScheduler.deactivate()
89d1d8ba 355 // Flush pool requests
c1a7ab7f 356 requestScheduler.forceSend()
89d1d8ba 357
6fcd19ba 358 return Promise.map(podsList, pod => {
528a9efa 359 const params = {
8a02bd04 360 url: REMOTE_SCHEME.HTTP + '://' + pod.host + '/api/' + API_VERSION + '/remote/pods/add',
69818c93 361 method: 'POST' as 'POST',
528a9efa 362 json: {
65fcc311
C
363 host: CONFIG.WEBSERVER.HOST,
364 email: CONFIG.ADMIN.EMAIL,
528a9efa
C
365 publicKey: cert
366 }
89d1d8ba
C
367 }
368
6fcd19ba
C
369 return makeRetryRequest(params)
370 .then(({ response, body }) => {
371 body = body as { cert: string, email: string }
372
373 if (response.statusCode === 200) {
374 const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert, email: body.email })
375 return podObj.save()
376 .then(podCreated => {
377
378 // Add our videos to the request scheduler
379 sendOwnedVideosToPod(podCreated.id)
380 })
381 .catch(err => {
ad0997ad 382 logger.error('Cannot add friend %s pod.', pod.host, err)
6fcd19ba
C
383 })
384 } else {
385 logger.error('Status not 200 for %s pod.', pod.host)
386 }
387 })
388 .catch(err => {
389 logger.error('Error with adding %s pod.', pod.host, { error: err.stack })
528a9efa 390 // Don't break the process
6fcd19ba
C
391 })
392 }, { concurrency: REQUESTS_IN_PARALLEL })
393 .then(() => logger.debug('makeRequestsToWinningPods finished.'))
394 .finally(() => {
528a9efa
C
395 // Final callback, we've ended all the requests
396 // Now we made new friends, we can re activate the pool of requests
c1a7ab7f 397 requestScheduler.activate()
89d1d8ba
C
398 })
399}
00057e85 400
55fa55a9 401// Wrapper that populate "toIds" argument with all our friends if it is not specified
69818c93
C
402type CreateRequestOptions = {
403 type: string
ee9e7b61 404 endpoint: RequestEndpoint
69818c93
C
405 data: Object
406 toIds?: number[]
407 transaction: Sequelize.Transaction
408}
6fcd19ba
C
409function createRequest (options: CreateRequestOptions) {
410 if (options.toIds !== undefined) return requestScheduler.createRequest(options as RequestSchedulerOptions)
feb4bdfd 411
55fa55a9 412 // If the "toIds" pods is not specified, we send the request to all our friends
6fcd19ba 413 return db.Pod.listAllIds(options.transaction).then(podIds => {
ed04d94f 414 const newOptions = Object.assign(options, { toIds: podIds })
6fcd19ba 415 return requestScheduler.createRequest(newOptions)
00057e85
C
416 })
417}
2c49ca42 418
6fcd19ba
C
419function createVideoQaduRequest (options: RequestVideoQaduSchedulerOptions) {
420 return requestVideoQaduScheduler.createRequest(options)
9e167724
C
421}
422
6fcd19ba
C
423function createVideoEventRequest (options: RequestVideoEventSchedulerOptions) {
424 return requestVideoEventScheduler.createRequest(options)
e4c87ec2
C
425}
426
69818c93 427function isMe (host: string) {
65fcc311 428 return host === CONFIG.WEBSERVER.HOST
2c49ca42 429}