mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-06-07 17:42:45 +08:00
[Hosts]Warn about duplicated entries (#22606)
* find duplicated entries * addressed PR feedback Co-authored-by: Davide <25966642+davidegiacometti@users.noreply.github.com>
This commit is contained in:
parent
b56e62e5de
commit
5b4e678f14
@ -35,6 +35,7 @@ namespace Hosts.Models
|
||||
{
|
||||
SetProperty(ref _hosts, value);
|
||||
OnPropertyChanged(nameof(Valid));
|
||||
SplittedHosts = _hosts.Split(' ');
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,8 +51,13 @@ namespace Hosts.Models
|
||||
[ObservableProperty]
|
||||
private bool _pinging;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _duplicate;
|
||||
|
||||
public bool Valid => ValidationHelper.ValidHosts(_hosts) && (ValidationHelper.ValidIPv4(_address) || ValidationHelper.ValidIPv6(_address));
|
||||
|
||||
public string[] SplittedHosts { get; private set; }
|
||||
|
||||
public Entry()
|
||||
{
|
||||
}
|
||||
|
@ -184,6 +184,9 @@
|
||||
<data name="DeleteDialogAreYouSure.Text" xml:space="preserve">
|
||||
<value>Are you sure you want to delete this entry?</value>
|
||||
</data>
|
||||
<data name="DuplicateEntryIcon.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Duplicate entry</value>
|
||||
</data>
|
||||
<data name="Entries.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Entries</value>
|
||||
</data>
|
||||
@ -234,6 +237,10 @@
|
||||
<value>Ping</value>
|
||||
<comment>"Ping" refers to the command-line utility, do not loc</comment>
|
||||
</data>
|
||||
<data name="PingIcon.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Ping response</value>
|
||||
<comment>"Ping" refers to the command-line utility, do not loc</comment>
|
||||
</data>
|
||||
<data name="Reload.Content" xml:space="preserve">
|
||||
<value>Reload</value>
|
||||
</data>
|
||||
@ -243,6 +250,9 @@
|
||||
<data name="SettingsBtn.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="ShowOnlyDuplicates.Header" xml:space="preserve">
|
||||
<value>Show only duplicates</value>
|
||||
</data>
|
||||
<data name="UpdateBtn" xml:space="preserve">
|
||||
<value>Update</value>
|
||||
</data>
|
||||
|
@ -1,8 +1,9 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -13,7 +14,6 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Hosts.Helpers;
|
||||
using Hosts.Models;
|
||||
using Hosts.Settings;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace Hosts.ViewModels
|
||||
@ -21,7 +21,6 @@ namespace Hosts.ViewModels
|
||||
public partial class MainViewModel : ObservableObject, IDisposable
|
||||
{
|
||||
private readonly IHostsService _hostsService;
|
||||
private readonly IUserSettings _userSettings;
|
||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
private bool _disposed;
|
||||
|
||||
@ -34,6 +33,9 @@ namespace Hosts.ViewModels
|
||||
[ObservableProperty]
|
||||
private bool _fileChanged;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _filtered;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _addressFilter;
|
||||
|
||||
@ -44,50 +46,15 @@ namespace Hosts.ViewModels
|
||||
private string _commentFilter;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _filtered;
|
||||
[NotifyPropertyChangedFor(nameof(Entries))]
|
||||
private bool _showOnlyDuplicates;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _additionalLines;
|
||||
|
||||
private ObservableCollection<Entry> _entries;
|
||||
|
||||
public ObservableCollection<Entry> Entries
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filtered)
|
||||
{
|
||||
var filter = _entries.AsEnumerable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_addressFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Address.Contains(_addressFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_hostsFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Hosts.Contains(_hostsFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_commentFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Comment.Contains(_commentFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return new ObservableCollection<Entry>(filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _entries;
|
||||
}
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_entries = value;
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
}
|
||||
public ObservableCollection<Entry> Entries => _filtered || _showOnlyDuplicates ? GetFilteredEntries() : _entries;
|
||||
|
||||
public ICommand ReadHostsCommand => new RelayCommand(ReadHosts);
|
||||
|
||||
@ -99,12 +66,9 @@ namespace Hosts.ViewModels
|
||||
|
||||
public ICommand OpenHostsFileCommand => new RelayCommand(OpenHostsFile);
|
||||
|
||||
public MainViewModel(
|
||||
IHostsService hostService,
|
||||
IUserSettings userSettings)
|
||||
public MainViewModel(IHostsService hostService)
|
||||
{
|
||||
_hostsService = hostService;
|
||||
_userSettings = userSettings;
|
||||
|
||||
_hostsService.FileChanged += (s, e) =>
|
||||
{
|
||||
@ -116,24 +80,35 @@ namespace Hosts.ViewModels
|
||||
{
|
||||
entry.PropertyChanged += Entry_PropertyChanged;
|
||||
_entries.Add(entry);
|
||||
|
||||
FindDuplicates(entry.Address, entry.SplittedHosts);
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
|
||||
public void Update(int index, Entry entry)
|
||||
{
|
||||
var existingEntry = _entries.ElementAt(index);
|
||||
var existingEntry = Entries.ElementAt(index);
|
||||
var oldAddress = existingEntry.Address;
|
||||
var oldHosts = existingEntry.SplittedHosts;
|
||||
|
||||
existingEntry.Address = entry.Address;
|
||||
existingEntry.Comment = entry.Comment;
|
||||
existingEntry.Hosts = entry.Hosts;
|
||||
existingEntry.Active = entry.Active;
|
||||
|
||||
FindDuplicates(oldAddress, oldHosts);
|
||||
FindDuplicates(entry.Address, entry.SplittedHosts);
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
|
||||
public void DeleteSelected()
|
||||
{
|
||||
var address = Selected.Address;
|
||||
var hosts = Selected.SplittedHosts;
|
||||
_entries.Remove(Selected);
|
||||
if (Filtered)
|
||||
{
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
|
||||
FindDuplicates(address, hosts);
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
|
||||
public void UpdateAdditionalLines(string lines)
|
||||
@ -157,7 +132,7 @@ namespace Hosts.ViewModels
|
||||
|
||||
await _dispatcherQueue.EnqueueAsync(() =>
|
||||
{
|
||||
Entries = new ObservableCollection<Entry>(entries);
|
||||
_entries = new ObservableCollection<Entry>(entries);
|
||||
|
||||
foreach (var e in _entries)
|
||||
{
|
||||
@ -165,17 +140,24 @@ namespace Hosts.ViewModels
|
||||
}
|
||||
|
||||
_entries.CollectionChanged += Entries_CollectionChanged;
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
FindDuplicates();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void ApplyFilters()
|
||||
{
|
||||
if (_entries != null)
|
||||
if (_entries == null)
|
||||
{
|
||||
Filtered = !string.IsNullOrWhiteSpace(_addressFilter) || !string.IsNullOrWhiteSpace(_hostsFilter) || !string.IsNullOrWhiteSpace(_commentFilter);
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
return;
|
||||
}
|
||||
|
||||
Filtered = !string.IsNullOrWhiteSpace(_addressFilter)
|
||||
|| !string.IsNullOrWhiteSpace(_hostsFilter)
|
||||
|| !string.IsNullOrWhiteSpace(_commentFilter);
|
||||
|
||||
OnPropertyChanged(nameof(Entries));
|
||||
}
|
||||
|
||||
public void ClearFilters()
|
||||
@ -183,6 +165,7 @@ namespace Hosts.ViewModels
|
||||
AddressFilter = null;
|
||||
HostsFilter = null;
|
||||
CommentFilter = null;
|
||||
ShowOnlyDuplicates = false;
|
||||
}
|
||||
|
||||
public async Task PingSelectedAsync()
|
||||
@ -212,8 +195,10 @@ namespace Hosts.ViewModels
|
||||
|
||||
private void Entry_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
// Ping should't trigger a file save
|
||||
if (e.PropertyName == nameof(Entry.Ping) || e.PropertyName == nameof(Entry.Pinging))
|
||||
// Ping and duplicate should't trigger a file save
|
||||
if (e.PropertyName == nameof(Entry.Ping)
|
||||
|| e.PropertyName == nameof(Entry.Pinging)
|
||||
|| e.PropertyName == nameof(Entry.Duplicate))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -234,6 +219,68 @@ namespace Hosts.ViewModels
|
||||
});
|
||||
}
|
||||
|
||||
private void FindDuplicates()
|
||||
{
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
SetDuplicate(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void FindDuplicates(string address, IEnumerable<string> hosts)
|
||||
{
|
||||
var entries = _entries.Where(e =>
|
||||
string.Equals(e.Address, address, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| hosts.Intersect(e.SplittedHosts, StringComparer.InvariantCultureIgnoreCase).Any());
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
SetDuplicate(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDuplicate(Entry entry)
|
||||
{
|
||||
var hosts = entry.SplittedHosts;
|
||||
|
||||
entry.Duplicate = _entries.FirstOrDefault(e =>
|
||||
e != entry
|
||||
&& (string.Equals(e.Address, entry.Address, StringComparison.InvariantCultureIgnoreCase)
|
||||
|| hosts.Intersect(e.SplittedHosts, StringComparer.InvariantCultureIgnoreCase).Any())) != null;
|
||||
}
|
||||
|
||||
private ObservableCollection<Entry> GetFilteredEntries()
|
||||
{
|
||||
if (_entries == null)
|
||||
{
|
||||
return new ObservableCollection<Entry>();
|
||||
}
|
||||
|
||||
var filter = _entries.AsEnumerable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_addressFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Address.Contains(_addressFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_hostsFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Hosts.Contains(_hostsFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_commentFilter))
|
||||
{
|
||||
filter = filter.Where(e => e.Comment.Contains(_commentFilter, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (_showOnlyDuplicates)
|
||||
{
|
||||
filter = filter.Where(e => e.Duplicate);
|
||||
}
|
||||
|
||||
return new ObservableCollection<Entry>(filter);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
|
@ -116,6 +116,9 @@
|
||||
</ic:EventTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</AutoSuggestBox>
|
||||
<ToggleSwitch
|
||||
x:Uid="ShowOnlyDuplicates"
|
||||
IsOn="{x:Bind ViewModel.ShowOnlyDuplicates, Mode=TwoWay}" />
|
||||
<Button
|
||||
x:Uid="ClearFiltersBtn"
|
||||
Margin="0,6,0,0"
|
||||
@ -151,7 +154,6 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
Orientation="Vertical"
|
||||
@ -205,6 +207,7 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FlyoutBase.AttachedFlyout>
|
||||
<MenuFlyout>
|
||||
@ -254,6 +257,7 @@
|
||||
Margin="0,0,8,0"
|
||||
IsActive="{x:Bind Pinging, Mode=OneWay}" />
|
||||
<FontIcon
|
||||
x:Uid="PingIcon"
|
||||
x:Name="PingIcon"
|
||||
Grid.Column="2"
|
||||
Margin="0,0,8,0"
|
||||
@ -306,9 +310,18 @@
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors>
|
||||
</FontIcon>
|
||||
<FontIcon
|
||||
x:Uid="DuplicateEntryIcon"
|
||||
Grid.Column="3"
|
||||
Margin="0,0,8,0"
|
||||
Foreground="{StaticResource SystemControlErrorTextForegroundBrush}"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="18"
|
||||
Glyph=""
|
||||
Visibility="{x:Bind Duplicate, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<ToggleSwitch
|
||||
x:Uid="ActiveToggle"
|
||||
Grid.Column="3"
|
||||
Grid.Column="4"
|
||||
Width="40"
|
||||
MinWidth="0"
|
||||
HorizontalAlignment="Right"
|
||||
@ -384,7 +397,6 @@
|
||||
ScrollViewer.VerticalScrollBarVisibility="Visible"
|
||||
ScrollViewer.VerticalScrollMode="Enabled"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
Loading…
Reference in New Issue
Block a user