Merge pull request #1719 from wabbajack-tools/fix-login-logout-buttons

Fix login logout buttons
This commit is contained in:
Timothy Baldridge 2021-11-10 16:04:26 -07:00 committed by GitHub
commit 9ea30334f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 380 additions and 76 deletions

View File

@ -9,6 +9,7 @@
<Application.Styles> <Application.Styles>
<StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml" /> <StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml" />
<FluentTheme Mode="Dark" /> <FluentTheme Mode="Dark" />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<StyleInclude Source="avares://Wabbajack.App/Assets/Wabbajack.axaml" /> <StyleInclude Source="avares://Wabbajack.App/Assets/Wabbajack.axaml" />
<Style Selector="Button:not(:pointerover) /template/ ContentPresenter"> <Style Selector="Button:not(:pointerover) /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />

View File

@ -41,6 +41,12 @@
<Setter Property="CornerRadius" Value="4"></Setter> <Setter Property="CornerRadius" Value="4"></Setter>
</Style> </Style>
<Style Selector="Border.StandardBorder">
<Setter Property="BorderThickness" Value="2"></Setter>
<Setter Property="BorderBrush" Value="DarkGray"></Setter>
<Setter Property="CornerRadius" Value="4"></Setter>
</Style>
<Style Selector="Border.Settings Grid"> <Style Selector="Border.Settings Grid">
<Setter Property="Margin" Value="4"></Setter> <Setter Property="Margin" Value="4"></Setter>
</Style> </Style>

View File

@ -21,6 +21,7 @@ using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
using Wabbajack.Services.OSIntegrated;
using Wabbajack.VFS; using Wabbajack.VFS;
namespace Wabbajack.App.Controls; namespace Wabbajack.App.Controls;

View File

@ -4,13 +4,10 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Wabbajack.App.Controls.ResourceView"> x:Class="Wabbajack.App.Controls.ResourceView">
<StackPanel Orientation="Horizontal"> <Grid RowDefinitions="Auto" ColumnDefinitions="140, 100, 140, 100">
<TextBlock x:Name="ResourceName" Width="100" HorizontalAlignment="Left" VerticalAlignment="Center" /> <TextBlock Grid.Column="0" VerticalAlignment="Center" Margin="4, 0" x:Name="ResourceName"></TextBlock>
<Label Width="100" HorizontalContentAlignment="Right" VerticalAlignment="Center">Tasks:</Label> <TextBox Grid.Column="1" Text="32" Margin="4, 0" x:Name="MaxTasks"></TextBox>
<TextBox x:Name="MaxTasks" Width="20" HorizontalAlignment="Left" VerticalAlignment="Center" /> <TextBox Grid.Column="2" Margin="4, 0" x:Name="MaxThroughput"></TextBox>
<Label Width="100" HorizontalContentAlignment="Right" VerticalAlignment="Center">Throughput:</Label> <TextBlock Grid.Column="3" Text="42GB" VerticalAlignment="Center" Margin="4, 0" x:Name="CurrentThroughput"></TextBlock>
<TextBox x:Name="MaxThroughput" Width="20" HorizontalAlignment="Left" VerticalAlignment="Center" /> </Grid>
<Label Width="100" HorizontalContentAlignment="Right" VerticalAlignment="Center">Status:</Label>
<TextBlock x:Name="CurrentThrougput" Width="50" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</UserControl> </UserControl>

View File

@ -17,10 +17,21 @@ public partial class ResourceView : ReactiveUserControl<ResourceViewModel>, IAct
this.Bind(ViewModel, vm => vm.MaxTasks, view => view.MaxTasks.Text) this.Bind(ViewModel, vm => vm.MaxTasks, view => view.MaxTasks.Text)
.DisposeWith(disposables); .DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.MaxThroughput, view => view.MaxThroughput.Text)
this.Bind(ViewModel, vm => vm.MaxThroughput, view => view.MaxThroughput.Text,
l => l is 0 or long.MaxValue ? "∞" : (l / 1024 / 1024).ToString(),
v =>
{
v = v.Trim();
if (v is "0" or "∞" || v == long.MaxValue.ToString())
{
return long.MaxValue;
}
return long.TryParse(v, out var l) ? l * 1024 * 1024 : long.MaxValue;
})
.DisposeWith(disposables); .DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.CurrentThroughput, view => view.CurrentThrougput.Text, this.OneWayBind(ViewModel, vm => vm.CurrentThroughput, view => view.CurrentThroughput.Text,
val => val.FileSizeToString()) val => val.FileSizeToString())
.DisposeWith(disposables); .DisposeWith(disposables);
}); });

View File

