From 73056534bb413516740982dca7327234edc8436b Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sat, 8 Jan 2022 15:50:27 -0600 Subject: [PATCH] Reindex when app install or deleted (and uwp update). --- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 16 +- .../Programs/UWP.cs | 151 +++++++++++++----- .../Programs/Win32.cs | 83 +++++++++- 3 files changed, 198 insertions(+), 52 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 634382109..cb0a2a4f3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -18,7 +18,7 @@ using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program { - public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable + public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, ISavable, IAsyncReloadable, IDisposable { internal static Win32[] _win32s { get; set; } internal static UWP.Application[] _uwps { get; set; } @@ -126,6 +126,9 @@ namespace Flow.Launcher.Plugin.Program if (!(_win32s.Any() && _uwps.Any())) await indexTask; + + Win32.WatchProgramUpdate(_settings); + UWP.WatchUWPInstallation(); } public static void IndexWin32Programs() @@ -209,13 +212,11 @@ namespace Flow.Launcher.Plugin.Program return; if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _uwps.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) - .FirstOrDefault() + _uwps.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)! .Enabled = false; if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) - _win32s.Where(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier) - .FirstOrDefault() + _win32s.FirstOrDefault(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)! .Enabled = false; _settings.DisabledProgramSources @@ -248,5 +249,10 @@ namespace Flow.Launcher.Plugin.Program { await IndexPrograms(); } + public void Dispose() + { + Win32.Dispose(); + UWP.Dispose(); + } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 2a04b2b2e..b3d39bdbc 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -78,7 +78,7 @@ namespace Flow.Launcher.Plugin.Program.Programs { var e = Marshal.GetExceptionForHR((int)hResult); ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" + - "|Error caused while trying to get the details of the UWP program", e); + "|Error caused while trying to get the details of the UWP program", e); Apps = new List().ToArray(); } @@ -91,28 +91,29 @@ namespace Flow.Launcher.Plugin.Program.Programs + /// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx private string[] XmlNamespaces(string path) { XDocument z = XDocument.Load(path); if (z.Root != null) { - var namespaces = z.Root.Attributes(). - Where(a => a.IsNamespaceDeclaration). - GroupBy( - a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, - a => XNamespace.Get(a.Value) - ).Select( - g => g.First().ToString() - ).ToArray(); + var namespaces = z.Root.Attributes().Where(a => a.IsNamespaceDeclaration).GroupBy( + a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName, + a => XNamespace.Get(a.Value) + ).Select( + g => g.First().ToString() + ).ToArray(); return namespaces; } else { ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" + - $"|Error occured while trying to get the XML from {path}", new ArgumentNullException()); + $"|Error occured while trying to get the XML from {path}", new ArgumentNullException()); - return new string[] { }; + return new string[] + { + }; } } @@ -120,9 +121,15 @@ namespace Flow.Launcher.Plugin.Program.Programs { var versionFromNamespace = new Dictionary { - {"http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10}, - {"http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81}, - {"http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8}, + { + "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 + }, + { + "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 + }, + { + "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 + }, }; foreach (var n in versionFromNamespace.Keys) @@ -135,8 +142,8 @@ namespace Flow.Launcher.Plugin.Program.Programs } ProgramLogger.LogException($"|UWP|XmlNamespaces|{Location}" + - "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version " - + $"{FullName} from location {Location} is returned.", new FormatException()); + "|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version " + + $"{FullName} from location {Location} is returned.", new FormatException()); Version = PackageVersion.Unknown; } @@ -172,15 +179,17 @@ namespace Flow.Launcher.Plugin.Program.Programs }).ToArray(); var updatedListWithoutDisabledApps = applications - .Where(t1 => !Main._settings.DisabledProgramSources - .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) - .Select(x => x); + .Where(t1 => !Main._settings.DisabledProgramSources + .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) + .Select(x => x); return updatedListWithoutDisabledApps.ToArray(); } else { - return new Application[] { }; + return new Application[] + { + }; } } @@ -216,7 +225,7 @@ namespace Flow.Launcher.Plugin.Program.Programs catch (Exception e) { ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occured and " - + $"unable to verify if package is valid", e); + + $"unable to verify if package is valid", e); return false; } @@ -226,10 +235,30 @@ namespace Flow.Launcher.Plugin.Program.Programs } else { - return new Package[] { }; + return new Package[] + { + }; } } + private static List _watchers = new(); + + private static void GenerateWatcher(string path) + { + var watcher = new FileSystemWatcher(path); + watcher.Created += static (_, _) => Task.Run(Main.IndexUwpPrograms); + watcher.Deleted += static (_, _) => Task.Run(Main.IndexUwpPrograms); + watcher.EnableRaisingEvents = true; + } + + public static void WatchUWPInstallation() + { + PackageCatalog.OpenForCurrentUser().PackageStatusChanged += (_, _) => + { + Task.Delay(10000).ContinueWith(t => Main.IndexUwpPrograms()); + }; + } + public override string ToString() { return FamilyName; @@ -326,7 +355,7 @@ namespace Flow.Launcher.Plugin.Program.Programs e.SpecialKeyState.ShiftPressed && !e.SpecialKeyState.AltPressed && !e.SpecialKeyState.WinPressed - ); + ); if (elevated && CanRunElevated) { @@ -359,14 +388,12 @@ namespace Flow.Launcher.Plugin.Program.Programs new Result { Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"), - Action = _ => { Main.Context.API.OpenDirectory(Package.Location); return true; }, - IcoPath = "Images/folder.png" } }; @@ -415,8 +442,7 @@ namespace Flow.Launcher.Plugin.Program.Programs var info = new ProcessStartInfo(command) { - UseShellExecute = true, - Verb = "runas", + UseShellExecute = true, Verb = "runas", }; Main.StartProcess(Process.Start, info); @@ -493,7 +519,7 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result " - + $"pri {source} in uwp location {Package.Location}", new NullReferenceException()); + + $"pri {source} in uwp location {Package.Location}", new NullReferenceException()); return string.Empty; } } @@ -533,9 +559,15 @@ namespace Flow.Launcher.Plugin.Program.Programs { var logoKeyFromVersion = new Dictionary { - { PackageVersion.Windows10, "Square44x44Logo" }, - { PackageVersion.Windows81, "Square30x30Logo" }, - { PackageVersion.Windows8, "SmallLogo" }, + { + PackageVersion.Windows10, "Square44x44Logo" + }, + { + PackageVersion.Windows81, "Square30x30Logo" + }, + { + PackageVersion.Windows8, "SmallLogo" + }, }; if (logoKeyFromVersion.ContainsKey(Package.Version)) { @@ -572,14 +604,40 @@ namespace Flow.Launcher.Plugin.Program.Programs { var end = path.Length - extension.Length; var prefix = path.Substring(0, end); - var paths = new List { path }; + var paths = new List + { + path + }; var scaleFactors = new Dictionary> { // scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables, - { PackageVersion.Windows10, new List { 100, 125, 150, 200, 400 } }, - { PackageVersion.Windows81, new List { 100, 120, 140, 160, 180 } }, - { PackageVersion.Windows8, new List { 100 } } + { + PackageVersion.Windows10, new List + { + 100, + 125, + 150, + 200, + 400 + } + }, + { + PackageVersion.Windows81, new List + { + 100, + 120, + 140, + 160, + 180 + } + }, + { + PackageVersion.Windows8, new List + { + 100 + } + } }; if (scaleFactors.ContainsKey(Package.Version)) @@ -598,15 +656,15 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + - $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException()); + $"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException()); return string.Empty; } } else { ProgramLogger.LogException($"|UWP|LogoPathFromUri|{Package.Location}" + - $"|Unable to find extension from {uri} for {UserModelId} " + - $"in package location {Package.Location}", new FileNotFoundException()); + $"|Unable to find extension from {uri} for {UserModelId} " + + $"in package location {Package.Location}", new FileNotFoundException()); return string.Empty; } } @@ -633,8 +691,8 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|ImageFromPath|{(string.IsNullOrEmpty(path) ? "Not Avaliable" : path)}" + - $"|Unable to get logo for {UserModelId} from {path} and" + - $" located in {Package.Location}", new FileNotFoundException()); + $"|Unable to get logo for {UserModelId} from {path} and" + + $" located in {Package.Location}", new FileNotFoundException()); return new BitmapImage(new Uri(Constant.MissingImgIcon)); } } @@ -682,8 +740,8 @@ namespace Flow.Launcher.Plugin.Program.Programs else { ProgramLogger.LogException($"|UWP|PlatedImage|{Package.Location}" + - $"|Unable to convert background string {BackgroundColor} " + - $"to color for {Package.Location}", new InvalidOperationException()); + $"|Unable to convert background string {BackgroundColor} " + + $"to color for {Package.Location}", new InvalidOperationException()); return new BitmapImage(new Uri(Constant.MissingImgIcon)); } @@ -701,6 +759,14 @@ namespace Flow.Launcher.Plugin.Program.Programs } } + public static void Dispose() + { + foreach (var fileSystemWatcher in _watchers) + { + fileSystemWatcher.Dispose(); + } + } + public enum PackageVersion { Windows10, @@ -728,5 +794,6 @@ namespace Flow.Launcher.Plugin.Program.Programs [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); + } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs index 1464bd785..3e568fa19 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/Win32.cs @@ -12,9 +12,11 @@ using Flow.Launcher.Plugin.Program.Logger; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Infrastructure.Logger; +using System.Collections; using System.Diagnostics; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; using System.Diagnostics.CodeAnalysis; +using System.Threading.Channels; namespace Flow.Launcher.Plugin.Program.Programs { @@ -109,7 +111,7 @@ namespace Flow.Launcher.Plugin.Program.Programs c.SpecialKeyState.ShiftPressed && !c.SpecialKeyState.AltPressed && !c.SpecialKeyState.WinPressed - ); + ); var info = new ProcessStartInfo { @@ -194,6 +196,9 @@ namespace Flow.Launcher.Plugin.Program.Programs return Name; } + public static List Watchers = new List(); + + private static Win32 Win32Program(string path) { try @@ -216,7 +221,10 @@ namespace Flow.Launcher.Plugin.Program.Programs ProgramLogger.LogException($"|Win32|Win32Program|{path}" + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() { Valid = false, Enabled = false }; + return new Win32() + { + Valid = false, Enabled = false + }; } } @@ -294,7 +302,10 @@ namespace Flow.Launcher.Plugin.Program.Programs ProgramLogger.LogException($"|Win32|ExeProgram|{path}" + $"|Permission denied when trying to load the program from {path}", e); - return new Win32() { Valid = false, Enabled = false }; + return new Win32() + { + Valid = false, Enabled = false + }; } } @@ -305,8 +316,7 @@ namespace Flow.Launcher.Plugin.Program.Programs return Directory.EnumerateFiles(directory, "*", new EnumerationOptions { - IgnoreInaccessible = true, - RecurseSubdirectories = true + IgnoreInaccessible = true, RecurseSubdirectories = true }).Where(x => suffixes.Contains(Extension(x))); } @@ -545,5 +555,68 @@ namespace Flow.Launcher.Plugin.Program.Programs return UniqueIdentifier == other.UniqueIdentifier; } + + private static IEnumerable GetStartMenuPaths() + { + var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs); + var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms); + return new[] + { + directory1, directory2 + }; + } + + public static void WatchProgramUpdate(Settings settings) + { + var paths = new List(); + if (settings.EnableStartMenuSource) + paths.AddRange(GetStartMenuPaths()); + + paths.AddRange(from source in settings.ProgramSources where source.Enabled select source.Location); + + foreach (var directory in from path in paths where Directory.Exists(path) select path) + { + WatchDirectory(directory); + } + + _ = Task.Run(MonitorDirectoryChangeAsync); + } + + private static Channel indexQueue = Channel.CreateBounded(1); + + public static async Task MonitorDirectoryChangeAsync() + { + var reader = indexQueue.Reader; + while (await reader.WaitToReadAsync()) + { + await Task.Delay(500); + while (reader.TryRead(out _)) + { + } + Main.IndexWin32Programs(); + } + } + + public static void WatchDirectory(string directory) + { + 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; + } + + public static void Dispose() + { + foreach (var fileSystemWatcher in Watchers) + { + fileSystemWatcher.Dispose(); + } + } } } \ No newline at end of file