From fd01ee391b50c55fb04faa3585aeece71fd4b061 Mon Sep 17 00:00:00 2001 From: Heiko <61519853+htcfreek@users.noreply.github.com> Date: Mon, 4 Apr 2022 13:47:58 +0200 Subject: [PATCH] [PTRun][System]Fix delay on many net interfaces (#17490) * code changes * small text fixes * update docs * comment improvements * update tests * fix typo * change * fix typo * fix error msg * fix bug * second fix --- .../modules/launcher/plugins/system.md | 5 + .../ImageTests.cs | 20 +-- .../QueryTests.cs | 24 +++- .../Components/Commands.cs | 19 ++- .../Components/NetworkConnectionProperties.cs | 117 ++++++++++-------- .../Main.cs | 63 ++++++---- 6 files changed, 156 insertions(+), 92 deletions(-) diff --git a/doc/devdocs/modules/launcher/plugins/system.md b/doc/devdocs/modules/launcher/plugins/system.md index cfc06958ad..6307686f51 100644 --- a/doc/devdocs/modules/launcher/plugins/system.md +++ b/doc/devdocs/modules/launcher/plugins/system.md @@ -68,6 +68,11 @@ Available commands: ### Network results on global queries - The network results (IP and MAC address) are only shown on global queries, if the search term starts with either IP, MAC or Address. (We compare case-insensitive.) +### Returning results +We return the results in two steps: +1. All results which we can create very fast like shutdown or logoff via [`Main.Query(Query query)`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs). +2. All results which need some time to create like the network results (IP, MAC) via [`Main.Query(Query query, bool delayedExecution)`](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs). + ## [Unit Tests](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests) We have a [Unit Test project](/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests) that executes various test to ensure that the plugin works as expected. diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/ImageTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/ImageTests.cs index 3a9ae7ed74..9541971a58 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/ImageTests.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/ImageTests.cs @@ -28,10 +28,10 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests [DataRow("hibernate", "Images\\sleep.dark.png")] [DataRow("empty recycle", "Images\\recyclebin.dark.png")] [DataRow("uefi firmware settings", "Images\\firmwareSettings.dark.png")] - [DataRow("ip v4 addr", "Images\\networkAdapter.dark.png")] - [DataRow("ip v6 addr", "Images\\networkAdapter.dark.png")] - [DataRow("mac addr", "Images\\networkAdapter.dark.png")] - public void IconThemeDarkTest(string typedString, string expectedResult) + [DataRow("ip v4 addr", "Images\\networkAdapter.dark.png", true)] + [DataRow("ip v6 addr", "Images\\networkAdapter.dark.png", true)] + [DataRow("mac addr", "Images\\networkAdapter.dark.png", true)] + public void IconThemeDarkTest(string typedString, string expectedResult, bool isDelayed = default) { // Setup Mock
main = new Mock
(); @@ -40,7 +40,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests Query expectedQuery = new Query(typedString); // Act - var result = main.Object.Query(expectedQuery).FirstOrDefault().IcoPath; + var result = !isDelayed ? main.Object.Query(expectedQuery).FirstOrDefault().IcoPath : main.Object.Query(expectedQuery, true).FirstOrDefault().IcoPath; // Assert Assert.AreEqual(expectedResult, result); @@ -55,10 +55,10 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests [DataRow("hibernate", "Images\\sleep.light.png")] [DataRow("empty recycle", "Images\\recyclebin.light.png")] [DataRow("uefi firmware settings", "Images\\firmwareSettings.light.png")] - [DataRow("ipv4 addr", "Images\\networkAdapter.light.png")] - [DataRow("ipv6 addr", "Images\\networkAdapter.light.png")] - [DataRow("mac addr", "Images\\networkAdapter.light.png")] - public void IconThemeLightTest(string typedString, string expectedResult) + [DataRow("ipv4 addr", "Images\\networkAdapter.light.png", true)] + [DataRow("ipv6 addr", "Images\\networkAdapter.light.png", true)] + [DataRow("mac addr", "Images\\networkAdapter.light.png", true)] + public void IconThemeLightTest(string typedString, string expectedResult, bool isDelayed = default) { // Setup Mock
main = new Mock
(); @@ -67,7 +67,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests Query expectedQuery = new Query(typedString); // Act - var result = main.Object.Query(expectedQuery).FirstOrDefault().IcoPath; + var result = !isDelayed ? main.Object.Query(expectedQuery).FirstOrDefault().IcoPath : main.Object.Query(expectedQuery, true).FirstOrDefault().IcoPath; // Assert Assert.AreEqual(expectedResult, result); diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/QueryTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/QueryTests.cs index 9c06c0f8c5..b474b31666 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/QueryTests.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System.UnitTests/QueryTests.cs @@ -28,11 +28,6 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests [DataRow("sleep", "Put computer to sleep")] [DataRow("hibernate", "Hibernate computer")] [DataRow("empty recycle", "Empty Recycle Bin")] - [DataRow("ip", "IPv4 address of")] - [DataRow("address", "IPv4 address of")] // searching for address should show ipv4 first - [DataRow("ip v4", "IPv4 address of")] - [DataRow("ip v6", "IPv6 address of")] - [DataRow("mac addr", "MAC address of")] public void EnvironmentIndependentQueryResults(string typedString, string expectedResult) { // Setup @@ -46,6 +41,25 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests Assert.IsTrue(result.StartsWith(expectedResult, StringComparison.OrdinalIgnoreCase)); } + [DataTestMethod] + [DataRow("ip", "IPv4 address of")] + [DataRow("address", "IPv4 address of")] // searching for address should show ipv4 first + [DataRow("ip v4", "IPv4 address of")] + [DataRow("ip v6", "IPv6 address of")] + [DataRow("mac addr", "MAC address of")] + public void DelayedQueryResults(string typedString, string expectedResult) + { + // Setup + Mock
main = new Mock
(); + Query expectedQuery = new Query(typedString); + + // Act + var result = main.Object.Query(expectedQuery, true).FirstOrDefault().SubTitle; + + // Assert + Assert.IsTrue(result.StartsWith(expectedResult, StringComparison.OrdinalIgnoreCase)); + } + [TestMethod] public void UefiCommandIsAvailableOnUefiSystems() { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/Commands.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/Commands.cs index 8792e3ccdf..bed3cad550 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/Commands.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/Commands.cs @@ -2,10 +2,9 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Net.NetworkInformation; using System.Windows; using System.Windows.Interop; using Microsoft.PowerToys.Run.Plugin.System.Properties; @@ -28,6 +27,11 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components internal const int EWXPOWEROFF = 0x00000008; internal const int EWXFORCEIFHUNG = 0x00000010; + // Cache for network interface information to save query time + private const int UpdateCacheIntervalSeconds = 5; + private static List networkPropertiesCache = new List(); + private static DateTime timeOfLastNetworkQuery; + /// /// Returns a list with all system command results /// @@ -154,11 +158,16 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components { var results = new List(); - var interfaces = NetworkInterface.GetAllNetworkInterfaces().Where(x => x.NetworkInterfaceType != NetworkInterfaceType.Loopback && x.GetPhysicalAddress() != null); - foreach (NetworkInterface i in interfaces) + // We update the cache only if the last query is older than 'updateCacheIntervalSeconds' seconds + DateTime timeOfLastNetworkQueryBefore = timeOfLastNetworkQuery; + timeOfLastNetworkQuery = DateTime.Now; // Set time of last query to this query + if ((timeOfLastNetworkQuery - timeOfLastNetworkQueryBefore).TotalSeconds >= UpdateCacheIntervalSeconds) { - NetworkConnectionProperties intInfo = new NetworkConnectionProperties(i); + networkPropertiesCache = NetworkConnectionProperties.GetList(); + } + foreach (NetworkConnectionProperties intInfo in networkPropertiesCache) + { if (!string.IsNullOrEmpty(intInfo.IPv4)) { results.Add(new Result() diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/NetworkConnectionProperties.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/NetworkConnectionProperties.cs index c2e36e6c86..775b750aa0 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/NetworkConnectionProperties.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Components/NetworkConnectionProperties.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; +using System.Linq; +using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using Microsoft.PowerToys.Run.Plugin.System.Properties; @@ -94,28 +97,29 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components /// /// Gets the list of gateway IPs as string /// - internal List Gateways { get; private set; } = new List(); + internal List Gateways { get; private set; } = new List(); /// /// Gets the list of DHCP server IPs as string /// - internal List DhcpServers { get; private set; } = new List(); + internal IPAddressCollection DhcpServers { get; private set; } /// /// Gets the list of DNS server IPs as string /// - internal List DnsServers { get; private set; } = new List(); + internal IPAddressCollection DnsServers { get; private set; } /// /// Gets the list of WINS server IPs as string /// - internal List WinsServers { get; private set; } = new List(); + internal IPAddressCollection WinsServers { get; private set; } /// /// Initializes a new instance of the class. + /// This private constructor is used when we crete the list of adapter (properties) by calling . /// /// Network interface of the connection - internal NetworkConnectionProperties(NetworkInterface networkInterface) + private NetworkConnectionProperties(NetworkInterface networkInterface) { // Setting adapter properties Adapter = networkInterface.Description; @@ -133,6 +137,23 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components } } + /// + /// Creates a list with all network adapters and their properties + /// + /// List containing all network adapters + internal static List GetList() + { + List list = new List(); + + var interfaces = NetworkInterface.GetAllNetworkInterfaces().Where(x => x.NetworkInterfaceType != NetworkInterfaceType.Loopback && x.GetPhysicalAddress() != null); + foreach (NetworkInterface i in interfaces) + { + list.Add(new NetworkConnectionProperties(i)); + } + + return list; + } + /// /// Gets a formatted string with the adapter details /// @@ -179,64 +200,59 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components /// Element of the type . private void SetIpProperties(IPInterfaceProperties properties) { - var ipList = properties.UnicastAddresses; + DateTime t = DateTime.Now; - foreach (var ip in ipList) + UnicastIPAddressInformationCollection ipList = properties.UnicastAddresses; + GatewayIPAddressInformationCollection gwList = properties.GatewayAddresses; + DhcpServers = properties.DhcpServerAddresses; + DnsServers = properties.DnsAddresses; + WinsServers = properties.WinsServersAddresses; + + for (int i = 0; i < ipList.Count; i++) { - if (ip.Address.AddressFamily == AddressFamily.InterNetwork) + IPAddress ip = ipList[i].Address; + + if (ip.AddressFamily == AddressFamily.InterNetwork) { - IPv4 = ip.Address.ToString(); - IPv4Mask = ip.IPv4Mask.ToString(); + IPv4 = ip.ToString(); + IPv4Mask = ipList[i].IPv4Mask.ToString(); } - else if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6) + else if (ip.AddressFamily == AddressFamily.InterNetworkV6) { if (string.IsNullOrEmpty(IPv6Primary)) { - IPv6Primary = ip.Address.ToString(); + IPv6Primary = ip.ToString(); } - if (ip.Address.IsIPv6LinkLocal) + if (ip.IsIPv6LinkLocal) { - IPv6LinkLocal = ip.Address.ToString(); + IPv6LinkLocal = ip.ToString(); } - else if (ip.Address.IsIPv6SiteLocal) + else if (ip.IsIPv6SiteLocal) { - IPv6SiteLocal = ip.Address.ToString(); + IPv6SiteLocal = ip.ToString(); } - else if (ip.Address.IsIPv6UniqueLocal) + else if (ip.IsIPv6UniqueLocal) { - IPv6UniqueLocal = ip.Address.ToString(); + IPv6UniqueLocal = ip.ToString(); } - else if (ip.SuffixOrigin == SuffixOrigin.Random) + else if (ipList[i].SuffixOrigin == SuffixOrigin.Random) { - IPv6Temporary = ip.Address.ToString(); + IPv6Temporary = ip.ToString(); } else { - IPv6Global = ip.Address.ToString(); + IPv6Global = ip.ToString(); } } } - foreach (var ip in properties.GatewayAddresses) + for (int i = 0; i < gwList.Count; i++) { - Gateways.Add(ip.Address.ToString()); + Gateways.Add(gwList[i].Address); } - foreach (var ip in properties.DhcpServerAddresses) - { - DhcpServers.Add(ip.ToString()); - } - - foreach (var ip in properties.DnsAddresses) - { - DnsServers.Add(ip.ToString()); - } - - foreach (var ip in properties.WinsServersAddresses) - { - WinsServers.Add(ip.ToString()); - } + Debug.Print($"time for getting ips: {DateTime.Now - t}"); } /// @@ -284,21 +300,20 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components /// If the parameter is not of the type or . private static string CreateIpInfoForDetailsText(string title, dynamic property) { - if (property is string) + switch (property) { - return $"\n{title}{property}"; - } - else if (property is List list) - { - return list.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}"; - } - else if (property is null) - { - return string.Empty; - } - else - { - throw new ArgumentException($"'{property}' is not of type 'string' or 'List'.", nameof(property)); + case string: + return $"\n{title}{property}"; + case List listString: + return listString.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}"; + case List listIP: + return listIP.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}"; + case IPAddressCollection collectionIP: + return collectionIP.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}"; + case null: + return string.Empty; + default: + throw new ArgumentException($"'{property}' is not of type 'string', 'List', 'List' or 'IPAddressCollection'.", nameof(property)); } } } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs index 0c6cb8c3a1..829e81aec5 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs @@ -17,7 +17,7 @@ using Wox.Plugin.Common.Win32; namespace Microsoft.PowerToys.Run.Plugin.System { - public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu + public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu, IDelayedExecutionPlugin { private PluginInitContext _context; @@ -73,17 +73,16 @@ namespace Microsoft.PowerToys.Run.Plugin.System public List Query(Query query) { - var results = new List(); + List results = new List(); + CultureInfo culture = _localizeSystemCommands ? CultureInfo.CurrentUICulture : new CultureInfo("en-US"); if (query == null) { return results; } - CultureInfo culture = _localizeSystemCommands ? CultureInfo.CurrentUICulture : new CultureInfo("en-US"); + // normal system commands are fast and can be returned immediately var systemCommands = Commands.GetSystemCommands(IsBootedInUefiMode, IconTheme, culture, _confirmSystemCommands); - var networkConnectionResults = Commands.GetNetworkConnectionResults(IconTheme, culture); - foreach (var c in systemCommands) { var resultMatch = StringMatcher.FuzzySearch(query.Search, c.Title); @@ -95,24 +94,46 @@ namespace Microsoft.PowerToys.Run.Plugin.System } } - foreach (var r in networkConnectionResults) - { - // On global queries the first word/part has to be 'ip', 'mac' or 'address' - if (string.IsNullOrEmpty(query.ActionKeyword)) - { - string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; "); - if (!keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase))) - { - continue; - } - } + // The following information result is not returned because delayed queries doesn't clear output if no results are available. + // On global queries the first word/part has to be 'ip', 'mac' or 'address' for network results + // string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; "); + // if (!string.IsNullOrEmpty(query.ActionKeyword) || keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase))) + // { + // results.Add(new Result() + // { + // Title = "Getting network informations. Please wait ...", + // IcoPath = $"Images\\networkAdapter.{IconTheme}.png", + // Score = StringMatcher.FuzzySearch("address", "ip address").Score, + // }); + // } + return results; + } - var resultMatch = StringMatcher.FuzzySearch(query.Search, r.SubTitle); - if (resultMatch.Score > 0) + public List Query(Query query, bool delayedExecution) + { + List results = new List(); + CultureInfo culture = _localizeSystemCommands ? CultureInfo.CurrentUICulture : new CultureInfo("en-US"); + + if (query == null) + { + return results; + } + + // Network (ip and mac) results are slow with many network cards and returned delayed. + // On global queries the first word/part has to be 'ip', 'mac' or 'address' for network results + string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; "); + if (!string.IsNullOrEmpty(query.ActionKeyword) || keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase))) + { + var networkConnectionResults = Commands.GetNetworkConnectionResults(IconTheme, culture); + foreach (var r in networkConnectionResults) { - r.Score = _reduceNetworkResultScore ? (int)(resultMatch.Score * 65 / 100) : resultMatch.Score; // Adjust score to improve user experience and priority order - r.SubTitleHighlightData = resultMatch.MatchData; - results.Add(r); + var resultMatch = StringMatcher.FuzzySearch(query.Search, r.SubTitle); + if (resultMatch.Score > 0) + { + r.Score = _reduceNetworkResultScore ? (int)(resultMatch.Score * 65 / 100) : resultMatch.Score; // Adjust score to improve user experience and priority order + r.SubTitleHighlightData = resultMatch.MatchData; + results.Add(r); + } } }