mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
fully load local maps, and prepare bsaver map infos
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { ipcMain } from "electron";
|
||||
import { LocalMapsManagerService } from "../services/maps/local-maps-manager.service";
|
||||
import { UtilsService } from "../services/utils.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { IpcRequest } from "shared/models/ipc";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
|
||||
ipcMain.on('get-version-maps', (event, request: IpcRequest<BSVersion>) => {
|
||||
const utilsService = UtilsService.getInstance();
|
||||
const localMaps = LocalMapsManagerService.getInstance();
|
||||
|
||||
localMaps.getMaps(request.args).then(maps => {
|
||||
utilsService.ipcSend<BsmLocalMap[]>(request.responceChannel, {success: true, data: maps});
|
||||
}).catch(() => {
|
||||
utilsService.ipcSend(request.responceChannel, {success: false});
|
||||
})
|
||||
|
||||
});
|
||||
@@ -8,3 +8,4 @@ import './supporters-ipcs';
|
||||
import './launcher-ipcs';
|
||||
import './window-manager-ipcs';
|
||||
import './bs-mods-ipcs';
|
||||
import './bs-maps-ipcs';
|
||||
|
||||
@@ -12,6 +12,11 @@ export class InstallationLocationService {
|
||||
|
||||
private readonly INSTALLATION_FOLDER = "BSManager";
|
||||
private readonly VERSIONS_FOLDER = "BSInstances";
|
||||
|
||||
private readonly SHARED_CONTENT_FOLDER = "SharedContent";
|
||||
private readonly SHARED_MAPS_FOLDER = "SharedMaps";
|
||||
private readonly SHARED_PLAYLISTS_FOLDER = "SharedPlaylists";
|
||||
|
||||
private readonly STORE_INSTALLATION_PATH_KEY = "installation-folder";
|
||||
|
||||
|
||||
@@ -36,9 +41,6 @@ export class InstallationLocationService {
|
||||
this._installationDirectory = this.configService.get<string>(this.STORE_INSTALLATION_PATH_KEY) || app.getPath("documents");
|
||||
}
|
||||
|
||||
public get installationDirectory(): string{ return path.join(this._installationDirectory, this.INSTALLATION_FOLDER); }
|
||||
public get versionsDirectory(): string { return path.join(this.installationDirectory, this.VERSIONS_FOLDER); }
|
||||
|
||||
public setInstallationDirectory(newDir: string): Promise<string>{
|
||||
const oldDir = this.installationDirectory;
|
||||
const newDest = path.join(newDir, this.INSTALLATION_FOLDER);
|
||||
@@ -53,7 +55,15 @@ export class InstallationLocationService {
|
||||
log.error(err);
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
public get installationDirectory(): string{ return path.join(this._installationDirectory, this.INSTALLATION_FOLDER); }
|
||||
|
||||
public get versionsDirectory(): string { return path.join(this.installationDirectory, this.VERSIONS_FOLDER); }
|
||||
|
||||
public get sharedContentPath(): string { return path.join(this.installationDirectory, this.SHARED_CONTENT_FOLDER); }
|
||||
public get sharedMapsPath(): string { return path.join(this.sharedContentPath, this.SHARED_MAPS_FOLDER); }
|
||||
public get sharedPlaylistsPath(): string { return path.join(this.sharedContentPath, this.SHARED_PLAYLISTS_FOLDER); }
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import path from "path";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { RawMapInfoData } from "shared/models/maps";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { BSLocalVersionService } from "../bs-local-version.service";
|
||||
import { InstallationLocationService } from "../installation-location.service";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import crypto from "crypto";
|
||||
|
||||
export class LocalMapsManagerService {
|
||||
|
||||
private static instance: LocalMapsManagerService;
|
||||
|
||||
public static getInstance(): LocalMapsManagerService{
|
||||
if(!LocalMapsManagerService.instance){ LocalMapsManagerService.instance = new LocalMapsManagerService(); }
|
||||
return LocalMapsManagerService.instance;
|
||||
}
|
||||
|
||||
private readonly LEVELS_ROOT_FOLDER = "Beat Saber_Data";
|
||||
private readonly CUSTOM_LEVELS_FOLDER = "CustomLevels";
|
||||
private readonly WIP_LEVELS_FOLDER = "CustomWIPLevels";
|
||||
|
||||
private readonly localVersion: BSLocalVersionService;
|
||||
private readonly installLocation: InstallationLocationService;
|
||||
private readonly utils: UtilsService;
|
||||
|
||||
private constructor(){
|
||||
this.localVersion = BSLocalVersionService.getInstance();
|
||||
this.installLocation = InstallationLocationService.getInstance();
|
||||
this.utils = UtilsService.getInstance();
|
||||
}
|
||||
|
||||
private async getMapsPath(version?: BSVersion): Promise<string>{
|
||||
if(version){ return path.join(await this.localVersion.getVersionPath(version), this.LEVELS_ROOT_FOLDER); }
|
||||
return this.installLocation.sharedMapsPath;
|
||||
}
|
||||
|
||||
private async computeMapHash(mapPath: string, rawInfoString: string): Promise<string>{
|
||||
const mapRawInfo = JSON.parse(rawInfoString);
|
||||
let content = rawInfoString;
|
||||
console.log("oui");
|
||||
for(const set of mapRawInfo._difficultyBeatmapSets){
|
||||
for(const diff of set._difficultyBeatmaps){
|
||||
const diffFilePath = path.join(mapPath, diff._beatmapFilename);
|
||||
if(!await this.utils.pathExist(diffFilePath)){ continue; }
|
||||
const diffContent = (await this.utils.readFileAsync(diffFilePath)).toString()
|
||||
content += diffContent;
|
||||
}
|
||||
}
|
||||
|
||||
const shasum = crypto.createHash("sha1");
|
||||
shasum.update(content);
|
||||
return shasum.digest("hex");
|
||||
}
|
||||
|
||||
private async loadMapInfoFromPath(mapPath: string): Promise<BsmLocalMap>{
|
||||
const infoFilePath = path.join(mapPath, "Info.dat");
|
||||
|
||||
if(!(await this.utils.pathExist(infoFilePath))){ return null; }
|
||||
|
||||
const rawInfoString = await (await (this.utils.readFileAsync(infoFilePath))).toString();
|
||||
|
||||
const rawInfo: RawMapInfoData = JSON.parse(rawInfoString);
|
||||
const coverUrl = new URL(`file:///${path.join(mapPath, rawInfo._coverImageFilename)}`).href;
|
||||
const songUrl = new URL(`file:///${path.join(mapPath, rawInfo._songFilename)}`).href;
|
||||
|
||||
const hash = await this.computeMapHash(mapPath, rawInfoString);
|
||||
|
||||
return {rawInfo, coverUrl, songUrl, hash};
|
||||
}
|
||||
|
||||
public async getMaps(version?: BSVersion): Promise<BsmLocalMap[]>{
|
||||
const levelsRoot = await this.getMapsPath(version);
|
||||
const levelsFolder = path.join(levelsRoot, this.CUSTOM_LEVELS_FOLDER);
|
||||
|
||||
const levelsPath = (await this.utils.pathExist(levelsFolder)) ? this.utils.listDirsInDir(levelsFolder, true) : [];
|
||||
|
||||
|
||||
|
||||
const mapsInfo = await Promise.all(levelsPath.map(levelPath => this.loadMapInfoFromPath(levelPath)))
|
||||
|
||||
console.log(mapsInfo);
|
||||
|
||||
return mapsInfo.filter(info => !!info);
|
||||
}
|
||||
|
||||
public deleteMap(version?: BSVersion){
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,10 +68,10 @@ export class UtilsService{
|
||||
});
|
||||
}
|
||||
|
||||
public listDirsInDir(dirPath: string): string[]{
|
||||
public listDirsInDir(dirPath: string, fullPath = false): string[]{
|
||||
let files = readdirSync(dirPath, { withFileTypes:true});
|
||||
files = files.filter(f => f.isDirectory())
|
||||
return files.map(f => f.name);
|
||||
return files.map(f => fullPath ? path.join(dirPath, f.name) : f.name);
|
||||
}
|
||||
|
||||
public deleteFolder(folderPath: string): Promise<void>{
|
||||
|
||||
@@ -16,10 +16,6 @@ export function AvailableVersionsSlide(props: {year: string}) {
|
||||
});
|
||||
}, [])
|
||||
|
||||
//w-full max-w-full max-h-full flex items-start justify-center overflow-x-hidden overflow-y-scroll content-start scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900
|
||||
|
||||
//relative left-[2px] flex justify-center items-start content-start flex-wrap max-w-6xl
|
||||
|
||||
return (
|
||||
<ol className="w-full flex items-start justify-center gap-6 shrink-0 content-start flex-wrap p-4 overflow-x-hidden overflow-y-scroll scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900">
|
||||
{availableVersions.map((version, index) =>
|
||||
|
||||
@@ -25,8 +25,8 @@ export function AvailableVersionsSlider() {
|
||||
<div className="w-full h-fit max-h-full flex flex-col items-center grow min-h-0">
|
||||
<TabNavBar className="mb-3" tabsText={availableYears} onTabChange={setSelectedYear}/>
|
||||
<ol className="w-full min-h-0 flex transition-transform duration-300" style={{transform: `translate(${-(yearIndex * 100)}%, 0)`}}>
|
||||
{ availableYears.map((year, index) =>
|
||||
<AvailableVersionsSlide key={index} year={year}></AvailableVersionsSlide>
|
||||
{ availableYears.map(year =>
|
||||
<AvailableVersionsSlide key={year} year={year}/>
|
||||
)}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
+16
-6
@@ -1,25 +1,35 @@
|
||||
import { useState } from "react"
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook"
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service"
|
||||
import { BSVersion } from "shared/bs-version.interface"
|
||||
import { TabNavBar } from "../shared/tab-nav-bar.component"
|
||||
|
||||
type Props = {
|
||||
oneBlock?: boolean,
|
||||
version?: BSVersion
|
||||
}
|
||||
|
||||
export function MapsPlaylistsPanel({version}: Props) {
|
||||
export function MapsPlaylistsPanel({version, oneBlock = false}: Props) {
|
||||
|
||||
const mapsManager = MapsManagerService.getInstance();
|
||||
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const maps = useObservable(mapsManager.getMaps(version));
|
||||
|
||||
console.log(tabIndex);
|
||||
console.log(maps);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!oneBlock && <TabNavBar className="mb-8 w-72" tabsText={["Maps", "Playlists"]} onTabChange={setTabIndex}/>}
|
||||
<div className="w-full h-full bg-main-color-1 rounded-md shadow-black shadow-md overflow-hidden">
|
||||
<TabNavBar className="!rounded-none shadow-sm" tabsText={["Maps", "Playlists"]} onTabChange={setTabIndex}/>
|
||||
<div className="w-full h-full flex flex-row items-center transition-transform duration-300" style={{transform: `translate(${-(tabIndex * 100)}%, 0)`}}>
|
||||
<div className="w-full h-full grow bg-red-300 shrink-0">a</div>
|
||||
<div className="w-full h-full bg-green-300 shrink-0">b</div>
|
||||
{oneBlock && <TabNavBar className="!rounded-none shadow-sm" tabsText={["Maps", "Playlists"]} onTabChange={setTabIndex}/>}
|
||||
<div className="w-full h-full min-h-0 flex flex-row items-center transition-transform duration-300" style={{transform: `translate(${-(tabIndex * 100)}%, 0)`}}>
|
||||
<div className="w-full h-full grow bg-red-300 shrink-0 overflow-y-scroll">
|
||||
a
|
||||
</div>
|
||||
<div className="w-full h-full grow bg-green-300 shrink-0">b</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function splitIntoChunk<T = unknown>(arr: T[], chunkSize: number): T[][] {
|
||||
const resArr = [];
|
||||
for(let i = 0; i < arr.length; i += chunkSize){
|
||||
resArr.push(arr.slice(i, i + chunkSize));
|
||||
}
|
||||
return resArr;
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export function AvailableVersionsList() {
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full flex items-center flex-col pt-2">
|
||||
<Slideshow className="absolute w-full h-full top-0"></Slideshow>
|
||||
<Slideshow className="absolute w-full h-full top-0"/>
|
||||
<h1 className="text-gray-100 text-2xl mb-4 z-[1]">{t("pages.available-versions.title")}</h1>
|
||||
<AvailableVersionsSlider/>
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ export default function MapsPage() {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center flex-col pt-2">
|
||||
<Slideshow className="absolute w-full h-full top-0"/>
|
||||
<h1 className="text-gray-100 text-2xl mb-4 z-[1]">Maps partagées</h1>
|
||||
<div className="z-[1] w-full grow p-10">
|
||||
<h1 className="text-gray-100 text-2xl z-[1]">Maps partagées</h1>
|
||||
<div className="z-[1] w-full min-h-0 grow p-10 flex flex-col items-center justify-start">
|
||||
<MapsPlaylistsPanel/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { BsvMapDetail } from "shared/models/maps";
|
||||
|
||||
export class BeatSaverService {
|
||||
|
||||
private static instance: BeatSaverService;
|
||||
|
||||
public static getInstance(): BeatSaverService{
|
||||
if(!BeatSaverService.instance){ BeatSaverService.instance = new BeatSaverService(); }
|
||||
return BeatSaverService.instance;
|
||||
}
|
||||
|
||||
private constructor(){}
|
||||
|
||||
public getMapDetailsFromHashs(hashs: string[]): BsvMapDetail{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Observable } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { IpcService } from "./ipc.service";
|
||||
|
||||
export class MapsManagerService {
|
||||
|
||||
@@ -9,12 +12,19 @@ export class MapsManagerService {
|
||||
return MapsManagerService.instance;
|
||||
}
|
||||
|
||||
private constructor(){
|
||||
private readonly ipcService: IpcService;
|
||||
|
||||
private constructor(){
|
||||
this.ipcService = IpcService.getInstance();
|
||||
}
|
||||
|
||||
public getMaps(version?: BSVersion): any[]{
|
||||
return [];
|
||||
public getMaps(version?: BSVersion): Observable<BsmLocalMap[]>{
|
||||
return new Observable(obs => {
|
||||
this.ipcService.send<BsmLocalMap[], BSVersion>("get-version-maps", {args: version}).then(res => {
|
||||
if(!res.success){ return obs.next(null);}
|
||||
obs.next(res.data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public downloadMap(map: any, version?: BSVersion){
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export { RawMapInfoData, RawMapDifficulty, RawDifficultySet } from "./raw-map.model";
|
||||
|
||||
export { ExportVersionMapsOption } from "./export-version-maps.model";
|
||||
|
||||
export {
|
||||
BsvInstant,
|
||||
BsvMapDetail,
|
||||
BsvMapDetailMetadata,
|
||||
BsvMapDifficulty,
|
||||
BsvMapParitySummary,
|
||||
BsvMapStats,
|
||||
BsvMapTestplay,
|
||||
BsvMapVersion,
|
||||
BsvUserDetail
|
||||
} from "./beat-saver.model";
|
||||
Reference in New Issue
Block a user