mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-09 04:37:30 +02:00
[Hosts]Duplicate check improvements (#32805)
* moved duplicate check in a dedicate service and made it async * addressed feedback
This commit is contained in:
committed by
GitHub
parent
e2f1ad6d40
commit
c00f37c8d7
@@ -47,6 +47,7 @@ namespace Hosts
|
|||||||
services.AddSingleton<IHostsService, HostsService>();
|
services.AddSingleton<IHostsService, HostsService>();
|
||||||
services.AddSingleton<IUserSettings, Hosts.Settings.UserSettings>();
|
services.AddSingleton<IUserSettings, Hosts.Settings.UserSettings>();
|
||||||
services.AddSingleton<IElevationHelper, ElevationHelper>();
|
services.AddSingleton<IElevationHelper, ElevationHelper>();
|
||||||
|
services.AddSingleton<IDuplicateService, DuplicateService>();
|
||||||
|
|
||||||
// Views and ViewModels
|
// Views and ViewModels
|
||||||
services.AddSingleton<ILogger, LoggerWrapper>();
|
services.AddSingleton<ILogger, LoggerWrapper>();
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using HostsUILib.Helpers;
|
|
||||||
using HostsUILib.Settings;
|
using HostsUILib.Settings;
|
||||||
using ManagedCommon;
|
using ManagedCommon;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library;
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
@@ -45,6 +44,8 @@ namespace Hosts.Settings
|
|||||||
// Moved from Settings.UI.Library
|
// Moved from Settings.UI.Library
|
||||||
public HostsEncoding Encoding { get; set; }
|
public HostsEncoding Encoding { get; set; }
|
||||||
|
|
||||||
|
public event EventHandler LoopbackDuplicatesChanged;
|
||||||
|
|
||||||
public UserSettings()
|
public UserSettings()
|
||||||
{
|
{
|
||||||
_settingsUtils = new SettingsUtils();
|
_settingsUtils = new SettingsUtils();
|
||||||
@@ -58,8 +59,6 @@ namespace Hosts.Settings
|
|||||||
_watcher = Helper.GetFileWatcher(HostsModuleName, "settings.json", () => LoadSettingsFromJson());
|
_watcher = Helper.GetFileWatcher(HostsModuleName, "settings.json", () => LoadSettingsFromJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler LoopbackDuplicatesChanged;
|
|
||||||
|
|
||||||
private void LoadSettingsFromJson()
|
private void LoadSettingsFromJson()
|
||||||
{
|
{
|
||||||
lock (_loadingSettingsLock)
|
lock (_loadingSettingsLock)
|
||||||
|
|||||||
165
src/modules/Hosts/HostsUILib/Helpers/DuplicateService.cs
Normal file
165
src/modules/Hosts/HostsUILib/Helpers/DuplicateService.cs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
// 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;
|
||||||
|
using HostsUILib.Models;
|
||||||
|
using HostsUILib.Settings;
|
||||||
|
using Microsoft.UI.Dispatching;
|
||||||
|
|
||||||
|
namespace HostsUILib.Helpers
|
||||||
|
{
|
||||||
|
public class DuplicateService : IDuplicateService, IDisposable
|
||||||
|
{
|
||||||
|
private record struct Check(string Address, string[] Hosts);
|
||||||
|
|
||||||
|
private readonly IUserSettings _userSettings;
|
||||||
|
private readonly DispatcherQueue _dispatcherQueue;
|
||||||
|
private readonly Queue<Check> _checkQueue;
|
||||||
|
private readonly ManualResetEvent _checkEvent;
|
||||||
|
private readonly Thread _queueThread;
|
||||||
|
|
||||||
|
private readonly string[] _loopbackAddresses =
|
||||||
|
{
|
||||||
|
"0.0.0.0",
|
||||||
|
"::",
|
||||||
|
"::0",
|
||||||
|
"0:0:0:0:0:0:0:0",
|
||||||
|
"127.0.0.1",
|
||||||
|
"::1",
|
||||||
|
"0:0:0:0:0:0:0:1",
|
||||||
|
};
|
||||||
|
|
||||||
|
private ReadOnlyCollection<Entry> _entries;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public DuplicateService(IUserSettings userSettings)
|
||||||
|
{
|
||||||
|
_userSettings = userSettings;
|
||||||
|
|
||||||
|
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||||
|
_checkQueue = new Queue<Check>();
|
||||||
|
_checkEvent = new ManualResetEvent(false);
|
||||||
|
|
||||||
|
_queueThread = new Thread(ProcessQueue);
|
||||||
|
_queueThread.IsBackground = true;
|
||||||
|
_queueThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(IList<Entry> entries)
|
||||||
|
{
|
||||||
|
_entries = entries.AsReadOnly();
|
||||||
|
|
||||||
|
if (_checkQueue.Count > 0)
|
||||||
|
{
|
||||||
|
_checkQueue.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in _entries)
|
||||||
|
{
|
||||||
|
if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkQueue.Enqueue(new Check(entry.Address, entry.SplittedHosts));
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CheckDuplicates(string address, string[] hosts)
|
||||||
|
{
|
||||||
|
_checkQueue.Enqueue(new Check(address, hosts));
|
||||||
|
_checkEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessQueue()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
_checkEvent.WaitOne();
|
||||||
|
|
||||||
|
while (_checkQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var check = _checkQueue.Dequeue();
|
||||||
|
FindDuplicates(check.Address, check.Hosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkEvent.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FindDuplicates(string address, string[] hosts)
|
||||||
|
{
|
||||||
|
var entries = _entries.Where(e =>
|
||||||
|
string.Equals(e.Address, address, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| hosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any());
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
SetDuplicate(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetDuplicate(Entry entry)
|
||||||
|
{
|
||||||
|
if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address))
|
||||||
|
{
|
||||||
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
entry.Duplicate = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicate = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
&& 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(() => entry.Duplicate = duplicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_checkEvent?.Dispose();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/modules/Hosts/HostsUILib/Helpers/IDuplicateService.cs
Normal file
16
src/modules/Hosts/HostsUILib/Helpers/IDuplicateService.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// 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 HostsUILib.Models;
|
||||||
|
|
||||||
|
namespace HostsUILib.Helpers
|
||||||
|
{
|
||||||
|
public interface IDuplicateService
|
||||||
|
{
|
||||||
|
void Initialize(IList<Entry> entries);
|
||||||
|
|
||||||
|
void CheckDuplicates(string address, string[] hosts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ using HostsUILib.Models;
|
|||||||
|
|
||||||
namespace HostsUILib.Helpers
|
namespace HostsUILib.Helpers
|
||||||
{
|
{
|
||||||
public interface IHostsService : IDisposable
|
public interface IHostsService
|
||||||
{
|
{
|
||||||
string HostsFilePath { get; }
|
string HostsFilePath { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// 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;
|
||||||
using System.Net;
|
|
||||||
|
|
||||||
namespace HostsUILib.Settings
|
namespace HostsUILib.Settings
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using System.Collections.ObjectModel;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
@@ -23,21 +22,14 @@ using static HostsUILib.Settings.IUserSettings;
|
|||||||
|
|
||||||
namespace HostsUILib.ViewModels
|
namespace HostsUILib.ViewModels
|
||||||
{
|
{
|
||||||
public partial class MainViewModel : ObservableObject, IDisposable
|
public partial class MainViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private readonly IHostsService _hostsService;
|
private readonly IHostsService _hostsService;
|
||||||
private readonly IUserSettings _userSettings;
|
private readonly IUserSettings _userSettings;
|
||||||
|
private readonly IDuplicateService _duplicateService;
|
||||||
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||||
private readonly string[] _loopbackAddresses =
|
|
||||||
{
|
|
||||||
"127.0.0.1",
|
|
||||||
"::1",
|
|
||||||
"0:0:0:0:0:0:0:1",
|
|
||||||
};
|
|
||||||
|
|
||||||
private bool _readingHosts;
|
private bool _readingHosts;
|
||||||
private bool _disposed;
|
|
||||||
private CancellationTokenSource _tokenSource;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Entry _selected;
|
private Entry _selected;
|
||||||
@@ -95,10 +87,16 @@ namespace HostsUILib.ViewModels
|
|||||||
|
|
||||||
private OpenSettingsFunction _openSettingsFunction;
|
private OpenSettingsFunction _openSettingsFunction;
|
||||||
|
|
||||||
public MainViewModel(IHostsService hostService, IUserSettings userSettings, ILogger logger, OpenSettingsFunction openSettingsFunction)
|
public MainViewModel(
|
||||||
|
IHostsService hostService,
|
||||||
|
IUserSettings userSettings,
|
||||||
|
IDuplicateService duplicateService,
|
||||||
|
ILogger logger,
|
||||||
|
OpenSettingsFunction openSettingsFunction)
|
||||||
{
|
{
|
||||||
_hostsService = hostService;
|
_hostsService = hostService;
|
||||||
_userSettings = userSettings;
|
_userSettings = userSettings;
|
||||||
|
_duplicateService = duplicateService;
|
||||||
|
|
||||||
_hostsService.FileChanged += (s, e) => _dispatcherQueue.TryEnqueue(() => FileChanged = true);
|
_hostsService.FileChanged += (s, e) => _dispatcherQueue.TryEnqueue(() => FileChanged = true);
|
||||||
_userSettings.LoopbackDuplicatesChanged += (s, e) => ReadHosts();
|
_userSettings.LoopbackDuplicatesChanged += (s, e) => ReadHosts();
|
||||||
@@ -111,8 +109,7 @@ namespace HostsUILib.ViewModels
|
|||||||
{
|
{
|
||||||
entry.PropertyChanged += Entry_PropertyChanged;
|
entry.PropertyChanged += Entry_PropertyChanged;
|
||||||
_entries.Add(entry);
|
_entries.Add(entry);
|
||||||
|
_duplicateService.CheckDuplicates(entry.Address, entry.SplittedHosts);
|
||||||
FindDuplicates(entry.Address, entry.SplittedHosts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(int index, Entry entry)
|
public void Update(int index, Entry entry)
|
||||||
@@ -126,8 +123,8 @@ namespace HostsUILib.ViewModels
|
|||||||
existingEntry.Hosts = entry.Hosts;
|
existingEntry.Hosts = entry.Hosts;
|
||||||
existingEntry.Active = entry.Active;
|
existingEntry.Active = entry.Active;
|
||||||
|
|
||||||
FindDuplicates(oldAddress, oldHosts);
|
_duplicateService.CheckDuplicates(oldAddress, oldHosts);
|
||||||
FindDuplicates(entry.Address, entry.SplittedHosts);
|
_duplicateService.CheckDuplicates(entry.Address, entry.SplittedHosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteSelected()
|
public void DeleteSelected()
|
||||||
@@ -135,8 +132,7 @@ namespace HostsUILib.ViewModels
|
|||||||
var address = Selected.Address;
|
var address = Selected.Address;
|
||||||
var hosts = Selected.SplittedHosts;
|
var hosts = Selected.SplittedHosts;
|
||||||
_entries.Remove(Selected);
|
_entries.Remove(Selected);
|
||||||
|
_duplicateService.CheckDuplicates(address, hosts);
|
||||||
FindDuplicates(address, hosts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateAdditionalLines(string lines)
|
public void UpdateAdditionalLines(string lines)
|
||||||
@@ -169,8 +165,7 @@ namespace HostsUILib.ViewModels
|
|||||||
var address = entry.Address;
|
var address = entry.Address;
|
||||||
var hosts = entry.SplittedHosts;
|
var hosts = entry.SplittedHosts;
|
||||||
_entries.Remove(entry);
|
_entries.Remove(entry);
|
||||||
|
_duplicateService.CheckDuplicates(address, hosts);
|
||||||
FindDuplicates(address, hosts);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,9 +208,7 @@ namespace HostsUILib.ViewModels
|
|||||||
});
|
});
|
||||||
_readingHosts = false;
|
_readingHosts = false;
|
||||||
|
|
||||||
_tokenSource?.Cancel();
|
_duplicateService.Initialize(_entries);
|
||||||
_tokenSource = new CancellationTokenSource();
|
|
||||||
FindDuplicates(_tokenSource.Token);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,12 +287,6 @@ namespace HostsUILib.ViewModels
|
|||||||
_ = Task.Run(SaveAsync);
|
_ = Task.Run(SaveAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(disposing: true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Entry_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
private void Entry_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Filtered && (e.PropertyName == nameof(Entry.Hosts)
|
if (Filtered && (e.PropertyName == nameof(Entry.Hosts)
|
||||||
@@ -326,82 +313,6 @@ namespace HostsUILib.ViewModels
|
|||||||
_ = Task.Run(SaveAsync);
|
_ = Task.Run(SaveAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FindDuplicates(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
foreach (var entry in _entries)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDuplicate(entry);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
LoggerInstance.Logger.LogInfo("FindDuplicates cancelled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FindDuplicates(string address, IEnumerable<string> hosts)
|
|
||||||
{
|
|
||||||
var entries = _entries.Where(e =>
|
|
||||||
string.Equals(e.Address, address, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| hosts.Intersect(e.SplittedHosts, StringComparer.OrdinalIgnoreCase).Any());
|
|
||||||
|
|
||||||
foreach (var entry in entries)
|
|
||||||
{
|
|
||||||
SetDuplicate(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetDuplicate(Entry entry)
|
|
||||||
{
|
|
||||||
if (!_userSettings.LoopbackDuplicates && _loopbackAddresses.Contains(entry.Address))
|
|
||||||
{
|
|
||||||
_dispatcherQueue.TryEnqueue(() =>
|
|
||||||
{
|
|
||||||
entry.Duplicate = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var duplicate = false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
&& 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(() =>
|
|
||||||
{
|
|
||||||
entry.Duplicate = duplicate;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveAsync()
|
private async Task SaveAsync()
|
||||||
{
|
{
|
||||||
bool error = true;
|
bool error = true;
|
||||||
@@ -444,17 +355,5 @@ namespace HostsUILib.ViewModels
|
|||||||
IsReadOnly = isReadOnly;
|
IsReadOnly = isReadOnly;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!_disposed)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_hostsService?.Dispose();
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user