we can export all or specific maps

This commit is contained in:
MathieuG-P
2022-12-08 00:09:59 +01:00
parent 6df2c98955
commit 5bf674d782
11 changed files with 98 additions and 134 deletions
+11
View File
@@ -61,6 +61,17 @@ ipcMain.on("delete-maps", async (event, request: IpcRequest<{version: BSVersion,
});
});
ipcMain.on("export-maps", async (event, request: IpcRequest<{version: BSVersion, maps: BsmLocalMap[], outPath: string}>) => {
const utils = UtilsService.getInstance();
const maps = LocalMapsManagerService.getInstance();
maps.exportMaps(request.args.version, request.args.maps, request.args.outPath).then(() => {
utils.ipcSend<void>(request.responceChannel, {success: true});
}).catch(err => {
utils.ipcSend<void>(request.responceChannel, {success: false, error: err});
});
});
ipcMain.on("download-map", async (event, request: IpcRequest<{map: BsvMapDetail, version: BSVersion}>) => {
const utils = UtilsService.getInstance();
const maps = LocalMapsManagerService.getInstance();
-1
View File
@@ -3,7 +3,6 @@ import './os-controls-ipcs';
import './bs-launcher-ipcs';
import './bs-version-ipcs';
import './bs-uninstall-ipcs';
import './map-ipcs';
import './supporters-ipcs';
import './launcher-ipcs';
import './window-manager-ipcs';
-13
View File
@@ -1,13 +0,0 @@
import { ipcMain} from 'electron';
import { MapService } from '../services/map.service';
import { UtilsService } from '../services/utils.service';
import { IpcRequest } from 'shared/models/ipc';
import { ExportVersionMapsOption } from 'shared/models/maps/export-version-maps.model';
ipcMain.on("map.export-version", (event, request: IpcRequest<ExportVersionMapsOption>) => {
const utils = UtilsService.getInstance();
const mapService = MapService.getInstance();
mapService.exportVersionMaps(request.args.version, request.args.path).then(() => {
utils.ipcSend(request.responceChannel, {success: true});
}).catch(() => utils.ipcSend(request.responceChannel, {success: false, error: {title: "", msg: ""}}));
});
-47
View File
@@ -1,47 +0,0 @@
import path from "path";
import { BSVersion } from "shared/bs-version.interface";
import { BSLocalVersionService } from "./bs-local-version.service";
import archiver from "archiver";
import { createWriteStream } from "fs";
import log from "electron-log";
export class MapService{
private readonly MAP_PATH = path.join("Beat Saber_Data", "CustomLevels");
private static instance: MapService;
private readonly localVersionService: BSLocalVersionService;
public static getInstance(): MapService{
if(!MapService.instance){ MapService.instance = new MapService(); }
return MapService.instance;
}
private constructor(){
this.localVersionService = BSLocalVersionService.getInstance();
}
private getMapsPath(versionPath: string): string{
return path.join(versionPath, this.MAP_PATH);
}
public async exportVersionMaps(version: BSVersion, outputZip: string): Promise<void>{
const versionPath = await this.localVersionService.getVersionPath(version);
const mapsPath = this.getMapsPath(versionPath);
const output = createWriteStream(outputZip);
const archive = archiver("zip", {zlib: {level: 9}});
const promise = new Promise<void>((resolve, reject) => {
archive.pipe(output);
archive.directory(mapsPath, false);
archive.on("error", reject);
output.on("close", resolve);
archive.finalize()
});
return promise;
}
}
@@ -6,11 +6,12 @@ import { BSLocalVersionService } from "../bs-local-version.service";
import { InstallationLocationService } from "../installation-location.service";
import { UtilsService } from "../utils.service";
import crypto from "crypto";
import { lstatSync, symlinkSync, unlinkSync, readdirSync } from "fs";
import { lstatSync, symlinkSync, unlinkSync, readdirSync, createWriteStream } from "fs";
import { copySync } from "fs-extra";
import StreamZip from "node-stream-zip";
import { RequestService } from "../request.service";
import sanitize from "sanitize-filename";
import archiver from "archiver";
export class LocalMapsManagerService {
@@ -90,6 +91,28 @@ export class LocalMapsManagerService {
return {zip, zipPath};
}
private async getAbsoluteFolderOfMaps(maps: BsmLocalMap[], version: BSVersion): Promise<string[]>{
const mapsFolder = await this.getMapsFolderPath(version);
const res: string[] = [];
const mapHashs = maps.map(map => map.hash);
const mapsFolders = readdirSync(mapsFolder, {withFileTypes: true});
for(const content of mapsFolders){
if(!content.isDirectory()){ continue; }
const mapFolderPath = path.join(mapsFolder, content.name);
const { hash } = await this.loadMapInfoFromPath(mapFolderPath);
if(mapHashs.includes(hash)){
res.push(mapFolderPath);
}
}
return res;
}
public async getMaps(version?: BSVersion): Promise<BsmLocalMap[]>{
const levelsFolder = await this.getMapsFolderPath(version);
@@ -146,18 +169,13 @@ export class LocalMapsManagerService {
public async deleteMaps(maps: BsmLocalMap[], verion?: BSVersion){
const mapsFolder = await this.getMapsFolderPath(verion);
const mapsFolders = await this.getAbsoluteFolderOfMaps(maps, verion);
const mapsHashsToDelete = maps.map(map => map.hash);
const mapsHashsToDelete = maps.map(m => m.hash);
const mapsFolders = readdirSync(mapsFolder, {withFileTypes: true});
for(const content of mapsFolders){
if(!content.isDirectory()){ continue; }
const mapFolderPath = path.join(mapsFolder, content.name);
const { hash } = await this.loadMapInfoFromPath(mapFolderPath);
for(const folder of mapsFolders){
const { hash } = await this.loadMapInfoFromPath(folder);
if(mapsHashsToDelete.includes(hash)){
await this.utils.deleteFolder(mapFolderPath);
await this.utils.deleteFolder(folder);
}
}
@@ -187,6 +205,34 @@ export class LocalMapsManagerService {
unlinkSync(zipPath);
}
public async exportMaps(version: BSVersion, maps: BsmLocalMap[], outPath: string){
const output = createWriteStream(outPath);
const archive = archiver("zip", {zlib: {level: 9}});
archive.pipe(output);
archive.on("error", (e) => {throw e});
if(!maps || maps.length === 0){
const mapsFolder = await this.getMapsFolderPath(version);
archive.directory(mapsFolder, false);
}
else{
const mapsFolders = await this.getAbsoluteFolderOfMaps(maps, version);
for(const folder of mapsFolders){
archive.directory(folder, path.basename(folder));
}
}
await archive.finalize();
}
}
@@ -32,7 +32,7 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
mapsManager.deleteMaps(mapsToDelete).finally(loadMaps);
},
exportMaps(){
console.log("TODO EXPORT MAPS");
mapsManager.exportMaps(version, selectedMaps)
}
}), [selectedMaps, maps]);
@@ -11,7 +11,6 @@ import { ModalExitCode, ModalService } from '../services/modale.service';
import DefautVersionImage from "../../../assets/images/default-version-img.jpg";
import { BsDownloaderService } from 'renderer/services/bs-downloader.service';
import { IpcService } from 'renderer/services/ipc.service';
import { MapService } from 'renderer/services/maps.service';
import { LaunchSlide } from 'renderer/components/version-viewer/slides/launch/launch-slide.component';
import { ModsSlide } from 'renderer/components/version-viewer/slides/mods/mods-slide.component';
import { UninstallModal } from 'renderer/components/modal/modal-types/uninstall-modal.component';
@@ -24,7 +23,6 @@ export function VersionViewer() {
const modalService = ModalService.getInsance();
const bsDownloaderService = BsDownloaderService.getInstance();
const ipcService = IpcService.getInstance();
const mapService = MapService.getInstance();
const {state} = useLocation() as {state: BSVersion};
const navigate = useNavigate();
@@ -32,7 +30,6 @@ export function VersionViewer() {
const navigateToVersion = (version: BSVersion) => navigate(`/bs-version/${version.BSVersion}`, {state: version});
const openFolder = () => ipcService.sendLazy("bs-version.open-folder", {args: state});
const exportMaps = () => mapService.exportVersionMaps(state);
const verifyFiles = () => bsDownloaderService.download(state, true);
const uninstall = async () => {
@@ -71,7 +68,7 @@ export function VersionViewer() {
<div className='mt-2 w-full min-h-0 grow flex transition-transform duration-300' style={{transform: `translate(${-(currentTabIndex * 100)}%, 0)`}}>
<LaunchSlide version={state}/>
<div className="w-full shrink-0 px-3 pb-3 flex flex-col items-center">
<MapsPlaylistsPanel version={state} oneBlock/>
<MapsPlaylistsPanel version={state}/>
</div>
<ModsSlide version={state}/>
</div>
@@ -79,7 +76,6 @@ export function VersionViewer() {
<BsmDropdownButton className='absolute top-5 right-5 h-9 w-9 bg-light-main-color-2 dark:bg-main-color-2 rounded-md' items={[
{text: "pages.version-viewer.dropdown.open-folder", icon: "folder", onClick: openFolder},
((!state.steam && !state.oculus) && {text: "pages.version-viewer.dropdown.verify-files", icon: "task", onClick: verifyFiles}),
((!state.steam && !state.oculus) && {text: "Exporter les misc.maps", icon: "export", onClick: exportMaps}),
((!state.steam && !state.oculus) && {text: "pages.version-viewer.dropdown.edit", icon: "edit", onClick: edit}),
(!state.oculus && {text: "pages.version-viewer.dropdown.clone", icon: "copy", onClick: clone}),
((!state.steam && !state.oculus) && {text: "pages.version-viewer.dropdown.uninstall", icon:"trash", onClick: uninstall})
@@ -9,6 +9,8 @@ import { IpcService } from "./ipc.service";
import { ModalExitCode, ModalService } from "./modale.service";
import { DeleteMapsModal } from "renderer/components/modal/modal-types/delete-maps-modal.component";
import { ProgressBarService } from "./progress-bar.service";
import { OpenSaveDialogOption } from "shared/models/ipc";
import { NotificationService } from "./notification.service";
export class MapsManagerService {
@@ -23,6 +25,7 @@ export class MapsManagerService {
private readonly bsaver: BeatSaverService;
private readonly modal: ModalService;
private readonly progressBar: ProgressBarService;
private readonly notifications: NotificationService
private readonly lastLinkedVersion$: Subject<BSVersion> = new Subject();
private readonly lastUnlinkedVersion$: Subject<BSVersion> = new Subject();
@@ -32,6 +35,7 @@ export class MapsManagerService {
this.bsaver = BeatSaverService.getInstance();
this.modal = ModalService.getInsance();
this.progressBar = ProgressBarService.getInstance();
this.notifications = NotificationService.getInstance();
}
public getMaps(version?: BSVersion, withDetails = true): Observable<BsmLocalMap[]>{
@@ -130,6 +134,30 @@ export class MapsManagerService {
return res.success;
}
public async exportMaps(version: BSVersion, maps?: BsmLocalMap[]): Promise<void>{
if(!this.progressBar.require()){ return; }
const resFile = await this.ipcService.send<string, OpenSaveDialogOption>("save-file", {args: {
filename: `${version.BSVersion} Maps`,
filters: [{name: "zip", extensions: ["zip"]}]
}});
if(!resFile.success){ return; }
this.progressBar.showFake(.008);
const resExport = await this.ipcService.send<string, {version: BSVersion, maps: BsmLocalMap[], outPath: string}>("export-maps", {args: {version, maps, outPath: resFile.data}});
if(!resExport.success && resExport.error.title){
const {title, msg} = resExport.error;
this.notifications.notifyError({title, desc: msg});
}
else {
this.notifications.notifySuccess({title: "Export terminé 🎉", duration: 3000});
}
this.progressBar.complete();
this.progressBar.hide(true);
}
public get versionLinked$(): Observable<BSVersion>{
return this.lastLinkedVersion$.asObservable();
}
-51
View File
@@ -1,51 +0,0 @@
import { BSVersion } from "shared/bs-version.interface";
import { OpenSaveDialogOption } from "shared/models/ipc";
import { ExportVersionMapsOption } from "shared/models/maps/export-version-maps.model";
import { IpcService } from "./ipc.service";
import { NotificationService } from "./notification.service";
import { ProgressBarService } from "./progress-bar.service";
export class MapService {
private static instance: MapService;
private readonly ipcService: IpcService;
private readonly progressService: ProgressBarService;
private readonly notificationService: NotificationService;
public static getInstance(): MapService{
if(!MapService.instance){ MapService.instance = new MapService(); }
return MapService.instance;
}
private constructor(){
this.ipcService = IpcService.getInstance();
this.notificationService = NotificationService.getInstance();
this.progressService = ProgressBarService.getInstance();
}
public async exportVersionMaps(version: BSVersion): Promise<void>{
if(!this.progressService.require()){ return; }
const resFile = await this.ipcService.send<string, OpenSaveDialogOption>("save-file", {args: {
filename: `${version.BSVersion} Maps`,
filters: [{name: "zip", extensions: ["zip"]}]
}});
if(!resFile.success){ return; }
this.progressService.showFake(.008);
const resExport = await this.ipcService.send<string, ExportVersionMapsOption>("map.export-version", {args: {version, path: resFile.data}});
if(!resExport.success && resExport.error.title){
const {title, msg} = resExport.error;
this.notificationService.notifyError({title, desc: msg});
}
else {
this.notificationService.notifySuccess({title: "Export terminé 🎉", duration: 3000});
}
this.progressService.complete();
this.progressService.hide(true);
}
}
@@ -1,3 +0,0 @@
import { BSVersion } from "shared/bs-version.interface";
export interface ExportVersionMapsOption {version: BSVersion, path: string};
-2
View File
@@ -1,7 +1,5 @@
export { RawMapInfoData, RawMapDifficulty, RawDifficultySet } from "./raw-map.model";
export { ExportVersionMapsOption } from "./export-version-maps.model";
export {
BsvInstant,
BsvMapDetail,