mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[bugfix-203] move install folder more resilant + handle symlinks
This commit is contained in:
@@ -269,12 +269,20 @@
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Übertragung läuft",
|
||||
"transfer-finished": "Transfer abgeschlossen"
|
||||
},
|
||||
"descs": {
|
||||
"transfer-started": "Die Übertragung hat begonnen und kann je nach Konfiguration mehrere Minuten dauern."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Transfer fehlgeschlagen 😕"
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "Der Zielordner darf kein Unterordner des Quellordners sein.",
|
||||
"restore-linked-folders": "Beim Wiederherstellen von freigegebenen Ordnern ist ein Fehler aufgetreten. Sie können sie dennoch manuell über das Menü 'Freigegebene Ordner' auf der Versionsseite wiederherstellen."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -269,12 +269,20 @@
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Transfer in progress",
|
||||
"transfer-finished": "Transfer complete"
|
||||
},
|
||||
"descs": {
|
||||
"transfer-started": "The transfer has started and may take several minutes depending on your configuration."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Transfer failed 😕"
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "The destination folder cannot be a subfolder of the source folder.",
|
||||
"restore-linked-folders": "An error occurred while restoring shared folders. You can still manually restore them via the 'Shared Folders' menu on the versions page."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -268,12 +268,20 @@
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Transferencia en curso",
|
||||
"transfer-finished": "Transferencia completa"
|
||||
},
|
||||
"descs": {
|
||||
"transfer-started": "La transferencia ha comenzado y puede tomar varios minutos dependiendo de su configuración."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Transferencia fallida 😕"
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "La carpeta de destino no puede ser una subcarpeta de la carpeta de origen.",
|
||||
"restore-linked-folders": "Se produjo un error al restaurar las carpetas compartidas. Aún puede restaurarlas manualmente a través del menú 'Carpetas compartidas' en la página de versiones."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -268,13 +268,21 @@
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Transfert en cours",
|
||||
"transfer-finished": "Transfert terminé 👌"
|
||||
},
|
||||
"descs":{
|
||||
"transfer-started": "Le transfert a commencé, il peut durer plusieurs minutes selon votre configuration."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Le transfert a échoué 😕"
|
||||
}
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "Le dossier de destination ne peut pas être un sous-dossier du dossier source.",
|
||||
"restore-linked-folders": "Une erreur s'est produite lors de la restauration des dossiers partagés. Vous pouvez toujours les restaurer manuellement via le menu 'Dossiers partagés' sur la page des versions."
|
||||
}
|
||||
}
|
||||
},
|
||||
"steam": {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { copyFile, ensureDir, move, symlink } from "fs-extra";
|
||||
import { CopyOptions, copy, ensureDir, move, symlink } from "fs-extra";
|
||||
import { access, mkdir, rm, readdir, unlink, lstat, readlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { Observable } from "rxjs";
|
||||
import log from "electron-log"
|
||||
import { BsmException } from "shared/models/bsm-exception.model";
|
||||
|
||||
export async function pathExist(path: string): Promise<boolean> {
|
||||
try{
|
||||
@@ -29,7 +30,7 @@ export async function unlinkPath(path: string): Promise<void>{
|
||||
return unlink(path);
|
||||
}
|
||||
|
||||
export async function getFoldersInFolder(folderPath: string): Promise<string[]> {
|
||||
export async function getFoldersInFolder(folderPath: string, opts?: {ignoreSymlinkTargetError?: boolean}): Promise<string[]> {
|
||||
if(!(await pathExist(folderPath))){ return []; }
|
||||
|
||||
const files = await readdir(folderPath, {withFileTypes: true});
|
||||
@@ -40,7 +41,10 @@ export async function getFoldersInFolder(folderPath: string): Promise<string[]>
|
||||
try{
|
||||
const targetPath = await readlink(path.join(folderPath, file.name));
|
||||
return (await lstat(targetPath)).isDirectory() ? path.join(folderPath, file.name) : undefined;
|
||||
}catch(e){
|
||||
}catch(e: any){
|
||||
if(e.code === "ENOENT" && opts?.ignoreSymlinkTargetError === true && !path.extname(file.name)){
|
||||
return path.join(folderPath, file.name);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
@@ -81,23 +85,44 @@ export function moveFolderContent(src: string, dest: string): Observable<Progres
|
||||
});
|
||||
}
|
||||
|
||||
export async function copyDirectoryWithJunctions(source: string, destination: string): Promise<void> {
|
||||
|
||||
await ensureDir(destination);
|
||||
const items = await readdir(source);
|
||||
export function isSubdirectory(parent: string, child: string): boolean {
|
||||
const parentNormalized = path.resolve(parent);
|
||||
const childNormalized = path.resolve(child);
|
||||
|
||||
if (parentNormalized === childNormalized) { return false; }
|
||||
|
||||
const relativePath = path.relative(parentNormalized, childNormalized);
|
||||
|
||||
if (path.parse(parentNormalized).root !== path.parse(childNormalized).root) { return false; }
|
||||
|
||||
return relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
||||
}
|
||||
|
||||
export async function copyDirectoryWithJunctions(src: string, dest: string, options?: CopyOptions): Promise<void> {
|
||||
|
||||
if(isSubdirectory(src, dest)){
|
||||
throw {message: `Cannot copy directory '${src}' into itself '${dest}'.`, code: "COPY_TO_SUBPATH"} as BsmException;
|
||||
}
|
||||
|
||||
await ensureDir(dest);
|
||||
const items = await readdir(src, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
const sourcePath = path.join(source, item);
|
||||
const destinationPath = path.join(destination, item);
|
||||
const stats = await lstat(sourcePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await copyDirectoryWithJunctions(sourcePath, destinationPath);
|
||||
} else if (stats.isFile()) {
|
||||
await copyFile(sourcePath, destinationPath);
|
||||
} else if (stats.isSymbolicLink()) {
|
||||
const sourcePath = path.join(src, item.name);
|
||||
const destinationPath = path.join(dest, item.name);
|
||||
|
||||
if (item.isDirectory()) {
|
||||
await copyDirectoryWithJunctions(sourcePath, destinationPath, options);
|
||||
} else if (item.isFile()) {
|
||||
await copy(sourcePath, destinationPath, options);
|
||||
} else if (item.isSymbolicLink()) {
|
||||
if(options?.overwrite){
|
||||
await unlinkPath(destinationPath);
|
||||
}
|
||||
const symlinkTarget = await readlink(sourcePath);
|
||||
await symlink(symlinkTarget, destinationPath, "junction");
|
||||
const relativePath = path.relative(src, symlinkTarget);
|
||||
const newTarget = path.join(dest, relativePath);
|
||||
await symlink(newTarget, destinationPath, 'junction');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,4 +151,9 @@ ipc.on("unlink-folder", async (req: IpcRequest<{ folder: string, options?: LinkO
|
||||
|
||||
});
|
||||
|
||||
ipc.on("relink-all-versions-folders", async (req: IpcRequest<void>, reply) => {
|
||||
const versionLinker = VersionFolderLinkerService.getInstance();
|
||||
reply(from(versionLinker.relinkAllVersionsFolders()));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@ export class BSLocalVersionService{
|
||||
public async getVersionPath(version: BSVersion): Promise<string>{
|
||||
if(version.steam){ return this.steamService.getGameFolder(BS_APP_ID, "Beat Saber") }
|
||||
if(version.oculus){ return this.oculusService.getGameFolder(OCULUS_BS_DIR); }
|
||||
console.log("getVersionPath", this.getVersionFolder(version), version);
|
||||
return path.join(
|
||||
this.installLocationService.versionsDirectory,
|
||||
this.getVersionFolder(version)
|
||||
@@ -184,7 +183,7 @@ export class BSLocalVersionService{
|
||||
}
|
||||
|
||||
public async editVersion(version: BSVersion, name: string, color: string): Promise<BSVersion>{
|
||||
if(version.steam || version.oculus){ throw {title: "CantEditSteam", msg: "CantEditSteam"} as BsmException; }
|
||||
if(version.steam || version.oculus){ throw {title: "CantEditSteam", message: "CantEditSteam"} as BsmException; }
|
||||
const oldPath = await this.getVersionPath(version);
|
||||
const editedVersion: BSVersion = version.BSVersion === name
|
||||
? {...version, name: undefined, color}
|
||||
@@ -205,7 +204,7 @@ export class BSLocalVersionService{
|
||||
return editedVersion;
|
||||
}).catch((err: Error) => {
|
||||
log.error("edit version error", err, version, name, color);
|
||||
throw {title: "CantRename", error: err} as BsmException;
|
||||
throw {title: "CantRename", ...err} as BsmException;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,7 +227,7 @@ export class BSLocalVersionService{
|
||||
return cloneVersion;
|
||||
}).catch((err: Error) => {
|
||||
log.error("clone version error", err, version, name, color);
|
||||
throw {title: "CantClone", error: err} as BsmException
|
||||
throw {title: "CantClone", ...err} as BsmException
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import log from "electron-log";
|
||||
import { deleteFolder, ensureFolderExist, moveFolderContent, pathExist, unlinkPath } from "../helpers/fs.helpers";
|
||||
import { lstat, symlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { copy } from "fs-extra";
|
||||
import { copy, readlink } from "fs-extra";
|
||||
|
||||
export class FolderLinkerService {
|
||||
|
||||
@@ -18,12 +18,12 @@ export class FolderLinkerService {
|
||||
|
||||
private readonly installLocationService = InstallationLocationService.getInstance();
|
||||
|
||||
private readonly sharedFolder: string;
|
||||
|
||||
private constructor(){
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
}
|
||||
|
||||
this.sharedFolder = this.installLocationService.sharedContentPath;
|
||||
private get sharedFolder(): string{
|
||||
return this.installLocationService.sharedContentPath;
|
||||
}
|
||||
|
||||
private getSharedFolder(folderPath: string, intermediateFolder?: string): string {
|
||||
@@ -48,10 +48,15 @@ export class FolderLinkerService {
|
||||
|
||||
public async linkFolder(folderPath: string, options?: LinkOptions): Promise<void> {
|
||||
|
||||
if(await this.isFolderSymlink(folderPath)){ return; }
|
||||
|
||||
const sharedPath = this.getSharedFolder(folderPath, options?.intermediateFolder);
|
||||
|
||||
if(await this.isFolderSymlink(folderPath)){
|
||||
const isTargetedToSharedPath = await readlink(folderPath).then(target => target === sharedPath).catch(() => false);
|
||||
if(isTargetedToSharedPath){ return; }
|
||||
await unlinkPath(folderPath);
|
||||
return symlink(sharedPath, folderPath, "junction");
|
||||
}
|
||||
|
||||
await ensureFolderExist(sharedPath);
|
||||
|
||||
if(options?.backup === true){
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import path from "path";
|
||||
import fs from 'fs-extra';
|
||||
import log from "electron-log";
|
||||
import { app } from "electron";
|
||||
import { BsmException } from "shared/models/bsm-exception.model";
|
||||
import ElectronStore from "electron-store";
|
||||
import { ensureFolderExist, pathExist } from "../helpers/fs.helpers";
|
||||
import { copyDirectoryWithJunctions, deleteFolder, ensureFolderExist } from "../helpers/fs.helpers";
|
||||
|
||||
export class InstallationLocationService {
|
||||
|
||||
@@ -48,15 +46,24 @@ export class InstallationLocationService {
|
||||
const oldDir = this.installationDirectory;
|
||||
const newDest = path.join(newDir, this.INSTALLATION_FOLDER);
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
if(!(await pathExist(oldDir))){ ensureFolderExist(oldDir); }
|
||||
fs.move(oldDir, newDest, { overwrite: true }).then(() => {
|
||||
|
||||
await ensureFolderExist(oldDir);
|
||||
|
||||
try{
|
||||
await copyDirectoryWithJunctions(oldDir, newDest, {overwrite: true});
|
||||
|
||||
this._installationDirectory = newDir;
|
||||
this.installPathConfig.set(this.STORE_INSTALLATION_PATH_KEY, newDir);
|
||||
resolve(this.installationDirectory);
|
||||
}).catch((err: Error) => {
|
||||
reject({title: "CantMoveFolder", error: err} as BsmException);
|
||||
|
||||
deleteFolder(oldDir);
|
||||
|
||||
return resolve(this.installationDirectory);
|
||||
}
|
||||
catch(err){
|
||||
log.error(err);
|
||||
})
|
||||
reject(err);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export class VersionFolderLinkerService {
|
||||
for(const versionPath of versionPaths){
|
||||
const folderPath = this.relativeToFullPath(versionPath, relativeFolder);
|
||||
if(folderPath === ignorePath){ continue; }
|
||||
if(await this.folderLinker.isFolderSymlink(folderPath)){ console.log(folderPath); return true; }
|
||||
if(await this.folderLinker.isFolderSymlink(folderPath)){ return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -58,7 +58,7 @@ export class VersionFolderLinkerService {
|
||||
action.options = this.specialFolderOption(action.relativeFolder, action.options);
|
||||
const versionPath = await this.localVersion.getVersionPath(action.version);
|
||||
const folderPath = this.relativeToFullPath(versionPath, action.relativeFolder);
|
||||
return this.folderLinker.linkFolder(folderPath, action.options).catch(() => false).then(() => true)
|
||||
return this.folderLinker.linkFolder(folderPath, action.options).catch((err) => {console.log(err); return false}).then(() => true)
|
||||
}
|
||||
|
||||
public async unlinkVersionFolder(action: VersionUnlinkFolderAction): Promise<boolean>{
|
||||
@@ -85,23 +85,34 @@ export class VersionFolderLinkerService {
|
||||
return this.folderLinker.isFolderSymlink(folderPath);
|
||||
}
|
||||
|
||||
public async getLinkedFolders(version: BSVersion, options?: { relative?: boolean }): Promise<string[]>{
|
||||
public async getLinkedFolders(version: BSVersion, options?: { relative?: boolean, ignoreSymlinkTargetError?: boolean }): Promise<string[]>{
|
||||
const versionPath = await this.localVersion.getVersionPath(version);
|
||||
const [rootFolders, beatSaberDataFolders] = await Promise.all([
|
||||
getFoldersInFolder(versionPath),
|
||||
getFoldersInFolder(path.join(versionPath, "Beat Saber_Data"))
|
||||
getFoldersInFolder(versionPath, {ignoreSymlinkTargetError: options?.ignoreSymlinkTargetError}),
|
||||
getFoldersInFolder(path.join(versionPath, "Beat Saber_Data"), {ignoreSymlinkTargetError: options?.ignoreSymlinkTargetError})
|
||||
]);
|
||||
|
||||
const linkedFolder = await Promise.all([...rootFolders, ...beatSaberDataFolders].map(async folder => {
|
||||
const linkedFolders = await Promise.all([...rootFolders, ...beatSaberDataFolders].map(async folder => {
|
||||
if(!(await this.folderLinker.isFolderSymlink(folder))){ return null; }
|
||||
return folder;
|
||||
}));
|
||||
|
||||
if(options?.relative){
|
||||
return linkedFolder.filter(folder => folder).map(folder => path.relative(versionPath, folder));
|
||||
return linkedFolders.filter(folder => folder).map(folder => path.relative(versionPath, folder));
|
||||
}
|
||||
|
||||
return linkedFolder.filter(folder => folder);
|
||||
return linkedFolders.filter(folder => folder);
|
||||
}
|
||||
|
||||
public async relinkAllVersionsFolders(): Promise<void>{
|
||||
const versions = await this.localVersion.getInstalledVersions();
|
||||
|
||||
for(const version of versions){
|
||||
const linkedFolders = await this.getLinkedFolders(version, { relative: true, ignoreSymlinkTargetError: true });
|
||||
console.log(linkedFolders);
|
||||
const actions = linkedFolders.map(folder => ({ type: "link", version, relativeFolder: folder } as VersionLinkFolderAction));
|
||||
await Promise.all(actions.map(action => this.doAction(action)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,8 +64,6 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
console.log(linked);
|
||||
|
||||
if(isVisible){
|
||||
loadMaps();
|
||||
mapsDownloader.addOnMapDownloadedListener((map, targerVersion) => {
|
||||
|
||||
@@ -24,7 +24,7 @@ export function BsmProgressBar() {
|
||||
|
||||
return (
|
||||
<AnimatePresence> { visible &&
|
||||
<motion.div initial={{y: "120%"}} animate={{y:"0%"}} exit={{y:"120%"}} className="w-full absolute h-14 flex justify-center items-center bottom-2 pointer-events-none" style={style}>
|
||||
<motion.div initial={{y: "120%"}} animate={{y:"0%"}} exit={{y:"120%"}} className="w-full absolute h-14 flex justify-center items-center bottom-2 pointer-events-none z-10" style={style}>
|
||||
<div className={`flex items-center content-center justify-center bottom-9 z-10 rounded-full bg-light-main-color-2 dark:bg-main-color-2 shadow-center shadow-black transition-all duration-300 ${!progressValue && "h-14 w-14 "} ${!!progressValue && "h-5 w-3/4 p-[6px]"}`}>
|
||||
{ !!progressValue && (
|
||||
<div className="relative h-full w-full rounded-full bg-black">
|
||||
|
||||
@@ -30,6 +30,7 @@ import { MapsManagerService } from "renderer/services/maps-manager.service";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { ModelsManagerService } from "renderer/services/models-manager.service";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service";
|
||||
|
||||
export function SettingsPage() {
|
||||
|
||||
@@ -45,6 +46,7 @@ export function SettingsPage() {
|
||||
const mapsManager = MapsManagerService.getInstance();
|
||||
const playlistsManager = PlaylistsManagerService.getInstance();
|
||||
const modelsManager = ModelsManagerService.getInstance();
|
||||
const versionLinker = VersionFolderLinkerService.getInstance();
|
||||
|
||||
const {firstColor, secondColor} = useThemeColor();
|
||||
|
||||
@@ -102,33 +104,48 @@ export function SettingsPage() {
|
||||
setLanguageSelected(languagesItems.find(l => l.value === i18nService.currentLanguage).id);
|
||||
}
|
||||
|
||||
const setDefaultInstallationFolder = () => {
|
||||
if(!progressBarService.require()){ return; }
|
||||
const setDefaultInstallationFolder = () => {
|
||||
if(!progressBarService.require()){ return; }
|
||||
|
||||
modalService.openModal(InstallationFolderModal).then(async res => {
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
modalService.openModal(InstallationFolderModal).then(async res => {
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const fileChooserRes = await ipcService.sendV2<{canceled: boolean, filePaths: string[]}>("choose-folder").toPromise();
|
||||
const fileChooserRes = await ipcService.sendV2<{canceled: boolean, filePaths: string[]}>("choose-folder").toPromise();
|
||||
|
||||
if(!fileChooserRes.canceled && fileChooserRes.filePaths?.length){
|
||||
progressBarService.showFake(.008);
|
||||
downloaderService.setInstallationFolder(fileChooserRes.filePaths[0]).then(res => {
|
||||
setTimeout(() => {
|
||||
progressBarService.complete();
|
||||
setTimeout(() => progressBarService.hide(true), 1000);
|
||||
}, 1000);
|
||||
if(res.success){
|
||||
setInstallationFolder(res.data);
|
||||
notificationService.notifySuccess({title: "notifications.settings.move-folder.success.titles.transfer-finished", duration: 3000});
|
||||
}
|
||||
else{
|
||||
notificationService.notifyError({title: "notifications.settings.move-folder.errors.titles.transfer-failed"});
|
||||
}
|
||||
});
|
||||
}
|
||||
if(!fileChooserRes.canceled && fileChooserRes.filePaths?.length){
|
||||
progressBarService.showFake(.008);
|
||||
|
||||
});
|
||||
}
|
||||
notificationService.notifySuccess({title: "notifications.settings.move-folder.success.titles.transfer-started", desc: "notifications.settings.move-folder.success.descs.transfer-started"});
|
||||
|
||||
downloaderService.setInstallationFolder(fileChooserRes.filePaths[0]).then(async res => {
|
||||
setTimeout(() => {
|
||||
progressBarService.complete();
|
||||
setTimeout(() => progressBarService.hide(true), 1000);
|
||||
}, 1000);
|
||||
|
||||
if(res.success){
|
||||
setInstallationFolder(res.data);
|
||||
|
||||
notificationService.notifySuccess({title: "notifications.settings.move-folder.success.titles.transfer-finished", duration: 3000});
|
||||
|
||||
versionLinker.relinkAllVersionsFolders().toPromise().catch(() => {
|
||||
notificationService.notifyError({title: "notifications.types.error", desc: "notifications.settings.move-folder.errors.descs.restore-linked-folders", duration: 15_000});
|
||||
});
|
||||
}
|
||||
else{
|
||||
|
||||
if(res?.error?.code === "COPY_TO_SUBPATH"){
|
||||
notificationService.notifyError({title: "notifications.settings.move-folder.errors.titles.transfer-failed", desc: "notifications.settings.move-folder.errors.descs.COPY_TO_SUBPATH", duration: 10_000});
|
||||
return;
|
||||
}
|
||||
|
||||
notificationService.notifyError({title: "notifications.settings.move-folder.errors.titles.transfer-failed"});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const toogleShowSupporters = () => {
|
||||
|
||||
@@ -84,7 +84,7 @@ export class BSVersionManagerService {
|
||||
if(!res.success){
|
||||
this.notificationService.notifyError({
|
||||
title: `notifications.custom-version.errors.titles.${res.error.title}`,
|
||||
...(res.error.msg && {desc: `notifications.custom-version.errors.msg.${res.error.msg}`})
|
||||
...(res.error.message && {desc: `notifications.custom-version.errors.msg.${res.error.message}`})
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export class BSVersionManagerService {
|
||||
if(!res.success){
|
||||
this.notificationService.notifyError({
|
||||
title: `notifications.custom-version.errors.titles.${res.error.title}`,
|
||||
...(res.error.msg && {desc: `notifications.custom-version.errors.msg.${res.error.msg}`})
|
||||
...(res.error.message && {desc: `notifications.custom-version.errors.msg.${res.error.message}`})
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ export class VersionFolderLinkerService{
|
||||
return this.ipcService.sendV2("get-linked-folders", { args: { version, options } });
|
||||
}
|
||||
|
||||
public relinkAllVersionsFolders(): Observable<void>{
|
||||
return this.ipcService.sendV2("relink-all-versions-folders");
|
||||
}
|
||||
|
||||
public get currentAction$(): Observable<VersionLinkerAction>{
|
||||
return this._queue$.pipe(map(actions => actions.at(0)), distinctUntilChanged());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface BsmException {
|
||||
title: string,
|
||||
msg?: string,
|
||||
error?: Error,
|
||||
}
|
||||
export interface BsmException extends Readonly<Omit<NodeJS.ErrnoException, "message">>{
|
||||
readonly title?: string,
|
||||
readonly message?: string,
|
||||
};
|
||||
Reference in New Issue
Block a user