Skip to content

Commit

Permalink
Improved detection of app bitness; Small search speedup
Browse files Browse the repository at this point in the history
  • Loading branch information
Klocman committed May 9, 2022
1 parent a32dbf6 commit 3321a93
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 62 deletions.
9 changes: 2 additions & 7 deletions source/BulkCrapUninstaller/Functions/AppUninstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -534,15 +534,10 @@ public void UninstallFromDirectory(IEnumerable<ApplicationUninstallerEntry> allU

var items = new List<ApplicationUninstallerEntry>();
LoadingDialog.ShowDialog(MessageBoxes.DefaultOwner, Localisable.UninstallFromDirectory_ScanningTitle,
_ =>
{
items.AddRange(DirectoryFactory.TryCreateFromDirectory(
new DirectoryInfo(result), null, Array.Empty<string>()));
});
_ => items.AddRange(DirectoryFactory.TryCreateFromDirectory(new DirectoryInfo(result), Array.Empty<string>())));

if (items.Count == 0)
items.AddRange(applicationUninstallerEntries
.Where(x => PathTools.PathsEqual(result, x.InstallLocation)));
items.AddRange(applicationUninstallerEntries.Where(x => PathTools.PathsEqual(result, x.InstallLocation)));

if (items.Count == 0)
MessageBoxes.UninstallFromDirectoryNothingFound();
Expand Down
54 changes: 24 additions & 30 deletions source/UninstallTools/Factory/DirectoryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ Apache License Version 2.0

