mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Implement installer UI error messaging
This commit is contained in:
parent
3edf568ef7
commit
165760e082
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -85,6 +86,12 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
|
|||||||
[Reactive]
|
[Reactive]
|
||||||
public InstallState InstallState { get; set; }
|
public InstallState InstallState { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
protected ErrorResponse[] Errors { get; private set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public ErrorResponse Error { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Slideshow Data
|
/// Slideshow Data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -114,6 +121,9 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
|
|||||||
[Reactive]
|
[Reactive]
|
||||||
public bool Installing { get; set; }
|
public bool Installing { get; set; }
|
||||||
|
|
||||||
|
[Reactive]
|
||||||
|
public ErrorResponse ErrorState { get; set; }
|
||||||
|
|
||||||
[Reactive]
|
[Reactive]
|
||||||
public bool ShowNSFWSlides { get; set; }
|
public bool ShowNSFWSlides { get; set; }
|
||||||
|
|
||||||
@ -221,10 +231,45 @@ public class InstallerVM : BackNavigatingVM, IBackNavigatingVM, ICpuStatusVM
|
|||||||
BeginSlideShow(token.Token).FireAndForget();
|
BeginSlideShow(token.Token).FireAndForget();
|
||||||
Disposable.Create(() => token.Cancel())
|
Disposable.Create(() => token.Cancel())
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.WhenAny(vm => vm.ModListLocation.ErrorState)
|
||||||
|
.CombineLatest(this.WhenAny(vm => vm.Installer.DownloadLocation.ErrorState),
|
||||||
|
this.WhenAny(vm => vm.Installer.Location.ErrorState),
|
||||||
|
this.WhenAny(vm => vm.ModListLocation.TargetPath),
|
||||||
|
this.WhenAny(vm => vm.Installer.Location.TargetPath),
|
||||||
|
this.WhenAny(vm => vm.Installer.DownloadLocation.TargetPath))
|
||||||
|
.Select(t =>
|
||||||
|
{
|
||||||
|
var errors = new[] {t.First, t.Second, t.Third}
|
||||||
|
.Where(t => t.Failed)
|
||||||
|
.Concat(Validate())
|
||||||
|
.ToArray();
|
||||||
|
if (!errors.Any()) return ErrorResponse.Success;
|
||||||
|
return ErrorResponse.Fail(string.Join("\n", errors.Select(e => e.Reason)));
|
||||||
|
})
|
||||||
|
.BindTo(this, vm => vm.ErrorState)
|
||||||
|
.DisposeWith(disposables);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<ErrorResponse> Validate()
|
||||||
|
{
|
||||||
|
if (!ModListLocation.TargetPath.FileExists())
|
||||||
|
yield return ErrorResponse.Fail("Mod list source does not exist");
|
||||||
|
|
||||||
|
var downloadPath = Installer.DownloadLocation.TargetPath;
|
||||||
|
if (downloadPath.Depth <= 1)
|
||||||
|
yield return ErrorResponse.Fail("Download path isn't set to a folder");
|
||||||
|
|
||||||
|
var installPath = Installer.Location.TargetPath;
|
||||||
|
if (installPath.Depth <= 1)
|
||||||
|
yield return ErrorResponse.Fail("Install path isn't set to a folder");
|
||||||
|
if (installPath.InFolder(KnownFolders.Windows))
|
||||||
|
yield return ErrorResponse.Fail("Don't install modlists into your Windows folder");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task BeginSlideShow(CancellationToken token)
|
private async Task BeginSlideShow(CancellationToken token)
|
||||||
{
|
{
|
||||||
while (!token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
|
@ -39,19 +39,27 @@ namespace Wabbajack
|
|||||||
this.WhenAny(x => x.ViewModel.BeginCommand)
|
this.WhenAny(x => x.ViewModel.BeginCommand)
|
||||||
.BindToStrict(this, x => x.BeginButton.Command)
|
.BindToStrict(this, x => x.BeginButton.Command)
|
||||||
.DisposeWith(dispose);
|
.DisposeWith(dispose);
|
||||||
|
|
||||||
// Error icon display
|
// Error handling
|
||||||
var vis = this.WhenAny(x => x.ViewModel.Installer.CanInstall)
|
|
||||||
.Select(err => err.Failed ? Visibility.Visible : Visibility.Hidden)
|
this.WhenAnyValue(x => x.ViewModel.ErrorState)
|
||||||
.Replay(1)
|
.Select(v => !v.Failed)
|
||||||
.RefCount();
|
.BindToStrict(this, view => view.BeginButton.IsEnabled)
|
||||||
vis.BindToStrict(this, x => x.ErrorSummaryIconGlow.Visibility)
|
|
||||||
.DisposeWith(dispose);
|
.DisposeWith(dispose);
|
||||||
vis.BindToStrict(this, x => x.ErrorSummaryIcon.Visibility)
|
|
||||||
|
this.WhenAnyValue(x => x.ViewModel.ErrorState)
|
||||||
|
.Select(v => v.Failed ? Visibility.Visible : Visibility.Hidden)
|
||||||
|
.BindToStrict(this, view => view.ErrorSummaryIcon.Visibility)
|
||||||
.DisposeWith(dispose);
|
.DisposeWith(dispose);
|
||||||
this.WhenAny(x => x.ViewModel.Installer.CanInstall)
|
|
||||||
.Select(x => x.Reason)
|
this.WhenAnyValue(x => x.ViewModel.ErrorState)
|
||||||
.BindToStrict(this, x => x.ErrorSummaryIcon.ToolTip)
|
.Select(v => v.Failed ? Visibility.Visible : Visibility.Hidden)
|
||||||
|
.BindToStrict(this, view => view.ErrorSummaryIconGlow.Visibility)
|
||||||
|
.DisposeWith(dispose);
|
||||||
|
|
||||||
|
this.WhenAnyValue(x => x.ViewModel.ErrorState)
|
||||||
|
.Select(v => v.Reason)
|
||||||
|
.BindToStrict(this, view => view.ErrorSummaryIcon.ToolTip)
|
||||||
.DisposeWith(dispose);
|
.DisposeWith(dispose);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,7 @@ namespace Wabbajack
|
|||||||
.BindToStrict(this, view => view.TopProgressBar.ProgressPercent)
|
.BindToStrict(this, view => view.TopProgressBar.ProgressPercent)
|
||||||
.DisposeWith(disposables);
|
.DisposeWith(disposables);
|
||||||
|
|
||||||
|
|
||||||
// Slideshow
|
// Slideshow
|
||||||
ViewModel.WhenAnyValue(vm => vm.SlideShowTitle)
|
ViewModel.WhenAnyValue(vm => vm.SlideShowTitle)
|
||||||
.Select(f => f)
|
.Select(f => f)
|
||||||
|
@ -40,4 +40,6 @@ public class NativeFileStreamFactory : IStreamFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IPath Name { get; }
|
public IPath Name { get; }
|
||||||
|
|
||||||
|
public AbsolutePath FullPath => (AbsolutePath) Name;
|
||||||
}
|
}
|
@ -3,9 +3,13 @@ using System.Net.Http;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.DTOs.Streams;
|
||||||
using Wabbajack.DTOs.Vfs;
|
using Wabbajack.DTOs.Vfs;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.Networking.Http;
|
using Wabbajack.Networking.Http;
|
||||||
|
using Wabbajack.Paths;
|
||||||
|
using Wabbajack.Paths.IO;
|
||||||
using Wabbajack.VFS.Interfaces;
|
using Wabbajack.VFS.Interfaces;
|
||||||
|
|
||||||
namespace Wabbajack.Networking.WabbajackClientApi;
|
namespace Wabbajack.Networking.WabbajackClientApi;
|
||||||
@ -14,6 +18,7 @@ public class CesiVFSCache : IVfsCache
|
|||||||
{
|
{
|
||||||
private readonly Client _client;
|
private readonly Client _client;
|
||||||
private readonly ILogger<CesiVFSCache> _logger;
|
private readonly ILogger<CesiVFSCache> _logger;
|
||||||
|
private const int Threshold = 1024 * 1024 * 128;
|
||||||
|
|
||||||
public CesiVFSCache(ILogger<CesiVFSCache> logger, Client client)
|
public CesiVFSCache(ILogger<CesiVFSCache> logger, Client client)
|
||||||
{
|
{
|
||||||
@ -21,8 +26,12 @@ public class CesiVFSCache : IVfsCache
|
|||||||
_client = client;
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IndexedVirtualFile?> Get(Hash hash, CancellationToken token)
|
public async Task<IndexedVirtualFile?> Get(Hash hash, IStreamFactory sf, CancellationToken token)
|
||||||
{
|
{
|
||||||
|
if (sf is not NativeFileStreamFactory nf)
|
||||||
|
return null;
|
||||||
|
if (nf.FullPath.Size() < Threshold) return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _client.GetCesiVfsEntry(hash, token);
|
var result = await _client.GetCesiVfsEntry(hash, token);
|
||||||
|
@ -281,6 +281,7 @@ public static class AbsolutePathExtensions
|
|||||||
|
|
||||||
public static IEnumerable<AbsolutePath> EnumerateDirectories(this AbsolutePath path, bool recursive = true)
|
public static IEnumerable<AbsolutePath> EnumerateDirectories(this AbsolutePath path, bool recursive = true)
|
||||||
{
|
{
|
||||||
|
if (!path.DirectoryExists()) return Array.Empty<AbsolutePath>();
|
||||||
return Directory.EnumerateDirectories(path.ToString(), "*",
|
return Directory.EnumerateDirectories(path.ToString(), "*",
|
||||||
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
||||||
.Select(p => (AbsolutePath) p);
|
.Select(p => (AbsolutePath) p);
|
||||||
|
@ -67,7 +67,7 @@ public struct AbsolutePath : IPath, IComparable<AbsolutePath>, IEquatable<Absolu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Depth => Parts.Length;
|
public int Depth => Parts?.Length ?? 0;
|
||||||
|
|
||||||
public AbsolutePath ReplaceExtension(Extension newExtension)
|
public AbsolutePath ReplaceExtension(Extension newExtension)
|
||||||
{
|
{
|
||||||
|
@ -6,8 +6,8 @@ public static class ArrayExtensions
|
|||||||
{
|
{
|
||||||
public static bool AreEqual<T>(T[] a, int startA, T[] b, int startB, int length)
|
public static bool AreEqual<T>(T[] a, int startA, T[] b, int startB, int length)
|
||||||
{
|
{
|
||||||
if (startA + length > a.Length) return false;
|
if (startA + length > (a?.Length ?? 0)) return false;
|
||||||
if (startB + length > b.Length) return false;
|
if (startB + length > (b?.Length ?? 0)) return false;
|
||||||
|
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
if (!a[startA + i]!.Equals(b[startB + i]))
|
if (!a[startA + i]!.Equals(b[startB + i]))
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
using Wabbajack.DTOs.Vfs;
|
using Wabbajack.DTOs.Streams;
|
||||||
|
using Wabbajack.DTOs.Vfs;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
|
using Wabbajack.Paths;
|
||||||
|
|
||||||
namespace Wabbajack.VFS.Interfaces;
|
namespace Wabbajack.VFS.Interfaces;
|
||||||
|
|
||||||
public interface IVfsCache
|
public interface IVfsCache
|
||||||
{
|
{
|
||||||
public Task<IndexedVirtualFile?> Get(Hash hash, CancellationToken token);
|
public Task<IndexedVirtualFile?> Get(Hash hash, IStreamFactory sf, CancellationToken token);
|
||||||
public Task Put(IndexedVirtualFile file, CancellationToken token);
|
public Task Put(IndexedVirtualFile file, CancellationToken token);
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Wabbajack.DTOs.Streams;
|
||||||
using Wabbajack.DTOs.Vfs;
|
using Wabbajack.DTOs.Vfs;
|
||||||
using Wabbajack.Hashing.xxHash64;
|
using Wabbajack.Hashing.xxHash64;
|
||||||
using Wabbajack.VFS.Interfaces;
|
using Wabbajack.VFS.Interfaces;
|
||||||
@ -15,14 +16,14 @@ public class FallthroughVFSCache : IVfsCache
|
|||||||
_caches = caches;
|
_caches = caches;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IndexedVirtualFile?> Get(Hash hash, CancellationToken token)
|
public async Task<IndexedVirtualFile?> Get(Hash hash, IStreamFactory sf, CancellationToken token)
|
||||||
{
|
{
|
||||||
IndexedVirtualFile? result = null;
|
IndexedVirtualFile? result = null;
|
||||||
foreach (var cache in _caches)
|
foreach (var cache in _caches)
|
||||||
{
|
{
|
||||||
if (result == null)
|
if (result == null)
|
||||||
{
|
{
|
||||||
result = await cache.Get(hash, token);
|
result = await cache.Get(hash, sf, token);
|
||||||
if (result == null) continue;
|
if (result == null) continue;
|
||||||
foreach (var upperCache in _caches)
|
foreach (var upperCache in _caches)
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@ public class VFSDiskCache : IVfsCache
|
|||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IndexedVirtualFile?> Get(Hash hash, CancellationToken token)
|
public async Task<IndexedVirtualFile?> Get(Hash hash, IStreamFactory sfn, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (hash == default)
|
if (hash == default)
|
||||||
throw new ArgumentException("Cannot cache default hashes");
|
throw new ArgumentException("Cannot cache default hashes");
|
||||||
|
@ -177,7 +177,7 @@ public class VirtualFile
|
|||||||
hash = await hstream.HashingCopy(Stream.Null, token, job);
|
hash = await hstream.HashingCopy(Stream.Null, token, job);
|
||||||
}
|
}
|
||||||
|
|
||||||
var found = await context.VfsCache.Get(hash, token);
|
var found = await context.VfsCache.Get(hash, extractedFile, token);
|
||||||
if (found != null)
|
if (found != null)
|
||||||
{
|
{
|
||||||
var file = ConvertFromIndexedFile(context, found!, relPath, parent!, extractedFile);
|
var file = ConvertFromIndexedFile(context, found!, relPath, parent!, extractedFile);
|
||||||
|
Loading…
Reference in New Issue
Block a user