@ -2,9 +2,11 @@ using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Timers; using System.Timers;
using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers; using ReactiveUI.Fody.Helpers;
using Wabbajack.App.ViewModels; using Wabbajack.App.ViewModels;
using Wabbajack.Common;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
namespace Wabbajack.App.Controls; namespace Wabbajack.App.Controls;
@ -18,7 +20,7 @@ public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposab
{ {
Activator = new ViewModelActivator(); Activator = new ViewModelActivator();
_resource = resource; _resource = resource;
_timer = new Timer(1.0); _timer = new Timer(250);
Name = resource.Name; Name = resource.Name;
@ -33,13 +35,8 @@ public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposab
_timer.Elapsed -= TimerElapsed; _timer.Elapsed -= TimerElapsed;
}).DisposeWith(disposables); }).DisposeWith(disposables);
this.WhenAnyValue(vm => vm.MaxThroughput) MaxTasks = _resource.MaxTasks;
.Skip(1) MaxThroughput = _resource.MaxThroughput;
.Subscribe(v => { _resource.MaxThroughput = MaxThroughput; }).DisposeWith(disposables);
this.WhenAnyValue(vm => vm.MaxTasks)
.Skip(1)
.Subscribe(v => { _resource.MaxTasks = MaxTasks; }).DisposeWith(disposables);
}); });
} }
@ -51,6 +48,8 @@ public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposab
[Reactive] public string Name { get; set; } [Reactive] public string Name { get; set; }
[Reactive] public string ThroughputHumanFriendly { get; set; }
public void Dispose() public void Dispose()
{ {
@ -59,8 +58,9 @@ public class ResourceViewModel : ViewModelBase, IActivatableViewModel, IDisposab
private void TimerElapsed(object? sender, ElapsedEventArgs e) private void TimerElapsed(object? sender, ElapsedEventArgs e)
{ {
MaxTasks = _resource.MaxTasks; Dispatcher.UIThread.Post(() => {
MaxThroughput = _resource.MaxThroughput;
CurrentThroughput = _resource.StatusReport.Transferred; CurrentThroughput = _resource.StatusReport.Transferred;
ThroughputHumanFriendly = _resource.StatusReport.Transferred.ToFileSizeString();
});
} }
} }

View File

@ -12,6 +12,7 @@ using Wabbajack.Hashing.xxHash64;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
using Wabbajack.Services.OSIntegrated;
using Wabbajack.VFS; using Wabbajack.VFS;
namespace Wabbajack.App.Models; namespace Wabbajack.App.Models;

View File

@ -31,7 +31,7 @@ namespace Wabbajack.App.Screens;
public class BrowseViewModel : ViewModelBase, IActivatableViewModel public class BrowseViewModel : ViewModelBase, IActivatableViewModel
{ {
private readonly Configuration _configuration; private readonly Wabbajack.Services.OSIntegrated.Configuration _configuration;
private readonly DownloadDispatcher _dispatcher; private readonly DownloadDispatcher _dispatcher;
private readonly IResource<DownloadDispatcher> _dispatcherLimiter; private readonly IResource<DownloadDispatcher> _dispatcherLimiter;
private readonly DTOSerializer _dtos; private readonly DTOSerializer _dtos;
@ -54,7 +54,7 @@ public class BrowseViewModel : ViewModelBase, IActivatableViewModel
IResource<HttpClient> limiter, FileHashCache hashCache, IResource<HttpClient> limiter, FileHashCache hashCache,
IResource<DownloadDispatcher> dispatcherLimiter, DownloadDispatcher dispatcher, GameLocator gameLocator, IResource<DownloadDispatcher> dispatcherLimiter, DownloadDispatcher dispatcher, GameLocator gameLocator,
ImageCache imageCache, ImageCache imageCache,
DTOSerializer dtos, Configuration configuration) DTOSerializer dtos, Wabbajack.Services.OSIntegrated.Configuration configuration)
{ {
LoadingLock = new LoadingLock(); LoadingLock = new LoadingLock();
Activator = new ViewModelActivator(); Activator = new ViewModelActivator();

View File

@ -18,6 +18,7 @@ using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Installer; using Wabbajack.Installer;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.Services.OSIntegrated;
using Consts = Wabbajack.Compiler.Consts; using Consts = Wabbajack.Compiler.Consts;
namespace Wabbajack.App.Screens; namespace Wabbajack.App.Screens;

View File

@ -24,9 +24,9 @@
<TextBox Grid.Column="1" Grid.Row="1" IsEnabled="False" Height="20" x:Name="InstallPath" /> <TextBox Grid.Column="1" Grid.Row="1" IsEnabled="False" Height="20" x:Name="InstallPath" />
<Grid Grid.Column="1" Grid.Row="3" Grid.ColumnDefinitions="*, *, *" HorizontalAlignment="Center"> <Grid Grid.Column="1" Grid.Row="3" Grid.ColumnDefinitions="*, *, *" HorizontalAlignment="Center">
<Button Grid.Column="0" x:Name="WebsiteButton">Website</Button> <Button Grid.Column="0" x:Name="WebsiteButton" Click="ShowWebsite">Website</Button>
<Button Grid.Column="1" x:Name="ReadmeButton">Readme</Button> <Button Grid.Column="1" x:Name="ReadmeButton" Click="ShowReadme">Readme</Button>
<Button Grid.Column="2" x:Name="LocalFilesButton">Local Files</Button> <Button Grid.Column="2" x:Name="LocalFilesButton" Click="ShowLocalFiles">Local Files</Button>
</Grid> </Grid>
<controls:LargeIconButton x:Name="PlayGame" Margin="40, 0, 0, 0" Grid.Row="0" Grid.Column="2" <controls:LargeIconButton x:Name="PlayGame" Margin="40, 0, 0, 0" Grid.Row="0" Grid.Column="2"

View File

@ -1,6 +1,10 @@
using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Interactivity;
using ReactiveUI; using ReactiveUI;
using Wabbajack.App.Utilities;
using Wabbajack.App.Views; using Wabbajack.App.Views;
using Wabbajack.Installer;
namespace Wabbajack.App.Screens; namespace Wabbajack.App.Screens;
@ -25,4 +29,19 @@ public partial class LauncherView : ScreenBase<LauncherViewModel>
.DisposeWith(disposables); .DisposeWith(disposables);
}); });
} }
private void ShowWebsite(object? sender, RoutedEventArgs e)
{
OSUtil.OpenWebsite(ViewModel!.Setting!.StrippedModListData?.Website!);
}
private void ShowReadme(object? sender, RoutedEventArgs e)
{
OSUtil.OpenWebsite(new Uri(ViewModel!.Setting!.StrippedModListData?.Readme!));
}
private void ShowLocalFiles(object? sender, RoutedEventArgs e)
{
OSUtil.OpenFolder(ViewModel!.Setting!.Install);
}
} }

