Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "devnullifier",
"version": "1.2.5",
"version": "1.2.6",
"description": "DevNullifier: Clean application data, dev caches (node_modules, .cache, Library, Binary, Intermediate, etc) with Electron, Vue 3, and Vuetify 3.",
"main": "dist-main/main/main.js",
"scripts": {
Expand Down
68 changes: 23 additions & 45 deletions src/main/__tests__/appDataScanWorker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { vi, describe, it, expect, beforeEach } from "vitest";
import { getDirSize } from "../fileUtils";
import { FolderScanner, initializeWorker, type IMessagePort, type WorkerResponse } from "../appDataScanWorker";
import path from "path";
import os from "os";

// Mock modules
vi.mock("fs", () => ({
Expand All @@ -12,7 +13,8 @@ vi.mock("fs", () => ({
}));

vi.mock("../fileUtils", () => ({
getDirSize: vi.fn()
getDirSize: vi.fn(),
setUserDataPath: vi.fn()
}));

// Helper to create a mock directory entry with only the properties we use
Expand All @@ -27,18 +29,15 @@ describe("FolderScanner", () => {
let mockFsOps: { readdir: typeof fs.readdir; getDirSize: typeof getDirSize };
let scanner: FolderScanner;
let messages: WorkerResponse[];
const mockUserDataPath = path.join(os.tmpdir(), `test-userdata-${Date.now()}`);

beforeEach(() => {
messages = [];
mockMessagePort = {
postMessage: (msg: WorkerResponse) => {
messages.push(msg);
},
on: (event: "message", listener: (message: WorkerResponse) => void) => {
if (event === "message") {
listener(messages[messages.length - 1]);
}
}
on: vi.fn()
};

mockFsOps = {
Expand All @@ -63,7 +62,7 @@ describe("FolderScanner", () => {
mockDirEntry("temp-dir", true)
] as any);

const results = await scanner.scanPaths(paths, 2, keywords);
const results = await scanner.scanPaths(paths, 2, keywords, mockUserDataPath);
scanner.sendFolders(true); // Force send any remaining folders

expect(results).toHaveLength(2);
Expand Down Expand Up @@ -98,21 +97,21 @@ describe("FolderScanner", () => {
] as any);

// With maxDepth = 0, it should only scan the root directory
await scanner.scanPaths(["/test"], 0, ["cache"]);
await scanner.scanPaths(["/test"], 0, ["cache"], mockUserDataPath);
expect(mockFsOps.readdir).toHaveBeenCalledTimes(1);

vi.clearAllMocks();

// With maxDepth = 1, it should scan one level deep
await scanner.scanPaths(["/test"], 1, ["cache"]);
await scanner.scanPaths(["/test"], 1, ["cache"], mockUserDataPath);
expect(mockFsOps.readdir).toHaveBeenCalledTimes(1);
});

it("should handle filesystem errors gracefully", async () => {
const mockError = new Error("Permission denied");
vi.mocked(mockFsOps.readdir).mockRejectedValue(mockError);

const results = await scanner.scanPaths(["/test"], 1, ["cache"]);
const results = await scanner.scanPaths(["/test"], 1, ["cache"], mockUserDataPath);

expect(results).toHaveLength(0);
expect(messages).toContainEqual({ type: "current-path", path: "/test" });
Expand All @@ -126,7 +125,7 @@ describe("FolderScanner", () => {
mockDirEntry("cache", true)
] as any);

const results = await scanner.scanPaths(["/test"], 1, ["cache"]);
const results = await scanner.scanPaths(["/test"], 1, ["cache"], mockUserDataPath);

expect(results).toHaveLength(0);
expect(messages.filter(m => m.type === "folder-found")).toHaveLength(0);
Expand All @@ -138,7 +137,7 @@ describe("FolderScanner", () => {
mockDirEntry("cache", true)
] as any);

await scanner.scanPaths(["/test"], 1, ["cache"]);
await scanner.scanPaths(["/test"], 1, ["cache"], mockUserDataPath);

// Should only process the directory, not the file
expect(mockFsOps.getDirSize).toHaveBeenCalledTimes(1);
Expand All @@ -152,7 +151,7 @@ describe("FolderScanner", () => {
mockDirEntry("cache", true)
] as any);

const results = await scanner.scanPaths(["/test"], 1, ["cache"]);
const results = await scanner.scanPaths(["/test"], 1, ["cache"], mockUserDataPath);
scanner.sendFolders(true); // Force send any remaining folders

expect(results).toHaveLength(3);
Expand Down Expand Up @@ -180,7 +179,7 @@ describe("FolderScanner", () => {
});

try {
const results = await scanner.scanPaths(["/test/path"], 1, ["cache"]);
const results = await scanner.scanPaths(["/test/path"], 1, ["cache"], mockUserDataPath);

expect(messages).toEqual([
{
Expand All @@ -201,41 +200,20 @@ describe("FolderScanner", () => {
});

it("initialize worker with injected port", () => {
let functions: any[] = [];
const port = {
postMessage: (message: WorkerResponse) => {
messages.push(message);
},
on: (event: string, listener: (message: WorkerResponse) => void) => {
functions.push(listener);
}
} as unknown as IMessagePort;
initializeWorker(port);
expect(functions).toHaveLength(1);
expect(functions[0]).toBeDefined();

// call function message.paths, message.maxDepth, message.keywords
functions[0]({
type: "message",
data: {
paths: ["/test"],
maxDepth: 1,
keywords: ["cache"]
}
});
const mockMessages: WorkerResponse[] = [];
const mockPort = {
postMessage: (msg: WorkerResponse) => mockMessages.push(msg),
on: vi.fn()
};

expect(messages).toContainEqual({
// Test error handling
const scanner = new FolderScanner(mockPort as any);
scanner.scanPaths(undefined as any, 1, ["cache"], mockUserDataPath);

expect(mockMessages).toContainEqual({
"error": "paths is not iterable",
"type": "error"
});

functions[0]({
type: "stop",
});

expect(messages).toContainEqual({
"type": "stop"
});
});
});
});
7 changes: 3 additions & 4 deletions src/main/__tests__/fileUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
getSavedDeveloperProjectsPath,
saveDeveloperProjects,
loadSavedDeveloperProjects,
getSavedDeveloperProjectsCount
getSavedDeveloperProjectsCount,
setUserDataPath
} from "../fileUtils";
import { promises as fs, existsSync } from "fs";
import path from "path";
Expand Down Expand Up @@ -36,7 +37,7 @@ describe("fileUtils", () => {
}
await fs.mkdir(tempDir, { recursive: true });
await fs.mkdir(mockUserDataPath, { recursive: true });
(app.getPath as any).mockReturnValue(mockUserDataPath);
setUserDataPath(mockUserDataPath);
});

afterEach(async () => {
Expand Down Expand Up @@ -84,7 +85,6 @@ describe("fileUtils", () => {
it("should get saved folders path correctly", async () => {
const result = await getSavedFoldersPath();
expect(result).toBe(path.join(mockUserDataPath, "saved-folders-appdata.json"));
expect(app.getPath).toHaveBeenCalledWith("userData");
});

it("should save and load folders correctly", async () => {
Expand Down Expand Up @@ -136,7 +136,6 @@ describe("fileUtils", () => {
it("should get saved developer projects path correctly", async () => {
const result = await getSavedDeveloperProjectsPath();
expect(result).toBe(path.join(mockUserDataPath, "saved-folders-developer.json"));
expect(app.getPath).toHaveBeenCalledWith("userData");
});

it("should save and load developer projects correctly", async () => {
Expand Down
2 changes: 0 additions & 2 deletions src/main/appDataCleaner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,6 @@ export async function deleteDirectory(
})
);

console.log("Results:", results);

let successCount = results.filter(res => res === true).length;
let partialCount = results.filter(res => res === "partial").length;
let failedCount = results.length - successCount - partialCount;
Expand Down
21 changes: 16 additions & 5 deletions src/main/appDataScanWorker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { parentPort } from "worker_threads";
import path from "path";
import { promises as fs, Dirent } from "fs";
import { getDirSize } from "./fileUtils";
import fs from "fs/promises";
import type { Dirent } from "fs";
import * as fileUtils from "./fileUtils";

export interface Folder {
path: string;
Expand All @@ -14,6 +15,7 @@ export interface WorkerMessage {
paths: string[];
maxDepth: number;
keywords: string[];
userDataPath: string;
}

export type WorkerResponse =
Expand All @@ -39,7 +41,7 @@ export class FolderScanner {
private readonly messagePort: IMessagePort,
private readonly fsOps = {
readdir: fs.readdir,
getDirSize
getDirSize: fileUtils.getDirSize
}
) {}

Expand Down Expand Up @@ -150,13 +152,17 @@ export class FolderScanner {
async scanPaths(
paths: string[],
maxDepth: number,
keywords: string[]
keywords: string[],
userDataPath: string
): Promise<Folder[]> {
const allResults: Folder[] = [];
this.foundCount = 0;
this.isScanning = true;

try {
// Set the userDataPath for fileUtils
fileUtils.setUserDataPath(userDataPath);

for (const basePath of paths) {
if (!this.isScanning) {
break;
Expand Down Expand Up @@ -203,7 +209,12 @@ export function initializeWorker(injectedPort: IMessagePort | null = null) {
scanner.stop();
return;
}
await scanner.scanPaths(message.paths, message.maxDepth, message.keywords);
await scanner.scanPaths(
message.paths,
message.maxDepth,
message.keywords,
message.userDataPath
);
scanner.sendFolders(true);
});
}
Expand Down
10 changes: 8 additions & 2 deletions src/main/developerScanWorker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { parentPort } from "worker_threads";
import { promises as fs, Dirent } from "fs";
import path from "path";
import { getDirSize } from "./fileUtils";
import * as fileUtils from "./fileUtils";
import { Category, CacheMatch, PatternGroup, Project, WorkerMessage, WorkerResponse } from "../types/developer-cleaner";

let isScanning = true;
Expand Down Expand Up @@ -233,7 +233,7 @@ export async function processMatchingPath(

let size = 0;
if (isDirectory) {
size = await getDirSize(cachePath);
size = await fileUtils.getDirSize(cachePath);
} else {
const stats = await fs.stat(cachePath);
size = stats.size;
Expand Down Expand Up @@ -476,6 +476,12 @@ if (parentPort) {
isScanning = false;
return;
}

// Set userDataPath if provided
if (message.userDataPath) {
fileUtils.setUserDataPath(message.userDataPath);
}

// enabledCategories already contains the full category objects from the frontend
const projects = await scanDeveloperProjects(message.basePaths, message.enabledCategories);

Expand Down
15 changes: 12 additions & 3 deletions src/main/fileUtils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import fs from 'fs/promises';
import path from 'path';
import { app } from 'electron';
import type { FolderItem } from '../renderer/types';
import type { ProjectInfo } from '../renderer/types/developer-cleaner';

let userDataPath: string;

export function setUserDataPath(path: string) {
userDataPath = path;
}

// Calculate directory size recursively
export async function getDirSize(dirPath: string): Promise<number> {
let size = 0;
Expand Down Expand Up @@ -32,7 +37,9 @@ export async function getDirSize(dirPath: string): Promise<number> {
}

export async function getSavedFoldersPath(): Promise<string> {
const userDataPath = app.getPath('userData');
if (!userDataPath) {
throw new Error('User data path not set');
}
return path.join(userDataPath, 'saved-folders-appdata.json');
}

Expand All @@ -57,7 +64,9 @@ export async function getSavedFoldersCount(): Promise<number> {
}

export async function getSavedDeveloperProjectsPath(): Promise<string> {
const userDataPath = app.getPath('userData');
if (!userDataPath) {
throw new Error('User data path not set');
}
return path.join(userDataPath, 'saved-folders-developer.json');
}

Expand Down
16 changes: 14 additions & 2 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import * as fileUtils from "./fileUtils";
import type { Project, Category, WorkerMessage, WorkerResponse } from "../types/developer-cleaner";
import { UpdateService } from "./updateService";

// Set user data path early
fileUtils.setUserDataPath(app.getPath('userData'));

let mainWindow: BrowserWindow | null = null;
let currentAppDataScanWorker: Worker | null = null;
let currentDeveloperScanWorker: Worker | null = null;
Expand Down Expand Up @@ -126,7 +129,12 @@ ipcMain.handle("scan-folders", async (event, { paths, maxDepth }) => {
return originalTerminate();
};

currentAppDataScanWorker.postMessage({ paths, maxDepth, keywords: appDataCleaner.KEYWORDS });
currentAppDataScanWorker.postMessage({
paths,
maxDepth,
keywords: appDataCleaner.KEYWORDS,
userDataPath: app.getPath('userData') // Pass userDataPath to worker
});
});
});

Expand Down Expand Up @@ -305,7 +313,11 @@ ipcMain.handle(
return originalTerminate();
};

currentDeveloperScanWorker.postMessage({ basePaths, enabledCategories } as WorkerMessage);
currentDeveloperScanWorker.postMessage({
basePaths,
enabledCategories,
userDataPath: app.getPath('userData') // Pass userDataPath to worker
} as WorkerMessage);
});
}
);
Expand Down
3 changes: 2 additions & 1 deletion src/types/developer-cleaner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ export interface Project {
}

export interface WorkerMessage {
type: "scan" | "stop";
type?: "stop";
basePaths: string[];
enabledCategories: Category[];
userDataPath?: string;
}

export type WorkerResponse =
Expand Down
Loading