Bit of work on how site logins are managed

This commit is contained in:
Timothy Baldridge 2022-01-01 15:55:39 -07:00
parent d52ede4611
commit c34a78bb4f
18 changed files with 123 additions and 156 deletions

View File

@ -12,6 +12,7 @@ using Splat;
using Wabbajack.Common; using Wabbajack.Common;
using Wabbajack; using Wabbajack;
using Wabbajack.DTOs; using Wabbajack.DTOs;
using Wabbajack.LoginManagers;
using Wabbajack.Models; using Wabbajack.Models;
using Wabbajack.Services.OSIntegrated; using Wabbajack.Services.OSIntegrated;
using Wabbajack.Util; using Wabbajack.Util;
@ -60,6 +61,9 @@ namespace Wabbajack
services.AddTransient<ModListGalleryVM>(); services.AddTransient<ModListGalleryVM>();
services.AddTransient<InstallerVM>(); services.AddTransient<InstallerVM>();
// Login Managers
services.AddAllSingleton<INeedsLogin, NexusLoginManager>();
return services; return services;
} }
private void OnStartup(object sender, StartupEventArgs e) private void OnStartup(object sender, StartupEventArgs e)

View File

@ -1,11 +1,13 @@
using System;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media;
namespace Wabbajack; namespace Wabbajack.LoginManagers;
public interface INeedsLogin public interface INeedsLogin
{ {
string SiteName { get; } string SiteName { get; }
ICommand TriggerLogin { get; set; } ICommand TriggerLogin { get; set; }
ICommand ClearLogin { get; set; } ICommand ClearLogin { get; set; }
object? IconUri { get; set; } ImageSource Icon { get; set; }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

View File

@ -0,0 +1,48 @@
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Extensions.Logging;
using ReactiveUI;
using Wabbajack.DTOs.Interventions;
using Wabbajack.DTOs.Logins;
using Wabbajack.Messages;
using Wabbajack.Networking.Http.Interfaces;
namespace Wabbajack.LoginManagers;
public class NexusLoginManager : INeedsLogin
{
private readonly ILogger<NexusLoginManager> _logger;
private readonly ITokenProvider<NexusApiState> _token;
private readonly IUserInterventionHandler _handler;
public string SiteName { get; } = "Nexus Mods";
public ICommand TriggerLogin { get; set; }
public ICommand ClearLogin { get; set; }
public ImageSource Icon { get; set; }
public NexusLoginManager(ILogger<NexusLoginManager> logger, ITokenProvider<NexusApiState> token)
{
_logger = logger;
_token = token;
ClearLogin = ReactiveCommand.Create(() =>
{
_logger.LogInformation("Deleting Login information for {SiteName}", SiteName);
_token.Delete();
});
Icon = BitmapFrame.Create(
typeof(NexusLoginManager).Assembly.GetManifestResourceStream("Wabbajack.LoginManagers.Icons.nexus.png")!);
TriggerLogin = ReactiveCommand.Create(() =>
{
_logger.LogInformation("Logging into {SiteName}", SiteName);
NexusLogin.Send();
});
}
}

View File

@ -0,0 +1,17 @@
using ReactiveUI;
using Wabbajack.Networking.Http.Interfaces;
namespace Wabbajack.Messages;
public class NexusLogin
{
public NexusLogin()
{
}
public static void Send()
{
MessageBus.Current.SendMessage(new NexusLogin());
}
}

View File

@ -34,7 +34,7 @@ public class ResourceMonitor : IDisposable
_compositeDisposable = new CompositeDisposable(); _compositeDisposable = new CompositeDisposable();
_resources = resources.ToArray(); _resources = resources.ToArray();
_timer = new Timer(); _timer = new Timer();
_timer.Interval = 1000; _timer.Interval = 250;
_timer.Elapsed += Elapsed; _timer.Elapsed += Elapsed;
_timer.Enabled = true; _timer.Enabled = true;

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,139 +11,31 @@ using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack; using Wabbajack;
using Wabbajack.LoginManagers;
namespace Wabbajack namespace Wabbajack
{ {
public class LoginManagerVM : BackNavigatingVM public class LoginManagerVM : BackNavigatingVM
{ {
public List<LoginTargetVM> Downloaders { get; } public LoginTargetVM[] Logins { get; }
public LoginManagerVM(ILogger<LoginManagerVM> logger, SettingsVM settingsVM) public LoginManagerVM(ILogger<LoginManagerVM> logger, SettingsVM settingsVM, IEnumerable<INeedsLogin> logins)
: base(logger) : base(logger)
{ {
/* Logins = logins.Select(l => new LoginTargetVM(l)).ToArray();
Downloaders = DownloadDispatcher.Downloaders
.OfType<INeedsLogin>()
.OrderBy(x => x.SiteName)
.Select(x => new LoginTargetVM(x))
.ToList();
*/
} }
} }
public class LoginTargetVM : ViewModel public class LoginTargetVM : ViewModel
{ {
private readonly ObservableAsPropertyHelper<string> _metaInfo;
public string MetaInfo => _metaInfo.Value;
public INeedsLogin Login { get; } public INeedsLogin Login { get; }
public INeedsLoginCredentials LoginWithCredentials { get; }
public bool UsesCredentials { get; }
public ReactiveCommand<Unit, Unit> TriggerCredentialsLogin;
private ImageSource _favicon = null;
public ImageSource Favicon { get => _favicon; set => RaiseAndSetIfChanged(ref _favicon, value); }
public LoginTargetVM(INeedsLogin login) public LoginTargetVM(INeedsLogin login)
{ {
Login = login; Login = login;
LoadImage();
if (login is INeedsLoginCredentials loginWithCredentials)
{
UsesCredentials = true;
LoginWithCredentials = loginWithCredentials;
}
/*
_metaInfo = (login.MetaInfo ?? Observable.Return(""))
.ToGuiProperty(this, nameof(MetaInfo));*/
if (!UsesCredentials)
return;
/*
TriggerCredentialsLogin = ReactiveCommand.Create(() =>
{
if (!(login is INeedsLoginCredentials))
return;
var loginWindow = new LoginWindowView(LoginWithCredentials);
loginWindow.Show();
}, LoginWithCredentials.IsLoggedIn.Select(b => !b).ObserveOnGuiThread());
*/
}
private void LoadImage()
{
/*
Task.Run(async () =>
{
if (Login.IconUri == null) return;
if(!Consts.FaviconCacheFolderPath.Exists)
Consts.FaviconCacheFolderPath.CreateDirectory();
var faviconIcon = Consts.FaviconCacheFolderPath.Combine($"{Login.SiteName}.ico");
if (faviconIcon.Exists)
{
var fsi = new FileInfo(faviconIcon.ToString());
var creationDate = fsi.CreationTimeUtc;
var now = DateTime.UtcNow;
//delete favicons older than 10 days
if ((now - creationDate).TotalDays > 10)
await faviconIcon.DeleteAsync();
}
if (faviconIcon.Exists)
{
await using var fs = await faviconIcon.OpenRead();
var ms = new MemoryStream((int)fs.Length);
await fs.CopyToAsync(ms);
ms.Position = 0;
var source = new BitmapImage();
source.BeginInit();
source.StreamSource = ms;
source.EndInit();
source.Freeze();
Favicon = source;
}
else
{
using var img = await new Lib.Http.Client().GetAsync(Login.IconUri, errorsAsExceptions: false);
if (!img.IsSuccessStatusCode) return;
var icoData = new MemoryStream(await img.Content.ReadAsByteArrayAsync());
var data = new Icon(icoData);
var ms = new MemoryStream();
data.ToBitmap().Save(ms, ImageFormat.Png);
ms.Position = 0;
await using (var fs = await faviconIcon.Create())
{
await ms.CopyToAsync(fs);
ms.Position = 0;
}
var source = new BitmapImage();
source.BeginInit();
source.StreamSource = ms;
source.EndInit();
source.Freeze();
Favicon = source;
}
});
*/
} }
} }

View File

@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ReactiveUI; using ReactiveUI;
using Wabbajack; using Wabbajack;
using Wabbajack.LoginManagers;
using Wabbajack.Networking.WabbajackClientApi; using Wabbajack.Networking.WabbajackClientApi;
using Wabbajack.Services.OSIntegrated.TokenProviders; using Wabbajack.Services.OSIntegrated.TokenProviders;
using Wabbajack.View_Models.Settings; using Wabbajack.View_Models.Settings;
@ -31,7 +32,8 @@ namespace Wabbajack
: base(logger) : base(logger)
{ {
MWVM = mainWindowVM; MWVM = mainWindowVM;
Login = new LoginManagerVM(provider.GetRequiredService<ILogger<LoginManagerVM>>(), this); Login = new LoginManagerVM(provider.GetRequiredService<ILogger<LoginManagerVM>>(), this,
provider.GetRequiredService<IEnumerable<INeedsLogin>>());
Performance = mainWindowVM.Settings.Performance; Performance = mainWindowVM.Settings.Performance;
AuthorFile = new AuthorFilesVM(provider.GetRequiredService<ILogger<AuthorFilesVM>>()!, AuthorFile = new AuthorFilesVM(provider.GetRequiredService<ILogger<AuthorFilesVM>>()!,
provider.GetRequiredService<WabbajackApiTokenProvider>()!, provider.GetRequiredService<Client>()!, this); provider.GetRequiredService<WabbajackApiTokenProvider>()!, provider.GetRequiredService<Client>()!, this);

View File

@ -11,46 +11,18 @@ namespace Wabbajack
InitializeComponent(); InitializeComponent();
this.WhenActivated(disposable => this.WhenActivated(disposable =>
{ {
this.WhenAny(x => x.ViewModel.Favicon) this.WhenAny(x => x.ViewModel.Login.Icon)
.ObserveOnGuiThread() .BindToStrict(this, view => view.Favicon.Source)
.Subscribe(x => Favicon.Source = x)
.DisposeWith(disposable); .DisposeWith(disposable);
this.OneWayBindStrict(ViewModel, x => x.Login.SiteName, x => x.SiteNameText.Text) this.OneWayBindStrict(ViewModel, x => x.Login.SiteName, x => x.SiteNameText.Text)
.DisposeWith(disposable); .DisposeWith(disposable);
if (!ViewModel!.UsesCredentials) this.OneWayBindStrict(ViewModel, x => x.Login.TriggerLogin, x => x.LoginButton.Command)
{
this.OneWayBindStrict(ViewModel, x => x.Login.TriggerLogin, x => x.LoginButton.Command)
.DisposeWith(disposable);
}
else
{
this.OneWayBindStrict(ViewModel, x => x.TriggerCredentialsLogin, x => x.LoginButton.Command)
.DisposeWith(disposable); .DisposeWith(disposable);
}
this.OneWayBindStrict(ViewModel, x => x.Login.ClearLogin, x => x.LogoutButton.Command) this.OneWayBindStrict(ViewModel, x => x.Login.ClearLogin, x => x.LogoutButton.Command)
.DisposeWith(disposable); .DisposeWith(disposable);
this.OneWayBindStrict(ViewModel, x => x.MetaInfo, x => x.MetaText.Text)
.DisposeWith(disposable);
/* TODO
// Modify label state
this.WhenAny(x => x.ViewModel.Login.TriggerLogin.CanExecute)
.Switch()
.Subscribe(x =>
{
LoginButton.Content = x ? "Login" : "Logged In";
});
this.WhenAny(x => x.ViewModel.Login.ClearLogin.CanExecute)
.Switch()
.Subscribe(x =>
{
LogoutButton.Content = x ? "Logout" : "Logged Out";
});
*/
}); });
} }
} }

View File

@ -27,7 +27,7 @@ namespace Wabbajack
InitializeComponent(); InitializeComponent();
this.WhenActivated(disposable => this.WhenActivated(disposable =>
{ {
this.OneWayBindStrict(this.ViewModel, x => x.Downloaders, x => x.DownloadersList.ItemsSource) this.OneWayBindStrict(this.ViewModel, x => x.Logins, x => x.DownloadersList.ItemsSource)
.DisposeWith(disposable); .DisposeWith(disposable);
}); });
} }