View File

@ -34,22 +34,28 @@
</Grid> </Grid>
</Border> </Border>
<Border x:Name="ResourcesBorder" Margin="5" BorderThickness="1" Classes="Settings"> <Border x:Name="ResourcesBorder" Margin="5" BorderThickness="1" Classes="ResourceSettings StandardBorder">
<Grid RowDefinitions="Auto, Auto"> <Grid RowDefinitions="Auto, Auto, Auto, Auto" ColumnDefinitions="140, 100, 140, 100">
<TextBlock FontSize="20" Grid.ColumnSpan="4">Resource Limits</TextBlock> <TextBlock Grid.Row="0" Text="Resources" FontSize="20" Grid.ColumnSpan="4" Margin="4"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Name" FontWeight="Bold" Margin="4, 4"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Max Tasks" FontWeight="Bold" Margin="4, 4"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="2" Text="Max Throughput" FontWeight="Bold" Margin="4, 4"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="3" Text="Transferred" FontWeight="Bold" Margin="4, 4"></TextBlock>
<ItemsControl Grid.Row="1" x:Name="ResourceList"> <ItemsRepeater Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" x:Name="ResourcesList" Margin="0, 4">
<ItemsControl.ItemsPanel> <ItemsRepeater.Layout>
<ItemsPanelTemplate> <StackLayout></StackLayout>
<StackPanel Orientation="Vertical" /> </ItemsRepeater.Layout>
</ItemsPanelTemplate> <ItemsRepeater.ItemTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<controls:ResourceView /> <controls:ResourceView></controls:ResourceView>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsRepeater.ItemTemplate>
</ItemsControl> </ItemsRepeater>
<Button Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" Margin="4" Click="SaveSettingsAndRestart">
<TextBlock Text="Save Settings and Restart Wabbajack" HorizontalAlignment="Center" TextAlignment="Center"></TextBlock>
</Button>
</Grid> </Grid>
</Border> </Border>

View File

@ -1,6 +1,8 @@
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Interactivity;
using ReactiveUI; using ReactiveUI;
using Wabbajack.App.Views; using Wabbajack.App.Views;
using Wabbajack.Common;
namespace Wabbajack.App.Screens; namespace Wabbajack.App.Screens;
@ -15,8 +17,27 @@ public partial class SettingsView : ScreenBase<SettingsViewModel>
.DisposeWith(disposables); .DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.NexusLogout, view => view.NexusLogOut) this.BindCommand(ViewModel, vm => vm.NexusLogout, view => view.NexusLogOut)
.DisposeWith(disposables); .DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.Resources, view => view.ResourceList.Items)
this.BindCommand(ViewModel, vm => vm.LoversLabLogin, view => view.LoversLabLogIn)
.DisposeWith(disposables); .DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.LoversLabLogout, view => view.LoversLabLogOut)
.DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.VectorPlexusLogin, view => view.VectorPlexusLogIn)
.DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.VectorPlexusLogout, view => view.VectorPlexusLogOut)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, vm => vm.Resources, view => view.ResourcesList.Items)
.DisposeWith(disposables);
}); });
} }
private void SaveSettingsAndRestart(object? sender, RoutedEventArgs e)
{
ViewModel!.SaveResourceSettingsAndRestart().FireAndForget();
}
} }

View File

