[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
This commit is contained in:
Heiko
2022-04-04 13:47:58 +02:00
committed by GitHub
parent 44165621f0
commit fd01ee391b
6 changed files with 156 additions and 92 deletions

View File

@@ -68,6 +68,11 @@ Available commands:
### Network results on global queries ### 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.) - 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) ## [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. 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.

View File

@@ -28,10 +28,10 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
[DataRow("hibernate", "Images\\sleep.dark.png")] [DataRow("hibernate", "Images\\sleep.dark.png")]
[DataRow("empty recycle", "Images\\recyclebin.dark.png")] [DataRow("empty recycle", "Images\\recyclebin.dark.png")]
[DataRow("uefi firmware settings", "Images\\firmwareSettings.dark.png")] [DataRow("uefi firmware settings", "Images\\firmwareSettings.dark.png")]
[DataRow("ip v4 addr", "Images\\networkAdapter.dark.png")] [DataRow("ip v4 addr", "Images\\networkAdapter.dark.png", true)]
[DataRow("ip v6 addr", "Images\\networkAdapter.dark.png")] [DataRow("ip v6 addr", "Images\\networkAdapter.dark.png", true)]
[DataRow("mac addr", "Images\\networkAdapter.dark.png")] [DataRow("mac addr", "Images\\networkAdapter.dark.png", true)]
public void IconThemeDarkTest(string typedString, string expectedResult) public void IconThemeDarkTest(string typedString, string expectedResult, bool isDelayed = default)
{ {
// Setup // Setup
Mock<Main> main = new Mock<Main>(); Mock<Main> main = new Mock<Main>();
@@ -40,7 +40,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
Query expectedQuery = new Query(typedString); Query expectedQuery = new Query(typedString);
// Act // 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
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);
@@ -55,10 +55,10 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
[DataRow("hibernate", "Images\\sleep.light.png")] [DataRow("hibernate", "Images\\sleep.light.png")]
[DataRow("empty recycle", "Images\\recyclebin.light.png")] [DataRow("empty recycle", "Images\\recyclebin.light.png")]
[DataRow("uefi firmware settings", "Images\\firmwareSettings.light.png")] [DataRow("uefi firmware settings", "Images\\firmwareSettings.light.png")]
[DataRow("ipv4 addr", "Images\\networkAdapter.light.png")] [DataRow("ipv4 addr", "Images\\networkAdapter.light.png", true)]
[DataRow("ipv6 addr", "Images\\networkAdapter.light.png")] [DataRow("ipv6 addr", "Images\\networkAdapter.light.png", true)]
[DataRow("mac addr", "Images\\networkAdapter.light.png")] [DataRow("mac addr", "Images\\networkAdapter.light.png", true)]
public void IconThemeLightTest(string typedString, string expectedResult) public void IconThemeLightTest(string typedString, string expectedResult, bool isDelayed = default)
{ {
// Setup // Setup
Mock<Main> main = new Mock<Main>(); Mock<Main> main = new Mock<Main>();
@@ -67,7 +67,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
Query expectedQuery = new Query(typedString); Query expectedQuery = new Query(typedString);
// Act // 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
Assert.AreEqual(expectedResult, result); Assert.AreEqual(expectedResult, result);

View File

@@ -28,11 +28,6 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
[DataRow("sleep", "Put computer to sleep")] [DataRow("sleep", "Put computer to sleep")]
[DataRow("hibernate", "Hibernate computer")] [DataRow("hibernate", "Hibernate computer")]
[DataRow("empty recycle", "Empty Recycle Bin")] [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) public void EnvironmentIndependentQueryResults(string typedString, string expectedResult)
{ {
// Setup // Setup
@@ -46,6 +41,25 @@ namespace Microsoft.PowerToys.Run.Plugin.System.UnitTests
Assert.IsTrue(result.StartsWith(expectedResult, StringComparison.OrdinalIgnoreCase)); 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> main = new Mock<Main>();
Query expectedQuery = new Query(typedString);
// Act
var result = main.Object.Query(expectedQuery, true).FirstOrDefault().SubTitle;
// Assert
Assert.IsTrue(result.StartsWith(expectedResult, StringComparison.OrdinalIgnoreCase));
}
[TestMethod] [TestMethod]
public void UefiCommandIsAvailableOnUefiSystems() public void UefiCommandIsAvailableOnUefiSystems()
{ {

View File

@@ -2,10 +2,9 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Net.NetworkInformation;
using System.Windows; using System.Windows;
using System.Windows.Interop; using System.Windows.Interop;
using Microsoft.PowerToys.Run.Plugin.System.Properties; 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 EWXPOWEROFF = 0x00000008;
internal const int EWXFORCEIFHUNG = 0x00000010; internal const int EWXFORCEIFHUNG = 0x00000010;
// Cache for network interface information to save query time
private const int UpdateCacheIntervalSeconds = 5;
private static List<NetworkConnectionProperties> networkPropertiesCache = new List<NetworkConnectionProperties>();
private static DateTime timeOfLastNetworkQuery;
/// <summary> /// <summary>
/// Returns a list with all system command results /// Returns a list with all system command results
/// </summary> /// </summary>
@@ -154,11 +158,16 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
{ {
var results = new List<Result>(); var results = new List<Result>();
var interfaces = NetworkInterface.GetAllNetworkInterfaces().Where(x => x.NetworkInterfaceType != NetworkInterfaceType.Loopback && x.GetPhysicalAddress() != null); // We update the cache only if the last query is older than 'updateCacheIntervalSeconds' seconds
foreach (NetworkInterface i in interfaces) 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)) if (!string.IsNullOrEmpty(intInfo.IPv4))
{ {
results.Add(new Result() results.Add(new Result()

View File

@@ -4,7 +4,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using Microsoft.PowerToys.Run.Plugin.System.Properties; using Microsoft.PowerToys.Run.Plugin.System.Properties;
@@ -94,28 +97,29 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
/// <summary> /// <summary>
/// Gets the list of gateway IPs as string /// Gets the list of gateway IPs as string
/// </summary> /// </summary>
internal List<string> Gateways { get; private set; } = new List<string>(); internal List<IPAddress> Gateways { get; private set; } = new List<IPAddress>();
/// <summary> /// <summary>
/// Gets the list of DHCP server IPs as string /// Gets the list of DHCP server IPs as string
/// </summary> /// </summary>
internal List<string> DhcpServers { get; private set; } = new List<string>(); internal IPAddressCollection DhcpServers { get; private set; }
/// <summary> /// <summary>
/// Gets the list of DNS server IPs as string /// Gets the list of DNS server IPs as string
/// </summary> /// </summary>
internal List<string> DnsServers { get; private set; } = new List<string>(); internal IPAddressCollection DnsServers { get; private set; }
/// <summary> /// <summary>
/// Gets the list of WINS server IPs as string /// Gets the list of WINS server IPs as string
/// </summary> /// </summary>
internal List<string> WinsServers { get; private set; } = new List<string>(); internal IPAddressCollection WinsServers { get; private set; }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="NetworkConnectionProperties"/> class. /// Initializes a new instance of the <see cref="NetworkConnectionProperties"/> class.
/// This private constructor is used when we crete the list of adapter (properties) by calling <see cref="NetworkConnectionProperties.GetList()"/>.
/// </summary> /// </summary>
/// <param name="networkInterface">Network interface of the connection</param> /// <param name="networkInterface">Network interface of the connection</param>
internal NetworkConnectionProperties(NetworkInterface networkInterface) private NetworkConnectionProperties(NetworkInterface networkInterface)
{ {
// Setting adapter properties // Setting adapter properties
Adapter = networkInterface.Description; Adapter = networkInterface.Description;
@@ -133,6 +137,23 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
} }
} }
/// <summary>
/// Creates a list with all network adapters and their properties
/// </summary>
/// <returns>List containing all network adapters</returns>
internal static List<NetworkConnectionProperties> GetList()
{
List<NetworkConnectionProperties> list = new List<NetworkConnectionProperties>();
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;
}
/// <summary> /// <summary>
/// Gets a formatted string with the adapter details /// Gets a formatted string with the adapter details
/// </summary> /// </summary>
@@ -179,64 +200,59 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
/// <param name="properties">Element of the type <see cref="IPInterfaceProperties"/>.</param> /// <param name="properties">Element of the type <see cref="IPInterfaceProperties"/>.</param>
private void SetIpProperties(IPInterfaceProperties properties) 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(); IPv4 = ip.ToString();
IPv4Mask = ip.IPv4Mask.ToString(); IPv4Mask = ipList[i].IPv4Mask.ToString();
} }
else if (ip.Address.AddressFamily == AddressFamily.InterNetworkV6) else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
{ {
if (string.IsNullOrEmpty(IPv6Primary)) 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 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) Debug.Print($"time for getting ips: {DateTime.Now - t}");
{
DhcpServers.Add(ip.ToString());
}
foreach (var ip in properties.DnsAddresses)
{
DnsServers.Add(ip.ToString());
}
foreach (var ip in properties.WinsServersAddresses)
{
WinsServers.Add(ip.ToString());
}
} }
/// <summary> /// <summary>
@@ -284,21 +300,20 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
/// <exception cref="ArgumentException">If the parameter <paramref name="property"/> is not of the type <see cref="string"/> or <see cref="List{String}"/>.</exception> /// <exception cref="ArgumentException">If the parameter <paramref name="property"/> is not of the type <see cref="string"/> or <see cref="List{String}"/>.</exception>
private static string CreateIpInfoForDetailsText(string title, dynamic property) private static string CreateIpInfoForDetailsText(string title, dynamic property)
{ {
if (property is string) switch (property)
{ {
return $"\n{title}{property}"; case string:
} return $"\n{title}{property}";
else if (property is List<string> list) case List<string> listString:
{ return listString.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
return list.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}"; case List<IPAddress> listIP:
} return listIP.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
else if (property is null) case IPAddressCollection collectionIP:
{ return collectionIP.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
return string.Empty; case null:
} return string.Empty;
else default:
{ throw new ArgumentException($"'{property}' is not of type 'string', 'List<string>', 'List<IPAddress>' or 'IPAddressCollection'.", nameof(property));
throw new ArgumentException($"'{property}' is not of type 'string' or 'List<string>'.", nameof(property));
} }
} }
} }

View File

@@ -17,7 +17,7 @@ using Wox.Plugin.Common.Win32;
namespace Microsoft.PowerToys.Run.Plugin.System namespace Microsoft.PowerToys.Run.Plugin.System
{ {
public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu, IDelayedExecutionPlugin
{ {
private PluginInitContext _context; private PluginInitContext _context;
@@ -73,17 +73,16 @@ namespace Microsoft.PowerToys.Run.Plugin.System
public List<Result> Query(Query query) public List<Result> Query(Query query)
{ {
var results = new List<Result>(); List<Result> results = new List<Result>();
CultureInfo culture = _localizeSystemCommands ? CultureInfo.CurrentUICulture : new CultureInfo("en-US");
if (query == null) if (query == null)
{ {
return results; 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 systemCommands = Commands.GetSystemCommands(IsBootedInUefiMode, IconTheme, culture, _confirmSystemCommands);
var networkConnectionResults = Commands.GetNetworkConnectionResults(IconTheme, culture);
foreach (var c in systemCommands) foreach (var c in systemCommands)
{ {
var resultMatch = StringMatcher.FuzzySearch(query.Search, c.Title); var resultMatch = StringMatcher.FuzzySearch(query.Search, c.Title);
@@ -95,24 +94,46 @@ namespace Microsoft.PowerToys.Run.Plugin.System
} }
} }
foreach (var r in networkConnectionResults) // 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
// On global queries the first word/part has to be 'ip', 'mac' or 'address' // string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; ");
if (string.IsNullOrEmpty(query.ActionKeyword)) // if (!string.IsNullOrEmpty(query.ActionKeyword) || keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase)))
{ // {
string[] keywordList = Resources.ResourceManager.GetString("Microsoft_plugin_sys_Search_NetworkKeywordList", culture).Split("; "); // results.Add(new Result()
if (!keywordList.Any(x => query.Search.StartsWith(x, StringComparison.CurrentCultureIgnoreCase))) // {
{ // Title = "Getting network informations. Please wait ...",
continue; // IcoPath = $"Images\\networkAdapter.{IconTheme}.png",
} // Score = StringMatcher.FuzzySearch("address", "ip address").Score,
} // });
// }
return results;
}
var resultMatch = StringMatcher.FuzzySearch(query.Search, r.SubTitle); public List<Result> Query(Query query, bool delayedExecution)
if (resultMatch.Score > 0) {
List<Result> results = new List<Result>();
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 var resultMatch = StringMatcher.FuzzySearch(query.Search, r.SubTitle);
r.SubTitleHighlightData = resultMatch.MatchData; if (resultMatch.Score > 0)
results.Add(r); {
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);
}
} }
} }