Skip to content

Commit cfdba16

Browse files
committed
Feature: Parse & Reload hosts file... and some cleanup / improvements
1 parent 420cb14 commit cfdba16

36 files changed

+1720
-1424
lines changed

Source/NETworkManager.Localization/Resources/Strings.Designer.cs

Lines changed: 1280 additions & 1270 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Source/NETworkManager.Localization/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3888,4 +3888,7 @@ Right-click for more options.</value>
38883888
<data name="HostsFileEditorAdminMessage" xml:space="preserve">
38893889
<value>To edit the hosts file, the application must be started with elevated rights!</value>
38903890
</data>
3891+
<data name="Comment" xml:space="preserve">
3892+
<value>Comment</value>
3893+
</data>
38913894
</root>

Source/NETworkManager.Models/HostsFileEditor/HostsFileEditor.cs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,71 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Text.RegularExpressions;
5+
using System.Threading.Tasks;
56
using log4net;
67
using NETworkManager.Utilities;
78

89
namespace NETworkManager.Models.HostsFileEditor;
910

10-
public class HostsFileEditor
11+
public static class HostsFileEditor
1112
{
1213
#region Events
13-
public event EventHandler HostsFileChanged;
14+
public static event EventHandler HostsFileChanged;
1415

15-
private void OnHostsFileChanged()
16+
private static void OnHostsFileChanged()
1617
{
1718
Log.Debug("OnHostsFileChanged - Hosts file changed.");
18-
HostsFileChanged?.Invoke(this, EventArgs.Empty);
19+
HostsFileChanged?.Invoke(null, EventArgs.Empty);
1920
}
2021
#endregion
2122

2223
#region Variables
2324
private static readonly ILog Log = LogManager.GetLogger(typeof(HostsFileEditor));
25+
26+
private static readonly FileSystemWatcher HostsFileWatcher;
2427

2528
/// <summary>
2629
/// Path to the hosts file.
2730
/// </summary>
2831
private static string HostsFilePath => Path.Combine(Environment.SystemDirectory, "drivers", "etc", "hosts");
2932

33+
/// <summary>
34+
/// Example values in the hosts file that should be ignored.
35+
/// </summary>
36+
private static readonly HashSet<(string IPAddress, string Hostname)> ExampleValuesToIgnore =
37+
[
38+
("102.54.94.97", "rhino.acme.com"),
39+
("38.25.63.10", "x.acme.com")
40+
];
41+
3042
/// <summary>
3143
/// Regex to match a hosts file entry with optional comments, supporting IPv4, IPv6, and hostnames
3244
/// </summary>
33-
private readonly Regex _hostsFileEntryRegex = new Regex(RegexHelper.HostsEntryRegex);
45+
private static readonly Regex HostsFileEntryRegex = new(RegexHelper.HostsEntryRegex);
3446

3547
#endregion
3648

3749
#region Constructor
38-
public HostsFileEditor()
50+
51+
static HostsFileEditor()
3952
{
4053
// Create a file system watcher to monitor changes to the hosts file
4154
try
4255
{
4356
Log.Debug("HostsFileEditor - Creating file system watcher for hosts file...");
4457

45-
FileSystemWatcher watcher = new();
46-
watcher.Path = Path.GetDirectoryName(HostsFilePath) ?? throw new InvalidOperationException("Hosts file path is invalid.");
47-
watcher.Filter = Path.GetFileName(HostsFilePath) ?? throw new InvalidOperationException("Hosts file name is invalid.");
48-
watcher.NotifyFilter = NotifyFilters.LastWrite;
58+
// Create the file system watcher
59+
HostsFileWatcher = new FileSystemWatcher();
60+
HostsFileWatcher.Path = Path.GetDirectoryName(HostsFilePath) ?? throw new InvalidOperationException("Hosts file path is invalid.");
61+
HostsFileWatcher.Filter = Path.GetFileName(HostsFilePath) ?? throw new InvalidOperationException("Hosts file name is invalid.");
62+
HostsFileWatcher.NotifyFilter = NotifyFilters.LastWrite;
4963

5064
// Maybe fired twice. This is a known bug/feature.
5165
// See: https://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice
52-
watcher.Changed += (_, _) => OnHostsFileChanged();
66+
HostsFileWatcher.Changed += (_, _) => OnHostsFileChanged();
5367

54-
watcher.EnableRaisingEvents = true;
68+
// Enable the file system watcher
69+
HostsFileWatcher.EnableRaisingEvents = true;
5570

5671
Log.Debug("HostsFileEditor - File system watcher for hosts file created.");
5772
}
@@ -63,11 +78,16 @@ public HostsFileEditor()
6378
#endregion
6479

6580
#region Methods
81+
public static Task<IEnumerable<HostsFileEntry>> GetHostsFileEntriesAsync()
82+
{
83+
return Task.Run(GetHostsFileEntries);
84+
}
85+
6686
/// <summary>
6787
///
6888
/// </summary>
6989
/// <returns></returns>
70-
public IEnumerable<HostsFileEntry> GetHostsFileEntries()
90+
private static IEnumerable<HostsFileEntry> GetHostsFileEntries()
7191
{
7292
var hostsFileLines = File.ReadAllLines(HostsFilePath);
7393

@@ -76,7 +96,7 @@ public IEnumerable<HostsFileEntry> GetHostsFileEntries()
7696

7797
foreach (var line in hostsFileLines)
7898
{
79-
var result = _hostsFileEntryRegex.Match(line.Trim());
99+
var result = HostsFileEntryRegex.Match(line.Trim());
80100

81101
if (result.Success)
82102
{
@@ -85,17 +105,20 @@ public IEnumerable<HostsFileEntry> GetHostsFileEntries()
85105
var entry = new HostsFileEntry
86106
{
87107
IsEnabled = !result.Groups[1].Value.Equals("#"),
88-
IpAddress = result.Groups[2].Value,
89-
HostName = result.Groups[3].Value.Replace(@"\s", "").Split([' ']),
90-
Comment = result.Groups[4].Value,
108+
IPAddress = result.Groups[2].Value,
109+
Hostname = result.Groups[3].Value.Replace(@"\s", "").Trim(),
110+
Comment = result.Groups[4].Value.TrimStart('#'),
91111
Line = line
92112
};
93113

94114
// Skip example entries
95-
if(!entry.IsEnabled && entry.IpAddress is "102.54.94.97" or "38.25.63.10" && entry.HostName[0] is "rhino.acme.com" or "x.acme.com")
115+
if(!entry.IsEnabled)
96116
{
97-
Log.Debug("GetHostsFileEntries - Matched example entry. Skipping...");
98-
continue;
117+
if (ExampleValuesToIgnore.Contains((entry.IPAddress, entry.Hostname)))
118+
{
119+
Log.Debug("GetHostsFileEntries - Matched example entry. Skipping...");
120+
continue;
121+
}
99122
}
100123

101124
entries.Add(entry);

Source/NETworkManager.Models/HostsFileEditor/HostsFileEntry.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ public class HostsFileEntry
1313
/// <summary>
1414
/// IP address of the host.
1515
/// </summary>
16-
public string IpAddress { get; init; }
16+
public string IPAddress { get; init; }
1717

1818
/// <summary>
19-
/// Host name(s) of the host.
19+
/// Host name(s) of the host. Multiple host names are separated by a
20+
/// space (equal to the hosts file format).
2021
/// </summary>
21-
public string[] HostName { get; init; }
22+
public string Hostname { get; init; }
2223

2324
/// <summary>
2425
/// Comment of the host.
@@ -43,13 +44,13 @@ public HostsFileEntry()
4344
/// </summary>
4445
/// <param name="isEnabled">Indicates whether the entry is enabled or not.</param>
4546
/// <param name="ipAddress">IP address of the host.</param>
46-
/// <param name="hostName">Host name(s) of the host.</param>
47+
/// <param name="hostname">Host name(s) of the host.</param>
4748
/// <param name="comment">Comment of the host.</param>
48-
public HostsFileEntry(bool isEnabled, string ipAddress, string[] hostName, string comment)
49+
public HostsFileEntry(bool isEnabled, string ipAddress, string hostname, string comment)
4950
{
5051
IsEnabled = isEnabled;
51-
IpAddress = ipAddress;
52-
HostName = hostName;
52+
IPAddress = ipAddress;
53+
Hostname = hostname;
5354
Comment = comment;
5455
}
5556

@@ -58,10 +59,10 @@ public HostsFileEntry(bool isEnabled, string ipAddress, string[] hostName, strin
5859
/// </summary>
5960
/// <param name="isEnabled">Indicates whether the entry is enabled or not.</param>
6061
/// <param name="ipAddress">IP address of the host.</param>
61-
/// <param name="hostName">Host name(s) of the host.</param>
62+
/// <param name="hostname">Host name(s) of the host.</param>
6263
/// <param name="comment">Comment of the host.</param>
6364
/// <param name="line">Line of the entry in the hosts file.</param>
64-
public HostsFileEntry(bool isEnabled, string ipAddress, string[] hostName, string comment, string line) : this(isEnabled, ipAddress, hostName, comment)
65+
public HostsFileEntry(bool isEnabled, string ipAddress, string hostname, string comment, string line) : this(isEnabled, ipAddress, hostname, comment)
6566
{
6667
Line = line;
6768
}

Source/NETworkManager.Models/Network/Connection.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public enum TcpTableClass
5252
}
5353

5454
// Cache for remote host names with some default values
55-
private static readonly Dictionary<IPAddress, string> _remoteHostNames = new()
55+
private static readonly Dictionary<IPAddress, string> RemoteHostNames = new()
5656
{
5757
{ IPAddress.Parse("127.0.0.1"), "localhost" },
5858
{ IPAddress.Parse("::1"), "localhost" },
@@ -94,9 +94,9 @@ private static List<ConnectionInfo> GetActiveTcpConnections()
9494
var row = (MibTcpRowOwnerPid)Marshal.PtrToStructure(rowPtr, typeof(MibTcpRowOwnerPid))!;
9595

9696
var localAddress = new IPAddress(row.localAddr);
97-
var localPort = BitConverter.ToUInt16(new[] { row.localPort2, row.localPort1 }, 0);
97+
var localPort = BitConverter.ToUInt16([row.localPort2, row.localPort1], 0);
9898
var remoteAddress = new IPAddress(row.remoteAddr);
99-
var remotePort = BitConverter.ToUInt16(new[] { row.remotePort2, row.remotePort1 }, 0);
99+
var remotePort = BitConverter.ToUInt16([row.remotePort2, row.remotePort1], 0);
100100
var state = (TcpState)row.state;
101101

102102
// Get process info by PID
@@ -116,14 +116,14 @@ private static List<ConnectionInfo> GetActiveTcpConnections()
116116
}
117117

118118
// Resolve remote host name if not cached
119-
if (!_remoteHostNames.ContainsKey(remoteAddress))
119+
if (!RemoteHostNames.ContainsKey(remoteAddress))
120120
{
121121
var dnsResolverTask = DNSClient.GetInstance().ResolvePtrAsync(remoteAddress);
122122

123123
dnsResolverTask.Wait();
124124

125125
// Cache the result
126-
_remoteHostNames.Add(remoteAddress,
126+
RemoteHostNames.Add(remoteAddress,
127127
!dnsResolverTask.Result.HasError ? dnsResolverTask.Result.Value : "-/-");
128128
}
129129

@@ -133,7 +133,7 @@ private static List<ConnectionInfo> GetActiveTcpConnections()
133133
localPort,
134134
remoteAddress,
135135
remotePort,
136-
_remoteHostNames.GetValueOrDefault(remoteAddress, "-/-"),
136+
RemoteHostNames.GetValueOrDefault(remoteAddress, "-/-"),
137137
state,
138138
processId,
139139
processName,

Source/NETworkManager/ViewModels/ARPTableViewModel.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ public ARPTableViewModel(IDialogCoordinator instance)
3838

3939
ResultsView.Filter = o =>
4040
{
41-
if (o is not ARPInfo info)
42-
return false;
43-
4441
if (string.IsNullOrEmpty(Search))
4542
return true;
43+
44+
if (o is not ARPInfo info)
45+
return false;
4646

4747
// Search by IPAddress and MACAddress
4848
return info.IPAddress.ToString().IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
@@ -95,7 +95,7 @@ public string Search
9595
}
9696
}
9797

98-
private ObservableCollection<ARPInfo> _results = new();
98+
private ObservableCollection<ARPInfo> _results = [];
9999

100100
public ObservableCollection<ARPInfo> Results
101101
{
@@ -429,8 +429,8 @@ private async Task Refresh()
429429
IsRefreshing = true;
430430

431431
Results.Clear();
432-
433-
(await ARP.GetTableAsync()).ForEach(x => Results.Add(x));
432+
433+
(await ARP.GetTableAsync()).ForEach(Results.Add);
434434

435435
IsRefreshing = false;
436436
}

Source/NETworkManager/ViewModels/AWSSessionManagerHostViewModel.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ private async Task SyncAllInstanceIDsFromAWS()
650650
}
651651

652652
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
653-
await Task.Delay(2000);
653+
await Task.Delay(2500);
654654

655655
Log.Info("All Instance IDs synced from AWS!");
656656

@@ -679,7 +679,7 @@ private async Task SyncGroupInstanceIDsFromAWS(string group)
679679
}
680680

681681
// Make the user happy, let him see a reload animation (and he cannot spam the reload command)
682-
await Task.Delay(2000);
682+
await Task.Delay(2500);
683683

684684
Log.Info("Group synced!");
685685

@@ -998,12 +998,12 @@ private void SetProfilesView(ProfileInfo profile = null)
998998

999999
Profiles.Filter = o =>
10001000
{
1001+
if (string.IsNullOrEmpty(Search))
1002+
return true ;
1003+
10011004
if (o is not ProfileInfo info)
10021005
return false;
10031006

1004-
if (string.IsNullOrEmpty(Search))
1005-
return true;
1006-
10071007
var search = Search.Trim();
10081008

10091009
// Search by: Tag=xxx (exact match, ignore case)

Source/NETworkManager/ViewModels/AboutViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ private async Task CheckForUpdatesAsync()
4242
IsUpdateCheckRunning = true;
4343

4444
// Show a loading animation for the user
45-
await Task.Delay(1000);
45+
await Task.Delay(1250);
4646

4747
var updater = new Updater();
4848

Source/NETworkManager/ViewModels/ConnectionsViewModel.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ public ConnectionsViewModel(IDialogCoordinator instance)
3939
IPAddressHelper.CompareIPAddresses(x.LocalIPAddress, y.LocalIPAddress));
4040

4141
ResultsView.Filter = o =>
42-
{
43-
if (o is not ConnectionInfo info)
44-
return false;
45-
42+
{
4643
if (string.IsNullOrEmpty(Search))
47-
return true;
44+
return true;
45+
46+
if (o is not ConnectionInfo info)
47+
return false;
4848

4949
// Search by local/remote IP Address, local/remote Port, Protocol and State
5050
return info.LocalIPAddress.ToString().IndexOf(Search, StringComparison.OrdinalIgnoreCase) > -1 ||
@@ -346,8 +346,8 @@ private async Task Refresh()
346346
IsRefreshing = true;
347347

348348
Results.Clear();
349-
350-
(await Connection.GetActiveTcpConnectionsAsync()).ForEach(x => Results.Add(x));
349+
350+
(await Connection.GetActiveTcpConnectionsAsync()).ForEach(Results.Add);
351351

352352
IsRefreshing = false;
353353
}

Source/NETworkManager/ViewModels/DNSLookupHostViewModel.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,11 +372,13 @@ private void SetProfilesView(ProfileInfo profile = null)
372372

373373
Profiles.Filter = o =>
374374
{
375+
if (string.IsNullOrEmpty(Search))
376+
return true;
377+
375378
if (o is not ProfileInfo info)
376379
return false;
377380

378-
if (string.IsNullOrEmpty(Search))
379-
return true;
381+
380382

381383
var search = Search.Trim();
382384

0 commit comments

Comments
 (0)