11import { Dir , Dirent } from 'fs' ;
2- import { lstat , opendir , readdir , stat } from 'fs/promises' ;
3-
2+ import { lstat , opendir , readdir } from 'fs/promises' ;
43import EventEmitter from 'events' ;
54import { WorkerMessage } from './files.worker.service' ;
65import { join } from 'path' ;
@@ -11,10 +10,17 @@ import { EVENTS, MAX_PROCS } from '../../constants/workers.constants.js';
1110enum ETaskOperation {
1211 'explore' ,
1312 'getFolderSize' ,
13+ 'getFolderSizeChild' ,
1414}
15+
1516interface 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