]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/app/shared/video-playlist/video-playlist.service.ts
Skip videos count on client if we don't use it
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / video-playlist / video-playlist.service.ts
index 376387082dfe2421d9c05e58bc48a1c864765279..1ec9315efddb41de0e4bb4fd1a8cfb5870d81080 100644 (file)
@@ -1,6 +1,6 @@
-import { bufferTime, catchError, filter, first, map, share, switchMap } from 'rxjs/operators'
+import { bufferTime, catchError, filter, map, share, switchMap, tap } from 'rxjs/operators'
 import { Injectable } from '@angular/core'
-import { Observable, ReplaySubject, Subject } from 'rxjs'
+import { merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
 import { RestExtractor } from '../rest/rest-extractor.service'
 import { HttpClient, HttpParams } from '@angular/common/http'
 import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared'
@@ -11,16 +11,22 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model'
 import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model'
 import { objectToFormData } from '@app/shared/misc/utils'
-import { ServerService } from '@app/core'
+import { AuthUser, ServerService } from '@app/core'
 import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 import { AccountService } from '@app/shared/account/account.service'
 import { Account } from '@app/shared/account/account.model'
 import { RestService } from '@app/shared/rest'
-import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
+import { VideoExistInPlaylist, VideosExistInPlaylists } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
 import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model'
-import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
+import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model'
 import { VideoPlaylistElement as ServerVideoPlaylistElement } from '@shared/models/videos/playlist/video-playlist-element.model'
 import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
+import { uniq } from 'lodash-es'
+import * as debug from 'debug'
+
+const logger = debug('peertube:playlists:VideoPlaylistService')
+
+export type CachedPlaylist = VideoPlaylist | { id: number, displayName: string }
 
 @Injectable()
 export class VideoPlaylistService {
@@ -28,8 +34,15 @@ export class VideoPlaylistService {
   static MY_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/users/me/video-playlists/'
 
   // Use a replay subject because we "next" a value before subscribing
-  private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1)
-  private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist>
+  private videoExistsInPlaylistNotifier = new ReplaySubject<number>(1)
+  private videoExistsInPlaylistCacheSubject = new Subject<VideosExistInPlaylists>()
+  private readonly videoExistsInPlaylistObservable: Observable<VideosExistInPlaylists>
+
+  private videoExistsObservableCache: { [ id: number ]: Observable<VideoExistInPlaylist[]> } = {}
+  private videoExistsCache: { [ id: number ]: VideoExistInPlaylist[] } = {}
+
+  private myAccountPlaylistCache: ResultList<CachedPlaylist> = undefined
+  private myAccountPlaylistCacheSubject = new Subject<ResultList<CachedPlaylist>>()
 
   constructor (
     private authHttp: HttpClient,
@@ -37,15 +50,20 @@ export class VideoPlaylistService {
     private restExtractor: RestExtractor,
     private restService: RestService
   ) {
-    this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe(
-      bufferTime(500),
-      filter(videoIds => videoIds.length !== 0),
-      switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
-      share()
+    this.videoExistsInPlaylistObservable = merge(
+      this.videoExistsInPlaylistNotifier.pipe(
+        bufferTime(500),
+        filter(videoIds => videoIds.length !== 0),
+        map(videoIds => uniq(videoIds)),
+        switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
+        share()
+      ),
+
+      this.videoExistsInPlaylistCacheSubject
     )
   }
 
-  listChannelPlaylists (videoChannel: VideoChannel, componentPagination: ComponentPagination): Observable<ResultList<VideoPlaylist>> {
+  listChannelPlaylists (videoChannel: VideoChannel, componentPagination: ComponentPaginationLight): Observable<ResultList<VideoPlaylist>> {
     const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/video-playlists'
     const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
 
@@ -59,7 +77,23 @@ export class VideoPlaylistService {
                )
   }
 
-  listAccountPlaylists (account: Account, componentPagination: ComponentPagination, sort: string): Observable<ResultList<VideoPlaylist>> {
+  listMyPlaylistWithCache (user: AuthUser, search?: string) {
+    if (!search && this.myAccountPlaylistCache) return of(this.myAccountPlaylistCache)
+
+    return this.listAccountPlaylists(user.account, undefined, '-updatedAt', search)
+               .pipe(
+                 tap(result => {
+                   if (!search) this.myAccountPlaylistCache = result
+                 })
+               )
+  }
+
+  listAccountPlaylists (
+    account: Account,
+    componentPagination: ComponentPaginationLight,
+    sort: string,
+    search?: string
+  ): Observable<ResultList<VideoPlaylist>> {
     const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-playlists'
     const pagination = componentPagination
       ? this.restService.componentPaginationToRestPagination(componentPagination)
@@ -67,6 +101,7 @@ export class VideoPlaylistService {
 
     let params = new HttpParams()
     params = this.restService.addRestGetParams(params, pagination, sort)
+    if (search) params = this.restService.addObjectParams(params, { search })
 
     return this.authHttp.get<ResultList<VideoPlaylist>>(url, { params })
                .pipe(
@@ -90,6 +125,16 @@ export class VideoPlaylistService {
 
     return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data)
                .pipe(
+                 tap(res => {
+                   this.myAccountPlaylistCache.total++
+
+                   this.myAccountPlaylistCache.data.push({
+                     id: res.videoPlaylist.id,
+                     displayName: body.displayName
+                   })
+
+                   this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
@@ -100,6 +145,12 @@ export class VideoPlaylistService {
     return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id, data)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   const playlist = this.myAccountPlaylistCache.data.find(p => p.id === videoPlaylist.id)
+                   playlist.displayName = body.displayName
+
+                   this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
@@ -108,30 +159,64 @@ export class VideoPlaylistService {
     return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   this.myAccountPlaylistCache.total--
+                   this.myAccountPlaylistCache.data = this.myAccountPlaylistCache.data
+                                                          .filter(p => p.id !== videoPlaylist.id)
+
+                   this.myAccountPlaylistCacheSubject.next(this.myAccountPlaylistCache)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
 
   addVideoInPlaylist (playlistId: number, body: VideoPlaylistElementCreate) {
-    return this.authHttp.post(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos', body)
+    const url = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos'
+
+    return this.authHttp.post<{ videoPlaylistElement: { id: number } }>(url, body)
                .pipe(
-                 map(this.restExtractor.extractDataBool),
+                 tap(res => {
+                   const existsResult = this.videoExistsCache[body.videoId]
+                   existsResult.push({
+                     playlistId,
+                     playlistElementId: res.videoPlaylistElement.id,
+                     startTimestamp: body.startTimestamp,
+                     stopTimestamp: body.stopTimestamp
+                   })
+
+                   this.runPlaylistCheck(body.videoId)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
 
-  updateVideoOfPlaylist (playlistId: number, playlistElementId: number, body: VideoPlaylistElementUpdate) {
+  updateVideoOfPlaylist (playlistId: number, playlistElementId: number, body: VideoPlaylistElementUpdate, videoId: number) {
     return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId, body)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   const existsResult = this.videoExistsCache[videoId]
+                   const elem = existsResult.find(e => e.playlistElementId === playlistElementId)
+
+                   elem.startTimestamp = body.startTimestamp
+                   elem.stopTimestamp = body.stopTimestamp
+
+                   this.runPlaylistCheck(videoId)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
 
-  removeVideoFromPlaylist (playlistId: number, playlistElementId: number) {
+  removeVideoFromPlaylist (playlistId: number, playlistElementId: number, videoId?: number) {
     return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + playlistElementId)
                .pipe(
                  map(this.restExtractor.extractDataBool),
+                 tap(() => {
+                   if (!videoId) return
+
+                   this.videoExistsCache[videoId] = this.videoExistsCache[videoId].filter(e => e.playlistElementId !== playlistElementId)
+                   this.runPlaylistCheck(videoId)
+                 }),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }
@@ -151,7 +236,7 @@ export class VideoPlaylistService {
 
   getPlaylistVideos (
     videoPlaylistId: number | string,
-    componentPagination: ComponentPagination
+    componentPagination: ComponentPaginationLight
   ): Observable<ResultList<VideoPlaylistElement>> {
     const path = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylistId + '/videos'
     const pagination = this.restService.componentPaginationToRestPagination(componentPagination)
@@ -167,14 +252,41 @@ export class VideoPlaylistService {
                )
   }
 
-  doesVideoExistInPlaylist (videoId: number) {
-    this.videoExistsInPlaylistSubject.next(videoId)
+  listenToMyAccountPlaylistsChange () {
+    return this.myAccountPlaylistCacheSubject.asObservable()
+  }
+
+  listenToVideoPlaylistChange (videoId: number) {
+    if (this.videoExistsObservableCache[ videoId ]) {
+      return this.videoExistsObservableCache[ videoId ]
+    }
+
+    const obs = this.videoExistsInPlaylistObservable
+                    .pipe(
+                      map(existsResult => existsResult[ videoId ]),
+                      filter(r => !!r),
+                      tap(result => this.videoExistsCache[ videoId ] = result)
+                    )
 
-    return this.videoExistsInPlaylistObservable.pipe(first())
+    this.videoExistsObservableCache[ videoId ] = obs
+    return obs
+  }
+
+  runPlaylistCheck (videoId: number) {
+    logger('Running playlist check.')
+
+    if (this.videoExistsCache[videoId]) {
+      logger('Found cache for %d.', videoId)
+
+      return this.videoExistsInPlaylistCacheSubject.next({ [videoId]: this.videoExistsCache[videoId] })
+    }
+
+    logger('Fetching from network for %d.', videoId)
+    return this.videoExistsInPlaylistNotifier.next(videoId)
   }
 
   extractPlaylists (result: ResultList<VideoPlaylistServerModel>) {
-    return this.serverService.localeObservable
+    return this.serverService.getServerLocale()
                .pipe(
                  map(translations => {
                    const playlistsJSON = result.data
@@ -191,12 +303,12 @@ export class VideoPlaylistService {
   }
 
   extractPlaylist (playlist: VideoPlaylistServerModel) {
-    return this.serverService.localeObservable
+    return this.serverService.getServerLocale()
                .pipe(map(translations => new VideoPlaylist(playlist, translations)))
   }
 
   extractVideoPlaylistElements (result: ResultList<ServerVideoPlaylistElement>) {
-    return this.serverService.localeObservable
+    return this.serverService.getServerLocale()
                .pipe(
                  map(translations => {
                    const elementsJson = result.data
@@ -212,13 +324,13 @@ export class VideoPlaylistService {
                )
   }
 
-  private doVideosExistInPlaylist (videoIds: number[]): Observable<VideoExistInPlaylist> {
+  private doVideosExistInPlaylist (videoIds: number[]): Observable<VideosExistInPlaylists> {
     const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist'
-    let params = new HttpParams()
 
+    let params = new HttpParams()
     params = this.restService.addObjectParams(params, { videoIds })
 
-    return this.authHttp.get<VideoExistInPlaylist>(url, { params })
+    return this.authHttp.get<VideoExistInPlaylist>(url, { params, headers: { ignoreLoadingBar: '' } })
                .pipe(catchError(err => this.restExtractor.handleError(err)))
   }
 }