@ -1,18 +1,25 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ReactiveUI; using ReactiveUI;
using Wabbajack.App.Controls; using Wabbajack.App.Controls;
using Wabbajack.App.Messages; using Wabbajack.App.Messages;
using Wabbajack.App.Models;
using Wabbajack.App.ViewModels; using Wabbajack.App.ViewModels;
using Wabbajack.Common;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.RateLimiter; using Wabbajack.RateLimiter;
using Wabbajack.Services.OSIntegrated;
using Wabbajack.Services.OSIntegrated.TokenProviders; using Wabbajack.Services.OSIntegrated.TokenProviders;
namespace Wabbajack.App.Screens; namespace Wabbajack.App.Screens;
@ -22,16 +29,26 @@ public class SettingsViewModel : ViewModelBase
private readonly Subject<AbsolutePath> _fileSystemEvents = new(); private readonly Subject<AbsolutePath> _fileSystemEvents = new();
private readonly ILogger<SettingsViewModel> _logger; private readonly ILogger<SettingsViewModel> _logger;
public readonly IEnumerable<ResourceViewModel> Resources; public readonly IEnumerable<ResourceViewModel> Resources;
private readonly ResourceSettingsManager _resourceSettingsManager;
public SettingsViewModel(ILogger<SettingsViewModel> logger, Configuration configuration, public SettingsViewModel(ILogger<SettingsViewModel> logger, Configuration configuration,
NexusApiTokenProvider nexusProvider, IEnumerable<IResource> resources) ResourceSettingsManager resourceSettingsManager,
NexusApiTokenProvider nexusProvider, IEnumerable<IResource> resources, LoversLabTokenProvider llProvider, VectorPlexusTokenProvider vpProvider)
{ {
_resourceSettingsManager = resourceSettingsManager;
_logger = logger; _logger = logger;
Resources = resources.Select(r => new ResourceViewModel(r)).ToArray(); Resources = resources.Select(r => new ResourceViewModel(r))
.OrderBy(o => o.Name)
.ToArray();
Activator = new ViewModelActivator(); Activator = new ViewModelActivator();
this.WhenActivated(disposables => this.WhenActivated(disposables =>
{ {
foreach (var resource in Resources)
{
resource.Activator.Activate().DisposeWith(disposables);
}
configuration.EncryptedDataLocation.CreateDirectory(); configuration.EncryptedDataLocation.CreateDirectory();
Watcher = new FileSystemWatcher(configuration.EncryptedDataLocation.ToString()); Watcher = new FileSystemWatcher(configuration.EncryptedDataLocation.ToString());
Watcher.DisposeWith(disposables); Watcher.DisposeWith(disposables);
@ -50,16 +67,61 @@ public class SettingsViewModel : ViewModelBase
ReactiveCommand.Create(() => { MessageBus.Current.SendMessage(new NavigateTo(typeof(NexusLoginViewModel))); }, ReactiveCommand.Create(() => { MessageBus.Current.SendMessage(new NavigateTo(typeof(NexusLoginViewModel))); },
haveNexusToken.Select(x => !x)); haveNexusToken.Select(x => !x));
NexusLogout = ReactiveCommand.Create(nexusProvider.DeleteToken, haveNexusToken.Select(x => x)); NexusLogout = ReactiveCommand.Create(nexusProvider.DeleteToken, haveNexusToken.Select(x => x));
var haveLLToken = _fileSystemEvents
.StartWith(AbsolutePath.Empty)
.Select(_ => llProvider.HaveToken());
LoversLabLogin =
ReactiveCommand.Create(() => { MessageBus.Current.SendMessage(new NavigateTo(typeof(LoversLabOAuthLoginViewModel))); },
haveLLToken.Select(x => !x));
LoversLabLogout = ReactiveCommand.Create(llProvider.DeleteToken, haveLLToken.Select(x => x));
var haveVectorPlexusToken = _fileSystemEvents
.StartWith(AbsolutePath.Empty)
.Select(_ => vpProvider.HaveToken());
VectorPlexusLogin =
ReactiveCommand.Create(() => { MessageBus.Current.SendMessage(new NavigateTo(typeof(VectorPlexusOAuthLoginViewModel))); },
haveVectorPlexusToken.Select(x => !x));
VectorPlexusLogout = ReactiveCommand.Create(vpProvider.DeleteToken, haveVectorPlexusToken.Select(x => x));
}); });
} }
public ReactiveCommand<Unit, Unit> NexusLogin { get; set; } public ReactiveCommand<Unit, Unit> NexusLogin { get; set; }
public ReactiveCommand<Unit, Unit> NexusLogout { get; set; } public ReactiveCommand<Unit, Unit> NexusLogout { get; set; }
public ReactiveCommand<Unit, Unit> LoversLabLogin { get; set; }
public ReactiveCommand<Unit, Unit> LoversLabLogout { get; set; }
public ReactiveCommand<Unit, Unit> VectorPlexusLogin { get; set; }
public ReactiveCommand<Unit, Unit> VectorPlexusLogout { get; set; }
public FileSystemWatcher Watcher { get; set; } public FileSystemWatcher Watcher { get; set; }
private void Pulse(object sender, FileSystemEventArgs e) private void Pulse(object sender, FileSystemEventArgs e)
{ {
_fileSystemEvents.OnNext(e.FullPath?.ToAbsolutePath() ?? default); _fileSystemEvents.OnNext(e.FullPath?.ToAbsolutePath() ?? default);
} }
public async Task SaveResourceSettingsAndRestart()
{
await _resourceSettingsManager.SaveSettings(Resources.ToDictionary(r => r.Name, r =>
new ResourceSettingsManager.ResourceSetting()
{
MaxTasks = r.MaxTasks,
MaxThroughput = r.MaxThroughput
}));
var proc = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = Process.GetCurrentProcess().MainModule!.FileName
}
};
proc.Start();
Environment.Exit(0);
}
} }

