[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
- 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.

View File

@@ -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> main = new Mock<Main>();
@@ -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> main = new Mock<Main>();
@@ -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);

View File

@@ -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> 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]
public void UefiCommandIsAvailableOnUefiSystems()
{

View File

@@ -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<NetworkConnectionProperties> networkPropertiesCache = new List<NetworkConnectionProperties>();
private static DateTime timeOfLastNetworkQuery;
/// <summary>
/// Returns a list with all system command results
/// </summary>
@@ -154,11 +158,16 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
{
var results = new List<Result>();
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()

View File

@@ -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
/// <summary>
/// Gets the list of gateway IPs as string
/// </summary>
internal List<string> Gateways { get; private set; } = new List<string>();
internal List<IPAddress> Gateways { get; private set; } = new List<IPAddress>();
/// <summary>
/// Gets the list of DHCP server IPs as string
/// </summary>
internal List<string> DhcpServers { get; private set; } = new List<string>();
internal IPAddressCollection DhcpServers { get; private set; }
/// <summary>
/// Gets the list of DNS server IPs as string
/// </summary>
internal List<string> DnsServers { get; private set; } = new List<string>();
internal IPAddressCollection DnsServers { get; private set; }
/// <summary>
/// Gets the list of WINS server IPs as string
/// </summary>
internal List<string> WinsServers { get; private set; } = new List<string>();
internal IPAddressCollection WinsServers { get; private set; }
/// <summary>
/// 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>
/// <param name="networkInterface">Network interface of the connection</param>
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
}
}
/// <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>
/// Gets a formatted string with the adapter details
/// </summary>
@@ -179,64 +200,59 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Components
/// <param name="properties">Element of the type <see cref="IPInterfaceProperties"/>.</param>
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}");
}
/// <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>
private static string CreateIpInfoForDetailsText(string title, dynamic property)
{
if (property is string)
switch (property)
{
case string:
return $"\n{title}{property}";
}
else if (property is List<string> list)
{
return list.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
}
else if (property is null)
{
case List<string> listString:
return listString.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)}";
case IPAddressCollection collectionIP:
return collectionIP.Count == 0 ? string.Empty : $"\n{title}{string.Join("\n\t", property)}";
case null:
return string.Empty;
}
else
{
throw new ArgumentException($"'{property}' is not of type 'string' or 'List<string>'.", nameof(property));
default:
throw new ArgumentException($"'{property}' is not of type 'string', 'List<string>', 'List<IPAddress>' or 'IPAddressCollection'.", nameof(property));
}
}
}

View File

@@ -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<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)
{
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,18 +94,39 @@ 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;
}
public List<Result> Query(Query query, bool delayedExecution)
{
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)
{
var resultMatch = StringMatcher.FuzzySearch(query.Search, r.SubTitle);
if (resultMatch.Score > 0)
{
@@ -115,6 +135,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System
results.Add(r);
}
}
}
return results;
}