@@ -25,8 +25,19 @@ public class Win32 : IProgram, IEquatable<Win32>
2525 public string Name { get ; set ; }
2626 public string UniqueIdentifier { get => _uid ; set => _uid = value == null ? string . Empty : value . ToLowerInvariant ( ) ; } // For path comparison
2727 public string IcoPath { get ; set ; }
28+ /// <summary>
29+ /// Path of the file. It's the path of .lnk or .url for .lnk and .url.
30+ /// </summary>
2831 public string FullPath { get ; set ; }
32+ /// <summary>
33+ /// Path of the excutable for .lnk, or the URL for .url.
34+ /// </summary>
2935 public string LnkResolvedPath { get ; set ; }
36+ /// <summary>
37+ /// Path of the actual executable file.
38+ /// </summary>
39+ public string ExecutablePath => LnkResolvedPath ?? FullPath ;
40+ public string WorkingDir => Directory . GetParent ( ExecutablePath ) ? . FullName ?? string . Empty ;
3041 public string ParentDirectory { get ; set ; }
3142 public string ExecutableName { get ; set ; }
3243 public string Description { get ; set ; }
@@ -97,10 +108,23 @@ public Result Result(string query, IPublicAPI api)
97108 matchResult . MatchData = new List < int > ( ) ;
98109 }
99110
111+ string subtitle = string . Empty ;
112+ if ( ! Main . _settings . HideAppsPath )
113+ {
114+ if ( Extension ( FullPath ) == UrlExtension )
115+ {
116+ subtitle = LnkResolvedPath ;
117+ }
118+ else
119+ {
120+ subtitle = FullPath ;
121+ }
122+ }
123+
100124 var result = new Result
101125 {
102126 Title = title ,
103- SubTitle = Main . _settings . HideAppsPath ? string . Empty : LnkResolvedPath ?? FullPath ,
127+ SubTitle = subtitle ,
104128 IcoPath = IcoPath ,
105129 Score = matchResult . Score ,
106130 TitleHighlightData = matchResult . MatchData ,
@@ -116,8 +140,8 @@ public Result Result(string query, IPublicAPI api)
116140
117141 var info = new ProcessStartInfo
118142 {
119- FileName = LnkResolvedPath ?? FullPath ,
120- WorkingDirectory = ParentDirectory ,
143+ FileName = ExecutablePath ,
144+ WorkingDirectory = WorkingDir ,
121145 UseShellExecute = true ,
122146 Verb = runAsAdmin ? "runas" : null
123147 } ;
@@ -143,8 +167,8 @@ public List<Result> ContextMenus(IPublicAPI api)
143167 {
144168 var info = new ProcessStartInfo
145169 {
146- FileName = FullPath ,
147- WorkingDirectory = ParentDirectory ,
170+ FileName = ExecutablePath ,
171+ WorkingDirectory = WorkingDir ,
148172 UseShellExecute = true
149173 } ;
150174
@@ -162,8 +186,8 @@ public List<Result> ContextMenus(IPublicAPI api)
162186 {
163187 var info = new ProcessStartInfo
164188 {
165- FileName = FullPath ,
166- WorkingDirectory = ParentDirectory ,
189+ FileName = ExecutablePath ,
190+ WorkingDirectory = WorkingDir ,
167191 Verb = "runas" ,
168192 UseShellExecute = true
169193 } ;
@@ -224,6 +248,15 @@ private static Win32 Win32Program(string path)
224248
225249 return Default ;
226250 }
251+ #if ! DEBUG
252+ catch ( Exception e )
253+ {
254+ ProgramLogger . LogException ( $ "|Win32|Win32Program|{ path } " +
255+ "|An unexpected error occurred in the calling method Win32Program" , e ) ;
256+
257+ return Default ;
258+ }
259+ #endif
227260 }
228261
229262 private static Win32 LnkProgram ( string path )
@@ -241,8 +274,7 @@ private static Win32 LnkProgram(string path)
241274 var extension = Extension ( target ) ;
242275 if ( extension == ExeExtension && File . Exists ( target ) )
243276 {
244- program . LnkResolvedPath = program . FullPath ;
245- program . FullPath = Path . GetFullPath ( target ) . ToLowerInvariant ( ) ;
277+ program . LnkResolvedPath = Path . GetFullPath ( target ) ;
246278 program . ExecutableName = Path . GetFileName ( target ) ;
247279
248280 var description = _helper . description ;
@@ -270,25 +302,22 @@ private static Win32 LnkProgram(string path)
270302 "|Error caused likely due to trying to get the description of the program" ,
271303 e ) ;
272304
273- program . Valid = false ;
274- return program ;
305+ return Default ;
275306 }
276307 catch ( FileNotFoundException e )
277308 {
278309 ProgramLogger . LogException ( $ "|Win32|LnkProgram|{ path } " +
279310 "|An unexpected error occurred in the calling method LnkProgram" , e ) ;
280311
281- program . Valid = false ;
282- return program ;
312+ return Default ;
283313 }
284314#if ! DEBUG //Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
285315 catch ( Exception e )
286316 {
287317 ProgramLogger . LogException ( $ "|Win32|LnkProgram|{ path } " +
288318 "|An unexpected error occurred in the calling method LnkProgram" , e ) ;
289319
290- program . Valid = false ;
291- return program ;
320+ return Default ;
292321 }
293322#endif
294323 }
@@ -342,6 +371,13 @@ private static Win32 ExeProgram(string path)
342371 program . Description = info . FileDescription ;
343372 return program ;
344373 }
374+ catch ( FileNotFoundException e )
375+ {
376+ ProgramLogger . LogException ( $ "|Win32|ExeProgram|{ path } " +
377+ $ "|File not found when trying to load the program from { path } ", e ) ;
378+
379+ return Default ;
380+ }
345381 catch ( Exception e ) when ( e is SecurityException || e is UnauthorizedAccessException )
346382 {
347383 ProgramLogger . LogException ( $ "|Win32|ExeProgram|{ path } " +
@@ -351,7 +387,7 @@ private static Win32 ExeProgram(string path)
351387 }
352388 }
353389
354- private static IEnumerable < string > ProgramPaths ( string directory , string [ ] suffixes , bool recursive = true )
390+ private static IEnumerable < string > EnumerateProgramsInDir ( string directory , string [ ] suffixes , bool recursive = true )
355391 {
356392 if ( ! Directory . Exists ( directory ) )
357393 return Enumerable . Empty < string > ( ) ;
@@ -376,24 +412,23 @@ private static string Extension(string path)
376412 }
377413 }
378414
379- private static IEnumerable < Win32 > UnregisteredPrograms ( List < ProgramSource > sources , string [ ] suffixes , string [ ] protocols )
415+ private static IEnumerable < Win32 > UnregisteredPrograms ( List < string > directories , string [ ] suffixes , string [ ] protocols )
380416 {
381417 // Disabled custom sources are not in DisabledProgramSources
382- var paths = ExceptDisabledSource ( sources . Where ( s => Directory . Exists ( s . Location ) && s . Enabled )
383- . AsParallel ( )
384- . SelectMany ( s => ProgramPaths ( s . Location , suffixes ) ) )
385- . Distinct ( ) ;
418+ var paths = directories . AsParallel ( )
419+ . SelectMany ( s => EnumerateProgramsInDir ( s , suffixes ) ) ;
386420
387- var programs = paths . Select ( x => GetProgramFromPath ( x , protocols ) ) ;
421+ // Remove disabled programs in DisabledProgramSources
422+ var programs = ExceptDisabledSource ( paths ) . Select ( x => GetProgramFromPath ( x , protocols ) ) ;
388423 return programs ;
389424 }
390425
391426 private static IEnumerable < Win32 > StartMenuPrograms ( string [ ] suffixes , string [ ] protocols )
392427 {
393428 var directory1 = Environment . GetFolderPath ( Environment . SpecialFolder . Programs ) ;
394429 var directory2 = Environment . GetFolderPath ( Environment . SpecialFolder . CommonPrograms ) ;
395- var paths1 = ProgramPaths ( directory1 , suffixes ) ;
396- var paths2 = ProgramPaths ( directory2 , suffixes ) ;
430+ var paths1 = EnumerateProgramsInDir ( directory1 , suffixes ) ;
431+ var paths2 = EnumerateProgramsInDir ( directory2 , suffixes ) ;
397432
398433 var toFilter = paths1 . Concat ( paths2 ) ;
399434
@@ -402,26 +437,22 @@ private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes, string[]
402437 return programs ;
403438 }
404439
405- private static IEnumerable < Win32 > PATHPrograms ( string [ ] suffixes , string [ ] protocols )
440+ private static IEnumerable < Win32 > PATHPrograms ( string [ ] suffixes , string [ ] protocols , List < string > commonParents )
406441 {
407442 var pathEnv = Environment . GetEnvironmentVariable ( "Path" ) ;
408- if ( String . IsNullOrEmpty ( pathEnv ) )
409- {
410- return Array . Empty < Win32 > ( ) ;
443+ if ( String . IsNullOrEmpty ( pathEnv ) )
444+ {
445+ return Array . Empty < Win32 > ( ) ;
411446 }
412447
413448 var paths = pathEnv . Split ( ";" , StringSplitOptions . RemoveEmptyEntries ) . DistinctBy ( p => p . ToLowerInvariant ( ) ) ;
414449
415- var toFilter = paths . AsParallel ( ) . SelectMany ( p => ProgramPaths ( p , suffixes , recursive : false ) ) ;
450+ paths = paths . Where ( x => commonParents . All ( parent => ! x . StartsWith ( parent , StringComparison . OrdinalIgnoreCase ) ) ) ;
451+
452+ var toFilter = paths . AsParallel ( ) . SelectMany ( p => EnumerateProgramsInDir ( p , suffixes , recursive : false ) ) ;
416453
417454 var programs = ExceptDisabledSource ( toFilter . Distinct ( ) )
418- . Select ( x => Extension ( x ) switch
419- {
420- ShortcutExtension => LnkProgram ( x ) ,
421- UrlExtension => UrlProgram ( x , protocols ) ,
422- ExeExtension => ExeProgram ( x ) ,
423- _ => Win32Program ( x )
424- } ) ;
455+ . Select ( x => GetProgramFromPath ( x , protocols ) ) ;
425456 return programs ;
426457 }
427458
@@ -496,9 +527,6 @@ private static Win32 GetProgramFromPath(string path, string[] protocols)
496527
497528 path = Environment . ExpandEnvironmentVariables ( path ) ;
498529
499- if ( ! File . Exists ( path ) )
500- return Default ;
501-
502530 return Extension ( path ) switch
503531 {
504532 ShortcutExtension => LnkProgram ( path ) ,
@@ -545,15 +573,15 @@ public static IEnumerable<T> DistinctBy<T, R>(IEnumerable<T> source, Func<T, R>
545573
546574 private static IEnumerable < Win32 > ProgramsHasher ( IEnumerable < Win32 > programs )
547575 {
548- return programs . GroupBy ( p => p . FullPath . ToLowerInvariant ( ) )
576+ return programs . GroupBy ( p => p . ExecutablePath . ToLowerInvariant ( ) )
549577 . AsParallel ( )
550578 . SelectMany ( g =>
551579 {
552580 var temp = g . Where ( g => ! string . IsNullOrEmpty ( g . Description ) ) . ToList ( ) ;
553581 if ( temp . Any ( ) )
554582 return DistinctBy ( temp , x => x . Description ) ;
555583 return g . Take ( 1 ) ;
556- } ) . ToArray ( ) ;
584+ } ) ;
557585 }
558586
559587
@@ -565,11 +593,15 @@ public static Win32[] All(Settings settings)
565593 var suffixes = settings . GetSuffixes ( ) ;
566594 var protocols = settings . GetProtocols ( ) ;
567595
568- var unregistered = UnregisteredPrograms ( settings . ProgramSources , suffixes , protocols ) ;
596+ // Disabled custom sources are not in DisabledProgramSources
597+ var sources = settings . ProgramSources . Where ( s => Directory . Exists ( s . Location ) && s . Enabled ) . Distinct ( ) ;
598+ var commonParents = GetCommonParents ( sources ) ;
599+
600+ var unregistered = UnregisteredPrograms ( commonParents , suffixes , protocols ) ;
569601
570602 programs = programs . Concat ( unregistered ) ;
571603
572- var autoIndexPrograms = Enumerable . Empty < Win32 > ( ) ;
604+ var autoIndexPrograms = Enumerable . Empty < Win32 > ( ) ; // for single programs, not folders
573605
574606 if ( settings . EnableRegistrySource )
575607 {
@@ -585,11 +617,11 @@ public static Win32[] All(Settings settings)
585617
586618 if ( settings . EnablePATHSource )
587619 {
588- var path = PATHPrograms ( settings . GetSuffixes ( ) , protocols ) ;
589- autoIndexPrograms = autoIndexPrograms . Concat ( path ) ;
620+ var path = PATHPrograms ( settings . GetSuffixes ( ) , protocols , commonParents ) ;
621+ programs = programs . Concat ( path ) ;
590622 }
591623
592- autoIndexPrograms = ProgramsHasher ( autoIndexPrograms ) ;
624+ autoIndexPrograms = ProgramsHasher ( autoIndexPrograms ) . ToArray ( ) ;
593625
594626 return programs . Concat ( autoIndexPrograms ) . Where ( x => x . Valid ) . Distinct ( ) . ToArray ( ) ;
595627 }
@@ -651,11 +683,13 @@ public static void WatchProgramUpdate(Settings settings)
651683 if ( settings . EnableStartMenuSource )
652684 paths . AddRange ( GetStartMenuPaths ( ) ) ;
653685
654- paths . AddRange ( from source in settings . ProgramSources where source . Enabled select source . Location ) ;
686+ var customSources = GetCommonParents ( settings . ProgramSources ) ;
687+ paths . AddRange ( customSources ) ;
655688
689+ var fileExtensionToWatch = settings . GetSuffixes ( ) ;
656690 foreach ( var directory in from path in paths where Directory . Exists ( path ) select path )
657691 {
658- WatchDirectory ( directory ) ;
692+ WatchDirectory ( directory , fileExtensionToWatch ) ;
659693 }
660694
661695 _ = Task . Run ( MonitorDirectoryChangeAsync ) ;
@@ -676,7 +710,7 @@ public static async Task MonitorDirectoryChangeAsync()
676710 }
677711 }
678712
679- public static void WatchDirectory ( string directory )
713+ public static void WatchDirectory ( string directory , string [ ] extensions )
680714 {
681715 if ( ! Directory . Exists ( directory ) )
682716 {
@@ -688,6 +722,10 @@ public static void WatchDirectory(string directory)
688722 watcher . Deleted += static ( _ , _ ) => indexQueue . Writer . TryWrite ( default ) ;
689723 watcher . EnableRaisingEvents = true ;
690724 watcher . IncludeSubdirectories = true ;
725+ foreach ( var extension in extensions )
726+ {
727+ watcher . Filters . Add ( $ "*.{ extension } ") ;
728+ }
691729
692730 Watchers . Add ( watcher ) ;
693731 }
@@ -699,5 +737,38 @@ public static void Dispose()
699737 fileSystemWatcher . Dispose ( ) ;
700738 }
701739 }
740+
741+ // https://stackoverflow.com/a/66877016
742+ private static bool IsSubPathOf ( string subPath , string basePath )
743+ {
744+ var rel = Path . GetRelativePath ( basePath , subPath ) ;
745+ return rel != "."
746+ && rel != ".."
747+ && ! rel . StartsWith ( "../" )
748+ && ! rel . StartsWith ( @"..\" )
749+ && ! Path . IsPathRooted ( rel ) ;
750+ }
751+
752+ private static List < string > GetCommonParents ( IEnumerable < ProgramSource > programSources )
753+ {
754+ // To avoid unnecessary io
755+ // like c:\windows and c:\windows\system32
756+ var grouped = programSources . GroupBy ( p => p . Location . ToLowerInvariant ( ) [ 0 ] ) ; // group by disk
757+ List < string > result = new ( ) ;
758+ foreach ( var group in grouped )
759+ {
760+ HashSet < ProgramSource > parents = group . ToHashSet ( ) ;
761+ foreach ( var source in group )
762+ {
763+ if ( parents . Any ( p => IsSubPathOf ( source . Location , p . Location ) &&
764+ source != p ) )
765+ {
766+ parents . Remove ( source ) ;
767+ }
768+ }
769+ result . AddRange ( parents . Select ( x => x . Location ) ) ;
770+ }
771+ return result . DistinctBy ( x => x . ToLowerInvariant ( ) ) . ToList ( ) ;
772+ }
702773 }
703774}
0 commit comments