can now link and unlink version maps

This commit is contained in:
MathieuG-P
2022-11-26 23:29:50 +01:00
parent 31f38820d2
commit d390050183
11 changed files with 171 additions and 51 deletions
+25 -2
View File
@@ -21,7 +21,30 @@ ipcMain.on("verion-have-maps-linked", async (event, request: IpcRequest<BSVersio
const utils = UtilsService.getInstance();
const maps = LocalMapsManagerService.getInstance();
console.log(request.args);
utils.ipcSend<boolean>(request.responceChannel, {success: true, data: await maps.versionIsLinked(request.args)});
});
});
ipcMain.on("link-version-maps", async (event, request: IpcRequest<{version: BSVersion, keepMaps: boolean}>) => {
const utils = UtilsService.getInstance();
const maps = LocalMapsManagerService.getInstance();
maps.linkVersionMaps(request.args.version, request.args.keepMaps).then(() => {
utils.ipcSend<void>(request.responceChannel, {success: true});
}).catch(err => {
utils.ipcSend<void>(request.responceChannel, {success: true, error: err});
});
});
ipcMain.on("unlink-version-maps", async (event, request: IpcRequest<{version: BSVersion, keepMaps: boolean}>) => {
const utils = UtilsService.getInstance();
const maps = LocalMapsManagerService.getInstance();
maps.unlinkVersionMaps(request.args.version, request.args.keepMaps).then(() => {
utils.ipcSend<void>(request.responceChannel, {success: true});
}).catch(err => {
utils.ipcSend<void>(request.responceChannel, {success: true, error: err});
});
});
@@ -6,7 +6,8 @@ import { BSLocalVersionService } from "../bs-local-version.service";
import { InstallationLocationService } from "../installation-location.service";
import { UtilsService } from "../utils.service";
import crypto from "crypto";
import { lstat, lstatSync, symlinkSync } from "fs";
import { lstat, lstatSync, symlinkSync, readdir, fstat, unlinkSync } from "fs";
import { copySync } from "fs-extra";
export class LocalMapsManagerService {
@@ -32,7 +33,11 @@ export class LocalMapsManagerService {
private async getMapsFolderPath(version?: BSVersion): Promise<string>{
if(version){ return path.join(await this.localVersion.getVersionPath(version), this.LEVELS_ROOT_FOLDER, this.CUSTOM_LEVELS_FOLDER); }
return path.join(this.installLocation.sharedMapsPath, this.CUSTOM_LEVELS_FOLDER);
const sharedMapsPath = path.join(this.installLocation.sharedMapsPath, this.CUSTOM_LEVELS_FOLDER);
if(!(await this.utils.pathExist(sharedMapsPath))){
this.utils.createFolderIfNotExist(sharedMapsPath);
}
return sharedMapsPath;
}
private async computeMapHash(mapPath: string, rawInfoString: string): Promise<string>{
@@ -42,7 +47,7 @@ export class LocalMapsManagerService {
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()
const diffContent = (await this.utils.readFileAsync(diffFilePath)).toString();
content += diffContent;
}
}
@@ -69,27 +74,19 @@ export class LocalMapsManagerService {
}
public async getMaps(version?: BSVersion): Promise<BsmLocalMap[]>{
const levelsFolder = await this.getMapsFolderPath(version)
const levelsFolder = await this.getMapsFolderPath(version);
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);
const mapsInfo = await Promise.all(levelsPath.map(levelPath => this.loadMapInfoFromPath(levelPath)));
return mapsInfo.filter(info => !!info);
}
public deleteMap(version?: BSVersion){
}
public async versionIsLinked(version: BSVersion): Promise<boolean>{
const levelsPath = await this.getMapsFolderPath(version);
console.log(levelsPath, version);
const isPathExist = await this.utils.pathExist(levelsPath);
if(!isPathExist){ return false; }
@@ -97,13 +94,37 @@ export class LocalMapsManagerService {
return lstatSync(levelsPath).isSymbolicLink()
}
public async linkVersionMaps(verion: BSVersion, includeVersionMaps: boolean): Promise<void>{
//TODO
//Can use fs.symlink to create symlink
public async linkVersionMaps(version: BSVersion, keepMaps: boolean): Promise<void>{
if(await this.versionIsLinked(version)){ return; }
const sharedMapsPath = await this.getMapsFolderPath();
const versionMapsPath = await this.getMapsFolderPath(version);
if(keepMaps){
await this.utils.moveDirContent(versionMapsPath, sharedMapsPath);
}
await this.utils.deleteFolder(versionMapsPath);
symlinkSync(sharedMapsPath, versionMapsPath, "junction");
}
public async unlinkVersionMaps(version: BSVersion, keepLinkedMaps: boolean): Promise<void>{
//TODO
public async unlinkVersionMaps(version: BSVersion, keepMaps: boolean): Promise<void>{
const sharedMapsPath = await this.getMapsFolderPath();
const versionMapsPath = await this.getMapsFolderPath(version);
if(await this.versionIsLinked(version)){
unlinkSync(versionMapsPath);
}
this.utils.createFolderIfNotExist(versionMapsPath);
if(keepMaps){
copySync(sharedMapsPath, versionMapsPath);
}
}
+15
View File
@@ -1,4 +1,5 @@
import { existsSync, mkdirSync, readdirSync, readFile, unlinkSync } from "fs";
import { moveSync } from "fs-extra"
import { spawnSync } from "child_process";
import { homedir } from "os";
import path from "path";
@@ -75,9 +76,23 @@ export class UtilsService{
}
public deleteFolder(folderPath: string): Promise<void>{
const folderExist = this.pathExist(folderPath);
if(!folderExist){ return; }
return rm(folderPath, {recursive: true});
}
public async moveDirContent(src: string, dest: string, overwrite = false): Promise<void>{
const [srcExist, destExist] = await Promise.all([this.pathExist(src), this.pathExist(dest)]);
if(!srcExist){ return; }
if(!destExist){ await this.createFolderIfNotExist(dest); }
readdirSync(src, {encoding: "utf-8"}).forEach(file => {
const srcFullPath = path.join(src, file);
const destFullPath = path.join(dest, file);
if(!overwrite && this.pathExist(destFullPath)){ return; }
moveSync(srcFullPath, destFullPath, {overwrite});
});
}
public ipcSend<T = any>(channel: string, response: IpcResponse<T>): void{
try {
this.mainWindow.webContents.send(channel, response);
@@ -27,9 +27,16 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
const subs: Subscription[] = [];
if(isVisible){
const loadMaps = () => {
console.log("LOAD MAPS");
subs.push(mapsManager.getMaps(version).subscribe(localMaps => setMaps(() => [...localMaps])));
}
if(isVisible){
loadMaps();
subs.push(mapsManager.versionLinked$.subscribe(loadMaps));
subs.push(mapsManager.versionUnlinked$.subscribe(loadMaps));
}
return () => {
setMaps(() => []);
@@ -8,7 +8,7 @@ import { MapFilter, MapTag } from "shared/models/maps/beat-saver.model"
import { BsmButton } from "../shared/bsm-button.component"
import { useThemeColor } from "renderer/hooks/use-theme-color.hook"
import { MapsManagerService } from "renderer/services/maps-manager.service"
import { motion } from "framer-motion";
import { motion, Variants } from "framer-motion";
type Props = {
oneBlock?: boolean,
@@ -31,9 +31,12 @@ export function MapsPlaylistsPanel({version, oneBlock = false}: Props) {
useEffect(() => {
loadMapIsLinked();
}, [version]);
const loadMapIsLinked = () => {
mapsService.versionHaveMapsLinked(version).then(setMapsLinked);
}, [version])
}
const handleSearch = (value: string) => {
if(tabIndex === 0){
@@ -42,16 +45,37 @@ export function MapsPlaylistsPanel({version, oneBlock = false}: Props) {
return setPlaylistSearch(() => value);
}
const renderTab = (props: DetailedHTMLProps<React.HTMLAttributes<any>, any>, text: string): JSX.Element => {
const handleMapsLinkClick = () => {
if(!mapsLinked){
return mapsService.linkVersion(version).then(loadMapIsLinked);
}
mapsService.unlinkVersion(version).then(loadMapIsLinked);
}
const handlePlaylistLinkClick = () => {
}
const renderTab = (props: DetailedHTMLProps<React.HTMLAttributes<HTMLLIElement>, HTMLLIElement>, text: string, index: number): JSX.Element => {
const mainColor = mapsLinked ? color : "red";
const variants: Variants = {
hover: {rotate: 22.5},
tap: {rotate: 45}
}
const onClickLink = (index: number) => {
if(index === 0){ return handleMapsLinkClick() };
handlePlaylistLinkClick();
}
return (
<li className="relative text-center text-lg font-bold hover:backdrop-brightness-75 flex justify-center items-center content-center" onClick={props.onClick}>
<span>{text}</span>
<motion.div whileHover={{rotate: [0, 20, 0]}} transition={{duration: .35}} className="absolute block p-1 right-3 h-[calc(100%-5px)] aspect-square blur-0 hover:brightness-75">
<motion.div variants={variants} whileHover="hover" whileTap="tap" initial={{rotate: 0}} className="absolute block p-1 right-3 h-[calc(100%-5px)] aspect-square blur-0 hover:brightness-75">
<span className="absolute top-0 left-0 h-full w-full rounded-full opacity-20" style={{backgroundColor: mainColor}}/>
<BsmButton className="p-1 absolute top-0 left-0 h-full w-full !bg-transparent -rotate-45" iconClassName="" icon={mapsLinked ? "link" : "unlink"} withBar={false} style={{color: mainColor}}/>
<BsmButton className="p-1 absolute top-0 left-0 h-full w-full !bg-transparent -rotate-45" iconClassName="" icon={mapsLinked ? "link" : "unlink"} withBar={false} style={{color: mainColor}} onClick={e => {e.stopPropagation(); onClickLink(index)}}/>
</motion.div>
</li>
)
@@ -6,7 +6,7 @@ type Props = {
tabsText: string[],
onTabChange: (index: number) => void,
className?: string,
renderTab?: (props: DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, text: string) => JSX.Element
renderTab?: (props: DetailedHTMLProps<React.HTMLAttributes<any>, any>, text: string, index?: number) => JSX.Element
}
export function TabNavBar(props: Props) {
@@ -30,7 +30,7 @@ export function TabNavBar(props: Props) {
{props.tabsText.map((text, index) => (
props.renderTab ? (
<Fragment key={text}>
{props.renderTab({onClick: () => selectTab(index)}, t(text))}
{props.renderTab({onClick: () => selectTab(index)}, t(text), index)}
</Fragment>
) : (
<li className="px-4 h-full text-center text-gray-800 dark:text-gray-200 text-lg font-bold hover:backdrop-brightness-75" key={text} onClick={() => selectTab(index)}>{t(text)}</li>
@@ -22,9 +22,18 @@ export class BeatSaverApiService {
const resp = await fetch(`${this.bsaverApiUrl}/maps/hash/${paramsHashs}`);
const data = await resp.json() as Record<Lowercase<T>, BsvMapDetail>;
const data = await resp.json() as Record<Lowercase<T>, BsvMapDetail> | BsvMapDetail;
return {status: resp.status, data: data};
if((data as BsvMapDetail).id){
const key = (data as BsvMapDetail).versions.at(0).hash.toLowerCase();
const parsedData = {
[key]: data as BsvMapDetail
} as Record<Lowercase<T>, BsvMapDetail>;
return {status: resp.status, data: parsedData}
}
return {status: resp.status, data: (data as Record<Lowercase<T>, BsvMapDetail>)};
}
+24 -5
View File
@@ -1,4 +1,4 @@
import { Observable } from "rxjs";
import { Subject, Observable } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
import { BeatSaverService } from "./beat-saver/beat-saver.service";
@@ -16,6 +16,9 @@ export class MapsManagerService {
private readonly ipcService: IpcService;
private readonly bsaver: BeatSaverService;
private readonly lastLinkedVersion$: Subject<BSVersion> = new Subject();
private readonly lastUnlinkedVersion$: Subject<BSVersion> = new Subject();
private constructor(){
this.ipcService = IpcService.getInstance();
this.bsaver = BeatSaverService.getInstance();
@@ -44,14 +47,30 @@ export class MapsManagerService {
})
}
public downloadMap(map: any, version?: BSVersion){
public linkVersion(version: BSVersion): Promise<void>{
return this.ipcService.send<void, {version: BSVersion, keepMaps: boolean}>("link-version-maps", {args: {version, keepMaps: true}}).then(res => {
if(res.success){
return this.lastLinkedVersion$.next(version);
}
throw res.error;
});
}
public deleteMaps(maps: any[], version?: BSVersion){
public unlinkVersion(version: BSVersion): Promise<void>{
return this.ipcService.send<void, {version: BSVersion, keepMaps: boolean}>("unlink-version-maps", {args: {version, keepMaps: true}}).then(res => {
if(res.success){
return this.lastUnlinkedVersion$.next(version);
}
throw res.error;
});
}
public get versionLinked$(): Observable<BSVersion>{
return this.lastLinkedVersion$.asObservable();
}
public get versionUnlinked$(): Observable<BSVersion>{
return this.lastUnlinkedVersion$.asObservable();
}
}