mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-107] We can now download playlist's missing maps
This commit is contained in:
@@ -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<string>("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();
|
||||
|
||||
|
||||
@@ -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<Progression<DownloadPlaylistProgressionData>> {
|
||||
|
||||
public downloadPlaylistSongs(bpList: PlaylistSong[], version: BSVersion): Observable<Progression<DownloadPlaylistProgressionData>> {
|
||||
return new Observable<Progression<DownloadPlaylistProgressionData>>(obs => {
|
||||
(async () => {
|
||||
|
||||
const bpListFilePath = await this.installBPListFile(bpListUrl, version);
|
||||
const bpList = await this.readPlaylistFile(bpListFilePath);
|
||||
|
||||
const progress: Progression<DownloadPlaylistProgressionData> = {
|
||||
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<Progression<DownloadPlaylistProgressionData>> {
|
||||
|
||||
return new Observable<Progression<DownloadPlaylistProgressionData>>(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<Progression>{
|
||||
|
||||
console.log("AAAAAA");
|
||||
|
||||
return new Observable<Progression>(obs => {
|
||||
(async () => {
|
||||
|
||||
@@ -247,8 +258,6 @@ export class LocalPlaylistsManagerService {
|
||||
progress.current += 1
|
||||
obs.next(progress);
|
||||
},
|
||||
error: err => obs.error(err),
|
||||
complete: () => obs.next(progress),
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+17
-6
@@ -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<unknown, Props>(({ 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<unknown, Props>(({ 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;
|
||||
|
||||
+24
-3
@@ -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<void, Props> = ({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<void, Props> = ({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<void, Props> = ({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"
|
||||
>
|
||||
<div className="size-[calc(100%-1rem)] bg-main-color-2 rounded-md translate-y-1.5 flex flex-row justify-center items-center gap-3">
|
||||
<BsmImage image={BeatConflict} className="size-24"/>
|
||||
<div className="text-white font-bold w-fit space-y-1.5 flex flex-col justify-center items-center">
|
||||
<p>Certaines maps de cette playlist sont manquantes</p>
|
||||
<BsmButton withBar={false} className="rounded-md h-8 flex items-center justify-center px-4" typeColor="primary" text="Télécharger.les.maps.manquantes"/>
|
||||
<BsmButton withBar={false} onClick={installPlaylist} className="rounded-md h-8 flex items-center justify-center px-4" typeColor="primary" text="Télécharger.les.maps.manquantes"/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function BsmProgressBar() {
|
||||
<AnimatePresence>
|
||||
{" "}
|
||||
{visible && (
|
||||
<motion.div initial={{ y: "120%" }} animate={{ y: "0%" }} exit={{ y: "120%" }} className="w-full absolute h-14 flex justify-center items-center bottom-2 pointer-events-none z-10" style={style}>
|
||||
<motion.div initial={{ y: "120%" }} animate={{ y: "0%" }} exit={{ y: "120%" }} className="w-full absolute h-14 flex justify-center items-center bottom-2 pointer-events-none z-[10000]" style={style}>
|
||||
<div className={`flex items-center content-center justify-center bottom-9 z-10 rounded-full bg-light-main-color-2 dark:bg-main-color-2 shadow-center shadow-black transition-all duration-300 ${!progressValue && "h-14 w-14 "} ${!!progressValue && "h-5 w-3/4 p-[6px]"}`}>
|
||||
{!!progressValue && (
|
||||
<div className="relative h-full w-full rounded-full bg-black">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IpcCompleteChannel, IpcErrorChannel, IpcTearDownChannel } from "shared/
|
||||
export class IpcService {
|
||||
private static instance: IpcService;
|
||||
|
||||
private readonly channelObservables: Map<string, Observable<IpcResponse<unknown>>>;
|
||||
private readonly channelObservables: Map<string, Observable<unknown>>;
|
||||
|
||||
public static getInstance(): IpcService {
|
||||
if (!IpcService.instance) {
|
||||
@@ -45,13 +45,13 @@ export class IpcService {
|
||||
}
|
||||
|
||||
// Also need a rework
|
||||
public watch<T>(channel: string): Observable<IpcResponse<T>> {
|
||||
public watch<T>(channel: string): Observable<T> {
|
||||
if (this.channelObservables.has(channel)) {
|
||||
return this.channelObservables.get(channel) as Observable<IpcResponse<T>>;
|
||||
return this.channelObservables.get(channel) as Observable<T>;
|
||||
}
|
||||
|
||||
const obs = new Observable<IpcResponse<T>>(observer => {
|
||||
window.electron.ipcRenderer.on(channel, (res: IpcResponse<T>) => {
|
||||
const obs = new Observable<T>(observer => {
|
||||
window.electron.ipcRenderer.on(channel, (res) => {
|
||||
observer.next(res);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<BPList[]>([]);
|
||||
|
||||
private constructor() {
|
||||
this.progress = ProgressBarService.getInstance();
|
||||
this.ipc = IpcService.getInstance();
|
||||
}
|
||||
|
||||
public oneClickInstallPlaylist(bpListUrl: string): Observable<Progression<DownloadPlaylistProgressionData>> {
|
||||
public installPlaylist(bpList: BPList, version?: BSVersion): Observable<Progression<DownloadPlaylistProgressionData>> {
|
||||
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<Progression<DownloadPlaylistProgressionData>>(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<Progression<DownloadPlaylistProgressionData>, 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<Progression<DownloadPlaylistProgressionData>> {
|
||||
|
||||
const download$ = this.ipc.sendV2<Progression<DownloadPlaylistProgressionData>, string>("one-click-install-playlist", { args: bpListUrl });
|
||||
const progress$ = download$.pipe(map(data => (data.current / data.total) * 100));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user