View File

@ -111,4 +111,9 @@
<SplashScreen Include="Resources\Wabba_Mouth_Small.png" /> <SplashScreen Include="Resources\Wabba_Mouth_Small.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="LoginManagers\Icons\nexus.png" />
<EmbeddedResource Include="LoginManagers\Icons\nexus.png" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,18 @@
namespace Wabbajack.DTOs.Interventions;
/// <summary>
/// Defines a message that requires user interaction. The user must perform some action
/// or make a choice.
/// </summary>
public interface IUserIntervention
{
/// <summary>
/// The user didn't make a choice, so this action should be aborted
/// </summary>
void Cancel();
/// <summary>
/// Whether the interaction has been handled and no longer needs attention
/// </summary>
bool Handled { get; }
}

View File

@ -0,0 +1,6 @@
namespace Wabbajack.DTOs.Interventions;
public interface IUserInterventionHandler
{
public void Raise(IUserIntervention intervention);
}

View File

@ -7,5 +7,5 @@ public abstract class GithubAuthTokenProvider : ITokenProvider<string>
{ {
public abstract ValueTask<string> Get(); public abstract ValueTask<string> Get();
public abstract ValueTask SetToken(string val); public abstract ValueTask SetToken(string val);
public abstract ValueTask<bool> TryDelete(string val); public abstract ValueTask<bool> Delete();
} }

