[bugfix] Improve folder linking reliability

This commit is contained in:
MathieuG-P
2024-08-25 15:03:08 +02:00
parent 462499fe81
commit aea325a254
13 changed files with 118 additions and 39 deletions
+8 -13
View File
@@ -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 -4
View File
@@ -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;
+1 -1
View File
@@ -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 };