namespace UninstallTools.Factory
{
using KVP = KeyValuePair<DirectoryInfo, bool?>;

public class DirectoryFactory : IUninstallerFactory
{
private readonly IEnumerable<ApplicationUninstallerEntry> _existingUninstallerEntries;
Expand All @@ -32,7 +30,7 @@ public IList<ApplicationUninstallerEntry> GetUninstallerEntries(ListGenerationPr

var existingUninstallers = _existingUninstallerEntries.ToList();

var pfDirs = UninstallToolsGlobalConfig.GetProgramFilesDirectories(true).ToList();
var pfDirs = UninstallToolsGlobalConfig.GetProgramFilesDirectories(true);
var dirsToSkip = GetDirectoriesToSkip(existingUninstallers, pfDirs).ToList();

var itemsToScan = GetDirectoriesToScan(existingUninstallers, pfDirs, dirsToSkip).ToList();
Expand All @@ -42,7 +40,7 @@ public IList<ApplicationUninstallerEntry> GetUninstallerEntries(ListGenerationPr
public static IEnumerable<ApplicationUninstallerEntry> TryGetApplicationsFromDirectories(
ICollection<DirectoryInfo> directoriesToScan, IEnumerable<ApplicationUninstallerEntry> existingUninstallers)
{
var pfDirs = UninstallToolsGlobalConfig.GetProgramFilesDirectories(true).ToList();
var pfDirs = UninstallToolsGlobalConfig.GetProgramFilesDirectories(true);
var dirsToSkip = GetDirectoriesToSkip(existingUninstallers, pfDirs).ToList();

var results = new List<ApplicationUninstallerEntry>();
Expand All @@ -52,7 +50,7 @@ public static IEnumerable<ApplicationUninstallerEntry> TryGetApplicationsFromDir
directory.Name.StartsWith("Windows", StringComparison.InvariantCultureIgnoreCase))
continue;

var detectedEntries = TryCreateFromDirectory(directory, null, dirsToSkip);
var detectedEntries = TryCreateFromDirectory(directory, dirsToSkip);

results.AddRange(detectedEntries);
}
Expand All @@ -62,16 +60,16 @@ public static IEnumerable<ApplicationUninstallerEntry> TryGetApplicationsFromDir
/// <summary>
/// Get directories to scan for applications
/// </summary>
private static IEnumerable<KVP> GetDirectoriesToScan(IEnumerable<ApplicationUninstallerEntry> existingUninstallers,
IEnumerable<KVP> pfDirs, IEnumerable<string> dirsToSkip)
private static IEnumerable<DirectoryInfo> GetDirectoriesToScan(IEnumerable<ApplicationUninstallerEntry> existingUninstallers,
IEnumerable<DirectoryInfo> pfDirs, IEnumerable<string> dirsToSkip)
{
var pfDirectories = pfDirs.ToList();

if (UninstallToolsGlobalConfig.AutoDetectCustomProgramFiles)
{
var extraPfDirectories = FindExtraPfDirectories(existingUninstallers)
.Where(extraDir => !extraDir.Key.FullName.Contains(@"\Common Files", StringComparison.InvariantCultureIgnoreCase))
.Where(extraDir => pfDirectories.All(pfDir => !PathTools.PathsEqual(pfDir.Key.FullName, extraDir.Key.FullName)));
.Where(extraDir => !extraDir.FullName.Contains(@"\Common Files", StringComparison.InvariantCultureIgnoreCase))
.Where(extraDir => pfDirectories.All(pfDir => !PathTools.PathsEqual(pfDir.FullName, extraDir.FullName)));

pfDirectories.AddRange(extraPfDirectories);

Expand All @@ -82,11 +80,9 @@ private static IEnumerable<KVP> GetDirectoriesToScan(IEnumerable<ApplicationUnin
"PortableApps", "LiberKey"
};

IEnumerable<KVP> FindDirsOnDrive(DriveInfo d)
IEnumerable<DirectoryInfo> FindDirsOnDrive(DriveInfo d)
{
return d.RootDirectory.GetDirectories()
.Where(x => goodNames.Contains(x.Name, StringComparison.InvariantCultureIgnoreCase))
.Select(x => new KVP(x, null));
return d.RootDirectory.GetDirectories().Where(x => goodNames.Contains(x.Name, StringComparison.InvariantCultureIgnoreCase));
}

var drives = DriveInfo.GetDrives();
Expand Down Expand Up @@ -132,26 +128,26 @@ IEnumerable<KVP> FindDirsOnDrive(DriveInfo d)
{
try
{
return x.Key.GetDirectories().Select(y => new KVP(y, x.Value));
return x.GetDirectories();
}
catch (SystemException ex)
{
Trace.WriteLine($"Could not access a program files directory: {x.Key?.Name} | Reason:{ex.Message}");
Trace.WriteLine($"Could not access a program files directory: {x?.Name} | Reason:{ex.Message}");
}
return Enumerable.Empty<KVP>();
return Enumerable.Empty<DirectoryInfo>();
});

// Get directories that can be relatively safely checked
return directoriesToCheck.Where(check => !directoriesToSkip.Any(skip =>
check.Key.FullName.Contains(skip, StringComparison.InvariantCultureIgnoreCase)))
.Distinct((pair, otherPair) => PathTools.PathsEqual(pair.Key.FullName, otherPair.Key.FullName));
check.FullName.StartsWith(skip, StringComparison.InvariantCultureIgnoreCase)))
.Distinct((pair, otherPair) => PathTools.PathsEqual(pair.FullName, otherPair.FullName));
}

/// <summary>
/// Get directories which are already used and should be skipped
/// </summary>
private static IEnumerable<string> GetDirectoriesToSkip(IEnumerable<ApplicationUninstallerEntry> existingUninstallers,
IEnumerable<KVP> pfDirectories)
IEnumerable<DirectoryInfo> pfDirectories)
{
var dirs = new List<string>();
foreach (var x in existingUninstallers)
Expand All @@ -175,10 +171,10 @@ private static IEnumerable<string> GetDirectoriesToSkip(IEnumerable<ApplicationU
}

return dirs.Where(x => !string.IsNullOrEmpty(x)).Distinct()
.Where(x => !pfDirectories.Any(pfd => pfd.Key.FullName.Contains(x, StringComparison.InvariantCultureIgnoreCase)));
.Where(x => !pfDirectories.Any(pfd => pfd.FullName.Contains(x, StringComparison.InvariantCultureIgnoreCase)));
}

private static IEnumerable<KVP> FindExtraPfDirectories(IEnumerable<ApplicationUninstallerEntry> existingUninstallers)
private static IEnumerable<DirectoryInfo> FindExtraPfDirectories(IEnumerable<ApplicationUninstallerEntry> existingUninstallers)
{
var extraSearchLocations = existingUninstallers
.Select(x => x.InstallLocation)
Expand Down Expand Up @@ -210,8 +206,7 @@ private static IEnumerable<KVP> FindExtraPfDirectories(IEnumerable<ApplicationUn
{
return null;
}
}).Where(x => x != null)
.Select(x => new KVP(x, null));
}).Where(x => x != null);
}


