Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] added logs when deleting files/folders #772

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/__tests__/unit/fs.helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import { mkdir, pathExistsSync, rm, writeFile } from "fs-extra";
import { getSize } from "main/helpers/fs.helpers";
import path from "path";

jest.mock("electron", () => ({ app: {
getPath: () => "",
getName: () => "",
}}));
jest.mock("electron-log", () => ({
info: jest.fn(),
error: jest.fn(),
}));

const TEST_FOLDER = path.resolve(__dirname, "..", "assets", "fs");

describe("Test fs.helpers getSize", () => {
Expand Down
100 changes: 68 additions & 32 deletions src/main/helpers/fs.helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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 { CopyOptions, MoveOptions, RmOptions, copy, createReadStream, ensureDir, move, pathExists, pathExistsSync, realpath, rm, stat, symlink, unlink, unlinkSync } from "fs-extra";
import { access, mkdir, readdir, lstat, readlink } from "fs/promises";
import path from "path";
import { Observable, concatMap, from } from "rxjs";
import log from "electron-log";
Expand Down Expand Up @@ -28,18 +28,53 @@
.then(() => {});
}

export async function deleteFolder(folderPath: string): Promise<void> {
export async function deleteFile(filepath: string) {
try {
log.info("Deleting file", `"${filepath}"`);
await unlink(filepath);
} catch (error: any) {
log.error("Could not delete file", `"${filepath}"`);
throw CustomError.fromError(error, "generic.fs.delete-file");
}
}

export function deleteFileSync(filepath: string) {
try {
log.info("Deleting file", `"${filepath}"`);
unlinkSync(filepath);
} catch (error: any) {
log.error("Could not delete file", `"${filepath}"`);
throw CustomError.fromError(error, "generic.fs.delete-file");
}
}

export async function deleteFolder(folderPath: string, options?: RmOptions) {
if (!(await pathExist(folderPath))) {
return;
}
return rm(folderPath, { recursive: true, force: true });

try {
options = options || { recursive: true, force: true };
log.info("Deleting folder", `"${folderPath}"`, options);
await rm(folderPath, options);
} catch (error: any) {
log.error("Could not delete folder", `"${folderPath}"`);
throw CustomError.fromError(error, "generic.fs.delete-folder");
}
}

export async function unlinkPath(path: string): Promise<void> {
if (!(await pathExist(path))) {
export function deleteFolderSync(folderPath: string, options?: RmOptions) {
if (!pathExistsSync(folderPath)) {
return;
}
return unlink(path);

try {
options = options || { recursive: true, force: true };
log.info("Deleting folder", `"${folderPath}"`, options);
} catch (error: any) {
log.error("Could not delete folder", `"${folderPath}"`);
throw CustomError.fromError(error, "generic.fs.delete-folder");
}
}

export async function getFoldersInFolder(folderPath: string, opts?: { ignoreSymlinkTargetError?: boolean }): Promise<string[]> {
Expand Down Expand Up @@ -97,31 +132,32 @@
const files = await readdir(src, { encoding: "utf-8", withFileTypes: true });
progress.total = files.length;

for(const file of files){
for (const file of files) {
const srcFullPath = path.join(src, file.name);
const destFullPath = path.join(dest, file.name);

const srcChilds = file.isDirectory() ? await readdir(srcFullPath, { encoding: "utf-8", recursive: true }) : [];
const allChildsAlreadyExist = srcChilds.every(child => pathExistsSync(path.join(destFullPath, child)));

if(file.isFile() || !allChildsAlreadyExist){
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){
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++;
subscriber.next(progress);
}
})().catch(err => subscriber.error(CustomError.fromError(err, err?.code))).finally(() => subscriber.complete());
})()
.catch(err => subscriber.error(CustomError.fromError(err, err?.code)))
.finally(() => subscriber.complete());
});
}

Expand All @@ -144,7 +180,7 @@

export async function copyDirectoryWithJunctions(src: string, dest: string, options?: CopyOptions): Promise<void> {
if (isSubdirectory(src, dest)) {
throw { message: `Cannot copy directory '${src}' into itself '${dest}'.`, code: "COPY_TO_SUBPATH" } as BsmException;

Check warning on line 183 in src/main/helpers/fs.helpers.ts

View workflow job for this annotation

GitHub Actions / build

Expected an error object to be thrown
}

await ensureDir(dest);
Expand All @@ -160,7 +196,7 @@
await copy(sourcePath, destinationPath, options);
} else if (item.isSymbolicLink()) {
if (options?.overwrite) {
await unlinkPath(destinationPath);
await deleteFile(destinationPath);
}
const symlinkTarget = await readlink(sourcePath);
const relativePath = path.relative(src, symlinkTarget);
Expand All @@ -180,8 +216,7 @@
});
}

