From 3da5af354efd48875607872e54aff70106503b53 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:01:07 +0100 Subject: [PATCH] [feature-107] We can now download playlist's missing maps --- src/main/ipcs/bs-playlist-ipcs.ts | 12 ++++++ .../local-playlists-manager.service.ts | 39 ++++++++++++------- .../maps/local-maps-manager.service.ts | 2 + .../maps/local-maps-list-panel.component.tsx | 23 ++++++++--- ...local-playlist-details-modal.component.tsx | 27 +++++++++++-- .../bsm-progress-bar.component.tsx | 2 +- src/renderer/services/ipc.service.ts | 10 ++--- .../services/maps-downloader.service.ts | 13 ++++--- .../services/playlist-downloader.service.ts | 31 ++++++++++++--- 9 files changed, 118 insertions(+), 41 deletions(-) diff --git a/src/main/ipcs/bs-playlist-ipcs.ts b/src/main/ipcs/bs-playlist-ipcs.ts index 31adb7b8..7a7eaa3b 100644 --- a/src/main/ipcs/bs-playlist-ipcs.ts +++ b/src/main/ipcs/bs-playlist-ipcs.ts @@ -2,6 +2,7 @@ import { BSVersion } from "shared/bs-version.interface"; import { LocalPlaylistsManagerService } from "../services/additional-content/local-playlists-manager.service"; import { IpcService } from "../services/ipc.service"; import { of, throwError } from "rxjs"; +import { BPList } from "shared/models/playlists/playlist.interface"; const ipc = IpcService.getInstance(); @@ -10,6 +11,17 @@ ipc.on("one-click-install-playlist", (req, reply) => { reply(playlists.oneClickInstallPlaylist(req.args)); }); +ipc.on<{playlist: BPList, version: BSVersion}>("install-playlist", (req, reply) => { + const playlists = LocalPlaylistsManagerService.getInstance(); + + if(req.args.playlist?.customData?.syncURL){ + return reply(playlists.downloadPlaylist(req.args.playlist.customData.syncURL, req.args.version)); + } + + reply(playlists.downloadPlaylistSongs(req.args.playlist.songs, req.args.version)); + +}); + ipc.on("register-playlists-deep-link", (_, reply) => { const playlists = LocalPlaylistsManagerService.getInstance(); diff --git a/src/main/services/additional-content/local-playlists-manager.service.ts b/src/main/services/additional-content/local-playlists-manager.service.ts index 243a1d35..11c33dfe 100644 --- a/src/main/services/additional-content/local-playlists-manager.service.ts +++ b/src/main/services/additional-content/local-playlists-manager.service.ts @@ -7,7 +7,7 @@ import { RequestService } from "../request.service"; import { LocalMapsManagerService } from "./maps/local-maps-manager.service"; import log from "electron-log"; import { WindowManagerService } from "../window-manager.service"; -import { BPList, DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface"; +import { BPList, DownloadPlaylistProgressionData, PlaylistSong } from "shared/models/playlists/playlist.interface"; import { readFileSync } from "fs"; import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service"; import { copy, copyFile, ensureDir, pathExists, pathExistsSync, readdirSync, realpath } from "fs-extra"; @@ -185,28 +185,23 @@ export class LocalPlaylistsManagerService { }); } - public downloadPlaylist(bpListUrl: string, version: BSVersion): Observable> { - + public downloadPlaylistSongs(bpList: PlaylistSong[], version: BSVersion): Observable> { return new Observable>(obs => { (async () => { - - const bpListFilePath = await this.installBPListFile(bpListUrl, version); - const bpList = await this.readPlaylistFile(bpListFilePath); - const progress: Progression = { - total: bpList.songs.length, + total: bpList.length, current: 0, data: { downloadedMaps: [], currentDownload: null, - playlistInfos: bpList, - playlistPath: bpListFilePath, + playlistInfos: null, + playlistPath: null, } }; obs.next(progress); - for (const song of bpList.songs) { + for (const song of bpList) { const [ mapDetail ] = await this.bsaver.getMapDetailsFromHashs([song.hash]); if(!mapDetail) { @@ -225,13 +220,29 @@ export class LocalPlaylistsManagerService { .catch(err => obs.error(err)) .finally(() => obs.complete()); }); + } + + public downloadPlaylist(bpListUrl: string, version: BSVersion): Observable> { + + return new Observable>(obs => { + (async () => { + + const bpListFilePath = await this.installBPListFile(bpListUrl, version); + const bpList = await this.readPlaylistFile(bpListFilePath); + + await lastValueFrom(this.downloadPlaylistSongs(bpList.songs, version).pipe( + tap({ next: p => obs.next(p) }), + )); + + })() + .catch(err => obs.error(err)) + .finally(() => obs.complete()); + }); } public deletePlaylist(opt: {path: string, deleteMaps?: boolean}): Observable{ - console.log("AAAAAA"); - return new Observable(obs => { (async () => { @@ -247,8 +258,6 @@ export class LocalPlaylistsManagerService { progress.current += 1 obs.next(progress); }, - error: err => obs.error(err), - complete: () => obs.next(progress), }), )); } diff --git a/src/main/services/additional-content/maps/local-maps-manager.service.ts b/src/main/services/additional-content/maps/local-maps-manager.service.ts index 071b5af9..8b1a1405 100644 --- a/src/main/services/additional-content/maps/local-maps-manager.service.ts +++ b/src/main/services/additional-content/maps/local-maps-manager.service.ts @@ -302,6 +302,8 @@ export class LocalMapsManagerService { const localMap = await this.loadMapInfoFromPath(mapPath); localMap.songDetails = this.songDetailsCache.getSongDetails(localMap.hash); + this.ipc.send<{map: BsmLocalMap, version?: BSVersion}>("map-downloaded", this.windows.getWindows("index.html").at(0), { map: localMap, version }); + return localMap; } diff --git a/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx b/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx index e35be457..b8ae4973 100644 --- a/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx @@ -20,6 +20,7 @@ import { useConstant } from "renderer/hooks/use-constant.hook"; import { BsContentLoader } from "renderer/components/shared/bs-content-loader.component"; import { InstalledMapsContext } from "../maps-playlists-panel.component"; import { useObservable } from "renderer/hooks/use-observable.hook"; +import equal from "fast-deep-equal"; type Props = { version: BSVersion; @@ -77,12 +78,6 @@ export const LocalMapsListPanel = forwardRef(({ version, classNa useEffect(() => { if (isActiveOnce) { loadMaps(); - mapsDownloader.addOnMapDownloadedListener((map, targerVersion) => { - if (targerVersion !== version) { - return; - } - setMaps((maps ? [map, ...maps] : [map])); - }); } return () => { @@ -93,6 +88,22 @@ export const LocalMapsListPanel = forwardRef(({ version, classNa }; }, [isActiveOnce, version, linked]); + useEffect(() => { + if(isActiveOnce){ + mapsDownloader.addOnMapDownloadedListener((map, targetVersion) => { + if (!equal(targetVersion, version)) { + return; + } + setMaps((maps ? [map, ...maps] : [map])); + }); + } + + return () => { + mapsDownloader.removeOnMapDownloadedListener(loadMaps); + } + + }, [isActiveOnce, version, maps]) + useEffect(() => { if (!isActiveOnce) { return; diff --git a/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx b/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx index 342e0e6e..edaf1d80 100644 --- a/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx @@ -1,6 +1,6 @@ import { ModalComponent } from "renderer/services/modale.service" import { PlaylistDetailsTemplate, PlaylistDetailsTemplateProps } from "./playlist-details-template.component" -import { Observable } from "rxjs" +import { Observable, first, lastValueFrom, map, shareReplay, switchMap, take, tap } from "rxjs" import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface" import { BSVersion } from "shared/bs-version.interface"; import { useObservable } from "renderer/hooks/use-observable.hook"; @@ -13,6 +13,8 @@ import { BsmImage } from "renderer/components/shared/bsm-image.component"; import { BsmButton } from "renderer/components/shared/bsm-button.component"; import BeatConflict from "../../../../../../assets/images/apngs/beat-conflict.png"; import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"; +import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service"; +import { ProgressBarService } from "renderer/services/progress-bar.service"; interface Props { version: BSVersion; @@ -23,6 +25,8 @@ interface Props { export const LocalPlaylistDetailsModal: ModalComponent = ({resolver, options}) => { const audioPlayer = useService(AudioPlayerService); + const playlistDownloader = useService(PlaylistDownloaderService); + const progressBar = useService(ProgressBarService); const localPlaylist = useObservable(() => options.data.localPlaylist$, null); const installedMaps = useObservable(() => options.data.installedMaps$, null); @@ -34,6 +38,23 @@ export const LocalPlaylistDetailsModal: ModalComponent = ({resolver audioPlayer.play(installedMaps.map(map => ({ src: map.songUrl, bpm: map.rawInfo?._beatsPerMinute ?? 0}))); }; + const installPlaylist = () => { + const obs$ = playlistDownloader.installPlaylist(localPlaylist, options.data.version); + + const progress$ = obs$.pipe( + map(progress => (progress.current / progress.total) * 100), + ); + + const obsWithProgress$ = obs$.pipe( + take(1), + tap(() => progressBar.show(progress$, true)), + switchMap(() => obs$), + tap({ complete: () => progressBar.hide(true) }) + ); + + return lastValueFrom(obsWithProgress$); + } + const renderMaps = () => { if (!installedMaps) { // loading maps @@ -54,14 +75,14 @@ export const LocalPlaylistDetailsModal: ModalComponent = ({resolver initial={{ height: 0 }} animate={{ height: "7rem" }} exit={{ height: 0 }} - transition={{delay: .2, duration: .25}} + transition={{delay: .25, duration: .25}} className="shrink-0 w-full text-center overflow-hidden flex justify-center items-center" >

