Flow.Launcher/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs

813 lines
29 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
2017-03-01 23:21:34 +00:00
using System.Security;
2016-08-20 17:18:41 +00:00
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32;
2020-06-24 02:55:21 +00:00
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin.Program.Logger;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin.SharedModels;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Channels;
2022-10-20 08:31:26 +00:00
using Flow.Launcher.Plugin.Program.Views.Models;
2022-10-16 09:25:47 +00:00
using IniParser;
using System.Windows.Input;
2020-06-24 02:55:21 +00:00
namespace Flow.Launcher.Plugin.Program.Programs
{
[Serializable]
public class Win32 : IProgram, IEquatable<Win32>
{
2016-08-20 17:50:14 +00:00
public string Name { get; set; }
2023-01-03 18:47:25 +00:00
public string UniqueIdentifier { get => _uid; set => _uid = value == null ? string.Empty : value.ToLowerInvariant(); } // For path comparison
2016-08-20 00:17:28 +00:00
public string IcoPath { get; set; }
2023-01-30 12:44:48 +00:00
/// <summary>
2022-11-21 11:36:11 +00:00
/// Path of the file. It's the path of .lnk and .url for .lnk and .url files.
/// </summary>
2022-10-20 09:00:18 +00:00
public string FullPath { get; set; }
2023-01-30 12:44:48 +00:00
/// <summary>
2022-12-31 09:42:42 +00:00
/// Path of the executable for .lnk, or the URL for .url. Arguments are included if any.
/// </summary>
2020-06-24 02:55:21 +00:00
public string LnkResolvedPath { get; set; }
2023-01-30 12:44:48 +00:00
/// <summary>
2022-12-31 09:42:42 +00:00
/// Path of the actual executable file. Args are included.
/// </summary>
public string ExecutablePath => LnkResolvedPath ?? FullPath;
2023-01-30 12:44:48 +00:00
public string ParentDirectory { get; set; }
2023-01-30 12:44:48 +00:00
2022-12-31 09:42:42 +00:00
/// <summary>
/// Name of the executable for .lnk files
/// </summary>
2016-08-20 00:17:28 +00:00
public string ExecutableName { get; set; }
2023-01-30 12:44:48 +00:00
2016-08-20 17:50:14 +00:00
public string Description { get; set; }
public bool Valid { get; set; }
2019-09-08 12:18:55 +00:00
public bool Enabled { get; set; }
public string Location => ParentDirectory;
// Localized name based on windows display language
public string LocalizedName { get; set; } = string.Empty;
private const string ShortcutExtension = "lnk";
2022-10-16 09:25:47 +00:00
private const string UrlExtension = "url";
private const string ExeExtension = "exe";
2022-10-20 08:31:26 +00:00
private string _uid = string.Empty;
private static readonly Win32 Default = new Win32()
{
Name = string.Empty,
Description = string.Empty,
IcoPath = string.Empty,
FullPath = string.Empty,
LnkResolvedPath = null,
ParentDirectory = string.Empty,
ExecutableName = null,
2022-10-20 09:00:18 +00:00
UniqueIdentifier = string.Empty,
Valid = false,
Enabled = false
};
2023-01-03 18:47:25 +00:00
private static MatchResult Match(string query, IReadOnlyCollection<string> candidates)
2022-12-31 09:59:46 +00:00
{
if (candidates.Count == 0)
return null;
2023-01-03 18:47:25 +00:00
var match = candidates.Select(candidate => StringMatcher.FuzzySearch(query, candidate))
.MaxBy(match => match.Score);
return match?.IsSearchPrecisionScoreMet() ?? false ? match : null;
2022-12-31 09:59:46 +00:00
}
public Result Result(string query, IPublicAPI api)
{
string title;
MatchResult matchResult;
2021-02-14 21:19:48 +00:00
// Name of the result
2022-12-31 09:59:46 +00:00
// Check equality to avoid matching again in candidates
bool useLocalizedName = !string.IsNullOrEmpty(LocalizedName) && !Name.Equals(LocalizedName);
string resultName = useLocalizedName ? LocalizedName : Name;
if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || resultName.Equals(Description))
{
title = resultName;
2022-12-31 09:59:46 +00:00
matchResult = StringMatcher.FuzzySearch(query, resultName);
2021-02-15 03:49:18 +00:00
}
else
{
// Search in both
title = $"{resultName}: {Description}";
var nameMatch = StringMatcher.FuzzySearch(query, resultName);
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
if (descriptionMatch.Score > nameMatch.Score)
2022-12-31 09:59:46 +00:00
{
for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
{
descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": "
}
matchResult = descriptionMatch;
2022-12-31 09:59:46 +00:00
}
else
2021-02-14 21:19:48 +00:00
{
matchResult = nameMatch;
2021-02-14 21:19:48 +00:00
}
}
2021-02-15 03:49:18 +00:00
2022-12-31 09:59:46 +00:00
List<string> candidates = new List<string>();
if (!matchResult.IsSearchPrecisionScoreMet())
{
if (ExecutableName != null) // only lnk program will need this one
2022-12-31 09:59:46 +00:00
{
candidates.Add(ExecutableName);
}
if (useLocalizedName)
{
candidates.Add(Name);
}
matchResult = Match(query, candidates);
if (matchResult == null)
{
return null;
2022-12-31 09:59:46 +00:00
}
else
{
// Nothing to highlight in title in this case
matchResult.MatchData.Clear();
}
}
string subtitle = string.Empty;
if (!Main._settings.HideAppsPath)
{
if (Extension(FullPath) == UrlExtension)
{
subtitle = LnkResolvedPath;
}
else
{
subtitle = FullPath;
}
}
var result = new Result
{
Title = title,
AutoCompleteText = resultName,
SubTitle = subtitle,
IcoPath = IcoPath,
Score = matchResult.Score,
TitleHighlightData = matchResult.MatchData,
ContextData = this,
Action = c =>
{
// Ctrl + Enter to open containing folder
bool openFolder = c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Control;
if (openFolder)
{
Main.Context.API.OpenDirectory(ParentDirectory, FullPath);
return true;
}
// Ctrl + Shift + Enter to run as admin
bool runAsAdmin = c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift);
var info = new ProcessStartInfo
{
2022-11-21 10:05:35 +00:00
FileName = FullPath,
WorkingDirectory = ParentDirectory,
UseShellExecute = true,
2022-11-28 17:02:53 +00:00
Verb = runAsAdmin ? "runas" : ""
};
2022-11-28 17:02:53 +00:00
_ = Task.Run(() => Main.StartProcess(Process.Start, info));
return true;
}
};
return result;
}
public List<Result> ContextMenus(IPublicAPI api)
{
var contextMenus = new List<Result>
{
new Result
{
2020-04-21 12:54:41 +00:00
Title = api.GetTranslation("flowlauncher_plugin_program_run_as_different_user"),
Action = _ =>
{
var info = new ProcessStartInfo
{
2023-01-30 12:51:41 +00:00
FileName = FullPath,
WorkingDirectory = ParentDirectory,
UseShellExecute = true
};
Task.Run(() => Main.StartProcess(ShellCommand.RunAsDifferentUser, info));
return true;
},
2021-10-07 10:58:54 +00:00
IcoPath = "Images/user.png",
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ee"),
},
new Result
{
2020-04-21 12:54:41 +00:00
Title = api.GetTranslation("flowlauncher_plugin_program_run_as_administrator"),
Action = _ =>
{
var info = new ProcessStartInfo
{
2022-11-21 11:36:11 +00:00
FileName = FullPath,
2022-11-21 10:05:35 +00:00
WorkingDirectory = ParentDirectory,
Verb = "runas",
UseShellExecute = true
};
Task.Run(() => Main.StartProcess(Process.Start, info));
return true;
},
2021-10-07 10:58:54 +00:00
IcoPath = "Images/cmd.png",
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe7ef"),
},
new Result
{
2020-04-21 12:54:41 +00:00
Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
Action = _ =>
{
2021-11-06 06:10:29 +00:00
Main.Context.API.OpenDirectory(ParentDirectory, FullPath);
return true;
},
2021-10-07 10:58:54 +00:00
IcoPath = "Images/folder.png",
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xe838"),
}
};
return contextMenus;
}
public override string ToString()
{
2021-02-15 03:49:18 +00:00
return Name;
}
2022-11-21 10:11:53 +00:00
private static List<FileSystemWatcher> Watchers = new List<FileSystemWatcher>();
private static Win32 Win32Program(string path)
{
try
{
var p = new Win32
{
Name = Path.GetFileNameWithoutExtension(path),
IcoPath = path,
FullPath = path,
2022-10-20 09:00:18 +00:00
UniqueIdentifier = path,
ParentDirectory = Directory.GetParent(path).FullName,
Description = string.Empty,
Valid = true,
Enabled = true
};
return p;
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
2021-01-17 15:45:15 +00:00
$"|Permission denied when trying to load the program from {path}", e);
return Default;
}
2022-11-17 07:41:25 +00:00
#if !DEBUG
catch (Exception e)
{
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
"|An unexpected error occurred in the calling method Win32Program", e);
return Default;
2022-11-17 07:50:54 +00:00
}
2022-11-17 07:41:25 +00:00
#endif
}
private static Win32 LnkProgram(string path)
{
var program = Win32Program(path);
try
{
2016-08-20 17:18:41 +00:00
const int MAX_PATH = 260;
StringBuilder buffer = new StringBuilder(MAX_PATH);
ShellLinkHelper _helper = new ShellLinkHelper();
string target = _helper.retrieveTargetPath(path);
2022-12-31 09:49:38 +00:00
if (!string.IsNullOrEmpty(target) && File.Exists(target))
{
2022-12-31 09:49:38 +00:00
program.LnkResolvedPath = Path.GetFullPath(target);
program.ExecutableName = Path.GetFileNameWithoutExtension(target);
2022-12-31 16:24:36 +00:00
var args = _helper.arguments;
2023-01-03 18:47:25 +00:00
if (!string.IsNullOrEmpty(args))
2022-12-31 09:49:38 +00:00
{
2022-12-31 16:24:36 +00:00
program.LnkResolvedPath += " " + args;
}
2022-11-21 11:36:11 +00:00
2022-12-31 16:24:36 +00:00
var description = _helper.description;
if (!string.IsNullOrEmpty(description))
{
program.Description = description;
}
else
{
var info = FileVersionInfo.GetVersionInfo(target);
if (!string.IsNullOrEmpty(info.FileDescription))
{
2022-12-31 16:24:36 +00:00
program.Description = info.FileDescription;
}
}
}
2021-01-17 15:45:15 +00:00
program.LocalizedName = ShellLocalization.GetLocalizedName(path);
return program;
}
catch (FileNotFoundException e)
{
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
2023-01-03 18:47:25 +00:00
"|An unexpected error occurred in the calling method LnkProgram", e);
2022-11-17 07:41:25 +00:00
return Default;
}
#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.
2016-11-29 01:55:25 +00:00
catch (Exception e)
{
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
"|An unexpected error occurred in the calling method LnkProgram", e);
2022-11-17 07:41:25 +00:00
return Default;
}
#endif
}
2022-11-03 09:33:38 +00:00
private static Win32 UrlProgram(string path, string[] protocols)
2022-10-16 09:25:47 +00:00
{
var program = Win32Program(path);
2022-10-23 12:01:18 +00:00
program.Valid = false;
2022-10-16 09:25:47 +00:00
try
{
2022-10-23 12:01:18 +00:00
var parser = new FileIniDataParser();
var data = parser.ReadFile(path);
2022-10-16 09:25:47 +00:00
var urlSection = data["InternetShortcut"];
2022-10-24 06:14:23 +00:00
var url = urlSection?["URL"];
2022-10-24 13:50:31 +00:00
if (String.IsNullOrEmpty(url))
{
return program;
}
2023-01-30 12:44:48 +00:00
foreach (var protocol in protocols)
2022-10-23 12:01:18 +00:00
{
if (url.StartsWith(protocol))
2022-10-23 12:01:18 +00:00
{
2022-10-24 06:14:23 +00:00
program.LnkResolvedPath = url;
program.Valid = true;
break;
2022-10-23 12:01:18 +00:00
}
}
2022-10-16 09:25:47 +00:00
2022-10-24 06:14:23 +00:00
var iconPath = urlSection?["IconFile"];
2022-10-30 17:57:24 +00:00
if (!String.IsNullOrEmpty(iconPath))
2022-10-16 09:25:47 +00:00
{
program.IcoPath = iconPath;
}
}
catch (Exception e)
{
// Many files do not have the required fields, so no logging is done.
}
return program;
}
private static Win32 ExeProgram(string path)
{
try
{
var program = Win32Program(path);
var info = FileVersionInfo.GetVersionInfo(path);
if (!string.IsNullOrEmpty(info.FileDescription))
program.Description = info.FileDescription;
return program;
}
2022-11-17 07:41:25 +00:00
catch (FileNotFoundException e)
{
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
2023-01-03 18:47:25 +00:00
$"|File not found when trying to load the program from {path}", e);
2022-11-17 07:41:25 +00:00
return Default;
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
2021-01-17 15:45:15 +00:00
$"|Permission denied when trying to load the program from {path}", e);
return Default;
2016-08-20 00:17:28 +00:00
}
}
2022-11-17 17:16:57 +00:00
private static IEnumerable<string> EnumerateProgramsInDir(string directory, string[] suffixes, bool recursive = true)
{
if (!Directory.Exists(directory))
2021-01-17 15:45:15 +00:00
return Enumerable.Empty<string>();
2023-01-30 12:51:41 +00:00
return Directory.EnumerateFiles(
directory, "*",
2023-01-30 12:44:48 +00:00
new EnumerationOptions { IgnoreInaccessible = true, RecurseSubdirectories = recursive })
.Where(x => suffixes.Contains(Extension(x)));
}
private static string Extension(string path)
{
2022-10-29 18:37:22 +00:00
var extension = Path.GetExtension(path)?.ToLowerInvariant();
if (!string.IsNullOrEmpty(extension))
2016-08-20 00:17:28 +00:00
{
2023-01-03 18:47:25 +00:00
return extension.Substring(1); // remove dot
2016-08-20 00:17:28 +00:00
}
else
{
return string.Empty;
2016-08-20 00:17:28 +00:00
}
}
2016-08-20 00:17:28 +00:00
2022-11-17 08:02:39 +00:00
private static IEnumerable<Win32> UnregisteredPrograms(List<string> directories, string[] suffixes, string[] protocols)
{
2022-11-03 10:38:36 +00:00
// Disabled custom sources are not in DisabledProgramSources
2022-11-17 08:02:39 +00:00
var paths = directories.AsParallel()
2023-01-03 18:47:25 +00:00
.SelectMany(s => EnumerateProgramsInDir(s, suffixes));
2022-11-17 06:32:29 +00:00
// Remove disabled programs in DisabledProgramSources
var programs = ExceptDisabledSource(paths).Select(x => GetProgramFromPath(x, protocols));
return programs;
}
2022-11-03 09:33:38 +00:00
private static IEnumerable<Win32> StartMenuPrograms(string[] suffixes, string[] protocols)
{
var allPrograms = GetStartMenuPaths()
.SelectMany(p => EnumerateProgramsInDir(p, suffixes))
.Distinct();
var startupPaths = GetStartupPaths();
var programs = ExceptDisabledSource(allPrograms)
.Where(x => !startupPaths.Any(startup => FilesFolders.PathContains(startup, x)))
2022-11-04 09:09:23 +00:00
.Select(x => GetProgramFromPath(x, protocols));
return programs;
}
private static IEnumerable<Win32> PATHPrograms(string[] suffixes, string[] protocols, List<string> commonParents)
{
2022-10-16 13:15:40 +00:00
var pathEnv = Environment.GetEnvironmentVariable("Path");
2022-11-17 17:42:15 +00:00
if (String.IsNullOrEmpty(pathEnv))
{
return Array.Empty<Win32>();
}
2022-10-31 17:18:54 +00:00
var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant());
2023-01-30 12:44:48 +00:00
var toFilter = paths.Where(x => commonParents.All(parent => !FilesFolders.PathContains(parent, x)))
2023-01-03 18:47:25 +00:00
.AsParallel()
.SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false));
2022-10-16 13:15:40 +00:00
var programs = ExceptDisabledSource(toFilter.Distinct())
2022-11-17 07:04:09 +00:00
.Select(x => GetProgramFromPath(x, protocols));
return programs;
}
2022-11-14 07:27:06 +00:00
private static IEnumerable<Win32> AppPathsPrograms(string[] suffixes, string[] protocols)
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
IEnumerable<string> toFilter = Enumerable.Empty<string>();
using var rootMachine = Registry.LocalMachine.OpenSubKey(appPaths);
using var rootUser = Registry.CurrentUser.OpenSubKey(appPaths);
if (rootMachine != null)
2016-08-20 00:17:28 +00:00
{
toFilter = toFilter.Concat(GetPathFromRegistry(rootMachine));
}
2021-01-17 15:45:15 +00:00
if (rootUser != null)
{
toFilter = toFilter.Concat(GetPathFromRegistry(rootUser));
}
toFilter = toFilter.Distinct().Where(p => suffixes.Contains(Extension(p)));
2022-11-03 09:33:38 +00:00
var programs = ExceptDisabledSource(toFilter)
2023-01-03 18:47:25 +00:00
.Select(x => GetProgramFromPath(x, protocols)).Where(x => x.Valid).ToList(); // ToList due to disposing issue
2022-11-03 09:33:38 +00:00
return programs;
}
private static IEnumerable<string> GetPathFromRegistry(RegistryKey root)
{
return root
2021-01-17 15:45:15 +00:00
.GetSubKeyNames()
.Select(x => GetProgramPathFromRegistrySubKeys(root, x))
.Distinct();
}
2022-12-31 15:43:07 +00:00
private static string GetProgramPathFromRegistrySubKeys(RegistryKey root, string subKey)
{
var path = string.Empty;
try
{
2022-12-31 15:43:07 +00:00
using (var key = root.OpenSubKey(subKey))
{
if (key == null)
return string.Empty;
var defaultValue = string.Empty;
path = key.GetValue(defaultValue) as string;
}
if (string.IsNullOrEmpty(path))
return string.Empty;
// fix path like this: ""\"C:\\folder\\executable.exe\""
return path = path.Trim('"', ' ');
2016-08-20 00:17:28 +00:00
}
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
{
ProgramLogger.LogException($"|Win32|GetProgramPathFromRegistrySubKeys|{path}" +
2021-01-17 15:45:15 +00:00
$"|Permission denied when trying to load the program from {path}", e);
return string.Empty;
}
}
2022-11-03 09:33:38 +00:00
private static Win32 GetProgramFromPath(string path, string[] protocols)
{
if (string.IsNullOrEmpty(path))
return Default;
path = Environment.ExpandEnvironmentVariables(path);
2022-11-03 09:33:38 +00:00
return Extension(path) switch
{
ShortcutExtension => LnkProgram(path),
ExeExtension => ExeProgram(path),
UrlExtension => UrlProgram(path, protocols),
_ => Win32Program(path)
2023-01-03 18:47:25 +00:00
};
;
}
2022-10-24 17:13:36 +00:00
public static IEnumerable<string> ExceptDisabledSource(IEnumerable<string> paths)
2021-02-08 04:56:40 +00:00
{
2022-10-24 17:13:36 +00:00
return ExceptDisabledSource(paths, x => x.ToLowerInvariant());
2021-02-08 04:56:40 +00:00
}
public static IEnumerable<TSource> ExceptDisabledSource<TSource>(IEnumerable<TSource> sources,
Func<TSource, string> keySelector)
{
return Main._settings.DisabledProgramSources.Count == 0
? sources
: ExceptDisabledSourceEnumerable(sources, keySelector);
static IEnumerable<TSource> ExceptDisabledSourceEnumerable(IEnumerable<TSource> elements,
Func<TSource, string> selector)
{
var set = Main._settings.DisabledProgramSources.Select(x => x.UniqueIdentifier).ToHashSet();
foreach (var element in elements)
{
if (!set.Contains(selector(element)))
yield return element;
}
}
}
2021-02-15 03:49:18 +00:00
public static IEnumerable<T> DistinctBy<T, R>(IEnumerable<T> source, Func<T, R> selector)
{
var set = new HashSet<R>();
foreach (var item in source)
2021-02-08 04:56:40 +00:00
{
if (set.Add(selector(item)))
yield return item;
2021-02-08 04:56:40 +00:00
}
}
private static IEnumerable<Win32> ProgramsHasher(IEnumerable<Win32> programs)
2021-01-17 15:45:15 +00:00
{
var startMenuPaths = GetStartMenuPaths();
return programs.GroupBy(p => p.ExecutablePath.ToLowerInvariant())
2022-10-29 18:37:22 +00:00
.AsParallel()
2021-01-17 15:45:15 +00:00
.SelectMany(g =>
{
// is shortcut and in start menu
var startMenu = g.Where(g => g.LnkResolvedPath != null && startMenuPaths.Any(x => FilesFolders.PathContains(x, g.FullPath))).ToList();
if (startMenu.Any())
return startMenu.Take(1);
// distinct by description
var temp = g.Where(g => !string.IsNullOrEmpty(g.Description)).ToList();
if (temp.Any())
return temp.Take(1);
return g.Take(1);
2022-11-16 17:27:58 +00:00
});
2021-01-17 15:45:15 +00:00
}
public static Win32[] All(Settings settings)
{
try
{
var programs = Enumerable.Empty<Win32>();
2022-11-03 09:33:38 +00:00
var suffixes = settings.GetSuffixes();
var protocols = settings.GetProtocols();
2022-11-17 08:02:39 +00:00
// Disabled custom sources are not in DisabledProgramSources
var sources = settings.ProgramSources.Where(s => Directory.Exists(s.Location) && s.Enabled).Distinct();
var commonParents = GetCommonParents(sources);
var unregistered = UnregisteredPrograms(commonParents, suffixes, protocols);
programs = programs.Concat(unregistered);
2022-11-17 06:32:29 +00:00
var autoIndexPrograms = Enumerable.Empty<Win32>(); // for single programs, not folders
if (settings.EnableRegistrySource)
{
2022-11-03 09:33:38 +00:00
var appPaths = AppPathsPrograms(suffixes, protocols);
autoIndexPrograms = autoIndexPrograms.Concat(appPaths);
}
if (settings.EnableStartMenuSource)
{
2022-11-03 09:33:38 +00:00
var startMenu = StartMenuPrograms(suffixes, protocols);
autoIndexPrograms = autoIndexPrograms.Concat(startMenu);
}
2021-01-17 15:45:15 +00:00
2022-12-17 13:29:25 +00:00
if (settings.EnablePathSource)
{
var path = PATHPrograms(settings.GetSuffixes(), protocols, commonParents);
programs = programs.Concat(path);
}
2022-11-16 17:27:58 +00:00
autoIndexPrograms = ProgramsHasher(autoIndexPrograms).ToArray();
2022-11-03 17:50:28 +00:00
return programs.Concat(autoIndexPrograms).Where(x => x.Valid).Distinct().ToArray();
}
#if DEBUG //This is to make developer aware of any unhandled exception and add in handling.
catch (Exception)
2016-08-20 00:17:28 +00:00
{
throw;
2019-10-24 02:32:12 +00:00
}
#endif
2019-10-24 02:32:12 +00:00
#if !DEBUG //Only do a catch all in production.
catch (Exception e)
{
ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e);
return Array.Empty<Win32>();
}
#endif
}
public override int GetHashCode()
{
return UniqueIdentifier.GetHashCode();
}
public bool Equals([AllowNull] Win32 other)
{
if (other == null)
return false;
return UniqueIdentifier == other.UniqueIdentifier;
}
public override bool Equals(object obj)
{
if (obj is Win32 other)
{
return UniqueIdentifier == other.UniqueIdentifier;
}
else
{
return false;
}
}
private static IEnumerable<string> GetStartMenuPaths()
{
var userStartMenu = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
var commonStartMenu = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu);
return new[] { userStartMenu, commonStartMenu };
}
private static IEnumerable<string> GetStartupPaths()
{
var userStartup = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
var commonStartup = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartup);
return new[] { userStartup, commonStartup };
}
public static void WatchProgramUpdate(Settings settings)
{
var paths = new List<string>();
if (settings.EnableStartMenuSource)
paths.AddRange(GetStartMenuPaths());
2022-11-17 09:39:09 +00:00
var customSources = GetCommonParents(settings.ProgramSources);
paths.AddRange(customSources);
2022-11-17 09:39:09 +00:00
var fileExtensionToWatch = settings.GetSuffixes();
foreach (var directory in from path in paths where Directory.Exists(path) select path)
{
2022-11-17 09:39:09 +00:00
WatchDirectory(directory, fileExtensionToWatch);
}
_ = Task.Run(MonitorDirectoryChangeAsync);
}
private static Channel<byte> indexQueue = Channel.CreateBounded<byte>(1);
public static async Task MonitorDirectoryChangeAsync()
{
var reader = indexQueue.Reader;
while (await reader.WaitToReadAsync())
{
await Task.Delay(500);
while (reader.TryRead(out _))
{
}
2022-02-24 00:21:13 +00:00
await Task.Run(Main.IndexWin32Programs);
}
}
2022-11-17 09:39:09 +00:00
public static void WatchDirectory(string directory, string[] extensions)
{
if (!Directory.Exists(directory))
{
throw new ArgumentException("Path Not Exist");
}
var watcher = new FileSystemWatcher(directory);
watcher.Created += static (_, _) => indexQueue.Writer.TryWrite(default);
watcher.Deleted += static (_, _) => indexQueue.Writer.TryWrite(default);
watcher.EnableRaisingEvents = true;
watcher.IncludeSubdirectories = true;
2022-11-17 17:42:15 +00:00
foreach (var extension in extensions)
{
2022-11-17 09:39:09 +00:00
watcher.Filters.Add($"*.{extension}");
}
2022-02-24 00:19:34 +00:00
Watchers.Add(watcher);
}
public static void Dispose()
{
foreach (var fileSystemWatcher in Watchers)
{
fileSystemWatcher.Dispose();
}
}
2022-11-17 07:50:54 +00:00
2022-11-17 08:02:39 +00:00
private static List<string> GetCommonParents(IEnumerable<ProgramSource> programSources)
2022-11-17 07:50:54 +00:00
{
// To avoid unnecessary io
// like c:\windows and c:\windows\system32
var grouped = programSources.GroupBy(p => p.Location.ToLowerInvariant()[0]); // group by disk
List<string> result = new();
foreach (var group in grouped)
{
HashSet<ProgramSource> parents = group.ToHashSet();
foreach (var source in group)
{
if (parents.Any(p => FilesFolders.PathContains(p.Location, source.Location)))
2022-11-17 07:50:54 +00:00
{
parents.Remove(source);
}
}
result.AddRange(parents.Select(x => x.Location));
}
return result.DistinctBy(x => x.ToLowerInvariant()).ToList();
}
}
}