export async function dirSize(dirPath: string): Promise<number>{

export async function dirSize(dirPath: string): Promise<number> {
const entries = await readdir(dirPath);

const paths = entries.map(async entry => {
Expand All @@ -200,11 +235,10 @@
return 0;
});

return (await Promise.all(paths)).flat(Infinity).reduce((acc, size ) => acc + size, 0);
return (await Promise.all(paths)).flat(Infinity).reduce((acc, size) => acc + size, 0);
}

export function rxCopy(src: string, dest: string, option?: CopyOptions): Observable<Progression> {

const dirSizePromise = dirSize(src).catch(err => {
log.error("dirSizePromise", err);
return 0;
Expand All @@ -216,15 +250,19 @@

return new Observable<Progression>(sub => {
sub.next(progress);
copy(src, dest, {...option, filter: (src) => {
stat(src).then(stats => {
progress.current += stats.size;
sub.next(progress);
});
return true;
}})
.then(() => sub.complete()).catch(err => sub.error(err))
})
copy(src, dest, {
...option,
filter: src => {
stat(src).then(stats => {
progress.current += stats.size;
sub.next(progress);
});
return true;
},
})
.then(() => sub.complete())
.catch(err => sub.error(err));
});
})
);
}
Expand Down Expand Up @@ -257,15 +295,15 @@
return destPath;
}

