@@ -25,8 +25,19 @@ public class Win32 : IProgram, IEquatable<Win32>
25
25
public string Name { get ; set ; }
26
26
public string UniqueIdentifier { get => _uid ; set => _uid = value == null ? string . Empty : value . ToLowerInvariant ( ) ; } // For path comparison
27
27
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>
28
31
public string FullPath { get ; set ; }
32
+ /// <summary>
33
+ /// Path of the excutable for .lnk, or the URL for .url.
34
+ /// </summary>
29
35
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 ;
30
41
public string ParentDirectory { get ; set ; }
31
42
public string ExecutableName { get ; set ; }
32
43
public string Description { get ; set ; }
@@ -97,10 +108,23 @@ public Result Result(string query, IPublicAPI api)
97
108
matchResult . MatchData = new List < int > ( ) ;
98
109
}
99
110
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
+
100
124
var result = new Result
101
125
{
102
126
Title = title ,
103
- SubTitle = Main . _settings . HideAppsPath ? string . Empty : LnkResolvedPath ?? FullPath ,
127
+ SubTitle = subtitle ,
104
128
IcoPath = IcoPath ,
105
129
Score = matchResult . Score ,
106
130
TitleHighlightData = matchResult . MatchData ,
@@ -116,8 +140,8 @@ public Result Result(string query, IPublicAPI api)
116
140
117
141
var info = new ProcessStartInfo
118
142
{
119
- FileName = LnkResolvedPath ?? FullPath ,
120
- WorkingDirectory = ParentDirectory ,
143
+ FileName = ExecutablePath ,
144
+ WorkingDirectory = WorkingDir ,
121
145
UseShellExecute = true ,
122
146
Verb = runAsAdmin ? "runas" : null
123
147
} ;
@@ -143,8 +167,8 @@ public List<Result> ContextMenus(IPublicAPI api)
143
167
{
144
168
var info = new ProcessStartInfo
145
169
{
146
- FileName = FullPath ,
147
- WorkingDirectory = ParentDirectory ,
170
+ FileName = ExecutablePath ,
171
+ WorkingDirectory = WorkingDir ,
148
172
UseShellExecute = true
149
173
} ;
150
174
@@ -162,8 +186,8 @@ public List<Result> ContextMenus(IPublicAPI api)
162
186
{
163
187
var info = new ProcessStartInfo
164
188
{
165
- FileName = FullPath ,
166
- WorkingDirectory = ParentDirectory ,
189
+ FileName = ExecutablePath ,
190
+ WorkingDirectory = WorkingDir ,
167
191
Verb = "runas" ,
168
192
UseShellExecute = true
169
193
} ;
@@ -224,6 +248,15 @@ private static Win32 Win32Program(string path)
224
248
225
249
return Default ;
226
250
}
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
227
260
}
228
261
229
262
private static Win32 LnkProgram ( string path )
@@ -241,8 +274,7 @@ private static Win32 LnkProgram(string path)
241
274
var extension = Extension ( target ) ;
242
275
if ( extension == ExeExtension && File . Exists ( target ) )
243
276
{
244
- program . LnkResolvedPath = program . FullPath ;
245
- program . FullPath = Path . GetFullPath ( target ) . ToLowerInvariant ( ) ;
277
+ program . LnkResolvedPath = Path . GetFullPath ( target ) ;
246
278
program . ExecutableName = Path . GetFileName ( target ) ;
247
279
248
280
var description = _helper . description ;
@@ -270,25 +302,22 @@ private static Win32 LnkProgram(string path)
270
302
"|Error caused likely due to trying to get the description of the program" ,
271
303
e ) ;
272
304
273
- program . Valid = false ;
274
- return program ;
305
+ return Default ;
275
306
}
276
307
catch ( FileNotFoundException e )
277
308
{
278
309
ProgramLogger . LogException ( $ "|Win32|LnkProgram|{ path } " +
279
310
"|An unexpected error occurred in the calling method LnkProgram" , e ) ;
280
311
281
- program . Valid = false ;
282
- return program ;
312
+ return Default ;
283
313
}
284
314
#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.
285
315
catch ( Exception e )
286
316
{
287
317
ProgramLogger . LogException ( $ "|Win32|LnkProgram|{ path } " +
288
318
"|An unexpected error occurred in the calling method LnkProgram" , e ) ;
289
319
290
- program . Valid = false ;
291
- return program ;
320
+ return Default ;
292
321
}
293
322
#endif
294
323
}
@@ -342,6 +371,13 @@ private static Win32 ExeProgram(string path)
342
371
program . Description = info . FileDescription ;
343
372
return program ;
344
373
}
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
+ }
345
381
catch ( Exception e ) when ( e is SecurityException || e is UnauthorizedAccessException )
346
382
{
347
383
ProgramLogger . LogException ( $ "|Win32|ExeProgram|{ path } " +
@@ -351,7 +387,7 @@ private static Win32 ExeProgram(string path)
351
387
}
352
388
}
353
389
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 )
355
391
{
356
392
if ( ! Directory . Exists ( directory ) )
357
393
return Enumerable . Empty < string > ( ) ;
@@ -376,24 +412,23 @@ private static string Extension(string path)
376
412
}
377
413
}
378
414
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 )
380
416
{
381
417
// 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 ) ) ;
386
420
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 ) ) ;
388
423
return programs ;
389
424
}
390
425
391
426
private static IEnumerable < Win32 > StartMenuPrograms ( string [ ] suffixes , string [ ] protocols )
392
427
{
393
428
var directory1 = Environment . GetFolderPath ( Environment . SpecialFolder . Programs ) ;
394
429
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 ) ;
397
432
398
433
var toFilter = paths1 . Concat ( paths2 ) ;
399
434
@@ -402,26 +437,22 @@ private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes, string[]
402
437
return programs ;
403
438
}
404
439
405
- private static IEnumerable < Win32 > PATHPrograms ( string [ ] suffixes , string [ ] protocols )
440
+ private static IEnumerable < Win32 > PATHPrograms ( string [ ] suffixes , string [ ] protocols , List < string > commonParents )
406
441
{
407
442
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 > ( ) ;
411
446
}
412
447
413
448
var paths = pathEnv . Split ( ";" , StringSplitOptions . RemoveEmptyEntries ) . DistinctBy ( p => p . ToLowerInvariant ( ) ) ;
414
449
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 ) ) ;
416
453
417
454
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 ) ) ;
425
456
return programs ;
426
457
}
427
458
@@ -496,9 +527,6 @@ private static Win32 GetProgramFromPath(string path, string[] protocols)
496
527
497
528
path = Environment . ExpandEnvironmentVariables ( path ) ;
498
529
499
- if ( ! File . Exists ( path ) )
500
- return Default ;
501
-
502
530
return Extension ( path ) switch
503
531
{
504
532
ShortcutExtension => LnkProgram ( path ) ,
@@ -545,15 +573,15 @@ public static IEnumerable<T> DistinctBy<T, R>(IEnumerable<T> source, Func<T, R>
545
573
546
574
private static IEnumerable < Win32 > ProgramsHasher ( IEnumerable < Win32 > programs )
547
575
{
548
- return programs . GroupBy ( p => p . FullPath . ToLowerInvariant ( ) )
576
+ return programs . GroupBy ( p => p . ExecutablePath . ToLowerInvariant ( ) )
549
577
. AsParallel ( )
550
578
. SelectMany ( g =>
551
579
{
552
580
var temp = g . Where ( g => ! string . IsNullOrEmpty ( g . Description ) ) . ToList ( ) ;
553
581
if ( temp . Any ( ) )
554
582
return DistinctBy ( temp , x => x . Description ) ;
555
583
return g . Take ( 1 ) ;
556
- } ) . ToArray ( ) ;
584
+ } ) ;
557
585
}
558
586
559
587
@@ -565,11 +593,15 @@ public static Win32[] All(Settings settings)
565
593
var suffixes = settings . GetSuffixes ( ) ;
566
594
var protocols = settings . GetProtocols ( ) ;
567
595
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 ) ;
569
601
570
602
programs = programs . Concat ( unregistered ) ;
571
603
572
- var autoIndexPrograms = Enumerable . Empty < Win32 > ( ) ;
604
+ var autoIndexPrograms = Enumerable . Empty < Win32 > ( ) ; // for single programs, not folders
573
605
574
606
if ( settings . EnableRegistrySource )
575
607
{
@@ -585,11 +617,11 @@ public static Win32[] All(Settings settings)
585
617
586
618
if ( settings . EnablePATHSource )
587
619
{
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 ) ;
590
622
}
591
623
592
- autoIndexPrograms = ProgramsHasher ( autoIndexPrograms ) ;
624
+ autoIndexPrograms = ProgramsHasher ( autoIndexPrograms ) . ToArray ( ) ;
593
625
594
626
return programs . Concat ( autoIndexPrograms ) . Where ( x => x . Valid ) . Distinct ( ) . ToArray ( ) ;
595
627
}
@@ -651,11 +683,13 @@ public static void WatchProgramUpdate(Settings settings)
651
683
if ( settings . EnableStartMenuSource )
652
684
paths . AddRange ( GetStartMenuPaths ( ) ) ;
653
685
654
- paths . AddRange ( from source in settings . ProgramSources where source . Enabled select source . Location ) ;
686
+ var customSources = GetCommonParents ( settings . ProgramSources ) ;
687
+ paths . AddRange ( customSources ) ;
655
688
689
+ var fileExtensionToWatch = settings . GetSuffixes ( ) ;
656
690
foreach ( var directory in from path in paths where Directory . Exists ( path ) select path )
657
691
{
658
- WatchDirectory ( directory ) ;
692
+ WatchDirectory ( directory , fileExtensionToWatch ) ;
659
693
}
660
694
661
695
_ = Task . Run ( MonitorDirectoryChangeAsync ) ;
@@ -676,7 +710,7 @@ public static async Task MonitorDirectoryChangeAsync()
676
710
}
677
711
}
678
712
679
- public static void WatchDirectory ( string directory )
713
+ public static void WatchDirectory ( string directory , string [ ] extensions )
680
714
{
681
715
if ( ! Directory . Exists ( directory ) )
682
716
{
@@ -688,6 +722,10 @@ public static void WatchDirectory(string directory)
688
722
watcher . Deleted += static ( _ , _ ) => indexQueue . Writer . TryWrite ( default ) ;
689
723
watcher . EnableRaisingEvents = true ;
690
724
watcher . IncludeSubdirectories = true ;
725
+ foreach ( var extension in extensions )
726
+ {
727
+ watcher . Filters . Add ( $ "*.{ extension } ") ;
728
+ }
691
729
692
730
Watchers . Add ( watcher ) ;
693
731
}
@@ -699,5 +737,38 @@ public static void Dispose()
699
737
fileSystemWatcher . Dispose ( ) ;
700
738
}
701
739
}
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
+ }
702
773
}
703
774
}
0 commit comments