mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[hotfix] move instead of copy if no other version use the linked folder
This commit is contained in:
@@ -5,6 +5,7 @@ import { ModalExitCode, ModalService } from './modale.service';
|
||||
import { NotificationService } from './notification.service';
|
||||
import { ProgressBarService } from './progress-bar.service';
|
||||
import { EditVersionModal } from 'renderer/components/modal/modal-types/edit-version-modal.component';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export class BSVersionManagerService {
|
||||
|
||||
@@ -113,4 +114,8 @@ export class BSVersionManagerService {
|
||||
})
|
||||
}
|
||||
|
||||
public getVersionPath(version: BSVersion): Observable<string>{
|
||||
return this.ipcService.sendV2("get-version-full-path", {args: version});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { map, distinctUntilChanged, filter } from "rxjs/operators";
|
||||
import { from } from "rxjs";
|
||||
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 {
|
||||
|
||||
private static instance: FolderLinkerService;
|
||||
|
||||
public static getInstance(): FolderLinkerService {
|
||||
if (!FolderLinkerService.instance) {
|
||||
FolderLinkerService.instance = new FolderLinkerService();
|
||||
}
|
||||
return FolderLinkerService.instance;
|
||||
}
|
||||
|
||||
private readonly ipc: IpcService;
|
||||
private readonly progress: ProgressBarService;
|
||||
private readonly notification: NotificationService;
|
||||
|
||||
private readonly onLinkedListeners = new Map<string, () => void>();
|
||||
private readonly onUnlinkedListeners = new Map<string, () => void>();
|
||||
private readonly onRemovedFromQueueListeners = new Map<string, () => void>();
|
||||
|
||||
private readonly publicOnLinkedListeners = new Set<(folder: string) => void>();
|
||||
private readonly publicOnUnlinkedListeners = new Set<(folder: string) => void>();
|
||||
|
||||
private readonly _currentAction$ = new BehaviorSubject<LinkAction>(null);
|
||||
private readonly _queue$ = new BehaviorSubject<LinkAction[]>([]);
|
||||
|
||||
private constructor() {
|
||||
this.ipc = IpcService.getInstance();
|
||||
this.progress = ProgressBarService.getInstance();
|
||||
this.notification = NotificationService.getInstance();
|
||||
|
||||
this.queue$.pipe(map(queue => !!queue.length), distinctUntilChanged(), filter(haveAction => haveAction)).subscribe(() => {
|
||||
this.startQueue();
|
||||
});
|
||||
}
|
||||
|
||||
private async startQueue(): Promise<void>{
|
||||
|
||||
while(this._queue$.value.at(0)){
|
||||
|
||||
const toDo = this._queue$.value.at(0);
|
||||
|
||||
let progressOpened = false;
|
||||
|
||||
if(!this.progress.isVisible){
|
||||
this.progress.showFake(.01, null, toDo.folder.split("\\").at(-1));
|
||||
progressOpened = true;
|
||||
}
|
||||
|
||||
this._currentAction$.next(toDo);
|
||||
await this.doAction(toDo).toPromise();
|
||||
|
||||
this.specialFolderNotification(toDo);
|
||||
|
||||
if(toDo.type === LinkActionType.Link){
|
||||
this.onLinkedListeners.get(toDo.folder)?.();
|
||||
this.publicOnLinkedListeners.forEach(listener => listener(toDo.folder));
|
||||
this.onLinkedListeners.delete(toDo.folder);
|
||||
}
|
||||
else{
|
||||
this.onUnlinkedListeners.get(toDo.folder)?.();
|
||||
this.publicOnUnlinkedListeners.forEach(listener => listener(toDo.folder));
|
||||
this.onUnlinkedListeners.delete(toDo.folder);
|
||||
}
|
||||
|
||||
const newArr = [...this._queue$.value];
|
||||
newArr.shift();
|
||||
this._queue$.next(newArr);
|
||||
|
||||
if(progressOpened){
|
||||
this.progress.hide(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._currentAction$.next(null);
|
||||
}
|
||||
|
||||
private specialFolderNotification(action: LinkAction): void{
|
||||
if(action.type === LinkActionType.Link && action.folder.includes("UserData")){
|
||||
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<void>{
|
||||
return this.ipc.sendV2<void, {folder: string, options?: LinkOptions}>(action.type === LinkActionType.Link ? "link-folder" : "unlink-folder", { args: { folder: action.folder, options: action.options } });
|
||||
}
|
||||
|
||||
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, options}]);
|
||||
}
|
||||
|
||||
private removeFolderFromQueue(folder: string){
|
||||
const queue = this._queue$.value;
|
||||
const index = queue.findIndex(action => action.folder === folder);
|
||||
if(index === -1){ return; }
|
||||
this.onRemovedFromQueueListeners.get(folder)?.();
|
||||
queue.splice(index, 1);
|
||||
this._queue$.next(queue);
|
||||
}
|
||||
|
||||
private onLinked(folder: string, callback: () => void){ this.onLinkedListeners.set(folder, callback); }
|
||||
private onUnlinked(folder: string, callback: () => void){ this.onUnlinkedListeners.set(folder, callback); }
|
||||
private onRemovedFromQueue(folder: string, callback: () => void){ this.onRemovedFromQueueListeners.set(folder, callback); }
|
||||
|
||||
public isFolderLinked(path: string): Observable<boolean> {
|
||||
return this.ipc.sendV2<boolean>("is-folder-symlink", {args: path});
|
||||
}
|
||||
|
||||
public linkFolders(...folders: string[]): Map<string, Observable<void>> {
|
||||
return new Map(folders.map(folder => [folder, this.linkFolder(folder)]));
|
||||
}
|
||||
|
||||
public linkFolder(folder: string, options: LinkOptions = {}): Observable<void> {
|
||||
options.keepContents ??= true;
|
||||
const promise = new Promise<void>(resolve => {
|
||||
this.onLinked(folder, resolve);
|
||||
this.onRemovedFromQueue(folder, resolve);
|
||||
});
|
||||
|
||||
this.addFolderToQueue(folder, LinkActionType.Link, options);
|
||||
return from(promise);
|
||||
}
|
||||
|
||||
public unlinkFolders(...folders: string[]): Map<string, Observable<void>> {
|
||||
return new Map(folders.map(folder => [folder, this.unlinkFolder(folder)]));
|
||||
}
|
||||
|
||||
public unlinkFolder(folder: string, options: LinkOptions = {}): Observable<void> {
|
||||
options.keepContents ??= true;
|
||||
const promise = new Promise<void>(resolve => {
|
||||
this.onUnlinked(folder, resolve);
|
||||
this.onRemovedFromQueue(folder, resolve);
|
||||
});
|
||||
|
||||
this.addFolderToQueue(folder, LinkActionType.Unlink, options);
|
||||
return from(promise);
|
||||
}
|
||||
|
||||
public cancelFolder(folder: string): void {
|
||||
this.removeFolderFromQueue(folder);
|
||||
}
|
||||
|
||||
public get queue$(): Observable<LinkAction[]> { return this._queue$.asObservable(); }
|
||||
public get currentAction$(): Observable<LinkAction> { return this._currentAction$.asObservable(); }
|
||||
|
||||
public isPending(folder: string): Observable<boolean> {
|
||||
return this.queue$.pipe(map(queue => queue.some(action => action.folder === folder), distinctUntilChanged()));
|
||||
}
|
||||
|
||||
public isProcessing(folder: string): Observable<boolean> {
|
||||
return this.currentAction$.pipe(distinctUntilChanged(), map(action => action?.folder === folder));
|
||||
}
|
||||
|
||||
public onFolderLinked(callback: (folder: string) => void): void { this.publicOnLinkedListeners.add(callback); }
|
||||
public removeOnFolderLinked(callback: (folder: string) => void): void { this.publicOnLinkedListeners.delete(callback); }
|
||||
public onFolderUnlinked(callback: (folder: string) => void): void { this.publicOnUnlinkedListeners.add(callback); }
|
||||
public removeOnFolderUnlinked(callback: (folder: string) => void): void { this.publicOnUnlinkedListeners.delete(callback); }
|
||||
|
||||
}
|
||||
|
||||
export interface LinkAction {
|
||||
folder: string;
|
||||
type: LinkActionType;
|
||||
options?: LinkOptions;
|
||||
}
|
||||
|
||||
export enum LinkActionType {
|
||||
Link,
|
||||
Unlink
|
||||
}
|
||||
@@ -13,10 +13,13 @@ import { ConfigurationService } from "./configuration.service";
|
||||
import { ArchiveProgress } from "shared/models/archive.interface";
|
||||
import { map, last, catchError, distinctUntilChanged, mergeMap } from "rxjs/operators";
|
||||
import { ProgressionInterface } from "shared/models/progress-bar";
|
||||
import { FolderLinkerService } from "./folder-linker.service";
|
||||
import { VersionFolderLinkerService, VersionLinkerActionType } from "./version-folder-linker.service";
|
||||
import equal from "fast-deep-equal";
|
||||
|
||||
export class MapsManagerService {
|
||||
|
||||
|
||||
|
||||
private static instance: MapsManagerService;
|
||||
|
||||
public static getInstance(): MapsManagerService{
|
||||
@@ -25,13 +28,14 @@ export class MapsManagerService {
|
||||
}
|
||||
|
||||
public static readonly REMEMBER_CHOICE_DELETE_MAP_KEY = "not-confirm-delete-map"
|
||||
public static readonly RELATIVE_MAPS_FOLDER = "Beat Saber_Data\\CustomLevels";
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly modal: ModalService;
|
||||
private readonly progressBar: ProgressBarService;
|
||||
private readonly notifications: NotificationService;
|
||||
private readonly config: ConfigurationService;
|
||||
private readonly linker: FolderLinkerService;
|
||||
private readonly linker: VersionFolderLinkerService;
|
||||
|
||||
private readonly lastLinkedVersion$: Subject<BSVersion> = new Subject();
|
||||
private readonly lastUnlinkedVersion$: Subject<BSVersion> = new Subject();
|
||||
@@ -42,11 +46,7 @@ export class MapsManagerService {
|
||||
this.progressBar = ProgressBarService.getInstance();
|
||||
this.notifications = NotificationService.getInstance();
|
||||
this.config = ConfigurationService.getInstance();
|
||||
this.linker = FolderLinkerService.getInstance();
|
||||
}
|
||||
|
||||
private async getVersionMapsPath(version: BSVersion): Promise<string>{
|
||||
return this.ipcService.sendV2<string>("get-version-maps-path", {args: version}).toPromise();
|
||||
this.linker = VersionFolderLinkerService.getInstance();
|
||||
}
|
||||
|
||||
public getMaps(version?: BSVersion): Observable<BsmLocalMapsProgress>{
|
||||
@@ -54,30 +54,35 @@ export class MapsManagerService {
|
||||
}
|
||||
|
||||
public async versionHaveMapsLinked(version: BSVersion): Promise<boolean>{
|
||||
const versionMapsPath = await this.getVersionMapsPath(version);
|
||||
return this.linker.isFolderLinked(versionMapsPath).toPromise();
|
||||
return this.linker.isVersionFolderLinked(version, MapsManagerService.RELATIVE_MAPS_FOLDER).toPromise();
|
||||
}
|
||||
|
||||
public async linkVersion(version: BSVersion): Promise<void>{
|
||||
public async linkVersion(version: BSVersion): Promise<boolean>{
|
||||
|
||||
const modalRes = await this.modal.openModal(LinkMapsModal);
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return Promise.resolve(false); }
|
||||
|
||||
const versionMapsPath = await this.getVersionMapsPath(version);
|
||||
|
||||
return this.linker.linkFolder(versionMapsPath, {keepContents: !!modalRes.data}).toPromise();
|
||||
return this.linker.linkVersionFolder({
|
||||
version,
|
||||
type: VersionLinkerActionType.Link,
|
||||
relativeFolder: MapsManagerService.RELATIVE_MAPS_FOLDER,
|
||||
options: { keepContents: !!modalRes.data }
|
||||
});
|
||||
}
|
||||
|
||||
public async unlinkVersion(version: BSVersion): Promise<void>{
|
||||
public async unlinkVersion(version: BSVersion): Promise<boolean>{
|
||||
|
||||
const modalRes = await this.modal.openModal(UnlinkMapsModal);
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return Promise.resolve(false); }
|
||||
|
||||
const versionMapsPath = await this.getVersionMapsPath(version);
|
||||
|
||||
return this.linker.unlinkFolder(versionMapsPath, {keepContents: !!modalRes.data}).toPromise();
|
||||
return this.linker.unlinkVersionFolder({
|
||||
version,
|
||||
type: VersionLinkerActionType.Unlink,
|
||||
relativeFolder: MapsManagerService.RELATIVE_MAPS_FOLDER,
|
||||
options: { keepContents: !!modalRes.data }
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteMaps(maps: BsmLocalMap[], version?: BSVersion): Promise<boolean>{
|
||||
@@ -155,8 +160,7 @@ export class MapsManagerService {
|
||||
|
||||
public $mapsLinkingPending(version: BSVersion): Observable<boolean>{
|
||||
return this.linker.queue$.pipe(mergeMap(async queue => {
|
||||
const versionMapsPath = await this.getVersionMapsPath(version);
|
||||
return queue.some(q => q.folder.includes(versionMapsPath));
|
||||
return queue.some(q => q.relativeFolder.includes(MapsManagerService.RELATIVE_MAPS_FOLDER) && equal(q.version, version));
|
||||
}), distinctUntilChanged());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
import { LinkOptions, UnlinkOptions } from "main/services/folder-linker.service";
|
||||
import { map, distinctUntilChanged, filter } from "rxjs/operators";
|
||||
import { BehaviorSubject, Observable } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
|
||||
export class VersionFolderLinkerService{
|
||||
|
||||
private static instance: VersionFolderLinkerService;
|
||||
|
||||
public static getInstance(): VersionFolderLinkerService{
|
||||
if(!VersionFolderLinkerService.instance){ VersionFolderLinkerService.instance = new VersionFolderLinkerService() }
|
||||
return VersionFolderLinkerService.instance;
|
||||
}
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly progress: ProgressBarService;
|
||||
|
||||
private readonly _queue$ = new BehaviorSubject<VersionLinkerAction[]>([]);
|
||||
|
||||
private readonly linkListeners = new Set<VersionLinkerActionListener>();
|
||||
private readonly unlinkListeners = new Set<VersionLinkerActionListener>();
|
||||
|
||||
private constructor(){
|
||||
this.ipcService = IpcService.getInstance();
|
||||
this.progress = ProgressBarService.getInstance();
|
||||
|
||||
this.currentAction$.pipe(filter(action => !!action)).subscribe(action => this.processAction(action));
|
||||
}
|
||||
|
||||
private async processAction(action: VersionLinkerAction){
|
||||
|
||||
let progressOpened = false;
|
||||
|
||||
if(!this.progress.isVisible){
|
||||
this.progress.showFake(.01, null, action.relativeFolder.split("\\").at(-1));
|
||||
progressOpened = true;
|
||||
}
|
||||
|
||||
const linked = await this.doAction(action).toPromise();
|
||||
|
||||
// Spécial notification
|
||||
|
||||
if(action.type === VersionLinkerActionType.Link){
|
||||
this.linkListeners.forEach(listener => listener(action, linked));
|
||||
}
|
||||
else{
|
||||
this.unlinkListeners.forEach(listener => listener(action, linked));
|
||||
}
|
||||
|
||||
if(progressOpened){
|
||||
this.progress.hide(true);
|
||||
}
|
||||
|
||||
const newArr = [...this._queue$.value];
|
||||
newArr.shift();
|
||||
this._queue$.next(newArr);
|
||||
|
||||
}
|
||||
|
||||
private doAction(action: VersionLinkerAction): Observable<boolean>{
|
||||
return this.ipcService.sendV2<boolean, VersionLinkerAction>("link-version-folder-action", { args: action });
|
||||
}
|
||||
|
||||
public linkVersionFolder(action: VersionLinkFolderAction): Promise<boolean>{
|
||||
const promise = new Promise<boolean>((resolve) => {
|
||||
|
||||
const callBack: VersionLinkerActionListener = (performedAction, linked) => {
|
||||
if(performedAction !== action){ return; }
|
||||
this.removeVersionFolderLinkedListener(callBack);
|
||||
resolve(linked);
|
||||
}
|
||||
|
||||
this.onVersionFolderLinked(callBack);
|
||||
});
|
||||
|
||||
this._queue$.next([...this._queue$.value, action]);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public unlinkVersionFolder(action: VersionUnlinkFolderAction): Promise<boolean>{
|
||||
const promise = new Promise<boolean>((resolve) => {
|
||||
|
||||
const callBack: VersionLinkerActionListener = (performedAction, linked) => {
|
||||
if(performedAction !== action){ return; }
|
||||
this.removeVersionFolderUnlinkedListener(callBack);
|
||||
resolve(linked);
|
||||
}
|
||||
|
||||
this.onVersionFolderUnlinked(callBack);
|
||||
});
|
||||
|
||||
this._queue$.next([...this._queue$.value, action]);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public onVersionFolderLinked(listener: VersionLinkerActionListener): void{
|
||||
this.linkListeners.add(listener);
|
||||
}
|
||||
|
||||
public removeVersionFolderLinkedListener(listener: VersionLinkerActionListener): void{
|
||||
this.linkListeners.delete(listener);
|
||||
}
|
||||
|
||||
public onVersionFolderUnlinked(listener: VersionLinkerActionListener): void{
|
||||
this.unlinkListeners.add(listener);
|
||||
}
|
||||
|
||||
public removeVersionFolderUnlinkedListener(listener: VersionLinkerActionListener): void{
|
||||
this.unlinkListeners.delete(listener);
|
||||
}
|
||||
|
||||
public cancelAction(version: BSVersion, relativeFolder: string): void{
|
||||
this._queue$.next(this._queue$.value.filter(a => a.version !== version || a.relativeFolder !== relativeFolder));
|
||||
}
|
||||
|
||||
public isVersionFolderLinked(version: BSVersion, relativeFolder: string): Observable<boolean>{
|
||||
return this.ipcService.sendV2("is-version-folder-linked", { args: { version, relativeFolder } });
|
||||
}
|
||||
|
||||
public isPending(version: BSVersion, relativeFolder: string): Observable<boolean> {
|
||||
return this.queue$.pipe(map(queue => queue.length > 0 && queue.some(action => action.version === version && action.relativeFolder === relativeFolder), distinctUntilChanged()));
|
||||
}
|
||||
|
||||
public isProcessing(version: BSVersion, relativeFolder: string): Observable<boolean> {
|
||||
return this.currentAction$.pipe(map(action => ( action && action.version === version && action.relativeFolder === relativeFolder)));
|
||||
}
|
||||
|
||||
public getLinkedFolders(version: BSVersion, options?: { relative?: boolean }): Observable<string[]>{
|
||||
return this.ipcService.sendV2("get-linked-folders", { args: { version, options } });
|
||||
}
|
||||
|
||||
public get currentAction$(): Observable<VersionLinkerAction>{
|
||||
return this._queue$.pipe(map(actions => actions.at(-1)), distinctUntilChanged());
|
||||
}
|
||||
|
||||
public get queue$(): Observable<VersionLinkerAction[]>{
|
||||
return this._queue$.asObservable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const enum VersionLinkerActionType {
|
||||
Link = "link",
|
||||
Unlink = "unlink"
|
||||
}
|
||||
|
||||
export interface VersionLinkerAction {
|
||||
version: BSVersion;
|
||||
relativeFolder: string;
|
||||
type: VersionLinkerActionType
|
||||
options?: LinkOptions
|
||||
}
|
||||
|
||||
export interface VersionLinkFolderAction extends VersionLinkerAction {
|
||||
type: VersionLinkerActionType.Link
|
||||
}
|
||||
|
||||
export interface VersionUnlinkFolderAction extends VersionLinkerAction {
|
||||
type: VersionLinkerActionType.Unlink
|
||||
options?: UnlinkOptions
|
||||
}
|
||||
|
||||
export type VersionLinkerActionListener = (action: VersionLinkerAction, linked: boolean) => void;
|
||||
Reference in New Issue
Block a user