export async function isJunction(path: string): Promise<boolean>{
export async function isJunction(path: string): Promise<boolean> {
const [stats, lstats] = await Promise.all([stat(path), lstat(path)]);
return lstats.isSymbolicLink() && stats.isDirectory();
}

export function resolveGUIDPath(guidPath: string): string {
const guidVolume = path.parse(guidPath).root;
const command = `powershell -command "(Get-WmiObject -Class Win32_Volume | Where-Object { $_.DeviceID -like '${guidVolume}' }).DriveLetter"`;
const {result: driveLetter, error} = tryit(() => execSync(command).toString().trim());
const { result: driveLetter, error } = tryit(() => execSync(command).toString().trim());
if (!driveLetter || error) {
throw new Error("Unable to resolve GUID path", error);
}
Expand All @@ -292,7 +330,7 @@
const visited = new Set<string>();

const computeSize = async (currentPath: string, depth: number): Promise<number> => {
if (visited.has(currentPath)){
if (visited.has(currentPath)) {
return 0;
}

Expand All @@ -309,9 +347,7 @@
}

const entries = await readdir(currentPath);
const sizes = await Promise.all(
entries.map((entry) => computeSize(path.join(currentPath, entry), depth + 1))
);
const sizes = await Promise.all(entries.map(entry => computeSize(path.join(currentPath, entry), depth + 1)));
return sizes.reduce((acc, cur) => acc + cur, 0);
};

Expand Down
18 changes: 5 additions & 13 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import { LivShortcut } from "./services/liv/liv-shortcut.service";
import { SteamLauncherService } from "./services/bs-launcher/steam-launcher.service";
import { FileAssociationService } from "./services/file-association.service";
import { SongDetailsCacheService } from "./services/additional-content/maps/song-details-cache.service";
import { Dirent, readdirSync, rmSync, unlinkSync } from "fs-extra";
import { Dirent, readdirSync } from "fs-extra";
import { StaticConfigurationService } from "./services/static-configuration.service";
import { configureProxy } from './helpers/proxy.helpers';
import { deleteFileSync, deleteFolderSync } from "./helpers/fs.helpers";
import { tryit } from "shared/helpers/error.helpers";

const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
const staticConfig = StaticConfigurationService.getInstance();
Expand Down Expand Up @@ -238,12 +240,7 @@ function deleteOldLogs(): void {

for (const folder of deleteLogFolders) {
const folderPath = path.join(folder.parentPath, folder.name);
try {
rmSync(folderPath, { recursive: true, force: true });
log.info("Deleted log folder:", folderPath);
} catch (error) {
log.error("Error deleting folder:", folderPath, error);
}
tryit(() => deleteFolderSync(folderPath));
}
}

Expand All @@ -255,12 +252,7 @@ function deleteOldestLogs(): void {

for (const file of logs) {
const filepath = path.join(file.parentPath, file.name);
try {
unlinkSync(filepath);
log.info("Deleted log file:", filepath);
} catch (error) {
log.error("Error deleting file:", filepath, error);
}
tryit(() => deleteFileSync(filepath));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import path from "path";
import { RequestService } from "../request.service";
import { copyFileSync } from "fs-extra";
import sanitize from "sanitize-filename";
import { Progression, ensureFolderExist, unlinkPath } from "../../helpers/fs.helpers";
import { Progression, deleteFile, ensureFolderExist } from "../../helpers/fs.helpers";
import { MODEL_FILE_EXTENSIONS, MODEL_TYPES, MODEL_TYPE_FOLDERS } from "../../../shared/models/models/constants";
import { InstallationLocationService } from "../installation-location.service";
import { Observable, Subscription, lastValueFrom } from "rxjs";
Expand Down Expand Up @@ -105,7 +105,9 @@ export class LocalModelsManagerService {
}

public async oneClickDownloadModel(model: MSModel): Promise<void> {
if (!model) { return; }
if (!model) {
return;
}

const versions = await this.localVersion.getInstalledVersions();
const downloaded = await lastValueFrom(this.downloadModel(model, versions.pop()));
Expand Down Expand Up @@ -198,7 +200,7 @@ export class LocalModelsManagerService {
};

for (const model of models) {
await unlinkPath(model.path);
await deleteFile(model.path);
progression.data.push(model);
progression.current = progression.data.length;
subscriber.next(progression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { BPList, DownloadPlaylistProgressionData, PlaylistSong } from "shared/mo
import { readFileSync, Stats } from "fs";
import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
import { copy, ensureDir, pathExists, pathExistsSync, realpath, writeFileSync } from "fs-extra";
import { Progression, getUniqueFileNamePath, unlinkPath } from "../../helpers/fs.helpers";
import { Progression, deleteFile, getUniqueFileNamePath } from "../../helpers/fs.helpers";
import { FileAssociationService } from "../file-association.service";
import { SongDetailsCacheService } from "./maps/song-details-cache.service";
import { sToMs } from "shared/helpers/time.helpers";
Expand Down Expand Up @@ -409,7 +409,7 @@ export class LocalPlaylistsManagerService {
}

public deletePlaylistFile(bpList: LocalBPList): Observable<void>{
return from(unlinkPath(bpList.path));
return from(deleteFile(bpList.path));
}

public exportPlaylists(opt: {version?: BSVersion, bpLists: LocalBPList[], dest: string, playlistsMaps?: BsmLocalMap[]}): Observable<Progression<string>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { InstallationLocationService } from "../../installation-location.service
import { UtilsService } from "../../utils.service";
import crypto, { BinaryLike } from "crypto";
import { lstatSync } from "fs";
import { copy, createReadStream, ensureDir, pathExists, pathExistsSync, realpath, unlink } from "fs-extra";
import { copy, createReadStream, ensureDir, pathExists, pathExistsSync, realpath } from "fs-extra";
import { RequestService } from "../../request.service";
import sanitize from "sanitize-filename";
import { DeepLinkService } from "../../deep-link.service";
import log from "electron-log";
import { WindowManagerService } from "../../window-manager.service";
import { Observable, Subject, lastValueFrom } from "rxjs";
import { Archive } from "../../../models/archive.class";
import { Progression, deleteFolder, ensureFolderExist, getFilesInFolder, getFoldersInFolder, pathExist } from "../../../helpers/fs.helpers";
import { Progression, deleteFile, deleteFolder, ensureFolderExist, getFilesInFolder, getFoldersInFolder, pathExist } from "../../../helpers/fs.helpers";
import { readFile } from "fs/promises";
import { FolderLinkerService } from "../../folder-linker.service";
import { allSettled } from "../../../../shared/helpers/promise.helpers";
Expand Down Expand Up @@ -297,7 +297,7 @@ export class LocalMapsManagerService {
observer.next(progress);
}
})()
.catch(e => {observer.error(e); console.log("AAAA", e)})
.catch(e => observer.error(e))
.finally(() => observer.complete());
});
}
Expand Down Expand Up @@ -449,7 +449,7 @@ export class LocalMapsManagerService {
const zip = await BsmZipExtractor.fromPath(zipPath);
await zip.extract(mapPath);
zip.close();
await unlink(zipPath);
await deleteFile(zipPath);

const localMap = await this.loadMapInfoFromPath(mapPath);
localMap.songDetails = this.songDetailsCache.getSongDetails(localMap.hash);
Expand Down
12 changes: 6 additions & 6 deletions src/main/services/folder-linker.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { InstallationLocationService } from "./installation-location.service";
import log from "electron-log";
import { deleteFolder, ensureFolderExist, moveFolderContent, pathExist, unlinkPath } from "../helpers/fs.helpers";
import { deleteFile, deleteFileSync, deleteFolder, deleteFolderSync, ensureFolderExist, moveFolderContent, pathExist } from "../helpers/fs.helpers";
import { lstat, symlink } from "fs/promises";
import path from "path";
import { copy, mkdirSync, readlink, rmSync, symlinkSync, unlinkSync } from "fs-extra";
import { copy, mkdirSync, readlink, symlinkSync } from "fs-extra";
import { lastValueFrom } from "rxjs";
import { noop } from "shared/helpers/function.helpers";
import { StaticConfigurationService } from "./static-configuration.service";
Expand Down Expand Up @@ -56,8 +56,8 @@ export class FolderLinkerService {
});

tryit(() => {
rmSync(testFolder, { force: true, recursive: true });
unlinkSync(testLink);
deleteFolderSync(testFolder);
deleteFileSync(testLink);
});

if(resLink.error){
Expand Down Expand Up @@ -113,7 +113,7 @@ export class FolderLinkerService {
if (isTargetedToSharedPath) {
return;
}
await unlinkPath(folderPath);
await deleteFile(folderPath);

log.info(`Linking ${folderPath} to ${sharedPath}; type: ${this.linkingType}`);
return symlink(sharedPath, folderPath, this.getLinkingType());
Expand Down Expand Up @@ -141,7 +141,7 @@ export class FolderLinkerService {
if (!(await this.isFolderSymlink(folderPath))) {
return;
}
await unlinkPath(folderPath);
await deleteFile(folderPath);

const sharedPath = await this.getSharedFolder(folderPath, options?.intermediateFolder);

Expand Down
Loading
Loading