From 5b804a1ff6dd5ddbf80c2008d4209a7322b1e4fb Mon Sep 17 00:00:00 2001 From: Roy Date: Mon, 14 Jun 2021 10:19:05 +0200 Subject: [PATCH] [PT Run] Improve the UWP Program Indexing speed (#11683) * Improve UWP Indexing speed * Optimize Linq usage Added static readonly vars * Optimzie GetAppsFromManifest * LogoUriFromManifest uses logoUri which is also an instance variable * Add IsPackageDotInstallationPathAvailable * Dispose FileStream * Fix InstalledPath after testing in build 1903 * Fix typo --- .../Programs/AppxPackageHelper.cs | 12 +-- .../Programs/PackageWrapper.cs | 10 +- .../Programs/PackagemanagerWrapper.cs | 33 ++++--- .../Microsoft.Plugin.Program/Programs/UWP.cs | 55 ++++------- .../Programs/UWPApplication.cs | 95 ++++++++++--------- 5 files changed, 97 insertions(+), 108 deletions(-) diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/AppxPackageHelper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/AppxPackageHelper.cs index 15419542c8..95e53670df 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/AppxPackageHelper.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/AppxPackageHelper.cs @@ -11,12 +11,12 @@ namespace Microsoft.Plugin.Program.Programs { public static class AppxPackageHelper { + private static readonly IAppxFactory AppxFactory = (IAppxFactory)new AppxFactory(); + // This function returns a list of attributes of applications - public static List GetAppsFromManifest(IStream stream) + public static IEnumerable GetAppsFromManifest(IStream stream) { - List apps = new List(); - var appxFactory = new AppxFactory(); - var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream); + var reader = AppxFactory.CreateManifestReader(stream); var manifestApps = reader.GetApplications(); while (manifestApps.GetHasCurrent()) @@ -26,13 +26,11 @@ namespace Microsoft.Plugin.Program.Programs _ = CheckHRAndReturnOrThrow(hr, appListEntry); if (appListEntry != "none") { - apps.Add(manifestApp); + yield return manifestApp; } manifestApps.MoveNext(); } - - return apps; } public static T CheckHRAndReturnOrThrow(Hresult hr, T result) diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackageWrapper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackageWrapper.cs index fdcc0a7161..afe3c0e50c 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackageWrapper.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackageWrapper.cs @@ -6,6 +6,7 @@ using System; using System.IO; using System.Reflection; using Microsoft.Plugin.Program.Logger; +using Windows.Foundation.Metadata; using Package = Windows.ApplicationModel.Package; namespace Microsoft.Plugin.Program.Programs @@ -38,6 +39,9 @@ namespace Microsoft.Plugin.Program.Programs InstalledLocation = installedLocation; } + private static readonly Lazy IsPackageDotInstallationPathAvailable = new Lazy(() => + ApiInformation.IsPropertyPresent(typeof(Package).FullName, nameof(Package.InstalledPath))); + public static PackageWrapper GetWrapperFromPackage(Package package) { if (package == null) @@ -48,7 +52,7 @@ namespace Microsoft.Plugin.Program.Programs string path; try { - path = package.InstalledLocation.Path; + path = IsPackageDotInstallationPathAvailable.Value ? GetInstalledPath(package) : package.InstalledLocation.Path; } catch (Exception e) when (e is ArgumentException || e is FileNotFoundException || e is DirectoryNotFoundException) { @@ -70,5 +74,9 @@ namespace Microsoft.Plugin.Program.Programs package.IsDevelopmentMode, path); } + + // This is a separate method so the reference to .InstalledPath won't be loaded in API versions which do not support this API (e.g. older then Build 19041) + private static string GetInstalledPath(Package package) + => package.InstalledPath; } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackagemanagerWrapper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackagemanagerWrapper.cs index a2b475e37e..258e81f337 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackagemanagerWrapper.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/PackagemanagerWrapper.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security.Principal; using Windows.Management.Deployment; using Wox.Plugin.Logger; @@ -20,30 +21,28 @@ namespace Microsoft.Plugin.Program.Programs _packageManager = new PackageManager(); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to catch all exception to prevent error in a program from affecting loading of program plugin.")] public IEnumerable FindPackagesForCurrentUser() { - List packages = new List(); var user = WindowsIdentity.GetCurrent().User; - if (user != null) + return user != null + ? _packageManager.FindPackagesForUser(user.Value).Select(TryGetWrapperFromPackage).Where(package => package != null) + : Enumerable.Empty(); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to catch all exception to prevent error in a program from affecting loading of program plugin.")] + private static PackageWrapper TryGetWrapperFromPackage(Package package) + { + try { - var id = user.Value; - var m = _packageManager.FindPackagesForUser(id); - foreach (Package p in m) - { - try - { - packages.Add(PackageWrapper.GetWrapperFromPackage(p)); - } - catch (Exception e) - { - Log.Error(e.Message, GetType()); - } - } + return PackageWrapper.GetWrapperFromPackage(package); + } + catch (Exception e) + { + Log.Error(e.Message, typeof(PackageManagerWrapper)); } - return packages; + return null; } } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs index b9df47e869..8f7c7c1503 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -21,6 +21,13 @@ namespace Microsoft.Plugin.Program.Programs { private static readonly IPath Path = new FileSystem().Path; + private static readonly Dictionary _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 }, + }; + public string Name { get; } public string FullName { get; } @@ -61,16 +68,7 @@ namespace Microsoft.Plugin.Program.Programs if (hResult == Hresult.Ok) { - var apps = new List(); - - List appsViaManifests = AppxPackageHelper.GetAppsFromManifest(stream); - foreach (var appInManifest in appsViaManifests) - { - var app = new UWPApplication(appInManifest, this); - apps.Add(app); - } - - Apps = apps.Where(a => + Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest => new UWPApplication(appInManifest, this)).Where(a => { var valid = !string.IsNullOrEmpty(a.UserModelId) && @@ -78,7 +76,7 @@ namespace Microsoft.Plugin.Program.Programs a.AppListEntry != "none"; return valid; - }).ToArray(); + }).ToList(); if (Marshal.ReleaseComObject(stream) > 0) { @@ -90,7 +88,7 @@ namespace Microsoft.Plugin.Program.Programs var e = Marshal.GetExceptionForHR((int)hResult); ProgramLogger.Exception("Error caused while trying to get the details of the UWP program", e, GetType(), path); - Apps = new List().ToArray(); + Apps = Array.Empty(); } } @@ -118,20 +116,10 @@ namespace Microsoft.Plugin.Program.Programs private void InitPackageVersion(string[] namespaces) { - var versionFromNamespace = new Dictionary + foreach (var n in _versionFromNamespace.Keys.Where(namespaces.Contains)) { - { "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) - { - if (namespaces.Contains(n)) - { - Version = versionFromNamespace[n]; - return; - } + Version = _versionFromNamespace[n]; + return; } ProgramLogger.Exception($"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version {FullName} from location {Location} is returned.", new FormatException(), GetType(), Location); @@ -162,11 +150,10 @@ namespace Microsoft.Plugin.Program.Programs } return u.Apps; - }).ToArray(); + }); var updatedListWithoutDisabledApps = applications - .Where(t1 => !Main.Settings.DisabledProgramSources - .Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) + .Where(t1 => Main.Settings.DisabledProgramSources.All(x => x.UniqueIdentifier != t1.UniqueIdentifier)) .Select(x => x); return updatedListWithoutDisabledApps.ToArray(); @@ -180,26 +167,20 @@ namespace Microsoft.Plugin.Program.Programs [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive.")] private static IEnumerable CurrentUserPackages() { - var ps = PackageManagerWrapper.FindPackagesForCurrentUser(); - ps = ps.Where(p => + return PackageManagerWrapper.FindPackagesForCurrentUser().Where(p => { - bool valid; try { var f = p.IsFramework; var path = p.InstalledLocation; - valid = !f && !string.IsNullOrEmpty(path); + return !f && !string.IsNullOrEmpty(path); } catch (Exception e) { ProgramLogger.Exception("An unexpected error occurred and unable to verify if package is valid", e, MethodBase.GetCurrentMethod().DeclaringType, "id"); return false; } - - return valid; }); - - return ps; } public override string ToString() diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWPApplication.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWPApplication.cs index feb52497c3..d4e6abed86 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWPApplication.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWPApplication.cs @@ -349,20 +349,20 @@ namespace Microsoft.Plugin.Program.Programs } } + private static readonly Dictionary _logoKeyFromVersion = new Dictionary + { + { PackageVersion.Windows10, "Square44x44Logo" }, + { PackageVersion.Windows81, "Square30x30Logo" }, + { PackageVersion.Windows8, "SmallLogo" }, + }; + internal string LogoUriFromManifest(IAppxManifestApplication app) { - var logoKeyFromVersion = new Dictionary - { - { PackageVersion.Windows10, "Square44x44Logo" }, - { PackageVersion.Windows81, "Square30x30Logo" }, - { PackageVersion.Windows8, "SmallLogo" }, - }; - if (logoKeyFromVersion.ContainsKey(Package.Version)) + if (_logoKeyFromVersion.TryGetValue(Package.Version, out var key)) { - var key = logoKeyFromVersion[Package.Version]; - var hr = app.GetStringValue(key, out var logoUri); - _ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUri); - return logoUri; + var hr = app.GetStringValue(key, out var logoUriFromApp); + _ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUriFromApp); + return logoUriFromApp; } else { @@ -372,9 +372,17 @@ namespace Microsoft.Plugin.Program.Programs public void UpdatePath(Theme theme) { - LogoPathFromUri(this.logoUri, theme); + LogoPathFromUri(logoUri, theme); } + // scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables, + private static readonly Dictionary> _scaleFactors = new Dictionary> + { + { PackageVersion.Windows10, new List { 100, 125, 150, 200, 400 } }, + { PackageVersion.Windows81, new List { 100, 120, 140, 160, 180 } }, + { PackageVersion.Windows8, new List { 100 } }, + }; + private bool SetScaleIcons(string path, string colorscheme, bool highContrast = false) { var extension = Path.GetExtension(path); @@ -383,22 +391,15 @@ namespace Microsoft.Plugin.Program.Programs var end = path.Length - extension.Length; var prefix = path.Substring(0, end); var paths = new List { }; - 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 } }, - }; if (!highContrast) { paths.Add(path); } - if (scaleFactors.ContainsKey(Package.Version)) + if (_scaleFactors.ContainsKey(Package.Version)) { - foreach (var factor in scaleFactors[Package.Version]) + foreach (var factor in _scaleFactors[Package.Version]) { if (highContrast) { @@ -440,7 +441,7 @@ namespace Microsoft.Plugin.Program.Programs var end = path.Length - extension.Length; var prefix = path.Substring(0, end); var paths = new List { }; - int appIconSize = 36; + const int appIconSize = 36; var targetSizes = new List { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel(); var pathFactorPairs = new Dictionary(); @@ -564,21 +565,22 @@ namespace Microsoft.Plugin.Program.Programs path = Path.Combine(Package.Location, "Assets", uri); } - if (theme == Theme.HighContrastBlack || theme == Theme.HighContrastOne || theme == Theme.HighContrastTwo) + switch (theme) { - isLogoUriSet = SetHighContrastIcon(path, ContrastBlack); - } - else if (theme == Theme.HighContrastWhite) - { - isLogoUriSet = SetHighContrastIcon(path, ContrastWhite); - } - else if (theme == Theme.Light) - { - isLogoUriSet = SetColoredIcon(path, ContrastWhite); - } - else - { - isLogoUriSet = SetColoredIcon(path, ContrastBlack); + case Theme.HighContrastBlack: + case Theme.HighContrastOne: + case Theme.HighContrastTwo: + isLogoUriSet = SetHighContrastIcon(path, ContrastBlack); + break; + case Theme.HighContrastWhite: + isLogoUriSet = SetHighContrastIcon(path, ContrastWhite); + break; + case Theme.Light: + isLogoUriSet = SetColoredIcon(path, ContrastWhite); + break; + default: + isLogoUriSet = SetColoredIcon(path, ContrastBlack); + break; } if (!isLogoUriSet) @@ -677,17 +679,18 @@ namespace Microsoft.Plugin.Program.Programs { if (File.Exists(path)) { - MemoryStream memoryStream = new MemoryStream(); + var memoryStream = new MemoryStream(); + using (var fileStream = File.OpenRead(path)) + { + fileStream.CopyTo(memoryStream); + memoryStream.Position = 0; - byte[] fileBytes = File.ReadAllBytes(path); - memoryStream.Write(fileBytes, 0, fileBytes.Length); - memoryStream.Position = 0; - - var image = new BitmapImage(); - image.BeginInit(); - image.StreamSource = memoryStream; - image.EndInit(); - return image; + var image = new BitmapImage(); + image.BeginInit(); + image.StreamSource = memoryStream; + image.EndInit(); + return image; + } } else {