Expand Down Expand Up @@ -284,8 +279,8 @@ private static void CreateFromDirectoryHelper(ICollection<ApplicationUninstaller
/// <summary>
/// Try to get the main executable from the filtered folders. If no executables are present check subfolders.
/// </summary>
public static IEnumerable<ApplicationUninstallerEntry> TryCreateFromDirectory(DirectoryInfo directory, bool? is64Bit,
ICollection<string> dirsToSkip)
public static IEnumerable<ApplicationUninstallerEntry> TryCreateFromDirectory(
DirectoryInfo directory, ICollection<string> dirsToSkip)
{
if (directory == null)
throw new ArgumentNullException(nameof(directory));
Expand All @@ -296,8 +291,8 @@ public static IEnumerable<ApplicationUninstallerEntry> TryCreateFromDirectory(Di

foreach (var tempEntry in results)
{
if (is64Bit.HasValue && tempEntry.Is64Bit == MachineType.Unknown)
tempEntry.Is64Bit = is64Bit.Value ? MachineType.X64 : MachineType.X86;
if (tempEntry.Is64Bit == MachineType.Unknown)
tempEntry.Is64Bit = UninstallToolsGlobalConfig.IsPathInsideProgramFiles(directory.FullName);

tempEntry.IsRegistered = false;
tempEntry.IsOrphaned = true;
Expand All @@ -309,15 +304,14 @@ public static IEnumerable<ApplicationUninstallerEntry> TryCreateFromDirectory(Di
public static IEnumerable<ApplicationUninstallerEntry> TryCreateFromDirectory(
DirectoryInfo directoryToScan, IEnumerable<ApplicationUninstallerEntry> existingUninstallers)
{
var pfDirs = UninstallToolsGlobalConfig.GetProgramFilesDirectories(true).ToList();
var pfDirs = UninstallToolsGlobalConfig.GetProgramFilesDirectories(true);
var dirsToSkip = GetDirectoriesToSkip(existingUninstallers, pfDirs).ToList();

if (UninstallToolsGlobalConfig.IsSystemDirectory(directoryToScan) ||
directoryToScan.Name.StartsWith("Windows", StringComparison.InvariantCultureIgnoreCase))
return Enumerable.Empty<ApplicationUninstallerEntry>();

return TryCreateFromDirectory(directoryToScan, null, dirsToSkip);

return TryCreateFromDirectory(directoryToScan, dirsToSkip);
}
}
}
16 changes: 8 additions & 8 deletions source/UninstallTools/Factory/FactoryThreadedHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ internal static class FactoryThreadedHelpers
public static IList<ApplicationUninstallerEntry> DriveApplicationScan(
ListGenerationProgress.ListGenerationCallback progressCallback,
List<string> dirsToSkip,
List<KeyValuePair<DirectoryInfo, bool?>> itemsToScan)
List<DirectoryInfo> itemsToScan)
{
var dividedItems = SplitByPhysicalDrives(itemsToScan, pair => pair.Key);
var dividedItems = SplitByPhysicalDrives(itemsToScan, d => d);

void GetUninstallerEntriesThread(KeyValuePair<DirectoryInfo, bool?> data, List<ApplicationUninstallerEntry> state)
void GetUninstallerEntriesThread(DirectoryInfo data, List<ApplicationUninstallerEntry> state)
{
if (UninstallToolsGlobalConfig.IsSystemDirectory(data.Key) ||
data.Key.Name.StartsWith("Windows", StringComparison.InvariantCultureIgnoreCase))
if (UninstallToolsGlobalConfig.IsSystemDirectory(data) ||
data.Name.StartsWith("Windows", StringComparison.InvariantCultureIgnoreCase))
return;

var detectedEntries = DirectoryFactory.TryCreateFromDirectory(data.Key, data.Value, dirsToSkip).ToList();
var detectedEntries = DirectoryFactory.TryCreateFromDirectory(data, dirsToSkip).ToList();

ApplicationUninstallerFactory.MergeResults(state, detectedEntries, null);
}

var workSpreader = new ThreadedWorkSpreader<KeyValuePair<DirectoryInfo, bool?>, List<ApplicationUninstallerEntry>>
(MaxThreadsPerDrive, GetUninstallerEntriesThread, list => new List<ApplicationUninstallerEntry>(list.Count), data => data.Key.FullName);
var workSpreader = new ThreadedWorkSpreader<DirectoryInfo, List<ApplicationUninstallerEntry>>
(MaxThreadsPerDrive, GetUninstallerEntriesThread, list => new List<ApplicationUninstallerEntry>(list.Count), data => data.FullName);

workSpreader.Start(dividedItems, progressCallback);

Expand Down
6 changes: 3 additions & 3 deletions source/UninstallTools/Junk/ProgramFilesOrphans.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class ProgramFilesOrphans : IJunkCreator
private string[] _otherInstallLocations;
private string[] _otherNames;
private string[] _otherPublishers;
private IEnumerable<KeyValuePair<DirectoryInfo, bool?>> _programFilesDirectories;
private List<DirectoryInfo> _programFilesDirectories;

public IEnumerable<IJunkResult> FindJunk(ApplicationUninstallerEntry target)
{
Expand All @@ -34,9 +34,9 @@ public IEnumerable<IJunkResult> FindAllJunk()
var output = new List<FileSystemJunk>();

foreach (var kvp in _programFilesDirectories)
FindJunkRecursively(output, kvp.Key, 0);
FindJunkRecursively(output, kvp, 0);

return output.Cast<IJunkResult>();
return output;
}

private void FindJunkRecursively(ICollection<FileSystemJunk> returnList, DirectoryInfo parentDirectory, int level)
Expand Down
40 changes: 26 additions & 14 deletions source/UninstallTools/UninstallToolsGlobalConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ static UninstallToolsGlobalConfig()
JunkSearchDirs = paths.Distinct().ToList().AsEnumerable();

AppInfoCachePath = Path.Combine(AssemblyLocation, "InfoCache.xml");

_pf32 = WindowsTools.GetProgramFilesX86Path();
_pf64 = WindowsTools.GetEnvironmentPath(CSIDL.CSIDL_PROGRAM_FILES);
if (string.IsNullOrWhiteSpace(_pf64) || PathTools.PathsEqual(_pf32, _pf64)) _pf64 = null;
}

public static bool EnableAppInfoCache
Expand Down Expand Up @@ -188,36 +192,32 @@ internal static IEnumerable<string> GetAllProgramFiles()
return StockProgramFiles.Concat(CustomProgramFiles).ToList();
}

private static readonly string _pf64, _pf32;

/// <summary>
/// Get a list of directiories containing programs. Optionally user-defined directories are added.
/// The boolean value is true if the directory is confirmed to contain 64bit applications, false if 32bit.
/// </summary>
/// <param name="includeUserDirectories">Add user-defined directories.</param>
internal static IEnumerable<KeyValuePair<DirectoryInfo, bool?>> GetProgramFilesDirectories(
bool includeUserDirectories)
internal static List<DirectoryInfo> GetProgramFilesDirectories(bool includeUserDirectories)
{
var pfDirectories = new List<KeyValuePair<string, bool?>>(2);
var pfDirectories = new List<string>(2);

var pf64 = WindowsTools.GetEnvironmentPath(CSIDL.CSIDL_PROGRAM_FILES);
var pf32 = WindowsTools.GetProgramFilesX86Path();
pfDirectories.Add(new KeyValuePair<string, bool?>(pf32, false));
if (!PathTools.PathsEqual(pf32, pf64))
pfDirectories.Add(new KeyValuePair<string, bool?>(pf64, true));
pfDirectories.Add(_pf32);
if (_pf64 != null) pfDirectories.Add(_pf64);

if (includeUserDirectories && CustomProgramFiles != null)
pfDirectories.AddRange(CustomProgramFiles.Where(
x => !pfDirectories.Any(y => PathTools.PathsEqual(x, y.Key)))
.Select(x => new KeyValuePair<string, bool?>(x, null)));
pfDirectories.AddRange(CustomProgramFiles.Where(x => !pfDirectories.Any(y => PathTools.PathsEqual(x, y))));

var output = new List<KeyValuePair<DirectoryInfo, bool?>>();
var output = new List<DirectoryInfo>();
foreach (var directory in pfDirectories.ToList())
{
// Ignore missing or inaccessible directories
try
{
var di = new DirectoryInfo(directory.Key);
var di = new DirectoryInfo(directory);
if (di.Exists)
output.Add(new KeyValuePair<DirectoryInfo, bool?>(di, directory.Value));
output.Add(di);
}
catch (Exception ex)
{
Expand All @@ -228,6 +228,18 @@ internal static IEnumerable<string> GetAllProgramFiles()
return output;
}

/// <summary>
/// Check if the path is inside of 64 or 32 bit program files
/// </summary>
public static MachineType IsPathInsideProgramFiles(string fullPath)
{
if (fullPath.StartsWith(_pf32, StringComparison.InvariantCultureIgnoreCase))
return MachineType.X86;
if (_pf64 != null && fullPath.StartsWith(_pf64, StringComparison.InvariantCultureIgnoreCase))
return MachineType.X64;
return MachineType.Unknown;
}

/// <summary>
/// Check if dir is a system directory and should be left alone.
/// </summary>
Expand Down

0 comments on commit 3321a93

Please sign in to comment.