diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index a5c4fa7d..53a007da 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -421,10 +421,10 @@ } }, "shared-folder": { - "success": { - "yeet-mods-disabled": { - "title": "YeetMods disabled", - "msg": "To prevent mod removal, the value of the 'YeetMods' variable in the 'Beat Saber IPA.json' file has been disabled" + "info": { + "userdata-backup-created":{ + "title": "Backup created", + "msg": "Sharing the 'UserData' folder can generate errors, in case of problems unlink the folder to restore the backup" } } } diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 5733c666..2c377a00 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -420,10 +420,10 @@ } }, "shared-folder": { - "success": { - "yeet-mods-disabled": { - "title": "YeetMods desactivado", - "msg": "Para evitar la eliminación de mods, se ha desactivado el valor de la variable 'YeetMods' en el archivo 'Beat Saber IPA.json'" + "info": { + "userdata-backup-created":{ + "title": "Backup creado", + "msg": "Compartir la carpeta 'UserData' puede generar errores, en caso de problemas desvincular la carpeta para restaurar la copia de seguridad" } } } diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index d1214096..6af4f1cd 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -420,10 +420,10 @@ } }, "shared-folder":{ - "success": { - "yeet-mods-disabled":{ - "title": "YeetMods désactivé", - "msg": "Pour éviter la suppression des mods, la valeur de la variable 'YeetMods' dans le fichier 'Beat Saber IPA.json' a été désactivée" + "info": { + "userdata-backup-created":{ + "title": "Sauvegarde créée", + "msg": "Le partage du dossier 'UserData' peut générer des erreurs, en cas de soucis déliez le dossier pour restaurer la sauvegarde" } } } diff --git a/src/main/ipcs/bs-version-ipcs.ts b/src/main/ipcs/bs-version-ipcs.ts index 36b2f9cd..bbd00224 100644 --- a/src/main/ipcs/bs-version-ipcs.ts +++ b/src/main/ipcs/bs-version-ipcs.ts @@ -10,7 +10,7 @@ import { IpcService } from '../services/ipc.service'; import { from } from 'rxjs'; import path from 'path'; import { pathExist } from '../helpers/fs.helpers'; -import { FolderLinkerService } from '../services/folder-linker.service'; +import { FolderLinkerService, LinkOptions } from '../services/folder-linker.service'; import { LocalMapsManagerService } from '../services/additional-content/local-maps-manager.service'; import { readJSON, writeJSON } from 'fs-extra'; import log from "electron-log" @@ -85,16 +85,23 @@ ipc.on("get-linked-folders", async (req: IpcRequest, reply) => { reply(from(localVersions.getLinkedFolders(req.args))); }); -ipc.on("link-folder", async (req: IpcRequest<{ folder: string, keepContents?: boolean}>, reply) => { +ipc.on("link-folder", async (req: IpcRequest<{ folder: string, options?: LinkOptions}>, reply) => { + + req.args.options ??= {}; + const linker = FolderLinkerService.getInstance(); const relativeMapsFolder = path.join(LocalMapsManagerService.LEVELS_ROOT_FOLDER, LocalMapsManagerService.CUSTOM_LEVELS_FOLDER) if(req.args.folder.includes(relativeMapsFolder)){ - return reply(from(linker.linkFolder(req.args.folder, {keepContents: req.args.keepContents, intermediateFolder: LocalMapsManagerService.SHARED_MAPS_FOLDER}))); + return reply(from(linker.linkFolder(req.args.folder, {keepContents: req.args.options?.keepContents, intermediateFolder: LocalMapsManagerService.SHARED_MAPS_FOLDER}))); } - const res = from(linker.linkFolder(req.args.folder, {keepContents: true})); + if(req.args.folder.includes("UserData")){ + req.args.options = { ...req.args.options, backup: true }; + } + + const res = from(linker.linkFolder(req.args.folder, req.args.options)); const jsonIPAPath = path.join(req.args.folder, "Beat Saber IPA.json"); @@ -114,16 +121,23 @@ ipc.on("link-folder", async (req: IpcRequest<{ folder: string, keepContents?: bo }); -ipc.on("unlink-folder", async (req: IpcRequest<{folder: string, keepContents?: boolean}>, reply) => { +ipc.on("unlink-folder", async (req: IpcRequest<{ folder: string, options?: LinkOptions}>, reply) => { + + req.args.options ??= {}; + const linker = FolderLinkerService.getInstance(); const relativeMapsFolder = path.join(LocalMapsManagerService.LEVELS_ROOT_FOLDER, LocalMapsManagerService.CUSTOM_LEVELS_FOLDER) if(req.args.folder.includes(relativeMapsFolder)){ - return reply(from(linker.unlinkFolder(req.args.folder, {keepContents: req.args.keepContents, intermediateFolder: LocalMapsManagerService.SHARED_MAPS_FOLDER}))); + return reply(from(linker.unlinkFolder(req.args.folder, {...req.args.options, intermediateFolder: LocalMapsManagerService.SHARED_MAPS_FOLDER}))); } - reply(from(linker.unlinkFolder(req.args.folder, {keepContents: req.args.keepContents}))); + if(req.args.folder.includes("UserData")){ + req.args.options = { ...req.args.options, backup: true }; + } + + reply(from(linker.unlinkFolder(req.args.folder, req.args.options))); }); diff --git a/src/main/services/folder-linker.service.ts b/src/main/services/folder-linker.service.ts index dc47f121..81a3884d 100644 --- a/src/main/services/folder-linker.service.ts +++ b/src/main/services/folder-linker.service.ts @@ -30,6 +30,22 @@ export class FolderLinkerService { return path.join(this.sharedFolder, intermediateFolder ?? "", path.basename(folderPath)); } + private getBackupFolder(folderPath: string): string { + return folderPath + "_backup"; + } + + private async backupFolder(folderPath: string): Promise { + if(!await pathExist(folderPath)){ return; } + return copy(folderPath, this.getBackupFolder(folderPath), { overwrite: true, errorOnExist: false }); + } + + private async restoreFolder(folderPath: string): Promise { + if(!await pathExist( this.getBackupFolder(folderPath) )){ return; } + return copy(this.getBackupFolder(folderPath), folderPath, { overwrite: true, errorOnExist: false }).then(() => { + return deleteFolder(this.getBackupFolder(folderPath)); + }); + } + public async linkFolder(folderPath: string, options?: LinkOptions): Promise { if(await this.isFolderSymlink(folderPath)){ return; } @@ -39,10 +55,14 @@ export class FolderLinkerService { await ensureFolderExist(folderPath); await ensureFolderExist(sharedPath); + if(options?.backup === true){ + await this.backupFolder(folderPath); + } + if(options?.keepContents !== false){ await moveFolderContent(folderPath, sharedPath).toPromise(); } - + await deleteFolder(folderPath); return symlink(sharedPath, folderPath, "junction"); @@ -57,11 +77,15 @@ export class FolderLinkerService { await ensureFolderExist(folderPath); + if(options?.backup === true){ + return this.restoreFolder(folderPath); + } + if(options?.keepContents === false){ return; } await ensureFolderExist(sharedPath); - return copy(sharedPath, folderPath, { errorOnExist: false }); + return copy(sharedPath, folderPath, { errorOnExist: false, recursive: true }); } public async isFolderSymlink(folder: string): Promise { @@ -80,4 +104,5 @@ export class FolderLinkerService { export interface LinkOptions { keepContents?: boolean, intermediateFolder?: string, + backup?: boolean } \ No newline at end of file diff --git a/src/main/services/ipc.service.ts b/src/main/services/ipc.service.ts index 9bd832ab..36ee016b 100644 --- a/src/main/services/ipc.service.ts +++ b/src/main/services/ipc.service.ts @@ -1,10 +1,11 @@ -import { IpcResponse, IpcRequest } from "shared/models/ipc"; +import { IpcRequest } from "shared/models/ipc"; import { ipcMain } from "electron"; import { Observable } from "rxjs"; import { IpcChannel, IpcCompleteChannel, IpcErrorChannel } 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"; export class IpcService { @@ -47,6 +48,7 @@ export class IpcService { observable.subscribe(data => { this.send(channel, window, data); }, error => { + log.error(error); this.send(this.getErrorChannel(channel), window, error); }, () => { this.send(this.getCompleteChannel(channel), window); diff --git a/src/renderer/components/notification/notification-item.component.tsx b/src/renderer/components/notification/notification-item.component.tsx index 4f2234e9..3b901331 100644 --- a/src/renderer/components/notification/notification-item.component.tsx +++ b/src/renderer/components/notification/notification-item.component.tsx @@ -16,6 +16,7 @@ export const NotificationItem = forwardRef(({resolver, notification}: {resolver? const renderImage = (() => { if(notification.type === NotificationType.SUCCESS){ return BeatRunningImg; } if(notification.type === NotificationType.WARNING){ return BeatWaitingImg; } + if(notification.type === NotificationType.INFO){ return BeatWaitingImg; } if(notification.type === NotificationType.ERROR){ return BeatConflictImg; } return BeatImpatientImg; })(); @@ -23,6 +24,7 @@ export const NotificationItem = forwardRef(({resolver, notification}: {resolver? const renderNeonColors = (() => { if(notification.type === NotificationType.SUCCESS){ return "bg-green-400 shadow-green-400"; } if(notification.type === NotificationType.WARNING){ return "bg-yellow-400 shadow-yellow-400"; } + if(notification.type === NotificationType.INFO){ return "bg-blue-500 shadow-blue-500"; } if(notification.type === NotificationType.ERROR){ return "bg-red-500 shadow-red-500"; } return "bg-gray-800 shadow-gray-800 dark:bg-white dark:shadow-white"; })(); diff --git a/src/renderer/services/folder-linker.service.ts b/src/renderer/services/folder-linker.service.ts index 554d4316..a5a57fec 100644 --- a/src/renderer/services/folder-linker.service.ts +++ b/src/renderer/services/folder-linker.service.ts @@ -1,4 +1,3 @@ -import { of, timer } from "rxjs"; import { BehaviorSubject } from "rxjs"; import { map, distinctUntilChanged, filter } from "rxjs/operators"; import { from } from "rxjs"; @@ -6,6 +5,7 @@ import { Observable } from "rxjs"; import { IpcService } from "./ipc.service"; import { ProgressBarService } from "./progress-bar.service"; import { NotificationService } from "./notification.service"; +import { LinkOptions } from "main/services/folder-linker.service"; export class FolderLinkerService { @@ -86,20 +86,22 @@ export class FolderLinkerService { private specialFolderNotification(action: LinkAction): void{ if(action.type === LinkActionType.Link && action.folder.includes("UserData")){ - this.notification.notifySuccess({ - title: "notifications.shared-folder.success.yeet-mods-disabled.title", - desc: "notifications.shared-folder.success.yeet-mods-disabled.msg" - }) + this.notification.notifyInfo({ + title: "notifications.shared-folder.info.userdata-backup-created.title", + desc: "notifications.shared-folder.info.userdata-backup-created.msg", + duration: 7000 + }); + return; } } private doAction(action: LinkAction): Observable{ - return this.ipc.sendV2(action.type === LinkActionType.Link ? "link-folder" : "unlink-folder", { args: { folder: action.folder, keepContents: action.keepContents } }); + return this.ipc.sendV2(action.type === LinkActionType.Link ? "link-folder" : "unlink-folder", { args: { folder: action.folder, options: action.options } }); } - private addFolderToQueue(folder: string, type: LinkActionType, keepContents: boolean){ + private addFolderToQueue(folder: string, type: LinkActionType, options: LinkOptions){ if(this._queue$.value.some(action => action.folder === folder)){ return; } - this._queue$.next([...this._queue$.value, {folder, type, keepContents}]); + this._queue$.next([...this._queue$.value, {folder, type, options}]); } private removeFolderFromQueue(folder: string){ @@ -123,13 +125,14 @@ export class FolderLinkerService { return new Map(folders.map(folder => [folder, this.linkFolder(folder)])); } - public linkFolder(folder: string, keepContents = true): Observable { + public linkFolder(folder: string, options: LinkOptions = {}): Observable { + options.keepContents ??= true; const promise = new Promise(resolve => { this.onLinked(folder, resolve); this.onRemovedFromQueue(folder, resolve); }); - this.addFolderToQueue(folder, LinkActionType.Link, keepContents); + this.addFolderToQueue(folder, LinkActionType.Link, options); return from(promise); } @@ -137,13 +140,14 @@ export class FolderLinkerService { return new Map(folders.map(folder => [folder, this.unlinkFolder(folder)])); } - public unlinkFolder(folder: string, keepContents = true): Observable { + public unlinkFolder(folder: string, options: LinkOptions = {}): Observable { + options.keepContents ??= true; const promise = new Promise(resolve => { this.onUnlinked(folder, resolve); this.onRemovedFromQueue(folder, resolve); }); - this.addFolderToQueue(folder, LinkActionType.Unlink, keepContents); + this.addFolderToQueue(folder, LinkActionType.Unlink, options); return from(promise); } @@ -172,7 +176,7 @@ export class FolderLinkerService { export interface LinkAction { folder: string; type: LinkActionType; - keepContents?: boolean; + options?: LinkOptions; } export enum LinkActionType { diff --git a/src/renderer/services/maps-manager.service.ts b/src/renderer/services/maps-manager.service.ts index 5f46a1c9..cdb2580f 100644 --- a/src/renderer/services/maps-manager.service.ts +++ b/src/renderer/services/maps-manager.service.ts @@ -66,7 +66,7 @@ export class MapsManagerService { const versionMapsPath = await this.getVersionMapsPath(version); - return this.linker.linkFolder(versionMapsPath, !!modalRes.data).toPromise(); + return this.linker.linkFolder(versionMapsPath, {keepContents: !!modalRes.data}).toPromise(); } public async unlinkVersion(version: BSVersion): Promise{ @@ -77,7 +77,7 @@ export class MapsManagerService { const versionMapsPath = await this.getVersionMapsPath(version); - return this.linker.unlinkFolder(versionMapsPath, !!modalRes.data).toPromise(); + return this.linker.unlinkFolder(versionMapsPath, {keepContents: !!modalRes.data}).toPromise(); } public async deleteMaps(maps: BsmLocalMap[], version?: BSVersion): Promise{ diff --git a/src/renderer/services/notification.service.ts b/src/renderer/services/notification.service.ts index c63d3748..942a365a 100644 --- a/src/renderer/services/notification.service.ts +++ b/src/renderer/services/notification.service.ts @@ -53,6 +53,11 @@ export class NotificationService{ return this.notify(notification); } + public notifyInfo(notification: Notification): Promise{ + notification.type = NotificationType.INFO; + return this.notify(notification); + } + public notifySystem(options: SystemNotificationOptions){ this.ipc.sendLazy("notify-system", {args: options}); } @@ -71,6 +76,7 @@ export enum NotificationType { SUCCESS = 0, WARNING = 1, ERROR = 2, + INFO = 3, } export interface NotificationAction {