View File

@ -198,9 +198,16 @@ public class StandardInstallationViewModel : ViewModelBase
_logger.LogInformation("Installer created, starting the installation process"); _logger.LogInformation("Installer created, starting the installation process");
try try
{ {
if (!string.IsNullOrWhiteSpace(_config.ModList.Readme))
OSUtil.OpenWebsite(new Uri(_config.ModList.Readme));
var result = await Task.Run(async () => await _installer.Begin(CancellationToken.None)); var result = await Task.Run(async () => await _installer.Begin(CancellationToken.None));
if (!result) throw new Exception("Installation failed"); if (!result) throw new Exception("Installation failed");
if (!string.IsNullOrWhiteSpace(_config.ModList.Readme))
OSUtil.OpenWebsite(new Uri(_config.ModList.Readme));
if (result) await SaveConfigAndContinue(_config); if (result) await SaveConfigAndContinue(_config);
} }
catch (Exception ex) catch (Exception ex)
@ -219,13 +226,15 @@ public class StandardInstallationViewModel : ViewModelBase
await image.CopyToAsync(os); await image.CopyToAsync(os);
} }
await _installStateManager.SetLastState(new InstallationConfigurationSetting await _installStateManager.SetLastState(new InstallationConfigurationSetting
{ {
Downloads = config.Downloads, Downloads = config.Downloads,
Install = config.Install, Install = config.Install,
Metadata = config.Metadata, Metadata = config.Metadata,
ModList = config.ModlistArchive, ModList = config.ModlistArchive,
Image = path Image = path,
StrippedModListData = config.ModList.Strip()
}); });
MessageBus.Current.SendMessage(new ConfigureLauncher(config.Install)); MessageBus.Current.SendMessage(new ConfigureLauncher(config.Install));

View File

@ -93,17 +93,6 @@ public static class ServiceExtensions
CachePath = KnownFolders.WabbajackAppLocal.Combine("cef_cache").ToString() CachePath = KnownFolders.WabbajackAppLocal.Combine("cef_cache").ToString()
}); });
services.AddSingleton(s => new Configuration
{
EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"),
ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"),
SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"),
LogLocation = KnownFolders.EntryPoint.Combine("logs"),
ImageCacheLocation = KnownFolders.WabbajackAppLocal.Combine("image_cache")
});
services.AddSingleton<SettingsManager>();
services.AddSingleton(s => services.AddSingleton(s =>
{ {
App.FrameworkInitialized += App_FrameworkInitialized; App.FrameworkInitialized += App_FrameworkInitialized;

View File

@ -10,6 +10,7 @@ using DynamicData;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.App.Utilities; namespace Wabbajack.App.Utilities;

View File

@ -0,0 +1,38 @@
using System;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Wabbajack.Common;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
namespace Wabbajack.App.Utilities;
public static class OSUtil
{
public static void OpenWebsite(Uri uri)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var helper = new ProcessHelper()
{
Path = "cmd.exe".ToRelativePath().RelativeTo(KnownFolders.WindowsSystem32),
Arguments = new[] {"/C", $"rundll32 url.dll,FileProtocolHandler {uri}"}
};
helper.Start().FireAndForget();
}
}
public static void OpenFolder(AbsolutePath path)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var helper = new ProcessHelper()
{
Path = "explorer.exe".ToRelativePath().RelativeTo(KnownFolders.Windows),
Arguments = new object[] {path}
};
helper.Start().FireAndForget();
}
}
}

View File

@ -20,6 +20,7 @@ using Wabbajack.DTOs.SavedSettings;
using Wabbajack.Installer; using Wabbajack.Installer;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.App.ViewModels; namespace Wabbajack.App.ViewModels;
@ -124,7 +125,8 @@ public class InstallConfigurationViewModel : ViewModelBase, IActivatableViewMode
ModList = ModListPath, ModList = ModListPath,
Downloads = Download, Downloads = Download,
Install = Install, Install = Install,
Metadata = metadata Metadata = metadata,
StrippedModListData = ModList?.Strip()
}); });
await _settingsManager.Save("last-install-path", ModListPath); await _settingsManager.Save("last-install-path", ModListPath);

