mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
[Hosts] Handle maximum of 9 hosts per entry (#26862)
* handle maximum of 9 hosts per entry * splitted entries teaching tip * fix entry * message changed
This commit is contained in:
committed by
GitHub
parent
8cb632a0c2
commit
9511d17063
@@ -75,6 +75,7 @@ namespace Hosts.Tests
|
||||
[DataRow(" host 10.1.1.1")]
|
||||
[DataRow("host 10.1.1.1")]
|
||||
[DataRow("# comment 10.1.1.1 host # comment")]
|
||||
[DataRow("10.1.1.1 host01 host02 host03 host04 host05 host06 host07 host08 host09 host10")]
|
||||
public void Not_Valid_Entry(string line)
|
||||
{
|
||||
var entry = new Entry(0, line);
|
||||
|
||||
@@ -69,9 +69,10 @@ namespace Hosts.Tests
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (_, entries) = await service.ReadAsync();
|
||||
var data = await service.ReadAsync();
|
||||
var entries = data.Entries.ToList();
|
||||
entries.Add(new Entry(0, "10.1.1.30", "host30 host30.local", "new entry", false));
|
||||
await service.WriteAsync(string.Empty, entries);
|
||||
await service.WriteAsync(data.AdditionalLines, entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
@@ -94,9 +95,10 @@ namespace Hosts.Tests
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (_, entries) = await service.ReadAsync();
|
||||
var data = await service.ReadAsync();
|
||||
var entries = data.Entries.ToList();
|
||||
entries.RemoveAt(0);
|
||||
await service.WriteAsync(string.Empty, entries);
|
||||
await service.WriteAsync(data.AdditionalLines, entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
@@ -120,13 +122,13 @@ namespace Hosts.Tests
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (_, entries) = await service.ReadAsync();
|
||||
var entry = entries[0];
|
||||
var data = await service.ReadAsync();
|
||||
var entry = data.Entries[0];
|
||||
entry.Address = "10.1.1.10";
|
||||
entry.Hosts = "host host.local host1.local";
|
||||
entry.Comment = "updated comment";
|
||||
entry.Active = false;
|
||||
await service.WriteAsync(string.Empty, entries);
|
||||
await service.WriteAsync(data.AdditionalLines, data.Entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
@@ -172,8 +174,8 @@ namespace Hosts.Tests
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (additionalLines, entries) = await service.ReadAsync();
|
||||
await service.WriteAsync(additionalLines, entries);
|
||||
var data = await service.ReadAsync();
|
||||
await service.WriteAsync(data.AdditionalLines, data.Entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
@@ -205,8 +207,33 @@ namespace Hosts.Tests
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var (additionalLines, entries) = await service.ReadAsync();
|
||||
await service.WriteAsync(additionalLines, entries);
|
||||
var data = await service.ReadAsync();
|
||||
await service.WriteAsync(data.AdditionalLines, data.Entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task LongHosts_Splitted()
|
||||
{
|
||||
var content =
|
||||
@"10.1.1.1 host01 host02 host03 host04 host05 host06 host07 host08 host09 host10 host11 host12 host13 host14 host15 host16 host17 host18 host19 # comment
|
||||
";
|
||||
|
||||
var contentResult =
|
||||
@"10.1.1.1 host01 host02 host03 host04 host05 host06 host07 host08 host09 # comment
|
||||
10.1.1.1 host10 host11 host12 host13 host14 host15 host16 host17 host18 # comment
|
||||
10.1.1.1 host19 # comment
|
||||
";
|
||||
|
||||
var fileSystem = new CustomMockFileSystem();
|
||||
var userSettings = new Mock<IUserSettings>();
|
||||
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
|
||||
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
|
||||
|
||||
var data = await service.ReadAsync();
|
||||
await service.WriteAsync(data.AdditionalLines, data.Entries);
|
||||
|
||||
var result = fileSystem.GetFile(service.HostsFilePath);
|
||||
Assert.AreEqual(result.TextContents, contentResult);
|
||||
|
||||
14
src/modules/Hosts/Hosts/Consts.cs
Normal file
14
src/modules/Hosts/Hosts/Consts.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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.
|
||||
|
||||
namespace Hosts
|
||||
{
|
||||
public static class Consts
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum number of hosts detected by the system in a single line
|
||||
/// </summary>
|
||||
public const int MaxHostsCount = 9;
|
||||
}
|
||||
}
|
||||
@@ -64,18 +64,21 @@ namespace Hosts.Helpers
|
||||
return _fileSystem.File.Exists(HostsFilePath);
|
||||
}
|
||||
|
||||
public async Task<(string Unparsed, List<Entry> Entries)> ReadAsync()
|
||||
public async Task<HostsData> ReadAsync()
|
||||
{
|
||||
var entries = new List<Entry>();
|
||||
var unparsedBuilder = new StringBuilder();
|
||||
var splittedEntries = false;
|
||||
|
||||
if (!Exists())
|
||||
{
|
||||
return (unparsedBuilder.ToString(), entries);
|
||||
return new HostsData(entries, unparsedBuilder.ToString(), false);
|
||||
}
|
||||
|
||||
var lines = await _fileSystem.File.ReadAllLinesAsync(HostsFilePath, Encoding);
|
||||
|
||||
var id = 0;
|
||||
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
@@ -85,11 +88,25 @@ namespace Hosts.Helpers
|
||||
continue;
|
||||
}
|
||||
|
||||
var entry = new Entry(i, line);
|
||||
var entry = new Entry(id, line);
|
||||
|
||||
if (entry.Valid)
|
||||
{
|
||||
entries.Add(entry);
|
||||
id++;
|
||||
}
|
||||
else if (entry.Validate(false))
|
||||
{
|
||||
foreach (var hostsChunk in entry.SplittedHosts.Chunk(Consts.MaxHostsCount))
|
||||
{
|
||||
var clonedEntry = entry.Clone();
|
||||
clonedEntry.Id = id;
|
||||
clonedEntry.Hosts = string.Join(' ', hostsChunk);
|
||||
entries.Add(clonedEntry);
|
||||
id++;
|
||||
}
|
||||
|
||||
splittedEntries = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -102,7 +119,7 @@ namespace Hosts.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
return (unparsedBuilder.ToString(), entries);
|
||||
return new HostsData(entries, unparsedBuilder.ToString(), splittedEntries);
|
||||
}
|
||||
|
||||
public async Task<bool> WriteAsync(string additionalLines, IEnumerable<Entry> entries)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Hosts.Helpers
|
||||
|
||||
event EventHandler FileChanged;
|
||||
|
||||
Task<(string Unparsed, List<Entry> Entries)> ReadAsync();
|
||||
Task<HostsData> ReadAsync();
|
||||
|
||||
Task<bool> WriteAsync(string additionalLines, IEnumerable<Entry> entries);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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.Text.RegularExpressions;
|
||||
|
||||
namespace Hosts.Helpers
|
||||
@@ -39,16 +40,23 @@ namespace Hosts.Helpers
|
||||
/// <summary>
|
||||
/// Determines whether the hosts are valid
|
||||
/// </summary>
|
||||
public static bool ValidHosts(string hosts)
|
||||
public static bool ValidHosts(string hosts, bool validateHostsLength)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hosts))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var host in hosts.Split(' '))
|
||||
var splittedHosts = hosts.Split(' ');
|
||||
|
||||
if (validateHostsLength && splittedHosts.Length > Consts.MaxHostsCount)
|
||||
{
|
||||
if (System.Uri.CheckHostName(host) == System.UriHostNameType.Unknown)
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var host in splittedHosts)
|
||||
{
|
||||
if (Uri.CheckHostName(host) == UriHostNameType.Unknown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Hosts.Models
|
||||
[ObservableProperty]
|
||||
private bool _duplicate;
|
||||
|
||||
public bool Valid => ValidationHelper.ValidHosts(Hosts) && Type != AddressType.Invalid;
|
||||
public bool Valid => Validate(true);
|
||||
|
||||
public string Line { get; private set; }
|
||||
|
||||
@@ -150,5 +150,10 @@ namespace Hosts.Models
|
||||
Active = Active,
|
||||
};
|
||||
}
|
||||
|
||||
public bool Validate(bool validateHostsLength)
|
||||
{
|
||||
return Type != AddressType.Invalid && ValidationHelper.ValidHosts(Hosts, validateHostsLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
src/modules/Hosts/Hosts/Models/HostsData.cs
Normal file
37
src/modules/Hosts/Hosts/Models/HostsData.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Hosts.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the parsed hosts file
|
||||
/// </summary>
|
||||
public class HostsData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the parsed entries
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Entry> Entries { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lines that couldn't be parsed
|
||||
/// </summary>
|
||||
public string AdditionalLines { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether some entries been splitted
|
||||
/// </summary>
|
||||
public bool SplittedEntries { get; }
|
||||
|
||||
public HostsData(List<Entry> entries, string additionalLines, bool splittedEntries)
|
||||
{
|
||||
Entries = entries.AsReadOnly();
|
||||
AdditionalLines = additionalLines;
|
||||
SplittedEntries = splittedEntries;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="Hosts.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Seperate multiple hosts by space (e.g. server server.local).</value>
|
||||
<value>Seperate multiple hosts by space (e.g. server server.local). Maximum 9 hosts per entry.</value>
|
||||
<comment>Do not localize "server" and "server.local"</comment>
|
||||
</data>
|
||||
<data name="HostsFilter.Header" xml:space="preserve">
|
||||
@@ -272,6 +272,17 @@
|
||||
<data name="ShowOnlyDuplicates.Header" xml:space="preserve">
|
||||
<value>Show only duplicates</value>
|
||||
</data>
|
||||
<data name="TooManyHostsTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Only 9 hosts per entry are supported</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="TooManyHostsTeachingTip.Title" xml:space="preserve">
|
||||
<value>Entries with too many hosts found</value>
|
||||
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
|
||||
</data>
|
||||
<data name="TooManyHostsTeachingTipContent.Text" xml:space="preserve">
|
||||
<value>The affected entries have been splitted. This will take effect on next change.</value>
|
||||
</data>
|
||||
<data name="UpdateBtn" xml:space="preserve">
|
||||
<value>Update</value>
|
||||
</data>
|
||||
|
||||
@@ -68,6 +68,9 @@ namespace Hosts.ViewModels
|
||||
[ObservableProperty]
|
||||
private bool _showOnlyDuplicates;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _showSplittedEntriesTooltip;
|
||||
|
||||
partial void OnShowOnlyDuplicatesChanged(bool value)
|
||||
{
|
||||
ApplyFilters();
|
||||
@@ -164,12 +167,13 @@ namespace Hosts.ViewModels
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_readingHosts = true;
|
||||
var (additionalLines, entries) = await _hostsService.ReadAsync();
|
||||
var data = await _hostsService.ReadAsync();
|
||||
|
||||
await _dispatcherQueue.EnqueueAsync(() =>
|
||||
{
|
||||
AdditionalLines = additionalLines;
|
||||
_entries = new ObservableCollection<Entry>(entries);
|
||||
ShowSplittedEntriesTooltip = data.SplittedEntries;
|
||||
AdditionalLines = data.AdditionalLines;
|
||||
_entries = new ObservableCollection<Entry>(data.Entries);
|
||||
|
||||
foreach (var e in _entries)
|
||||
{
|
||||
@@ -346,12 +350,28 @@ namespace Hosts.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
var hosts = entry.SplittedHosts;
|
||||
var duplicate = false;
|
||||
|
||||
var duplicate = _entries.FirstOrDefault(e => e != entry
|
||||
/*
|
||||
* Duplicate are based on the following criteria:
|
||||
* Entries with the same type and at least one host in common
|
||||
* Entries with the same type and address, except when there is only one entry with less than 9 hosts for that type and address
|
||||
*/
|
||||
if (_entries.Any(e => e != entry
|
||||
&& e.Type == entry.Type
|
||||
&& (string.Equals(e.Address, entry.Address, StringComparison.OrdinalIgnoreCase)
|
||||
|| hosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any())) != null;
|
||||
&& entry.SplittedHosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any()))
|
||||
{
|
||||
duplicate = true;
|
||||
}
|
||||
else if (_entries.Any(e => e != entry
|
||||
&& e.Type == entry.Type
|
||||
&& string.Equals(e.Address, entry.Address, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
duplicate = entry.SplittedHosts.Length < Consts.MaxHostsCount
|
||||
&& _entries.Count(e => e.Type == entry.Type
|
||||
&& string.Equals(e.Address, entry.Address, StringComparison.OrdinalIgnoreCase)
|
||||
&& e.SplittedHosts.Length < Consts.MaxHostsCount) > 1;
|
||||
}
|
||||
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
|
||||
@@ -435,7 +435,7 @@
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
HorizontalScrollMode="Auto">
|
||||
<StackPanel
|
||||
MinWidth="480"
|
||||
Width="480"
|
||||
Margin="0,12,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Spacing="24">
|
||||
@@ -446,10 +446,22 @@
|
||||
<TextBox
|
||||
x:Uid="Hosts"
|
||||
IsSpellCheckEnabled="False"
|
||||
AcceptsReturn="False"
|
||||
TextWrapping="Wrap"
|
||||
Height="100"
|
||||
ScrollViewer.IsVerticalRailEnabled="True"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Enabled"
|
||||
Text="{Binding Hosts, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBox
|
||||
x:Uid="Comment"
|
||||
IsSpellCheckEnabled="False"
|
||||
IsSpellCheckEnabled="False"
|
||||
AcceptsReturn="False"
|
||||
TextWrapping="Wrap"
|
||||
Height="100"
|
||||
ScrollViewer.IsVerticalRailEnabled="True"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Enabled"
|
||||
Text="{Binding Comment, Mode=TwoWay}" />
|
||||
<ToggleSwitch
|
||||
x:Uid="Active"
|
||||
@@ -486,5 +498,20 @@
|
||||
ScrollViewer.VerticalScrollMode="Enabled"
|
||||
TextWrapping="Wrap" />
|
||||
</ContentDialog>
|
||||
|
||||
<TeachingTip
|
||||
x:Uid="TooManyHostsTeachingTip"
|
||||
IsOpen="{x:Bind ViewModel.ShowSplittedEntriesTooltip, Mode=OneWay}"
|
||||
PreferredPlacement="Top"
|
||||
PlacementMargin="20">
|
||||
<TeachingTip.IconSource>
|
||||
<FontIconSource Glyph="" />
|
||||
</TeachingTip.IconSource>
|
||||
<TeachingTip.Content>
|
||||
<TextBlock x:Uid="TooManyHostsTeachingTipContent"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,16,0,0" />
|
||||
</TeachingTip.Content>
|
||||
</TeachingTip>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
Reference in New Issue
Block a user