Skip to content

Commit 3912071

Browse files
committed
feat(worker): enhance folder size calculation with size collector
1 parent fe60a8a commit 3912071

1 file changed

Lines changed: 169 additions & 59 deletions

File tree

src/services/files/files.worker.ts

Lines changed: 169 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Dir, Dirent } from 'fs';
2-
import { lstat, opendir, readdir, stat } from 'fs/promises';
3-
2+
import { lstat, opendir, readdir } from 'fs/promises';
43
import EventEmitter from 'events';
54
import { WorkerMessage } from './files.worker.service';
65
import { join } from 'path';
@@ -11,10 +10,17 @@ import { EVENTS, MAX_PROCS } from '../../constants/workers.constants.js';
1110
enum ETaskOperation {
1211
'explore',
1312
'getFolderSize',
13+
'getFolderSizeChild',
1414
}
15+
1516
interface Task {
1617
operation: ETaskOperation;
1718
path: string;
19+
sizeCollector?: {
20+
total: number;
21+
pending: number;
22+
onComplete: (total: number) => void;
23+
};
1824
}
1925

2026
(() => {
@@ -108,11 +114,17 @@ class FileWalker {
108114
path: string,
109115
operation: ETaskOperation,
110116
priorize: boolean = false,
117+
sizeCollector?: Task['sizeCollector'],
111118
): void {
119+
const task: Task = { path, operation };
120+
if (sizeCollector) {
121+
task.sizeCollector = sizeCollector;
122+
}
123+
112124
if (priorize) {
113-
this.taskQueue.unshift({ path, operation });
125+
this.taskQueue.unshift(task);
114126
} else {
115-
this.taskQueue.push({ path, operation });
127+
this.taskQueue.push(task);
116128
}
117129

118130
this.processQueue();
@@ -137,7 +149,6 @@ class FileWalker {
137149
}
138150

139151
this.events.emit('newResult', { results });
140-
141152
await dir.close();
142153
this.completeTask();
143154

@@ -149,39 +160,140 @@ class FileWalker {
149160
private async runGetFolderSize(path: string): Promise<void> {
150161
this.updateProcs(1);
151162

163+
const collector = {
164+
total: 0,
165+
pending: 0,
166+
onComplete: (finalSize: number) => {
167+
this.events.emit('folderSizeResult', { path, size: finalSize });
168+
},
169+
};
170+
171+
this.calculateFolderSizeRecursive(path, collector);
172+
this.completeTask();
173+
}
174+
175+
private async calculateFolderSizeRecursive(
176+
path: string,
177+
collector: Task['sizeCollector'],
178+
): Promise<void> {
179+
if (!collector) return;
180+
181+
collector.pending += 1;
182+
this.updateProcs(1);
183+
152184
try {
153-
const size = await this.getFolderSize(path);
154-
this.events.emit('folderSizeResult', { path, size });
155-
} catch (_) {
156-
this.events.emit('folderSizeResult', { path, size: 0 });
185+
const entries = await readdir(path, { withFileTypes: true });
186+
187+
for (const entry of entries) {
188+
const fullPath = join(path, entry.name);
189+
190+
let stats;
191+
try {
192+
stats = await lstat(fullPath);
193+
} catch {
194+
continue; // Skip files we can't access
195+
}
196+
197+
if (stats.isSymbolicLink()) {
198+
continue;
199+
}
200+
201+
const size =
202+
typeof stats.blocks === 'number' ? stats.blocks * 512 : stats.size;
203+
collector.total += size;
204+
205+
if (stats.isDirectory()) {
206+
// Process subdirectory recursively
207+
this.calculateFolderSizeRecursive(fullPath, collector);
208+
}
209+
}
210+
} catch (error) {
211+
// Handle directory access errors gracefully
212+
console.warn(`Failed to read directory: ${path}`, error);
157213
} finally {
158-
this.completeTask();
214+
// Always decrement pending count
215+
collector.pending -= 1;
216+
this.updateProcs(-1);
217+
218+
// Check if all work is complete
219+
if (collector.pending === 0) {
220+
collector.onComplete(collector.total);
221+
}
159222
}
160223
}
161224

162-
async getFolderSize(dir: string): Promise<number> {
163-
async function calculateDirectorySize(directory: string): Promise<number> {
164-
const entries = await readdir(directory, { withFileTypes: true });
225+
private async runGetFolderSizeChild(
226+
path: string,
227+
collector: Task['sizeCollector'],
228+
): Promise<void> {
229+
if (!collector) {
230+
this.completeTask();
231+
return;
232+
}
233+
234+
this.updateProcs(1);
235+
236+
try {
237+
const entries = await readdir(path, { withFileTypes: true });
238+
const directoriesToProcess: string[] = [];
239+
const promises: Promise<void>[] = [];
240+
241+
for (const entry of entries) {
242+
const fullPath = join(path, entry.name);
165243

166-
const tasks = entries.map(async (entry) => {
167-
const fullPath = join(directory, entry.name);
244+
if (entry.isSymbolicLink()) {
245+
continue;
246+
}
168247

169248
if (entry.isDirectory()) {
170-
// Ignore errors.
171-
return calculateDirectorySize(fullPath).catch(() => 0);
249+
directoriesToProcess.push(fullPath);
250+
promises.push(
251+
lstat(fullPath)
252+
.then((stats) => {
253+
const size =
254+
typeof stats.blocks === 'number'
255+
? stats.blocks * 512
256+
: stats.size;
257+
collector.total += size;
258+
})
259+
.catch(() => {}),
260+
);
172261
} else if (entry.isFile()) {
173-
const fileStat = await lstat(fullPath);
174-
return fileStat.size;
262+
promises.push(
263+
lstat(fullPath)
264+
.then((stats) => {
265+
const size =
266+
typeof stats.blocks === 'number'
267+
? stats.blocks * 512
268+
: stats.size;
269+
collector.total += size;
270+
})
271+
.catch(() => {}),
272+
);
175273
}
274+
}
176275

177-
return 0;
178-
});
276+
await Promise.all(promises);
179277

180-
const sizes = await Promise.all(tasks);
181-
return sizes.reduce((total, size) => total + size, 0);
182-
}
278+
collector.pending += directoriesToProcess.length;
279+
for (const dirPath of directoriesToProcess) {
280+
this.enqueueTask(
281+
dirPath,
282+
ETaskOperation.getFolderSizeChild,
283+
false,
284+
collector,
285+
);
286+
}
287+
} catch (error) {
288+
} finally {
289+
collector.pending -= 1;
290+
291+
if (collector.pending === 0) {
292+
collector.onComplete(collector.total);
293+
}
183294

184-
return calculateDirectorySize(dir);
295+
this.completeTask();
296+
}
185297
}
186298

187299
private newDirEntry(path: string, entry: Dirent, results: any[]): void {
@@ -198,22 +310,13 @@ class FileWalker {
198310
}
199311

200312
private isExcluded(path: string): boolean {
201-
if (this.searchConfig.exclude === undefined) {
313+
if (!this.searchConfig.exclude) {
202314
return false;
203315
}
204-
205-
for (let i = 0; i < this.searchConfig.exclude.length; i++) {
206-
const excludeString = this.searchConfig.exclude[i];
207-
if (path.includes(excludeString)) {
208-
return true;
209-
}
210-
}
211-
212-
return false;
316+
return this.searchConfig.exclude.some((ex) => path.includes(ex));
213317
}
214318

215319
private isTargetFolder(path: string): boolean {
216-
// return basename(path) === this.searchConfig.target;
217320
return path === this.searchConfig.target;
218321
}
219322

@@ -230,29 +333,36 @@ class FileWalker {
230333
private processQueue(): void {
231334
while (this.procs < MAX_PROCS && this.taskQueue.length > 0) {
232335
const task = this.taskQueue.shift();
233-
234-
if (task === null || task === undefined) {
235-
return;
236-
}
237-
238-
const path = task.path;
239-
240-
if (path === undefined || path === '') {
241-
return;
242-
}
243-
244-
if (task.operation === ETaskOperation.explore) {
245-
this.run(path).then(
246-
() => {},
247-
() => {},
248-
);
249-
}
250-
251-
if (task.operation === ETaskOperation.getFolderSize) {
252-
this.runGetFolderSize(path).then(
253-
() => {},
254-
() => {},
255-
);
336+
if (!task || !task.path) continue;
337+
338+
switch (task.operation) {
339+
case ETaskOperation.explore:
340+
this.run(task.path).catch((error) => {
341+
console.warn(`Explore task failed for ${task.path}:`, error);
342+
this.completeTask();
343+
});
344+
break;
345+
case ETaskOperation.getFolderSize:
346+
this.runGetFolderSize(task.path).catch((error) => {
347+
console.warn(`GetFolderSize task failed for ${task.path}:`, error);
348+
});
349+
break;
350+
case ETaskOperation.getFolderSizeChild:
351+
this.runGetFolderSizeChild(task.path, task.sizeCollector!).catch(
352+
(error) => {
353+
console.warn(
354+
`GetFolderSizeChild task failed for ${task.path}:`,
355+
error,
356+
);
357+
if (task.sizeCollector) {
358+
task.sizeCollector.pending -= 1;
359+
if (task.sizeCollector.pending === 0) {
360+
task.sizeCollector.onComplete(task.sizeCollector.total);
361+
}
362+
}
363+
},
364+
);
365+
break;
256366
}
257367
}
258368
}

0 commit comments

Comments
 (0)