View File

@ -97,5 +97,7 @@ public class NexusLoginViewModel : GuidedWebViewModel
Cookies = cookies, Cookies = cookies,
ApiKey = key ApiKey = key
}); });
MessageBus.Current.SendMessage(new NavigateBack());
} }
} }

View File

@ -9,8 +9,11 @@ using System.Web;
using CefNet; using CefNet;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Wabbajack.App.Extensions; using Wabbajack.App.Extensions;
using Wabbajack.App.Messages;
using Wabbajack.DTOs.Logins; using Wabbajack.DTOs.Logins;
using Wabbajack.Services.OSIntegrated; using Wabbajack.Services.OSIntegrated;
using Xunit.Sdk;
using MessageBus = ReactiveUI.MessageBus;
namespace Wabbajack.App.ViewModels; namespace Wabbajack.App.ViewModels;
@ -87,6 +90,8 @@ public abstract class OAuthLoginViewModel<TLoginType> : GuidedWebViewModel
Cookies = cookies, Cookies = cookies,
ResultState = data! ResultState = data!
}); });
MessageBus.Current.SendMessage(new NavigateBack());
} }
private class AsyncSchemeHandler : CefSchemeHandlerFactory private class AsyncSchemeHandler : CefSchemeHandlerFactory

View File

@ -46,7 +46,6 @@ internal class Program
services.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount}); services.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount});
services.AddSingleton<Client>(); services.AddSingleton<Client>();
services.AddSingleton<Networking.WabbajackClientApi.Client>(); services.AddSingleton<Networking.WabbajackClientApi.Client>();
services.AddSingleton<Configuration>();
services.AddSingleton(s => new GitHubClient(new ProductHeaderValue("wabbajack"))); services.AddSingleton(s => new GitHubClient(new ProductHeaderValue("wabbajack")));
services.AddOSIntegrated(); services.AddOSIntegrated();

View File

@ -4,6 +4,7 @@ using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Services.OSIntegrated; using Wabbajack.Services.OSIntegrated;
using Xunit.DependencyInjection; using Xunit.DependencyInjection;
using Xunit.DependencyInjection.Logging; using Xunit.DependencyInjection.Logging;
using Configuration = Wabbajack.Services.OSIntegrated.Configuration;
namespace Wabbajack.Compiler.Test; namespace Wabbajack.Compiler.Test;

View File

@ -66,4 +66,20 @@ public class ModList
/// Whether the Modlist is NSFW or not /// Whether the Modlist is NSFW or not
/// </summary> /// </summary>
public bool IsNSFW { get; set; } public bool IsNSFW { get; set; }
public ModList Strip()
{
return new ModList
{
Author = Author,
Description = Description,
GameType = GameType,
Name = Name,
Readme = Readme,
WabbajackVersion = WabbajackVersion,
Website = Website,
Version = Version,
IsNSFW = IsNSFW,
};
}
} }

View File

@ -20,5 +20,5 @@ public class InstallationConfigurationSetting
public ModlistMetadata? Metadata { get; set; } public ModlistMetadata? Metadata { get; set; }
public AbsolutePath Image { get; set; } public AbsolutePath Image { get; set; }
public ModList? StrippedModListData { get; set; }
} }

View File

@ -4,6 +4,7 @@ using Wabbajack.Downloaders.IPS4OAuth2Downloader;
using Wabbajack.Downloaders.MediaFire; using Wabbajack.Downloaders.MediaFire;
using Wabbajack.Downloaders.ModDB; using Wabbajack.Downloaders.ModDB;
using Wabbajack.DTOs.JsonConverters; using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Networking.WabbajackClientApi;
namespace Wabbajack.Downloaders; namespace Wabbajack.Downloaders;
@ -23,6 +24,7 @@ public static class ServiceExtensions
.AddIPS4OAuth2Downloaders() .AddIPS4OAuth2Downloaders()
.AddWabbajackCDNDownloader() .AddWabbajackCDNDownloader()
.AddGameFileDownloader() .AddGameFileDownloader()
.AddWabbajackClient()
.AddSingleton<DownloadDispatcher>(); .AddSingleton<DownloadDispatcher>();
} }
} }

View File

@ -4,9 +4,10 @@ namespace Wabbajack.Networking.WabbajackClientApi;
public static class ServiceExtensions public static class ServiceExtensions
{ {
public static void AddWabbajackClient(this IServiceCollection services) public static IServiceCollection AddWabbajackClient(this IServiceCollection services)
{ {
services.AddSingleton<Configuration>(); services.AddSingleton<Configuration>();
services.AddSingleton<Client>(); services.AddSingleton<Client>();
return services;
} }
} }

View File

