[Hosts]Improved save error handling (#28215)

This commit is contained in:
Davide Giacometti
2023-09-08 17:25:36 +02:00
committed by GitHub
parent 5e353640d7
commit 6c09565244
8 changed files with 81 additions and 28 deletions

View File

@@ -1849,6 +1849,7 @@ subquery
subresource subresource
Superbar Superbar
sut sut
svchost
SVE SVE
SVGIn SVGIn
SVGIO SVGIO

View File

@@ -238,5 +238,17 @@ namespace Hosts.Tests
var result = fileSystem.GetFile(service.HostsFilePath); var result = fileSystem.GetFile(service.HostsFilePath);
Assert.AreEqual(result.TextContents, contentResult); Assert.AreEqual(result.TextContents, contentResult);
} }
[TestMethod]
public async Task Save_NotRunningElevatedException()
{
var fileSystem = new CustomMockFileSystem();
var userSettings = new Mock<IUserSettings>();
var elevationHelper = new Mock<IElevationHelper>();
elevationHelper.Setup(m => m.IsElevated).Returns(false);
var service = new HostsService(fileSystem, userSettings.Object, elevationHelper.Object);
await Assert.ThrowsExceptionAsync<NotRunningElevatedException>(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>()));
}
} }
} }

View File

@@ -122,11 +122,11 @@ namespace Hosts.Helpers
return new HostsData(entries, unparsedBuilder.ToString(), splittedEntries); return new HostsData(entries, unparsedBuilder.ToString(), splittedEntries);
} }
public async Task<bool> WriteAsync(string additionalLines, IEnumerable<Entry> entries) public async Task WriteAsync(string additionalLines, IEnumerable<Entry> entries)
{ {
if (!_elevationHelper.IsElevated) if (!_elevationHelper.IsElevated)
{ {
return false; throw new NotRunningElevatedException();
} }
var lines = new List<string>(); var lines = new List<string>();
@@ -195,18 +195,11 @@ namespace Hosts.Helpers
await _fileSystem.File.WriteAllLinesAsync(HostsFilePath, lines, Encoding); await _fileSystem.File.WriteAllLinesAsync(HostsFilePath, lines, Encoding);
} }
catch (Exception ex)
{
Logger.LogError("Failed to write hosts file", ex);
return false;
}
finally finally
{ {
_fileSystemWatcher.EnableRaisingEvents = true; _fileSystemWatcher.EnableRaisingEvents = true;
_asyncLock.Release(); _asyncLock.Release();
} }
return true;
} }
public async Task<bool> PingAsync(string address) public async Task<bool> PingAsync(string address)

View File

@@ -17,7 +17,7 @@ namespace Hosts.Helpers
Task<HostsData> ReadAsync(); Task<HostsData> ReadAsync();
Task<bool> WriteAsync(string additionalLines, IEnumerable<Entry> entries); Task WriteAsync(string additionalLines, IEnumerable<Entry> entries);
Task<bool> PingAsync(string address); Task<bool> PingAsync(string address);

View File

@@ -0,0 +1,12 @@
// 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;
namespace Hosts.Helpers
{
public class NotRunningElevatedException : Exception
{
}
}

View File

@@ -390,6 +390,7 @@
x:Uid="FileSaveError" x:Uid="FileSaveError"
Margin="0,8,0,0" Margin="0,8,0,0"
Severity="Error" Severity="Error"
Message="{x:Bind ViewModel.ErrorMessage, Mode=TwoWay}"
IsOpen="{x:Bind ViewModel.Error, Mode=TwoWay}" IsOpen="{x:Bind ViewModel.Error, Mode=TwoWay}"
Visibility="{x:Bind ViewModel.Error, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}}" /> Visibility="{x:Bind ViewModel.Error, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}}" />

View File

@@ -216,8 +216,16 @@
<value>Hosts file was modified externally.</value> <value>Hosts file was modified externally.</value>
<comment>"Hosts" refers to the system hosts file, do not loc</comment> <comment>"Hosts" refers to the system hosts file, do not loc</comment>
</data> </data>
<data name="FileSaveError.Message" xml:space="preserve"> <data name="FileSaveError_FileInUse" xml:space="preserve">
<value>Failed to save hosts file.</value> <value>The hosts file cannot be saved because it is being used by another process.</value>
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
</data>
<data name="FileSaveError_Generic" xml:space="preserve">
<value>Unable to save the hosts file.</value>
<comment>"Hosts" refers to the system hosts file, do not loc</comment>
</data>
<data name="FileSaveError_NotElevated" xml:space="preserve">
<value>The hosts file cannot be saved because the program isn't running as administrator.</value>
<comment>"Hosts" refers to the system hosts file, do not loc</comment> <comment>"Hosts" refers to the system hosts file, do not loc</comment>
</data> </data>
<data name="FilterBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve"> <data name="FilterBtn.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">

View File

@@ -5,6 +5,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading; using System.Threading;
@@ -44,6 +45,9 @@ namespace Hosts.ViewModels
[ObservableProperty] [ObservableProperty]
private bool _error; private bool _error;
[ObservableProperty]
private string _errorMessage;
[ObservableProperty] [ObservableProperty]
private bool _fileChanged; private bool _fileChanged;
@@ -126,12 +130,7 @@ namespace Hosts.ViewModels
public void UpdateAdditionalLines(string lines) public void UpdateAdditionalLines(string lines)
{ {
AdditionalLines = lines; AdditionalLines = lines;
_ = Task.Run(SaveAsync);
Task.Run(async () =>
{
var error = !await _hostsService.WriteAsync(AdditionalLines, _entries);
await _dispatcherQueue.EnqueueAsync(() => Error = error);
});
} }
public void Move(int oldIndex, int newIndex) public void Move(int oldIndex, int newIndex)
@@ -287,20 +286,12 @@ namespace Hosts.ViewModels
return; return;
} }
Task.Run(async () => _ = Task.Run(SaveAsync);
{
var error = !await _hostsService.WriteAsync(AdditionalLines, _entries);
await _dispatcherQueue.EnqueueAsync(() => Error = error);
});
} }
private void Entries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) private void Entries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{ {
Task.Run(async () => _ = Task.Run(SaveAsync);
{
var error = !await _hostsService.WriteAsync(AdditionalLines, _entries);
await _dispatcherQueue.EnqueueAsync(() => Error = error);
});
} }
private void FindDuplicates(CancellationToken cancellationToken) private void FindDuplicates(CancellationToken cancellationToken)
@@ -379,6 +370,41 @@ namespace Hosts.ViewModels
}); });
} }
private async Task SaveAsync()
{
bool error = true;
string errorMessage = string.Empty;
try
{
await _hostsService.WriteAsync(AdditionalLines, _entries);
error = false;
}
catch (NotRunningElevatedException)
{
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
errorMessage = resourceLoader.GetString("FileSaveError_NotElevated");
}
catch (IOException ex) when ((ex.HResult & 0x0000FFFF) == 32)
{
// There are some edge cases where a big hosts file is being locked by svchost.exe https://github.com/microsoft/PowerToys/issues/28066
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
errorMessage = resourceLoader.GetString("FileSaveError_FileInUse");
}
catch (Exception ex)
{
Logger.LogError("Failed to save hosts file", ex);
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
errorMessage = resourceLoader.GetString("FileSaveError_Generic");
}
await _dispatcherQueue.EnqueueAsync(() =>
{
Error = error;
ErrorMessage = errorMessage;
});
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!_disposed) if (!_disposed)