[feature-107] Fix various bugs and add new features for playlists

This commit is contained in:
MathieuG-P
2024-02-23 21:49:29 +01:00
parent 59b04ee33e
commit a36ccb0c13
26 changed files with 379 additions and 70 deletions
@@ -13,6 +13,11 @@ import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
import { copy, copyFile, pathExists, pathExistsSync, readdirSync, realpath } from "fs-extra";
import { Progression, ensureFolderExist, pathExist } from "../../helpers/fs.helpers";
import { FileAssociationService } from "../file-association.service";
import { SongDetailsCacheService } from "./maps/song-details-cache.service";
import { sToMs } from "shared/helpers/time.helpers";
import { LocalBPList, LocalBpListSong } from "shared/models/playlists/local-playlist.models";
import { SongCacheService } from "./maps/song-cache.service";
import { pathToFileURL } from "url";
export class LocalPlaylistsManagerService {
private static instance: LocalPlaylistsManagerService;
@@ -36,6 +41,8 @@ export class LocalPlaylistsManagerService {
private readonly fileAssociation: FileAssociationService;
private readonly windows: WindowManagerService;
private readonly bsaver: BeatSaverService;
private readonly songDetails: SongDetailsCacheService;
private readonly songCache: SongCacheService;
private constructor() {
this.maps = LocalMapsManagerService.getInstance();
@@ -45,6 +52,9 @@ export class LocalPlaylistsManagerService {
this.fileAssociation = FileAssociationService.getInstance();
this.windows = WindowManagerService.getInstance();
this.bsaver = BeatSaverService.getInstance();
this.songDetails = SongDetailsCacheService.getInstance();
this.songCache = SongCacheService.getInstance();
this.deepLink.addLinkOpenedListener(this.DEEP_LINKS.BeatSaver, link => {
log.info("DEEP-LINK RECEIVED FROM", this.DEEP_LINKS.BeatSaver, link);
@@ -100,34 +110,61 @@ export class LocalPlaylistsManagerService {
this.windows.openWindow(`oneclick-download-playlist.html?playlistUrl=${downloadUrl}`);
}
private getReadBPListOfFolder(folerPath: string): Observable<Progression<BPList[]>> {
return new Observable<Progression<BPList[]>>(obs => {
private readLocalBPListsOfFolder(folerPath: string, version?: BSVersion): Observable<Progression<LocalBPList[]>> {
return new Observable<Progression<LocalBPList[]>>(obs => {
const progress: Progression<BPList[]> = { current: 0, total: 0, data: [] };
const progress: Progression<LocalBPList[]> = { current: 0, total: 0, data: [] };
const bpLists: LocalBPList[] = [];
(async () => {
await this.songDetails.waitLoaded(sToMs(15));
if(!pathExistsSync(folerPath)) {
throw new Error(`Playlists folder not found ${folerPath}`);
}
const mapsFolder = version ? await this.maps.getMapsFolderPath(version) : null;
const playlists = readdirSync(folerPath).filter(file => path.extname(file) === ".bplist");
progress.total = playlists.length;
for (const playlist of playlists) {
const playlistPath = path.join(folerPath, playlist);
const playlistContent = await this.readPlaylistFile(playlistPath);
progress.data.push(playlistContent);
const bpList = await this.readPlaylistFile(playlistPath);
const localBpListSongs = bpList.songs.map(song => {
const localInfos = mapsFolder ? this.songCache.getMapInfoFromHash(song.hash) : null;
const coverPath = localInfos ? path.join(mapsFolder, localInfos.path, localInfos.info.rawInfo._coverImageFilename) : null;
const songFilePath = localInfos ? path.join(mapsFolder, localInfos.path, localInfos.info.rawInfo._songFilename) : null;
return ({
song,
songDetails: this.songDetails.getSongDetails(song.hash),
coverUrl: (coverPath && pathExistsSync(coverPath)) ? pathToFileURL(coverPath).href : null,
songUrl: (songFilePath && pathExistsSync(songFilePath)) ? pathToFileURL(songFilePath).href : null,
} as LocalBpListSong)
});
const localBpList: LocalBPList = { ...bpList, path: playlistPath, songs: localBpListSongs };
bpLists.push(localBpList);
progress.current += 1;
obs.next(progress);
}
progress.data = bpLists;
obs.next(progress);
})().catch(err => obs.error(err)).finally(() => obs.complete());
});
}
public getVersionPlaylists(version: BSVersion): Observable<Progression<BPList[]>> {
return new Observable<Progression<BPList[]>>(obs => {
public getVersionPlaylists(version: BSVersion): Observable<Progression<LocalBPList[]>> {
return new Observable<Progression<LocalBPList[]>>(obs => {
this.getPlaylistsFolder(version)
.then(folder => this.getReadBPListOfFolder(folder).subscribe(obs))
.then(folder => this.readLocalBPListsOfFolder(folder, version))
.then(progress$ => lastValueFrom(progress$.pipe(
tap(progress => obs.next({...progress, data: []})),
)))
.then(res => obs.next(res))
.catch(err => obs.error(err))
.finally(() => obs.complete());
});
}
@@ -25,6 +25,7 @@ import { IpcService } from "../../ipc.service";
import { SongDetailsCacheService } from "./song-details-cache.service";
import { sToMs } from "shared/helpers/time.helpers";
import { SongCacheService } from "./song-cache.service";
import { pathToFileURL } from "url";
export class LocalMapsManagerService {
private static instance: LocalMapsManagerService;
@@ -38,6 +39,7 @@ export class LocalMapsManagerService {
public static readonly LEVELS_ROOT_FOLDER = "Beat Saber_Data";
public static readonly CUSTOM_LEVELS_FOLDER = "CustomLevels";
public static readonly RELATIVE_MAPS_FOLDER = path.join(LocalMapsManagerService.LEVELS_ROOT_FOLDER, LocalMapsManagerService.CUSTOM_LEVELS_FOLDER);
public static readonly SHARED_MAPS_FOLDER = "SharedMaps";
private readonly DEEP_LINKS = {
@@ -81,7 +83,7 @@ export class LocalMapsManagerService {
public async getMapsFolderPath(version?: BSVersion): Promise<string> {
if (version) {
return path.join(await this.localVersion.getVersionPath(version), LocalMapsManagerService.LEVELS_ROOT_FOLDER, LocalMapsManagerService.CUSTOM_LEVELS_FOLDER);
return path.join(await this.localVersion.getVersionPath(version), LocalMapsManagerService.RELATIVE_MAPS_FOLDER);
}
const sharedMapsPath = path.join(await this.installLocation.sharedContentPath(), LocalMapsManagerService.SHARED_MAPS_FOLDER, LocalMapsManagerService.CUSTOM_LEVELS_FOLDER);
if (!(await pathExist(sharedMapsPath))) {
@@ -117,8 +119,8 @@ export class LocalMapsManagerService {
private async loadMapInfoFromPath(mapPath: string): Promise<BsmLocalMap> {
const getUrlsAndReturn = (rawInfo: RawMapInfoData, hash: string, mapPath: string) => {
const coverUrl = new URL(`file:///${path.join(mapPath, rawInfo._coverImageFilename)}`).href;
const songUrl = new URL(`file:///${path.join(mapPath, rawInfo._songFilename)}`).href;
const coverUrl = pathToFileURL(path.join(mapPath, rawInfo._coverImageFilename)).href;
const songUrl = pathToFileURL(path.join(mapPath, rawInfo._songFilename)).href;
return { rawInfo, coverUrl, songUrl, hash, path: mapPath, songDetails: this.songDetailsCache.getSongDetails(hash) } as BsmLocalMap;
};
@@ -26,6 +26,11 @@ export class SongCacheService {
return this.rawInfosCache.get(dirname);
}
public getMapInfoFromHash(hash: string): { path: string, info: CachedRawInfoWithHash } | undefined {
const res = Object.entries(this.rawInfosCache.cache).find(([, info]) => info.hash === hash);
return res ? { path: res[0], info: res[1] } : undefined;
}
public setMapInfoFromDirname(dirname: string, info: CachedRawInfoWithHash): void {
this.rawInfosCache.set(dirname, info);
}
@@ -160,7 +160,7 @@ export class SongDetailsCacheService {
}))));
}
public getSongDetails(hash: string): any {
public getSongDetails(hash: string): SongDetails {
return this.songDetailsCache[hash.toLocaleLowerCase()];
}
+1 -1
View File
@@ -47,7 +47,7 @@ export class WindowManagerService {
if(isValidUrl(url)){
shell.openExternal(url);
}
return { action: "deny"}
});