Merge branch 'master' into feature/oculus-pcvr-download/274

This commit is contained in:
MathieuG-P
2023-10-15 14:21:57 +02:00
27 changed files with 223 additions and 215 deletions
+8
View File
@@ -573,5 +573,13 @@
"ReleaseImg": "https://clan.akamai.steamstatic.com/images/32055887/58621b3cb8bf42fcfbf3b97fe47aa91b290eea04.png",
"ReleaseDate": "1693824747",
"year": "2023"
},
{
"BSVersion": "1.32.0",
"BSManifest": "1660149393914874041",
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/3723971545002767483",
"ReleaseImg": "https://clan.cloudflare.steamstatic.com/images/32055887/02284793f06188caf740da66939679afc25415e4.png",
"ReleaseDate": "1696517680",
"year": "2023"
}
]
+2 -1
View File
@@ -17,5 +17,6 @@
{"username": "Phil", "type": "gold"},
{"username": "Protocrush", "type": "gold"},
{"username": "ServerMensch", "type": "gold"},
{"username": "policepigou"}
{"username": "policepigou"},
{"username": "Karlito", "type": "gold"}
]
+2 -2
View File
@@ -44,7 +44,7 @@
"maps": {
"title": "Shared maps",
"search-bar": {
"search-placeholder": "Search installed maps",
"search-placeholder": "Search a map",
"filters-btn": "Filters",
"dropdown": {
"export-maps": "Export maps",
@@ -738,7 +738,7 @@
},
"panel": {
"actions": {
"search": "Search installed models",
"search": "Search a model",
"drop-down": {
"delete": "Delete models",
"export": "Export models"
+41 -20
View File
@@ -19,7 +19,7 @@
"electron-debug": "^3.2.0",
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",
"electron-updater": "^6.1.4",
"electron-updater": "^6.1.5",
"fast-deep-equal": "^3.1.3",
"framer-motion": "^10.16.4",
"fs-extra": "^11.1.1",
@@ -88,7 +88,7 @@
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.1.0",
"detect-port": "^1.3.0",
"electron": "^26.2.1",
"electron": "^27.0.0",
"electron-builder": "^24.6.3",
"electron-devtools-installer": "^3.2.0",
"electron-notarize": "^1.2.1",
@@ -5513,6 +5513,19 @@
"typescript": "^4.0.2"
}
},
"node_modules/config-file-ts/node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/confusing-browser-globals": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
@@ -6736,9 +6749,9 @@
}
},
"node_modules/electron": {
"version": "26.2.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-26.2.1.tgz",
"integrity": "sha512-SNT24Cf/wRvfcFZQoERXjzswUlg5ouqhIuA2t9x2L7VdTn+2Jbs0QXRtOfzcnOV/raVMz3e8ICyaU2GGeciKLg==",
"version": "27.0.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-27.0.0.tgz",
"integrity": "sha512-mr3Zoy82l8XKK/TgguE5FeNeHZ9KHXIGIpUMjbjZWIREfAv+X2Q3vdX6RG0Pmi1K23AFAxANXQezIHBA2Eypwg==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -6920,9 +6933,9 @@
"dev": true
},
"node_modules/electron-updater": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz",
"integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==",
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.5.tgz",
"integrity": "sha512-RFoncoSbAWYN3CVOFFrEUB5O2V81REAnA4IXQ8enGTSbUjjv+z54TYp23y/3e63oiV3W8grhblJmn9oyH/LnVg==",
"dependencies": {
"builder-util-runtime": "9.2.1",
"fs-extra": "^10.1.0",
@@ -18031,9 +18044,9 @@
}
},
"node_modules/typescript": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -23468,6 +23481,14 @@
"requires": {
"glob": "^7.1.6",
"typescript": "^4.0.2"
},
"dependencies": {
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true
}
}
},
"confusing-browser-globals": {
@@ -24358,9 +24379,9 @@
}
},
"electron": {
"version": "26.2.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-26.2.1.tgz",
"integrity": "sha512-SNT24Cf/wRvfcFZQoERXjzswUlg5ouqhIuA2t9x2L7VdTn+2Jbs0QXRtOfzcnOV/raVMz3e8ICyaU2GGeciKLg==",
"version": "27.0.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-27.0.0.tgz",
"integrity": "sha512-mr3Zoy82l8XKK/TgguE5FeNeHZ9KHXIGIpUMjbjZWIREfAv+X2Q3vdX6RG0Pmi1K23AFAxANXQezIHBA2Eypwg==",
"dev": true,
"requires": {
"@electron/get": "^2.0.0",
@@ -24523,9 +24544,9 @@
"dev": true
},
"electron-updater": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz",
"integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==",
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.5.tgz",
"integrity": "sha512-RFoncoSbAWYN3CVOFFrEUB5O2V81REAnA4IXQ8enGTSbUjjv+z54TYp23y/3e63oiV3W8grhblJmn9oyH/LnVg==",
"requires": {
"builder-util-runtime": "9.2.1",
"fs-extra": "^10.1.0",
@@ -32866,9 +32887,9 @@
}
},
"typescript": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA=="
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ=="
},
"unbox-primitive": {
"version": "1.0.2",
+2 -2
View File
@@ -179,7 +179,7 @@
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.1.0",
"detect-port": "^1.3.0",
"electron": "^26.2.1",
"electron": "^27.0.0",
"electron-builder": "^24.6.3",
"electron-devtools-installer": "^3.2.0",
"electron-notarize": "^1.2.1",
@@ -240,7 +240,7 @@
"electron-debug": "^3.2.0",
"electron-log": "^4.4.8",
"electron-store": "^8.1.0",
"electron-updater": "^6.1.4",
"electron-updater": "^6.1.5",
"fast-deep-equal": "^3.1.3",
"framer-motion": "^10.16.4",
"fs-extra": "^11.1.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bs-manager",
"version": "1.3.1-alpha.2",
"version": "1.3.2",
"description": "BSManager",
"main": "./dist/main/main.js",
"author": {
+2
View File
@@ -6,6 +6,7 @@ import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
import { BsvMapDetail } from "shared/models/maps";
import { IpcService } from "../services/ipc.service";
import { from } from "rxjs";
import log from "electron-log"
const ipc = IpcService.getInstance();
@@ -71,6 +72,7 @@ ipc.on("one-click-install-map", async (request: IpcRequest<BsvMapDetail>) => {
utils.ipcSend(request.responceChannel, { success: true });
})
.catch(err => {
log.error(err);
utils.ipcSend(request.responceChannel, { success: false, error: err });
});
});
+2
View File
@@ -2,6 +2,7 @@ import { ipcMain } from "electron";
import { IpcRequest } from "shared/models/ipc";
import { LocalPlaylistsManagerService } from "../services/additional-content/local-playlists-manager.service";
import { UtilsService } from "../services/utils.service";
import log from "electron-log";
ipcMain.on("one-click-install-playlist", async (event, request: IpcRequest<string>) => {
const utils = UtilsService.getInstance();
@@ -13,6 +14,7 @@ ipcMain.on("one-click-install-playlist", async (event, request: IpcRequest<strin
utils.ipcSend(request.responceChannel, { success: true });
})
.catch(e => {
log.error(e);
utils.ipcSend(request.responceChannel, { success: false, error: e });
});
});
+2 -18
View File
@@ -1,4 +1,4 @@
import { ipcMain, shell, dialog, app } from "electron";
import { ipcMain, shell, dialog, app, BrowserWindow } from "electron";
import { UtilsService } from "../services/utils.service";
import { IpcRequest } from "shared/models/ipc";
import { SystemNotificationOptions } from "shared/models/notification/system-notification.model";
@@ -11,21 +11,6 @@ import { from, of } from "rxjs";
const ipc = IpcService.getInstance();
ipcMain.on("window.maximize", async () => {
const utils = UtilsService.getInstance();
utils.getMainWindows("index.html")?.maximize();
});
ipcMain.on("window.minimize", async () => {
const utils = UtilsService.getInstance();
utils.getMainWindows("index.html")?.minimize();
});
ipcMain.on("window.reset", async () => {
const utils = UtilsService.getInstance();
utils.getMainWindows("index.html")?.restore();
});
ipcMain.on("new-window", async (event, request: IpcRequest<string>) => {
shell.openExternal(request.args);
});
@@ -35,8 +20,7 @@ ipc.on<string>("choose-folder", async (req, reply) => {
});
ipcMain.on("window.progression", async (event, request: IpcRequest<number>) => {
const utils = UtilsService.getInstance();
utils.getMainWindows("index.html")?.setProgressBar(request.args / 100);
BrowserWindow.fromWebContents(event.sender)?.setProgressBar(request.args / 100);
});
ipcMain.on("save-file", async (event, request: IpcRequest<{ filename?: string; filters?: Electron.FileFilter[] }>) => {
+20 -15
View File
@@ -1,12 +1,29 @@
import { WindowManagerService } from "../services/window-manager.service";
import { AppWindow } from "shared/models/window-manager/app-window.model";
import { BSLauncherService } from "../services/bs-launcher.service";
import { IpcService } from "../services/ipc.service";
import { from, of } from "rxjs";
import { from } from "rxjs";
import { BrowserWindow, ipcMain } from "electron";
const launcher = BSLauncherService.getInstance();
const ipc = IpcService.getInstance();
// Native windows control, do not pass through IPC service
ipcMain.on("close-window", async (event) => {
BrowserWindow.fromWebContents(event.sender)?.close();
});
ipcMain.on("maximise-window", async (event) => {
BrowserWindow.fromWebContents(event.sender)?.maximize();
});
ipcMain.on("minimise-window", async (event) => {
BrowserWindow.fromWebContents(event.sender)?.minimize();
});
ipcMain.on("unmaximise-window", async (event) => {
BrowserWindow.fromWebContents(event.sender)?.unmaximize();
});
ipc.on<AppWindow>("open-window-then-close-all", (req, reply) => {
const windowManager = WindowManagerService.getInstance();
@@ -17,18 +34,6 @@ ipc.on<AppWindow>("open-window-then-close-all", (req, reply) => {
reply(from(res));
});
ipc.on<AppWindow>("close-all-windows", async (req, reply) => {
await launcher.restoreSteamVR();
const windowManager = WindowManagerService.getInstance();
reply(of(windowManager.closeAllWindows(req.args)));
});
ipc.on<AppWindow[]>("close-windows", async (req, reply) => {
await launcher.restoreSteamVR();
const windowManager = WindowManagerService.getInstance();
reply(of(windowManager.close(...req.args)));
});
ipc.on<AppWindow>("open-window-or-focus", (req, reply) => {
const windowManager = WindowManagerService.getInstance();
reply(from(windowManager.openWindowOrFocus(req.args)));
+5
View File
@@ -89,6 +89,11 @@ if (!gotTheLock) {
DeepLinkService.getInstance().dispatchLinkOpened(deepLink);
});
app.on("window-all-closed", () => {
if (process.platform === "darwin") { return; }
app.quit();
})
app.whenReady().then(() => {
app.setAppUserModelId(APP_NAME);
+8 -1
View File
@@ -26,5 +26,12 @@ contextBridge.exposeInMainWorld("electron", {
join: (...args: string[]): string => {
return args.join(sep);
}
},
window: {
close: () => { ipcRenderer.send("close-window"); },
minimise: () => { ipcRenderer.send("minimise-window"); },
maximise: () => { ipcRenderer.send("maximise-window"); },
unmaximise: () => { ipcRenderer.send("unmaximise-window"); },
}
});
});
@@ -7,22 +7,21 @@ import { InstallationLocationService } from "../installation-location.service";
import { UtilsService } from "../utils.service";
import crypto from "crypto";
import { lstatSync } from "fs";
import { copy, createReadStream, ensureDir, unlink } from "fs-extra";
import { copy, createReadStream, ensureDir, realpath, unlink } from "fs-extra";
import StreamZip from "node-stream-zip";
import { RequestService } from "../request.service";
import sanitize from "sanitize-filename";
import { DeepLinkService } from "../deep-link.service";
import log from "electron-log";
import { WindowManagerService } from "../window-manager.service";
import { ipcMain } from "electron";
import { IpcRequest } from "shared/models/ipc";
import { Observable, lastValueFrom } from "rxjs";
import { Observable, lastValueFrom, of } from "rxjs";
import { Archive } from "../../models/archive.class";
import { deleteFolder, ensureFolderExist, getFilesInFolder, getFoldersInFolder, pathExist } from "../../helpers/fs.helpers";
import { readFile } from "fs/promises";
import { FolderLinkerService } from "../folder-linker.service";
import { allSettled } from "../../../shared/helpers/promise.helpers";
import { splitIntoChunk } from "../../../shared/helpers/array.helpers";
import { IpcService } from "../ipc.service";
export class LocalMapsManagerService {
private static instance: LocalMapsManagerService;
@@ -49,6 +48,7 @@ export class LocalMapsManagerService {
private readonly reqService: RequestService;
private readonly deepLink: DeepLinkService;
private readonly windows: WindowManagerService;
private readonly ipc: IpcService;
private readonly linker = FolderLinkerService.getInstance();
private constructor() {
@@ -59,6 +59,7 @@ export class LocalMapsManagerService {
this.deepLink = DeepLinkService.getInstance();
this.windows = WindowManagerService.getInstance();
this.linker = FolderLinkerService.getInstance();
this.ipc = IpcService.getInstance();
this.deepLink.addLinkOpenedListener(this.DEEP_LINKS.BeatSaver, link => {
log.info("DEEP-LINK RECEIVED FROM", this.DEEP_LINKS.BeatSaver, link);
@@ -126,7 +127,7 @@ export class LocalMapsManagerService {
}
private async downloadMapZip(zipUrl: string): Promise<{ zip: StreamZip.StreamZipAsync; zipPath: string }> {
const fileName = path.basename(zipUrl);
const fileName = `${path.basename(zipUrl, ".zip")}-${crypto.randomUUID()}.zip`;
const tempPath = this.utils.getTempPath();
await ensureFolderExist(this.utils.getTempPath());
const dest = path.join(tempPath, fileName);
@@ -138,11 +139,15 @@ export class LocalMapsManagerService {
}
private openOneClickDownloadMapWindow(mapId: string, isHash = false): void {
ipcMain.once("one-click-map-info", async (event, req: IpcRequest<void>) => {
this.utils.ipcSend(req.responceChannel, { success: true, data: { id: mapId, isHash } });
this.windows.openWindow("oneclick-download-map.html").then(window => {
this.ipc.once("one-click-map-info", async (_, reply) => {
reply(of({ id: mapId, isHash }));
}, window.webContents.ipc);
});
this.windows.openWindow("oneclick-download-map.html");
}
public getMaps(version?: BSVersion): Observable<BsmLocalMapsProgress> {
@@ -290,10 +295,16 @@ export class LocalMapsManagerService {
const versions = await this.localVersion.getInstalledVersions();
const downloadedMap = await this.downloadMap(map, versions.pop());
const downloadedMapRealFolder = await realpath(path.dirname(downloadedMap.path));
for (const version of versions) {
const versionMapsPath = await this.getMapsFolderPath(version);
await ensureDir(versionMapsPath);
if((await realpath(versionMapsPath)) === downloadedMapRealFolder){
continue;
}
await copy(downloadedMap.path, path.join(versionMapsPath, path.basename(downloadedMap.path)), { overwrite: true });
}
}
@@ -1,8 +1,5 @@
import { DeepLinkService } from "../deep-link.service";
import log from "electron-log";
import { ipcMain } from "electron";
import { IpcRequest } from "shared/models/ipc";
import { UtilsService } from "../utils.service";
import { WindowManagerService } from "../window-manager.service";
import { MSModel, MSModelType } from "../../../shared/models/models/model-saber.model";
import { BSVersion } from "shared/bs-version.interface";
@@ -14,13 +11,14 @@ import sanitize from "sanitize-filename";
import { Progression, ensureFolderExist, unlinkPath } from "../../helpers/fs.helpers";
import { MODEL_FILE_EXTENSIONS, MODEL_TYPES, MODEL_TYPE_FOLDERS } from "../../../shared/models/models/constants";
import { InstallationLocationService } from "../installation-location.service";
import { Observable, Subscription, lastValueFrom } from "rxjs";
import { Observable, Subscription, lastValueFrom, of } from "rxjs";
import { readdir } from "fs/promises";
import md5File from "md5-file";
import { allSettled } from "../../../shared/helpers/promise.helpers";
import { ModelSaberService } from "../thrid-party/model-saber/model-saber.service";
import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface";
import { Archive } from "../../models/archive.class";
import { IpcService } from "../ipc.service";
export class LocalModelsManagerService {
private static instance: LocalModelsManagerService;
@@ -37,21 +35,21 @@ export class LocalModelsManagerService {
};
private readonly deepLink: DeepLinkService;
private readonly utils: UtilsService;
private readonly windows: WindowManagerService;
private readonly localVersion: BSLocalVersionService;
private readonly installPaths: InstallationLocationService;
private readonly request: RequestService;
private readonly modelSaber: ModelSaberService;
private readonly ipc: IpcService;
private constructor() {
this.deepLink = DeepLinkService.getInstance();
this.utils = UtilsService.getInstance();
this.windows = WindowManagerService.getInstance();
this.localVersion = BSLocalVersionService.getInstance();
this.request = RequestService.getInstance();
this.installPaths = InstallationLocationService.getInstance();
this.modelSaber = ModelSaberService.getInstance();
this.ipc = IpcService.getInstance();
this.deepLink.addLinkOpenedListener(this.DEEP_LINKS.ModelSaber, link => {
log.info("DEEP-LINK RECEIVED FROM", this.DEEP_LINKS.ModelSaber, link);
@@ -65,11 +63,13 @@ export class LocalModelsManagerService {
}
private openOneClickDownloadModelWindow(id: string, type: string) {
ipcMain.once("one-click-model-info", async (_event, req: IpcRequest<void>) => {
this.utils.ipcSend(req.responceChannel, { success: true, data: { id, type } });
});
this.windows.openWindow("oneclick-download-model.html").then(window => {
this.windows.openWindow("oneclick-download-model.html");
this.ipc.once("one-click-model-info", (_, reply) => {
reply(of({ id, type }));
}, window.webContents.ipc);
});
}
private async getModelFolderPath(type: MSModelType, version?: BSVersion): Promise<string> {
@@ -1,5 +1,5 @@
import path from "path";
import { BehaviorSubject, Observable, lastValueFrom } from "rxjs";
import { BehaviorSubject, Observable, lastValueFrom, of } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
import { BSLocalVersionService } from "../bs-local-version.service";
import { DeepLinkService } from "../deep-link.service";
@@ -8,14 +8,13 @@ import { UtilsService } from "../utils.service";
import { LocalMapsManagerService } from "./local-maps-manager.service";
import log from "electron-log";
import { isValidUrl } from "../../../shared/helpers/url.helpers";
import { ipcMain } from "electron";
import { WindowManagerService } from "../window-manager.service";
import { IpcRequest } from "shared/models/ipc";
import { BPList, DownloadPlaylistProgression } from "shared/models/playlists/playlist.interface";
import { copyFileSync, readFileSync } from "fs";
import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
import { copySync } from "fs-extra";
import { copy, realpath } from "fs-extra";
import { ensureFolderExist, pathExist } from "../../helpers/fs.helpers";
import { IpcService } from "../ipc.service";
export class LocalPlaylistsManagerService {
private static instance: LocalPlaylistsManagerService;
@@ -39,6 +38,7 @@ export class LocalPlaylistsManagerService {
private readonly deepLink: DeepLinkService;
private readonly windows: WindowManagerService;
private readonly bsaver: BeatSaverService;
private readonly ipc: IpcService;
private constructor() {
this.maps = LocalMapsManagerService.getInstance();
@@ -48,6 +48,7 @@ export class LocalPlaylistsManagerService {
this.deepLink = DeepLinkService.getInstance();
this.windows = WindowManagerService.getInstance();
this.bsaver = BeatSaverService.getInstance();
this.ipc = IpcService.getInstance();
this.deepLink.addLinkOpenedListener(this.DEEP_LINKS.BeatSaver, link => {
log.info("DEEP-LINK RECEIVED FROM", this.DEEP_LINKS.BeatSaver, link);
@@ -112,11 +113,13 @@ export class LocalPlaylistsManagerService {
}
private openOneClickDownloadPlaylistWindow(downloadUrl: string): void {
ipcMain.once("one-click-playlist-info", async (event, req: IpcRequest<void>) => {
this.utils.ipcSend(req.responceChannel, { success: true, data: { bpListUrl: downloadUrl, id: this.getPlaylistIdFromDownloadUrl(downloadUrl) } });
});
this.windows.openWindow("oneclick-download-playlist.html").then(window => {
this.windows.openWindow("oneclick-download-playlist.html");
this.ipc.once("one-click-playlist-info", (_, reply) => {
reply(of({ bpListUrl: downloadUrl, id: this.getPlaylistIdFromDownloadUrl(downloadUrl) }))
}, window.webContents.ipc);
});
}
public downloadPlaylist(bpListUrl: string, version: BSVersion): Observable<DownloadPlaylistProgression> {
@@ -180,14 +183,27 @@ export class LocalPlaylistsManagerService {
const { bpListPath, mapsPath } = await lastValueFrom(this.downloadPlaylist(bpListUrl, versions.pop()));
if(mapsPath?.length === 0 || !bpListPath) {
return;
}
const realSourceMapsFolder = await realpath(path.dirname(mapsPath[0]));
for (const version of versions) {
await this.installBPListFile(bpListPath, version);
const versionMapsFolder = await this.maps.getMapsFolderPath(version);
const realDestMapsFolder = await realpath(versionMapsFolder);
if(realSourceMapsFolder === realDestMapsFolder) {
continue;
}
for (const mapPath of mapsPath) {
const versionMapsFolder = await this.maps.getMapsFolderPath(version);
const mapDest = path.join(versionMapsFolder, path.basename(mapPath));
copySync(mapPath, mapDest, { overwrite: true });
await copy(mapPath, mapDest, { overwrite: true });
}
}
}
+2 -4
View File
@@ -6,12 +6,12 @@ import { spawnSync } from "child_process";
import log from "electron-log";
import { InstallationLocationService } from "./installation-location.service";
import { BSLocalVersionService } from "./bs-local-version.service";
import { WindowManagerService } from "./window-manager.service";
import { copy, ensureDir } from "fs-extra";
import { pathExist } from "../helpers/fs.helpers";
import { Observable, map } from "rxjs";
import { DepotDownloaderArgsOptions, DepotDownloaderErrorEvent, DepotDownloaderEvent, DepotDownloaderEventType, DepotDownloaderInfoEvent } from "../../shared/models/depot-downloader.model";
import { DepotDownloader } from "../models/depot-downloader.class";
import { app } from "electron";
export class BSInstallerService {
private static instance: BSInstallerService;
@@ -19,7 +19,6 @@ export class BSInstallerService {
private readonly utils: UtilsService;
private readonly installLocationService: InstallationLocationService;
private readonly localVersionService: BSLocalVersionService;
private readonly windows: WindowManagerService;
private depotDownloader: DepotDownloader;
@@ -27,9 +26,8 @@ export class BSInstallerService {
this.utils = UtilsService.getInstance();
this.installLocationService = InstallationLocationService.getInstance();
this.localVersionService = BSLocalVersionService.getInstance();
this.windows = WindowManagerService.getInstance();
this.windows.getWindow("index.html")?.on("close", () => {
app.on("before-quit", () => {
this.depotDownloader?.stop();
});
}
+8 -2
View File
@@ -161,7 +161,7 @@ export class BSLauncherService {
bsProcess.removeAllListeners("error");
bsProcess.removeAllListeners("exit");
resolve(-1);
}, 15_000);
}, 30_000);
}).then(exitCode => {
log.info("BS process exit code", exitCode);
@@ -183,7 +183,13 @@ export class BSLauncherService {
shortcutLink = new URL(shortcutLink);
}
return objectFromEntries(shortcutLink.searchParams.entries()) as ShortcutParams;
const params = objectFromEntries(shortcutLink.searchParams.entries()) as ShortcutParams;
if(typeof params.additionalArgs === "string"){
params.additionalArgs = [params.additionalArgs];
}
return params;
}
public shortcutLinkToLaunchOptions(shortcutLink: string|URL): LaunchOption{
+12 -18
View File
@@ -1,9 +1,7 @@
import { IpcRequest } from "shared/models/ipc";
import { ipcMain } from "electron";
import { BrowserWindow, ipcMain } from "electron";
import { Observable } from "rxjs";
import { IpcCompleteChannel, IpcErrorChannel, IpcTearDownChannel } from "shared/models/ipc/ipc-response.interface";
import { AppWindow } from "shared/models/window-manager/app-window.model";
import { WindowManagerService } from "./window-manager.service";
import { IpcReplier } from "shared/models/ipc/ipc-request.interface";
import log from "electron-log";
@@ -17,11 +15,7 @@ export class IpcService {
return IpcService.instance;
}
private readonly windows: WindowManagerService;
private constructor() {
this.windows = WindowManagerService.getInstance();
}
private constructor() {}
private getErrorChannel(channel: string): IpcErrorChannel {
return `${channel}_error`;
@@ -37,17 +31,17 @@ export class IpcService {
private buildProxyListener<T>(listener: IpcListener<T>) {
return (event: Electron.IpcMainEvent, req: IpcRequest<T>) => {
const window = this.windows.getAppWindowFromWebContents(event.sender);
const window = BrowserWindow.fromWebContents(event.sender);
const replier = (data: Observable<unknown>) => this.connectStream(req.responceChannel, window, data);
listener(req, replier);
};
}
public send<T>(channel: string, window: AppWindow, response?: T | Error): void {
this.windows.getWindow(window)?.webContents?.send(channel, response);
public send<T>(channel: string, window: BrowserWindow, response?: T | Error): void {
window.webContents?.send(channel, response);
}
private connectStream(channel: string, window: AppWindow, observable: Observable<unknown>): void {
private connectStream(channel: string, window: BrowserWindow, observable: Observable<unknown>): void {
const sub = observable.subscribe({
next: data => this.send(channel, window, data),
@@ -58,19 +52,19 @@ export class IpcService {
complete: () => this.send(this.getCompleteChannel(channel), window)
})
ipcMain.once(this.getTearDownChannel(channel), () => sub.unsubscribe());
window.webContents.ipc.once(this.getTearDownChannel(channel), () => sub.unsubscribe());
sub.add(() => {
ipcMain.removeAllListeners(this.getTearDownChannel(channel));
window.webContents.ipc.removeAllListeners(this.getTearDownChannel(channel));
});
}
public on<T>(channel: string, listener: IpcListener<T>): void {
ipcMain.on(channel, this.buildProxyListener(listener));
public on<T>(channel: string, listener: IpcListener<T>, ipc = ipcMain): void {
ipc.on(channel, this.buildProxyListener(listener));
}
public once<T>(channel: string, listener: IpcListener<T>): void {
ipcMain.once(channel, this.buildProxyListener(listener));
public once<T>(channel: string, listener: IpcListener<T>, ipc = ipcMain): void {
ipc.once(channel, this.buildProxyListener(listener));
}
}
+1 -11
View File
@@ -2,7 +2,6 @@ import path from "path";
import { app, BrowserWindow } from "electron";
import { IpcResponse } from "shared/models/ipc";
import log from "electron-log";
import { AppWindow } from "shared/models/window-manager/app-window.model";
import psList from "ps-list";
// TODO : REFACTOR
@@ -12,8 +11,6 @@ export class UtilsService {
private assetsPath: string = app.isPackaged ? path.join(process.resourcesPath, "assets") : path.join(__dirname, "../../../assets");
private windows: Map<AppWindow, BrowserWindow> = new Map<AppWindow, BrowserWindow>();
private constructor() {}
public static getInstance() {
@@ -40,13 +37,6 @@ export class UtilsService {
return path.join(app.getPath("temp"), app.getName());
}
public setMainWindows(windows: Map<AppWindow, BrowserWindow>) {
this.windows = windows;
}
public getMainWindows(win: AppWindow) {
return this.windows.get(win);
}
public async taskRunning(task: string): Promise<boolean> {
try {
const processes = await psList();
@@ -60,7 +50,7 @@ export class UtilsService {
public ipcSend<T = unknown>(channel: string, response: IpcResponse<T>): void {
try {
Array.from(this.windows.values()).forEach(window => window?.webContents?.send(channel, response));
BrowserWindow.getAllWindows().forEach(window => window?.webContents?.send(channel, response));
} catch (error) {
log.error(error);
}
+11 -31
View File
@@ -32,8 +32,6 @@ export class WindowManagerService {
webPreferences: { preload: this.PRELOAD_PATH, webSecurity: !this.IS_DEBUG },
};
private readonly windows = new Map<string, BrowserWindow>();
public static getInstance(): WindowManagerService {
if (!WindowManagerService.instance) {
WindowManagerService.instance = new WindowManagerService();
@@ -65,17 +63,6 @@ export class WindowManagerService {
window.show();
});
window.once("closed", () => {
this.windows.delete(url);
if (!this.windows.size) {
app.quit();
}
});
this.windows.set(url, window);
this.utilsService.setMainWindows(this.windows as Map<AppWindow, BrowserWindow>); // TODO : remove
return promise.then(() => window);
}
@@ -85,33 +72,26 @@ export class WindowManagerService {
}
public closeAllWindows(except?: AppWindow) {
this.windows.forEach((window, key) => {
if (key === except) {
return;
}
BrowserWindow.getAllWindows().forEach(window => {
if (except && window.webContents.getURL().includes(except)) { return; }
window.close();
});
}
public close(...win: AppWindow[]) {
win.forEach(window => {
this.windows.get(window)?.close();
});
public getWindows(window: AppWindow): BrowserWindow[] {
return BrowserWindow.getAllWindows().filter(w => w.webContents.getURL().includes(window));
}
public getWindow(window: AppWindow): BrowserWindow {
return this.windows.get(window);
}
public getAppWindowFromWebContents(sender: WebContents): AppWindow {
return Array.from(this.windows.entries()).find(([, value]) => value.webContents.id === sender.id)[0];
public getBrowserWindowsFromWebContents(sender: Electron.WebContents): BrowserWindow {
return BrowserWindow.fromWebContents(sender);
}
public openWindowOrFocus(window: AppWindow): Promise<void> {
const win = this.getWindow(window);
if (win) {
win.focus();
const win = this.getWindows(window);
if (win.length) {
win[0].focus();
return Promise.resolve();
}
@@ -3,7 +3,6 @@ import { useObservable } from "renderer/hooks/use-observable.hook";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { AudioPlayerService } from "renderer/services/audio-player.service";
import { IpcService } from "renderer/services/ipc.service";
import { WindowManagerService } from "renderer/services/window-manager.service";
import { AppWindow } from "shared/models/window-manager/app-window.model";
import { BsmButton } from "../shared/bsm-button.component";
import { BsmRange } from "../shared/bsm-range.component";
@@ -15,7 +14,6 @@ import { lastValueFrom } from "rxjs";
export default function TitleBar({ template = "index.html" }: { template: AppWindow }) {
const ipcService = useService(IpcService);
const windows = useService(WindowManagerService);
const audio = useService(AudioPlayerService);
const volume = useObservable(audio.volume$, audio.volume);
@@ -37,19 +35,19 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
const [maximized, setMaximized] = useState(false);
const closeWindow = () => {
return windows.close(template);
return window.electron.window.close();
};
const maximizeWindow = () => {
ipcService.sendLazy("window.maximize");
window.electron.window.maximise();
};
const minimizeWindow = () => {
ipcService.sendLazy("window.minimize");
window.electron.window.minimise();
};
const resetWindow = () => {
ipcService.sendLazy("window.reset");
window.electron.window.unmaximise();
};
const toogleMaximize = () => {
@@ -119,22 +117,21 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
</header>
);
}
if (template === "oneclick-download-map.html" || template === "oneclick-download-playlist.html" || template === "oneclick-download-model.html" || "shortcut-launch.html") {
return (
<header id="titlebar" className="min-h-[22px] bg-transparent w-screen h-[22px] flex content-center items-center justify-start z-10">
<div id="drag-region" className="grow h-full">
<div id="window-title" className="pl-1">
<span className="text-gray-100 font-bold text-xs italic">BSManager</span>
</div>
return (
<header id="titlebar" className="min-h-[22px] bg-transparent w-screen h-[22px] flex content-center items-center justify-start z-10">
<div id="drag-region" className="grow h-full">
<div id="window-title" className="pl-1">
<span className="text-gray-100 font-bold text-xs italic">BSManager</span>
</div>
<div id="window-controls" className="h-full flex shrink-0">
<div onClick={closeWindow} className="text-gray-200 cursor-pointer w-7 h-full shrink-0 flex justify-center items-center rounded-bl-md" id="close-button" draggable="false">
<svg aria-hidden="false" width="12" height="12" viewBox="0 0 12 12">
<polygon fill="currentColor" fillRule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"/>
</svg>
</div>
</div>
<div id="window-controls" className="h-full flex shrink-0">
<div onClick={closeWindow} className="text-gray-200 cursor-pointer w-7 h-full shrink-0 flex justify-center items-center rounded-bl-md" id="close-button" draggable="false">
<svg aria-hidden="false" width="12" height="12" viewBox="0 0 12 12">
<polygon fill="currentColor" fillRule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"/>
</svg>
</div>
</header>
);
}
</div>
</header>
);
}
+6
View File
@@ -11,6 +11,12 @@ declare global {
sep: "/"|"\\";
join: (...args: string[]) => string;
};
window: {
close: () => void;
minimise: () => void;
maximise: () => void;
unmaximise: () => void;
};
};
}
}
@@ -22,14 +22,6 @@ export class WindowManagerService {
return lastValueFrom(this.ipcService.sendV2<void>("open-window-then-close-all", { args: window }));
}
public closeAll(except?: AppWindow): Promise<void> {
return lastValueFrom(this.ipcService.sendV2<void>("close-all-windows", { args: except }));
}
public close(...win: AppWindow[]): Promise<void> {
return lastValueFrom(this.ipcService.sendV2<void>("close-windows", { args: win }));
}
public openWindowOrFocus(window: AppWindow): Promise<void> {
return lastValueFrom(this.ipcService.sendV2<void, AppWindow>("open-window-or-focus", { args: window }));
}
@@ -8,8 +8,7 @@ import { MapsDownloaderService } from "renderer/services/maps-downloader.service
import { NotificationService } from "renderer/services/notification.service";
import { ProgressBarService } from "renderer/services/progress-bar.service";
import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service";
import { WindowManagerService } from "renderer/services/window-manager.service";
import { timer } from "rxjs";
import { lastValueFrom } from "rxjs";
import { BsvMapDetail } from "shared/models/maps";
import defaultImage from "../../../../assets/images/default-version-img.jpg";
import { useService } from "renderer/hooks/use-service.hook";
@@ -20,7 +19,6 @@ export default function OneClickDownloadMap() {
const bsv = useService(BeatSaverService);
const mapsDownloader = useService(MapsDownloaderService);
const progressBar = useService(ProgressBarService);
const windows = useService(WindowManagerService);
const notification = useService(NotificationService);
const [mapInfo, setMapInfo] = useState<BsvMapDetail>(null);
@@ -34,13 +32,9 @@ export default function OneClickDownloadMap() {
progressBar.open();
const promise = (async () => {
const ipcRes = await ipc.send<{ id: string; isHash: boolean }>("one-click-map-info");
if (!ipcRes.success) {
throw ipcRes.error;
}
const ipcRes = await lastValueFrom(ipc.sendV2<{ id: string; isHash: boolean }>("one-click-map-info"));
const mapDetails = ipcRes.data.isHash ? (await bsv.getMapDetailsFromHashs([ipcRes.data.id])).at(0) : await bsv.getMapDetailsById(ipcRes.data.id);
const mapDetails = ipcRes.isHash ? (await bsv.getMapDetailsFromHashs([ipcRes.id])).at(0) : await bsv.getMapDetailsById(ipcRes.id);
setMapInfo(() => mapDetails);
@@ -52,7 +46,6 @@ export default function OneClickDownloadMap() {
throw new Error("Failed to download map with OneClick");
}
await timer(300).toPromise();
})();
promise.catch(() => {
@@ -64,7 +57,7 @@ export default function OneClickDownloadMap() {
});
promise.finally(() => {
windows.close("oneclick-download-map.html");
window.electron.window.close();
});
}, []);
@@ -7,11 +7,11 @@ import { IpcService } from "renderer/services/ipc.service";
import { NotificationService } from "renderer/services/notification.service";
import { ProgressBarService } from "renderer/services/progress-bar.service";
import { ModelSaberService } from "renderer/services/thrird-partys/model-saber.service";
import { WindowManagerService } from "renderer/services/window-manager.service";
import { MSModel } from "shared/models/models/model-saber.model";
import defaultImage from "../../../../assets/images/default-version-img.jpg";
import { ModelsDownloaderService } from "renderer/services/models-management/models-downloader.service";
import { useService } from "renderer/hooks/use-service.hook";
import { lastValueFrom } from "rxjs";
export default function OneClickDownloadModel() {
@@ -19,7 +19,6 @@ export default function OneClickDownloadModel() {
const modelSaber = useService(ModelSaberService);
const progress = useService(ProgressBarService);
const modelDownloader = useService(ModelsDownloaderService);
const windows = useService(WindowManagerService);
const notification = useService(NotificationService);
const [model, setModel] = useState<MSModel>(null);
@@ -31,13 +30,9 @@ export default function OneClickDownloadModel() {
useEffect(() => {
const promise = (async () => {
const infos = await ipc.send<{ id: string; type: string }>("one-click-model-info");
const infos = await lastValueFrom(ipc.sendV2<{ id: string; type: string }>("one-click-model-info"));
if (!infos.success) {
throw infos.error;
}
const model = await modelSaber.getModelById(infos.data.id);
const model = await modelSaber.getModelById(infos.id);
if (!model) {
throw new Error("Failed to get model from ModelSaber");
@@ -63,7 +58,7 @@ export default function OneClickDownloadModel() {
});
promise.finally(() => {
windows.close("oneclick-download-model.html")
window.electron.window.close();
});
}, []);
@@ -9,7 +9,6 @@ import { IpcService } from "renderer/services/ipc.service";
import { NotificationService } from "renderer/services/notification.service";
import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service";
import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service";
import { WindowManagerService } from "renderer/services/window-manager.service";
import { map, filter } from "rxjs/operators";
import { BsvPlaylist } from "shared/models/maps/beat-saver.model";
import defaultImage from "../../../../assets/images/default-version-img.jpg";
@@ -22,7 +21,6 @@ export default function OneClickDownloadPlaylist() {
const bSaver = useService(BeatSaverService);
const playlistDownloader = useService(PlaylistDownloaderService);
const mapsContainer = useRef<HTMLDivElement>(null);
const windows = useService(WindowManagerService);
const notification = useService(NotificationService);
const [playlist, setPlaylist] = useState<BsvPlaylist>(null);
@@ -41,15 +39,11 @@ export default function OneClickDownloadPlaylist() {
useEffect(() => {
const promise = (async () => {
const infos = await ipc.send<{ bpListUrl: string; id: string }>("one-click-playlist-info");
const infos = await lastValueFrom(ipc.sendV2<{ bpListUrl: string; id: string }>("one-click-playlist-info"));
if (!infos.success) {
throw infos.error;
}
bSaver.getPlaylistDetailsById(infos.id).then(details => setPlaylist(details));
bSaver.getPlaylistDetailsById(infos.data.id).then(details => setPlaylist(details));
return lastValueFrom(playlistDownloader.oneClickInstallPlaylist(infos.data.bpListUrl));
return lastValueFrom(playlistDownloader.oneClickInstallPlaylist(infos.bpListUrl));
})();
promise.catch(() => {
@@ -61,8 +55,9 @@ export default function OneClickDownloadPlaylist() {
});
promise.finally(() => {
windows.close("oneclick-download-playlist.html");
window.electron.window.close();
});
}, []);
useEffect(() => {
+1 -1
View File
@@ -59,7 +59,7 @@ export default function ShortcutLaunch() {
});
sub.add(() => {
windows.close("shortcut-launch.html");
window.electron.window.close();
});
return () => sub.unsubscribe();