View File

@ -11,5 +11,5 @@ public interface ITokenProvider<T>
public ValueTask<T?> Get(); public ValueTask<T?> Get();
public ValueTask SetToken(T val); public ValueTask SetToken(T val);
public ValueTask<bool> TryDelete(T val); public ValueTask<bool> Delete();
} }

View File

@ -8,7 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Wabbajack.Hashing.xxHash64\Wabbajack.Hashing.xxHash64.csproj"/> <ProjectReference Include="..\Wabbajack.Hashing.xxHash64\Wabbajack.Hashing.xxHash64.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -31,7 +31,7 @@ public class EncryptedJsonTokenProvider<T> : ITokenProvider<T>
await token.AsEncryptedJsonFile(KeyPath); await token.AsEncryptedJsonFile(KeyPath);
} }
public async ValueTask<bool> TryDelete(T val) public async ValueTask<bool> Delete()
{ {
if (!KeyPath.FileExists()) return false; if (!KeyPath.FileExists()) return false;
KeyPath.Delete(); KeyPath.Delete();

View File

@ -43,7 +43,7 @@ public class WabbajackApiTokenProvider : ITokenProvider<WabbajackApiState>
throw new NotImplementedException(); throw new NotImplementedException();
} }
public ValueTask<bool> TryDelete(WabbajackApiState val) public ValueTask<bool> Delete()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }