mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[bugfix] Improve folder linking reliability
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { CopyOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, stat, symlink } from "fs-extra";
|
||||
import { CopyOptions, MoveOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, stat, symlink } from "fs-extra";
|
||||
import { access, mkdir, rm, readdir, unlink, lstat, readlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { Observable, concatMap, from } from "rxjs";
|
||||
@@ -7,6 +7,7 @@ import { BsmException } from "shared/models/bsm-exception.model";
|
||||
import crypto from "crypto";
|
||||
import { execSync } from "child_process";
|
||||
import { tryit } from "../../shared/helpers/error.helpers";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
|
||||
export async function pathExist(path: string): Promise<boolean> {
|
||||
try {
|
||||
@@ -78,7 +79,7 @@ export async function getFilesInFolder(folderPath: string): Promise<string[]> {
|
||||
return dirEntries.filter(entry => entry.isFile()).map(file => path.join(folderPath, file.name));
|
||||
}
|
||||
|
||||
export function moveFolderContent(src: string, dest: string): Observable<Progression> {
|
||||
export function moveFolderContent(src: string, dest: string, option?: MoveOptions): Observable<Progression> {
|
||||
const progress: Progression = { current: 0, total: 0 };
|
||||
return new Observable<Progression>(subscriber => {
|
||||
subscriber.next(progress);
|
||||
@@ -89,25 +90,19 @@ export function moveFolderContent(src: string, dest: string): Observable<Progres
|
||||
return subscriber.complete();
|
||||
}
|
||||
|
||||
ensureFolderExist(dest);
|
||||
await ensureFolderExist(dest);
|
||||
|
||||
const files = await readdir(src, { encoding: "utf-8" });
|
||||
progress.total = files.length;
|
||||
|
||||
const promises = files.map(async file => {
|
||||
for(const file of files){
|
||||
const srcFullPath = path.join(src, file);
|
||||
const destFullPath = path.join(dest, file);
|
||||
if (await pathExist(destFullPath)) {
|
||||
progress.current++;
|
||||
return subscriber.next(progress);
|
||||
}
|
||||
await move(srcFullPath, destFullPath);
|
||||
await move(srcFullPath, destFullPath, option);
|
||||
progress.current++;
|
||||
subscriber.next(progress);
|
||||
});
|
||||
|
||||
Promise.allSettled(promises).then(() => subscriber.complete());
|
||||
})();
|
||||
}
|
||||
})().catch(err => subscriber.error(CustomError.fromError(err, err?.code))).finally(() => subscriber.complete());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { deleteFolder, ensureFolderExist, moveFolderContent, pathExist, unlinkPa
|
||||
import { lstat, symlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { copy, readlink } from "fs-extra";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { noop } from "shared/helpers/function.helpers";
|
||||
|
||||
export class FolderLinkerService {
|
||||
private static instance: FolderLinkerService;
|
||||
@@ -72,7 +74,7 @@ export class FolderLinkerService {
|
||||
await ensureFolderExist(folderPath);
|
||||
|
||||
if (options?.keepContents !== false) {
|
||||
await moveFolderContent(folderPath, sharedPath).toPromise();
|
||||
await lastValueFrom(moveFolderContent(folderPath, sharedPath, { overwrite: true }));
|
||||
}
|
||||
|
||||
await deleteFolder(folderPath);
|
||||
@@ -95,9 +97,7 @@ export class FolderLinkerService {
|
||||
}
|
||||
|
||||
if (options.moveContents === true) {
|
||||
return moveFolderContent(sharedPath, folderPath)
|
||||
.toPromise()
|
||||
.then(() => {});
|
||||
return lastValueFrom(moveFolderContent(sharedPath, folderPath, { overwrite: true })).then(noop);
|
||||
}
|
||||
|
||||
if (options?.keepContents === false) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getFoldersInFolder } from "../helpers/fs.helpers";
|
||||
import path from "path";
|
||||
import { VersionLinkerAction, VersionLinkFolderAction, VersionUnlinkFolderAction } from "renderer/services/version-folder-linker.service";
|
||||
import { VersionLinkerAction, VersionUnlinkFolderAction } from "renderer/services/version-folder-linker.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { LocalMapsManagerService } from "./additional-content/maps/local-maps-manager.service";
|
||||
import { BSLocalVersionService } from "./bs-local-version.service";
|
||||
@@ -58,17 +58,14 @@ export class VersionFolderLinkerService {
|
||||
return path.join(parentPath, relativePath);
|
||||
}
|
||||
|
||||
public async linkVersionFolder(action: VersionLinkerAction): Promise<boolean> {
|
||||
public async linkVersionFolder(action: VersionLinkerAction): Promise<void> {
|
||||
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)
|
||||
}
|
||||
|
||||
public async unlinkVersionFolder(action: VersionUnlinkFolderAction): Promise<boolean> {
|
||||
public async unlinkVersionFolder(action: VersionUnlinkFolderAction): Promise<void> {
|
||||
action.options = this.specialFolderOption(action.relativeFolder, action.options);
|
||||
|
||||
const versionPath = await this.localVersion.getVersionPath(action.version);
|
||||
@@ -76,13 +73,10 @@ export class VersionFolderLinkerService {
|
||||
|
||||
action.options.moveContents = !(await this.isOtherVersionHaveFolderLinked(action.relativeFolder, folderPath));
|
||||
|
||||
return this.folderLinker
|
||||
.unlinkFolder(folderPath, action.options)
|
||||
.catch(() => false)
|
||||
.then(() => true);
|
||||
return this.folderLinker.unlinkFolder(folderPath, action.options);
|
||||
}
|
||||
|
||||
public async doAction(action: VersionLinkerAction): Promise<boolean> {
|
||||
public doAction(action: VersionLinkerAction): Promise<void> {
|
||||
if (action.type === "link") {
|
||||
return this.linkVersionFolder(action);
|
||||
}
|
||||
@@ -123,7 +117,7 @@ export class VersionFolderLinkerService {
|
||||
|
||||
for (const version of versions) {
|
||||
const linkedFolders = await this.getLinkedFolders(version, { relative: true, ignoreSymlinkTargetError: true });
|
||||
const actions = linkedFolders.map(folder => ({ type: "link", version, relativeFolder: folder } as VersionLinkFolderAction));
|
||||
const actions = linkedFolders.map(folder => ({ type: "link", version, relativeFolder: folder } as VersionLinkerAction));
|
||||
await Promise.all(actions.map(action => this.doAction(action)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { LinkOptions } from "main/services/folder-linker.service";
|
||||
import { LinkOptions, UnlinkOptions } from "main/services/folder-linker.service";
|
||||
import { map, distinctUntilChanged, filter, mergeMap, shareReplay } from "rxjs/operators";
|
||||
import { BehaviorSubject, Observable, of } from "rxjs";
|
||||
import { BehaviorSubject, lastValueFrom, Observable, of } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import equal from "fast-deep-equal";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
import { NotificationService } from "./notification.service";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
|
||||
|
||||
|
||||
export class VersionFolderLinkerService {
|
||||
private static instance: VersionFolderLinkerService;
|
||||
@@ -16,8 +21,11 @@ export class VersionFolderLinkerService {
|
||||
return VersionFolderLinkerService.instance;
|
||||
}
|
||||
|
||||
private readonly KNOWN_ERROR_CODES = ["EPERM", "EACCES", "ENOSPC"];
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly progress: ProgressBarService;
|
||||
private readonly notifications: NotificationService;
|
||||
|
||||
private readonly _queue$ = new BehaviorSubject<VersionLinkerAction[]>([]);
|
||||
|
||||
@@ -27,6 +35,7 @@ export class VersionFolderLinkerService {
|
||||
private constructor() {
|
||||
this.ipcService = IpcService.getInstance();
|
||||
this.progress = ProgressBarService.getInstance();
|
||||
this.notifications = NotificationService.getInstance();
|
||||
|
||||
this.currentAction$.pipe(filter(action => !!action)).subscribe(action => this.processAction(action));
|
||||
}
|
||||
@@ -39,14 +48,22 @@ export class VersionFolderLinkerService {
|
||||
progressOpened = true;
|
||||
}
|
||||
|
||||
const linked = await this.doAction(action).toPromise();
|
||||
const { error } = await tryit(() => lastValueFrom(this.doAction(action)));
|
||||
|
||||
// Spécial notification
|
||||
if(error){
|
||||
const { code } = (error as CustomError);
|
||||
const message = this.KNOWN_ERROR_CODES.includes(code) ? `notifications.shared-folder.linking-error.msg.${code}` : "notifications.shared-folder.linking-error.msg.UNKNOWN_ERROR";
|
||||
this.notifications.notifyError({
|
||||
title: "notifications.shared-folder.linking-error.title",
|
||||
desc: message
|
||||
})
|
||||
}
|
||||
|
||||
// Special notification
|
||||
if (action.type === VersionLinkerActionType.Link) {
|
||||
this.linkListeners.forEach(listener => listener(action, linked));
|
||||
this.linkListeners.forEach(listener => listener(action, !error));
|
||||
} else {
|
||||
this.unlinkListeners.forEach(listener => listener(action, linked));
|
||||
this.unlinkListeners.forEach(listener => listener(action, !error));
|
||||
}
|
||||
|
||||
if (progressOpened) {
|
||||
@@ -58,7 +75,7 @@ export class VersionFolderLinkerService {
|
||||
this._queue$.next(newArr);
|
||||
}
|
||||
|
||||
private doAction(action: VersionLinkerAction): Observable<boolean> {
|
||||
private doAction(action: VersionLinkerAction): Observable<void> {
|
||||
return this.ipcService.sendV2("link-version-folder-action", action);
|
||||
}
|
||||
|
||||
@@ -180,7 +197,7 @@ export interface VersionLinkerAction {
|
||||
}
|
||||
|
||||
export type VersionLinkFolderAction = Omit<VersionLinkerAction, "type">;
|
||||
export type VersionUnlinkFolderAction = Omit<VersionLinkerAction, "type">;
|
||||
export type VersionUnlinkFolderAction = Omit<VersionLinkerAction, "type"> & { options: UnlinkOptions };
|
||||
|
||||
export type VersionLinkerActionListener = (action: VersionLinkerAction, linked: boolean) => void;
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ export interface IpcChannelMapping {
|
||||
"get-version-full-path": { request: BSVersion, response: string };
|
||||
"full-version-path-to-relative": { request: { version: BSVersion; fullPath: string }, response: string };
|
||||
"get-linked-folders": { request: { version: BSVersion; options?: { relative?: boolean } }, response: string[] };
|
||||
"link-version-folder-action": { request: VersionLinkerAction, response: boolean };
|
||||
"link-version-folder-action": { request: VersionLinkerAction, response: void };
|
||||
"is-version-folder-linked": { request: { version: BSVersion; relativeFolder: string }, response: boolean };
|
||||
"relink-all-versions-folders": { request: void, response: void };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user