@ -11,6 +11,9 @@ public static class KnownFolders
public static AbsolutePath AppDataLocal => public static AbsolutePath AppDataLocal =>
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).ToAbsolutePath(); Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).ToAbsolutePath();
public static AbsolutePath WindowsSystem32 => Environment.GetFolderPath(Environment.SpecialFolder.System).ToAbsolutePath();
public static AbsolutePath WabbajackAppLocal => AppDataLocal.Combine("Wabbajack"); public static AbsolutePath WabbajackAppLocal => AppDataLocal.Combine("Wabbajack");
public static AbsolutePath CurrentDirectory => Directory.GetCurrentDirectory().ToAbsolutePath(); public static AbsolutePath CurrentDirectory => Directory.GetCurrentDirectory().ToAbsolutePath();
public static AbsolutePath Windows => Environment.GetFolderPath(Environment.SpecialFolder.Windows).ToAbsolutePath();
} }

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
using System.Threading.Channels; using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,9 +11,9 @@ namespace Wabbajack.RateLimiter;
public class Resource<T> : IResource<T> public class Resource<T> : IResource<T>
{ {
private readonly Channel<PendingReport> _channel; private Channel<PendingReport> _channel;
private readonly SemaphoreSlim _semaphore; private SemaphoreSlim _semaphore;
private readonly ConcurrentDictionary<ulong, Job<T>> _tasks; private ConcurrentDictionary<ulong, Job<T>> _tasks;
private ulong _nextId; private ulong _nextId;
private long _totalUsed; private long _totalUsed;
@ -22,7 +23,6 @@ public class Resource<T> : IResource<T>
Name = humanName ?? "<unknown>"; Name = humanName ?? "<unknown>";
MaxTasks = maxTasks ?? Environment.ProcessorCount; MaxTasks = maxTasks ?? Environment.ProcessorCount;
MaxThroughput = maxThroughput; MaxThroughput = maxThroughput;
_semaphore = new SemaphoreSlim(MaxTasks); _semaphore = new SemaphoreSlim(MaxTasks);
_channel = Channel.CreateBounded<PendingReport>(10); _channel = Channel.CreateBounded<PendingReport>(10);
_tasks = new ConcurrentDictionary<ulong, Job<T>>(); _tasks = new ConcurrentDictionary<ulong, Job<T>>();
@ -30,6 +30,23 @@ public class Resource<T> : IResource<T>
var tsk = StartTask(CancellationToken.None); var tsk = StartTask(CancellationToken.None);
} }
public Resource(string humanName, Func<Task<(int MaxTasks, long MaxThroughput)>> settingGetter)
{
Name = humanName;
_tasks = new ConcurrentDictionary<ulong, Job<T>>();
Task.Run(async () =>
{
var (maxTasks, maxThroughput) = await settingGetter();
MaxTasks = maxTasks;
MaxThroughput = maxThroughput;
_semaphore = new SemaphoreSlim(MaxTasks);
_channel = Channel.CreateBounded<PendingReport>(10);
await StartTask(CancellationToken.None);
});
}
public int MaxTasks { get; set; } public int MaxTasks { get; set; }
public long MaxThroughput { get; set; } public long MaxThroughput { get; set; }
public string Name { get; } public string Name { get; }
@ -87,7 +104,7 @@ public class Resource<T> : IResource<T>
await foreach (var item in _channel.Reader.ReadAllAsync(token)) await foreach (var item in _channel.Reader.ReadAllAsync(token))
{ {
Interlocked.Add(ref _totalUsed, item.Size); Interlocked.Add(ref _totalUsed, item.Size);
if (MaxThroughput == long.MaxValue) if (MaxThroughput is long.MaxValue or 0)
{ {
item.Result.TrySetResult(); item.Result.TrySetResult();
sw.Restart(); sw.Restart();

View File

@ -1,6 +1,6 @@
using Wabbajack.Paths; using Wabbajack.Paths;
namespace Wabbajack.App; namespace Wabbajack.Services.OSIntegrated;
public class Configuration public class Configuration
{ {

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.RateLimiter;
using Wabbajack.Services.OSIntegrated;
namespace Wabbajack.App.Models;
public class ResourceSettingsManager
{
private readonly SettingsManager _manager;
private Dictionary<string,ResourceSetting>? _settings;
public ResourceSettingsManager(SettingsManager manager)
{
_manager = manager;
}
private SemaphoreSlim _lock = new(1);
public async Task<ResourceSetting> GetSettings(string name)
{
await _lock.WaitAsync();
try
{
_settings ??= await _manager.Load<Dictionary<string, ResourceSetting>>("resource_settings");
if (_settings.TryGetValue(name, out var found)) return found;
var newSetting = new ResourceSetting
{
MaxTasks = Environment.ProcessorCount,
MaxThroughput = 0
};
_settings.Add(name, newSetting);
await _manager.Save("resource_settings", _settings);
return _settings[name];
}
finally
{
_lock.Release();
}
}
public class ResourceSetting
{
public long MaxTasks { get; set; }
public long MaxThroughput { get; set; }
}
public async Task SaveSettings(Dictionary<string, ResourceSetting> settings)
{
await _manager.Save("resource_settings", settings);
}
}

View File

@ -4,6 +4,7 @@ using System.Net.Http;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Wabbajack.App.Models;
using Wabbajack.Compiler; using Wabbajack.Compiler;
using Wabbajack.Downloaders; using Wabbajack.Downloaders;
using Wabbajack.Downloaders.GameFile; using Wabbajack.Downloaders.GameFile;
@ -54,17 +55,28 @@ public static class ServiceExtensions
: new BinaryPatchCache(KnownFolders.EntryPoint.Combine("patchCache.sqlite"))); : new BinaryPatchCache(KnownFolders.EntryPoint.Combine("patchCache.sqlite")));
service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount}); service.AddSingleton(new ParallelOptions {MaxDegreeOfParallelism = Environment.ProcessorCount});
Func<Task<(int MaxTasks, long MaxThroughput)>> GetSettings(IServiceProvider provider, string name)
{
return async () =>
{
var s = await provider.GetService<ResourceSettingsManager>()!.GetSettings(name);
return ((int) s.MaxTasks, s.MaxThroughput);
};
}
service.AddAllSingleton<IResource, IResource<DownloadDispatcher>>(s => service.AddAllSingleton<IResource, IResource<DownloadDispatcher>>(s =>
new Resource<DownloadDispatcher>("Downloads", 12)); new Resource<DownloadDispatcher>("Downloads", GetSettings(s, "Downloads")));
service.AddAllSingleton<IResource, IResource<HttpClient>>(s => new Resource<HttpClient>("Web Requests", 12));
service.AddAllSingleton<IResource, IResource<Context>>(s => new Resource<Context>("VFS", 12)); service.AddAllSingleton<IResource, IResource<HttpClient>>(s => new Resource<HttpClient>("Web Requests", GetSettings(s, "Web Requests")));
service.AddAllSingleton<IResource, IResource<Context>>(s => new Resource<Context>("VFS", GetSettings(s, "VFS")));
service.AddAllSingleton<IResource, IResource<FileHashCache>>(s => service.AddAllSingleton<IResource, IResource<FileHashCache>>(s =>
new Resource<FileHashCache>("File Hashing", 12)); new Resource<FileHashCache>("File Hashing", GetSettings(s, "File Hashing")));
service.AddAllSingleton<IResource, IResource<FileExtractor.FileExtractor>>(s => service.AddAllSingleton<IResource, IResource<FileExtractor.FileExtractor>>(s =>
new Resource<FileExtractor.FileExtractor>("File Extractor", 12)); new Resource<FileExtractor.FileExtractor>("File Extractor", GetSettings(s, "File Extractor")));
service.AddAllSingleton<IResource, IResource<ACompiler>>(s => service.AddAllSingleton<IResource, IResource<ACompiler>>(s =>
new Resource<ACompiler>("Compiler", 12)); new Resource<ACompiler>("Compiler", GetSettings(s, "Compiler")));
service.AddSingleton<LoggingRateLimiterReporter>(); service.AddSingleton<LoggingRateLimiterReporter>();
@ -122,6 +134,21 @@ public static class ServiceExtensions
OSVersion = Environment.OSVersion.VersionString, OSVersion = Environment.OSVersion.VersionString,
Version = version Version = version
}); });
// Settings
service.AddSingleton(s => new Configuration
{
EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"),
ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"),
SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"),
LogLocation = KnownFolders.EntryPoint.Combine("logs"),
ImageCacheLocation = KnownFolders.WabbajackAppLocal.Combine("image_cache")
});
service.AddSingleton<SettingsManager>();
service.AddSingleton<ResourceSettingsManager>();
return service; return service;
} }

View File

@ -9,7 +9,7 @@ using Wabbajack.DTOs.JsonConverters;
using Wabbajack.Paths; using Wabbajack.Paths;
using Wabbajack.Paths.IO; using Wabbajack.Paths.IO;
namespace Wabbajack.App.Models; namespace Wabbajack.Services.OSIntegrated;
public class SettingsManager public class SettingsManager
{ {
@ -35,7 +35,11 @@ public class SettingsManager
var tmp = GetPath(key).WithExtension(Ext.Temp); var tmp = GetPath(key).WithExtension(Ext.Temp);
await using (var s = tmp.Open(FileMode.Create, FileAccess.Write)) await using (var s = tmp.Open(FileMode.Create, FileAccess.Write))
{ {
await JsonSerializer.SerializeAsync(s, value, _dtos.Options); var opts = new JsonSerializerOptions(_dtos.Options)
{
WriteIndented = true
};
await JsonSerializer.SerializeAsync(s, value, opts);
} }
await tmp.MoveToAsync(GetPath(key), true, CancellationToken.None); await tmp.MoveToAsync(GetPath(key), true, CancellationToken.None);