mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge pull request #756 from Zagrios/bugfix/add-file-size-check-when-moving-folder-content
[chore] Enhance folder linking reliability by ensuring file size consistency during content movement
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import { mkdir, pathExistsSync, rm, writeFile } from "fs-extra";
|
||||
import { getSize } from "main/helpers/fs.helpers";
|
||||
import path from "path";
|
||||
|
||||
const TEST_FOLDER = path.resolve(__dirname, "..", "assets", "fs");
|
||||
|
||||
describe("Test fs.helpers getSize", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
if (pathExistsSync(TEST_FOLDER)) {
|
||||
await rm(TEST_FOLDER, { recursive: true, force: true });
|
||||
}
|
||||
await mkdir(TEST_FOLDER);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rm(TEST_FOLDER, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("should return 0 for empty folder", async () => {
|
||||
const size = await getSize(TEST_FOLDER);
|
||||
expect(size).toBe(0);
|
||||
});
|
||||
|
||||
it("should throw error for non-existing folder", async () => {
|
||||
await expect(getSize(`${TEST_FOLDER}1`)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("should return the total size of files in the directory", async () => {
|
||||
const filePath = path.join(TEST_FOLDER, "testFile.bin");
|
||||
const buffer = Buffer.alloc(10);
|
||||
|
||||
await writeFile(filePath, buffer);
|
||||
|
||||
const size = await getSize(TEST_FOLDER);
|
||||
expect(size).toBe(10);
|
||||
});
|
||||
|
||||
it("should include the sizes of all files in the directory", async () => {
|
||||
const filePath1 = path.join(TEST_FOLDER, "testFile1.bin");
|
||||
const filePath2 = path.join(TEST_FOLDER, "testFile2.bin");
|
||||
const buffer = Buffer.alloc(10);
|
||||
|
||||
await writeFile(filePath1, buffer);
|
||||
await writeFile(filePath2, buffer);
|
||||
|
||||
const size = await getSize(TEST_FOLDER);
|
||||
expect(size).toBe(20);
|
||||
});
|
||||
|
||||
it("should include the sizes of files in nested directories", async () => {
|
||||
const subFolder = path.join(TEST_FOLDER, "subFolder");
|
||||
const filePath1 = path.join(TEST_FOLDER, "testFile1.bin");
|
||||
const filePath2 = path.join(subFolder, "testFile2.bin");
|
||||
const buffer = Buffer.alloc(10);
|
||||
|
||||
await mkdir(subFolder);
|
||||
|
||||
await writeFile(filePath1, buffer);
|
||||
await writeFile(filePath2, buffer);
|
||||
|
||||
const size = await getSize(TEST_FOLDER);
|
||||
expect(size).toBe(20);
|
||||
});
|
||||
|
||||
it("should not include files beyond the default depth limit", async () => {
|
||||
const subFolder = path.join(TEST_FOLDER, "1", "2", "3", "4", "5");
|
||||
const filePath = path.join(subFolder, "testFile.bin");
|
||||
const buffer = Buffer.alloc(10);
|
||||
|
||||
await mkdir(subFolder, { recursive: true });
|
||||
await writeFile(filePath, buffer);
|
||||
|
||||
const size = await getSize(TEST_FOLDER);
|
||||
expect(size).toBe(0);
|
||||
});
|
||||
|
||||
it("should include files within the specified depth limit", async () => {
|
||||
const subFolder = path.join(TEST_FOLDER, "1", "2", "3", "4", "5");
|
||||
const filePath = path.join(subFolder, "testFile.bin");
|
||||
const filePath2 = path.join(TEST_FOLDER, "1", "2", "testFile2.bin");
|
||||
const buffer = Buffer.alloc(10);
|
||||
|
||||
await mkdir(subFolder, { recursive: true });
|
||||
await writeFile(filePath, buffer);
|
||||
await writeFile(filePath2, buffer);
|
||||
|
||||
const size = await getSize(TEST_FOLDER, 6);
|
||||
expect(size).toBe(20);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -81,6 +81,7 @@ export async function getFilesInFolder(folderPath: string): Promise<string[]> {
|
||||
}
|
||||
|
||||
export function moveFolderContent(src: string, dest: string, option?: MoveOptions): Observable<Progression> {
|
||||
log.info(`(moveFolderContent) Moving ${src} to ${dest}`);
|
||||
const progress: Progression = { current: 0, total: 0 };
|
||||
return new Observable<Progression>(subscriber => {
|
||||
subscriber.next(progress);
|
||||
@@ -104,7 +105,17 @@ export function moveFolderContent(src: string, dest: string, option?: MoveOption
|
||||
const allChildsAlreadyExist = srcChilds.every(child => pathExistsSync(path.join(destFullPath, child)));
|
||||
|
||||
if(file.isFile() || !allChildsAlreadyExist){
|
||||
const prevSize = await getSize(srcFullPath);
|
||||
await move(srcFullPath, destFullPath, option);
|
||||
const afterSize = await getSize(destFullPath);
|
||||
|
||||
// The size after moving should be the same or greater than the size before moving but never less
|
||||
if(afterSize < prevSize){
|
||||
throw new CustomError(`File size mismath. before: ${prevSize}, after: ${afterSize} (${srcFullPath})`, "FILE_SIZE_MISMATCH");
|
||||
}
|
||||
|
||||
} else {
|
||||
log.info(`Skipping ${srcFullPath} to ${destFullPath}, all child already exist in destination`);
|
||||
}
|
||||
|
||||
progress.current++;
|
||||
@@ -274,6 +285,39 @@ export function getUniqueFileNamePath(filePath: string): string {
|
||||
return path.join(dir, newFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws {Error} Can throw file system errors
|
||||
*/
|
||||
export async function getSize(targetPath: string, maxDepth = 5): Promise<number> {
|
||||
const visited = new Set<string>();
|
||||
|
||||
const computeSize = async (currentPath: string, depth: number): Promise<number> => {
|
||||
if (visited.has(currentPath)){
|
||||
return 0;
|
||||
}
|
||||
|
||||
visited.add(currentPath);
|
||||
|
||||
const stats = await stat(currentPath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
return stats.size;
|
||||
}
|
||||
|
||||
if (!stats.isDirectory() || depth >= maxDepth) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const entries = await readdir(currentPath);
|
||||
const sizes = await Promise.all(
|
||||
entries.map((entry) => computeSize(path.join(currentPath, entry), depth + 1))
|
||||
);
|
||||
return sizes.reduce((acc, cur) => acc + cur, 0);
|
||||
};
|
||||
|
||||
return computeSize(targetPath, 0);
|
||||
}
|
||||
|
||||
export interface Progression<T = unknown, D = unknown> {
|
||||
total: number;
|
||||
current: number;
|
||||
|
||||
@@ -8,8 +8,8 @@ export function tryit<Return>(func: () => Return): TryitReturn<Return> {
|
||||
|
||||
if(isPromise(result)){
|
||||
return result
|
||||
.then((value) => ({ error: null, result: value }))
|
||||
.catch((err) => ({ error: err instanceof Error ? err : new Error(`${err}`), result: null })) as Return extends Promise<any>
|
||||
.then((value) => ({ error: null as null, result: value }))
|
||||
.catch((err) => ({ error: err instanceof Error ? err : new Error(`${err}`), result: null as null })) as Return extends Promise<any>
|
||||
? Promise<{error: Error, result: undefined} | {error: undefined, result: Awaited<Return>}>
|
||||
: {error: Error, result: undefined} | {error: undefined, result: Return};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user