Fetch things in bulk for the homepage

pull/4300/head
Chocobozzz 2021-08-02 15:29:09 +02:00
parent 200eaf5152
commit 3da38d6e9f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
14 changed files with 213 additions and 52 deletions

View File

@ -44,13 +44,21 @@ export class RestService {
return newParams
}
addArrayParams (params: HttpParams, name: string, values: (string | number)[]) {
for (const v of values) {
params = params.append(name, v)
}
return params
}
addObjectParams (params: HttpParams, object: { [ name: string ]: any }) {
for (const name of Object.keys(object)) {
const value = object[name]
if (value === undefined || value === null) continue
if (Array.isArray(value)) {
for (const v of value) params = params.append(name, v)
params = this.addArrayParams(params, name, value)
} else {
params = params.append(name, value)
}

View File

@ -1,5 +1,6 @@
export * from './locales'
export * from './constants'
export * from './i18n-utils'
export * from './rxjs'
export * from './utils'
export * from './zone'

View File

@ -0,0 +1,29 @@
import { uniq } from 'lodash-es'
import { asyncScheduler, Observable } from 'rxjs'
import { bufferTime, distinctUntilChanged, filter, map, observeOn, share, switchMap } from 'rxjs/operators'
import { NgZone } from '@angular/core'
import { enterZone, leaveZone } from './zone'
function buildBulkObservable <T extends number | string, R> (options: {
ngZone: NgZone
notifierObservable: Observable<T>
time: number
bulkGet: (params: T[]) => Observable<R>
}) {
const { ngZone, notifierObservable, time, bulkGet } = options
return notifierObservable.pipe(
distinctUntilChanged(),
// We leave Angular zone so Protractor does not get stuck
bufferTime(time, leaveZone(ngZone, asyncScheduler)),
filter(params => params.length !== 0),
map(params => uniq(params)),
observeOn(enterZone(ngZone, asyncScheduler)),
switchMap(params => bulkGet(params)),
share()
)
}
export {
buildBulkObservable
}

View File

@ -65,15 +65,15 @@ export class CustomMarkupService {
for (const selector of Object.keys(this.htmlBuilders)) {
rootElement.querySelectorAll(selector)
.forEach((e: HTMLElement) => {
try {
const element = this.execHTMLBuilder(selector, e)
// Insert as first child
e.insertBefore(element, e.firstChild)
} catch (err) {
console.error('Cannot inject component %s.', selector, err)
}
})
.forEach((e: HTMLElement) => {
try {
const element = this.execHTMLBuilder(selector, e)
// Insert as first child
e.insertBefore(element, e.firstChild)
} catch (err) {
console.error('Cannot inject component %s.', selector, err)
}
})
}
const loadedPromises: Promise<boolean>[] = []

View File

@ -2,8 +2,9 @@ import { from } from 'rxjs'
import { finalize, map, switchMap, tap } from 'rxjs/operators'
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { MarkdownService, Notifier, UserService } from '@app/core'
import { FindInBulkService } from '@app/shared/shared-search'
import { Video, VideoSortField } from '@shared/models/videos'
import { VideoChannel, VideoChannelService, VideoService } from '../../shared-main'
import { VideoChannel, VideoService } from '../../shared-main'
import { CustomMarkupComponent } from './shared'
/*
@ -29,14 +30,14 @@ export class ChannelMiniatureMarkupComponent implements CustomMarkupComponent, O
constructor (
private markdown: MarkdownService,
private channelService: VideoChannelService,
private findInBulk: FindInBulkService,
private videoService: VideoService,
private userService: UserService,
private notifier: Notifier
) { }
ngOnInit () {
this.channelService.getVideoChannel(this.name)
this.findInBulk.getChannel(this.name)
.pipe(
tap(channel => this.channel = channel),
switchMap(() => from(this.markdown.textMarkdownToHTML(this.channel.description))),

View File

@ -1,8 +1,9 @@
import { finalize } from 'rxjs/operators'
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { Notifier } from '@app/core'
import { FindInBulkService } from '@app/shared/shared-search'
import { MiniatureDisplayOptions } from '../../shared-video-miniature'
import { VideoPlaylist, VideoPlaylistService } from '../../shared-video-playlist'
import { VideoPlaylist } from '../../shared-video-playlist'
import { CustomMarkupComponent } from './shared'
/*
@ -33,12 +34,12 @@ export class PlaylistMiniatureMarkupComponent implements CustomMarkupComponent,
}
constructor (
private playlistService: VideoPlaylistService,
private findInBulkService: FindInBulkService,
private notifier: Notifier
) { }
ngOnInit () {
this.playlistService.getVideoPlaylist(this.uuid)
this.findInBulkService.getPlaylist(this.uuid)
.pipe(finalize(() => this.loaded.emit(true)))
.subscribe(
playlist => this.playlist = playlist,

View File

@ -4,6 +4,7 @@ import { AuthService, Notifier } from '@app/core'
import { Video, VideoService } from '../../shared-main'
import { MiniatureDisplayOptions } from '../../shared-video-miniature'
import { CustomMarkupComponent } from './shared'
import { FindInBulkService } from '@app/shared/shared-search'
/*
* Markup component that creates a video miniature only
@ -35,7 +36,7 @@ export class VideoMiniatureMarkupComponent implements CustomMarkupComponent, OnI
constructor (
private auth: AuthService,
private videoService: VideoService,
private findInBulk: FindInBulkService,
private notifier: Notifier
) { }
@ -50,7 +51,7 @@ export class VideoMiniatureMarkupComponent implements CustomMarkupComponent, OnI
}
}
this.videoService.getVideo({ videoId: this.uuid })
this.findInBulk.getVideo(this.uuid)
.pipe(finalize(() => this.loaded.emit(true)))
.subscribe(
video => this.video = video,

View File

@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'
import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
import { SharedGlobalIconModule } from '../shared-icons'
import { SharedMainModule } from '../shared-main'
import { SharedSearchModule } from '../shared-search'
import { SharedVideoMiniatureModule } from '../shared-video-miniature'
import { SharedVideoPlaylistModule } from '../shared-video-playlist'
import { CustomMarkupContainerComponent } from './custom-markup-container.component'
@ -26,7 +27,8 @@ import {
SharedGlobalIconModule,
SharedVideoMiniatureModule,
SharedVideoPlaylistModule,
SharedActorImageModule
SharedActorImageModule,
SharedSearchModule
],
declarations: [

View File

@ -0,0 +1,118 @@
import * as debug from 'debug'
import { Observable, Subject } from 'rxjs'
import { map } from 'rxjs/operators'
import { Injectable, NgZone } from '@angular/core'
import { buildBulkObservable } from '@app/helpers'
import { ResultList } from '@shared/models/common'
import { Video, VideoChannel } from '../shared-main'
import { VideoPlaylist } from '../shared-video-playlist'
import { SearchService } from './search.service'
const logger = debug('peertube:search:FindInBulkService')
type BulkObservables <P extends number | string, R> = {
notifier: Subject<P>
result: Observable<R>
}
@Injectable()
export class FindInBulkService {
private getVideoInBulk: BulkObservables<string, ResultList<Video>>
private getChannelInBulk: BulkObservables<string, ResultList<VideoChannel>>
private getPlaylistInBulk: BulkObservables<string, ResultList<VideoPlaylist>>
constructor (
private searchService: SearchService,
private ngZone: NgZone
) {
this.getVideoInBulk = this.buildBulkObservableObject(this.getVideosInBulk.bind(this))
this.getChannelInBulk = this.buildBulkObservableObject(this.getChannelsInBulk.bind(this))
this.getPlaylistInBulk = this.buildBulkObservableObject(this.getPlaylistsInBulk.bind(this))
}
getVideo (uuid: string): Observable<Video> {
logger('Schedule video fetch for uuid %s.', uuid)
return this.getData({
observableObject: this.getVideoInBulk,
finder: v => v.uuid === uuid,
param: uuid
})
}
getChannel (handle: string): Observable<VideoChannel> {
logger('Schedule channel fetch for handle %s.', handle)
return this.getData({
observableObject: this.getChannelInBulk,
finder: c => c.nameWithHost === handle || c.nameWithHostForced === handle,
param: handle
})
}
getPlaylist (uuid: string): Observable<VideoPlaylist> {
logger('Schedule playlist fetch for uuid %s.', uuid)
return this.getData({
observableObject: this.getPlaylistInBulk,
finder: p => p.uuid === uuid,
param: uuid
})
}
private getData <P extends number | string, R> (options: {
observableObject: BulkObservables<P, ResultList<R>>
param: P
finder: (d: R) => boolean
}) {
const { observableObject, param, finder } = options
return new Observable<R>(obs => {
observableObject.result
.pipe(
map(({ data }) => data),
map(data => data.find(finder))
)
.subscribe(result => {
obs.next(result)
obs.complete()
})
observableObject.notifier.next(param)
})
}
private getVideosInBulk (uuids: string[]) {
logger('Fetching videos %s.', uuids.join(', '))
return this.searchService.searchVideos({ uuids })
}
private getChannelsInBulk (handles: string[]) {
logger('Fetching channels %s.', handles.join(', '))
return this.searchService.searchVideoChannels({ handles })
}
private getPlaylistsInBulk (uuids: string[]) {
logger('Fetching playlists %s.', uuids.join(', '))
return this.searchService.searchVideoPlaylists({ uuids })
}
private buildBulkObservableObject <T extends number | string, R> (bulkGet: (params: T[]) => Observable<R>) {
const notifier = new Subject<T>()
return {
notifier,
result: buildBulkObservable({
time: 500,
bulkGet,
ngZone: this.ngZone,
notifierObservable: notifier.asObservable()
})
}
}
}

View File

@ -1,3 +1,4 @@
export * from './advanced-search.model'
export * from './find-in-bulk.service'
export * from './search.service'
export * from './shared-search.module'

View File

@ -32,11 +32,12 @@ export class SearchService {
}
searchVideos (parameters: {
search: string
search?: string
componentPagination?: ComponentPaginationLight
advancedSearch?: AdvancedSearch
uuids?: string[]
}): Observable<ResultList<Video>> {
const { search, componentPagination, advancedSearch } = parameters
const { search, uuids, componentPagination, advancedSearch } = parameters
const url = SearchService.BASE_SEARCH_URL + 'videos'
let pagination: RestPagination
@ -49,6 +50,7 @@ export class SearchService {
params = this.restService.addRestGetParams(params, pagination)
if (search) params = params.append('search', search)
if (uuids) params = this.restService.addArrayParams(params, 'uuids', uuids)
if (advancedSearch) {
const advancedSearchObject = advancedSearch.toVideosAPIObject()
@ -64,11 +66,12 @@ export class SearchService {
}
searchVideoChannels (parameters: {
search: string
search?: string
advancedSearch?: AdvancedSearch
componentPagination?: ComponentPaginationLight
handles?: string[]
}): Observable<ResultList<VideoChannel>> {
const { search, advancedSearch, componentPagination } = parameters
const { search, advancedSearch, componentPagination, handles } = parameters
const url = SearchService.BASE_SEARCH_URL + 'video-channels'
@ -81,6 +84,7 @@ export class SearchService {
params = this.restService.addRestGetParams(params, pagination)
if (search) params = params.append('search', search)
if (handles) params = this.restService.addArrayParams(params, 'handles', handles)
if (advancedSearch) {
const advancedSearchObject = advancedSearch.toChannelAPIObject()
@ -96,11 +100,12 @@ export class SearchService {
}
searchVideoPlaylists (parameters: {
search: string
search?: string
advancedSearch?: AdvancedSearch
componentPagination?: ComponentPaginationLight
uuids?: string[]
}): Observable<ResultList<VideoPlaylist>> {
const { search, advancedSearch, componentPagination } = parameters
const { search, advancedSearch, componentPagination, uuids } = parameters
const url = SearchService.BASE_SEARCH_URL + 'video-playlists'
@ -113,6 +118,7 @@ export class SearchService {
params = this.restService.addRestGetParams(params, pagination)
if (search) params = params.append('search', search)
if (uuids) params = this.restService.addArrayParams(params, 'uuids', uuids)
if (advancedSearch) {
const advancedSearchObject = advancedSearch.toPlaylistAPIObject()

View File

@ -1,6 +1,7 @@
import { NgModule } from '@angular/core'
import { SharedMainModule } from '../shared-main'
import { SharedVideoPlaylistModule } from '../shared-video-playlist'
import { FindInBulkService } from './find-in-bulk.service'
import { SearchService } from './search.service'
@NgModule({
@ -16,6 +17,7 @@ import { SearchService } from './search.service'
],
providers: [
FindInBulkService,
SearchService
]
})

View File

@ -1,11 +1,10 @@
import * as debug from 'debug'
import { uniq } from 'lodash-es'
import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators'
import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
import { catchError, filter, map, switchMap, tap } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable, NgZone } from '@angular/core'
import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
import { enterZone, leaveZone } from '@app/helpers'
import { buildBulkObservable } from '@app/helpers'
import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
import { ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models'
import { environment } from '../../../environments/environment'
@ -35,15 +34,12 @@ export class UserSubscriptionService {
private ngZone: NgZone
) {
this.existsObservable = merge(
this.existsSubject.pipe(
// We leave Angular zone so Protractor does not get stuck
bufferTime(500, leaveZone(this.ngZone, asyncScheduler)),
filter(uris => uris.length !== 0),
map(uris => uniq(uris)),
observeOn(enterZone(this.ngZone, asyncScheduler)),
switchMap(uris => this.doSubscriptionsExist(uris)),
share()
),
buildBulkObservable({
time: 500,
ngZone: this.ngZone,
notifierObservable: this.existsSubject,
bulkGet: this.doSubscriptionsExist.bind(this)
}),
this.myAccountSubscriptionCacheSubject
)

View File

@ -1,11 +1,10 @@
import * as debug from 'debug'
import { uniq } from 'lodash-es'
import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap, distinctUntilChanged } from 'rxjs/operators'
import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
import { catchError, filter, map, share, switchMap, tap } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable, NgZone } from '@angular/core'
import { AuthUser, ComponentPaginationLight, RestExtractor, RestService, ServerService } from '@app/core'
import { enterZone, leaveZone, objectToFormData } from '@app/helpers'
import { buildBulkObservable, objectToFormData } from '@app/helpers'
import { Account, AccountService, VideoChannel, VideoChannelService } from '@app/shared/shared-main'
import {
ResultList,
@ -52,16 +51,12 @@ export class VideoPlaylistService {
private ngZone: NgZone
) {
this.videoExistsInPlaylistObservable = merge(
this.videoExistsInPlaylistNotifier.pipe(
distinctUntilChanged(),
// We leave Angular zone so Protractor does not get stuck
bufferTime(500, leaveZone(this.ngZone, asyncScheduler)),
filter(videoIds => videoIds.length !== 0),
map(videoIds => uniq(videoIds)),
observeOn(enterZone(this.ngZone, asyncScheduler)),
switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
share()
),
buildBulkObservable({
time: 500,
ngZone: this.ngZone,
bulkGet: this.doVideosExistInPlaylist.bind(this),
notifierObservable: this.videoExistsInPlaylistNotifier
}),
this.videoExistsInPlaylistCacheSubject
)