Certaines maps de cette playlist sont manquantes

- +
diff --git a/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx b/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx index 1b005c79..ff9756a4 100644 --- a/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx +++ b/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx @@ -30,7 +30,7 @@ export function BsmProgressBar() { {" "} {visible && ( - +
{!!progressValue && (
diff --git a/src/renderer/services/ipc.service.ts b/src/renderer/services/ipc.service.ts index 996de6f5..1a577df0 100644 --- a/src/renderer/services/ipc.service.ts +++ b/src/renderer/services/ipc.service.ts @@ -7,7 +7,7 @@ import { IpcCompleteChannel, IpcErrorChannel, IpcTearDownChannel } from "shared/ export class IpcService { private static instance: IpcService; - private readonly channelObservables: Map>>; + private readonly channelObservables: Map>; public static getInstance(): IpcService { if (!IpcService.instance) { @@ -45,13 +45,13 @@ export class IpcService { } // Also need a rework - public watch(channel: string): Observable> { + public watch(channel: string): Observable { if (this.channelObservables.has(channel)) { - return this.channelObservables.get(channel) as Observable>; + return this.channelObservables.get(channel) as Observable; } - const obs = new Observable>(observer => { - window.electron.ipcRenderer.on(channel, (res: IpcResponse) => { + const obs = new Observable(observer => { + window.electron.ipcRenderer.on(channel, (res) => { observer.next(res); }); }); diff --git a/src/renderer/services/maps-downloader.service.ts b/src/renderer/services/maps-downloader.service.ts index 42173beb..95db40de 100644 --- a/src/renderer/services/maps-downloader.service.ts +++ b/src/renderer/services/maps-downloader.service.ts @@ -1,6 +1,6 @@ import { DownloadMapsModal } from "renderer/components/modal/modal-types/download-maps-modal.component"; import { map, filter } from "rxjs/operators"; -import { BehaviorSubject, timer, Observable } from "rxjs"; +import { BehaviorSubject, timer, Observable, lastValueFrom } from "rxjs"; import { BSVersion } from "shared/bs-version.interface"; import { ModalResponse, ModalService } from "./modale.service"; import { ProgressBarService } from "./progress-bar.service"; @@ -43,6 +43,11 @@ export class MapsDownloaderService { this.mapsQueue$.pipe(filter(queue => queue.length === 0)).subscribe(() => { this.queueMaxLenght = 0; }); + + this.ipc.watch<{map: BsmLocalMap, version?: BSVersion}>("map-downloaded").subscribe((data) => { + console.log(data); + this.downloadedListerners.forEach(func => func(data.map, data.version)); + }); } private async startDownloadMaps() { @@ -53,11 +58,7 @@ export class MapsDownloaderService { while (this.mapsQueue$.value.at(0)) { const toDownload = this.mapsQueue$.value.at(0); this.currentDownload$.next(toDownload); - const downloaded = await this.downloadMap(toDownload.map, toDownload.version)?.toPromise(); - - if (downloaded) { - this.downloadedListerners.forEach(func => func(downloaded, toDownload.version)); - } + await lastValueFrom(this.downloadMap(toDownload.map, toDownload.version)); const newArr = [...this.mapsQueue$.value]; newArr.shift(); diff --git a/src/renderer/services/playlist-downloader.service.ts b/src/renderer/services/playlist-downloader.service.ts index 94b761a2..5e72fd06 100644 --- a/src/renderer/services/playlist-downloader.service.ts +++ b/src/renderer/services/playlist-downloader.service.ts @@ -1,8 +1,10 @@ -import { Observable, map, tap, throwError } from "rxjs"; -import { DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface"; +import { BehaviorSubject, Observable, filter, lastValueFrom, map, shareReplay, take, tap } from "rxjs"; +import { BPList, DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface"; import { IpcService } from "./ipc.service"; import { ProgressBarService } from "./progress-bar.service"; import { Progression } from "main/helpers/fs.helpers"; +import equal from "fast-deep-equal"; +import { BSVersion } from "shared/bs-version.interface"; export class PlaylistDownloaderService { private static instance: PlaylistDownloaderService; @@ -18,17 +20,36 @@ export class PlaylistDownloaderService { private readonly progress: ProgressBarService; private readonly ipc: IpcService; + private readonly playlistQueue$ = new BehaviorSubject([]); + private constructor() { this.progress = ProgressBarService.getInstance(); this.ipc = IpcService.getInstance(); } - public oneClickInstallPlaylist(bpListUrl: string): Observable> { + public installPlaylist(bpList: BPList, version?: BSVersion): Observable> { + this.playlistQueue$.next([...this.playlistQueue$.value, bpList]); - if(!this.progress.require()){ - return throwError(() => new Error("Download already in progress")); + const clear = () => { + this.playlistQueue$.next(this.playlistQueue$.value.filter(p => p !== bpList)); } + return new Observable>(subscriber => { + (async () => { + const playlist = await lastValueFrom(this.playlistQueue$.pipe(map(queue => queue.at(0)), filter(p => equal(bpList, p)), take(1))); + const download$ = this.ipc.sendV2, unknown>("install-playlist", { args: { version, playlist } }); + + await lastValueFrom(download$.pipe(tap(subscriber))); + })() + .then(() => subscriber.complete()) + .catch(err => subscriber.error(err)) + .finally(clear); + + }).pipe(shareReplay(1)) + } + + public oneClickInstallPlaylist(bpListUrl: string): Observable> { + const download$ = this.ipc.sendV2, string>("one-click-install-playlist", { args: bpListUrl }); const progress$ = download$.pipe(map(data => (data.current / data.total) * 100));