Merge pull request #1667 from wabbajack-tools/issue-1663

Issue 1663
This commit is contained in:
Timothy Baldridge 2021-10-18 21:45:41 -06:00 committed by GitHub
commit 0f73b20f46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 201 additions and 44 deletions

View File

@ -8,5 +8,7 @@ namespace Wabbajack.App
public AbsolutePath SavedSettingsLocation { get; set; } public AbsolutePath SavedSettingsLocation { get; set; }
public AbsolutePath EncryptedDataLocation { get; set; } public AbsolutePath EncryptedDataLocation { get; set; }
public AbsolutePath LogLocation { get; set; }
} }
} }

View File

@ -3,9 +3,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Wabbajack.App.Controls" xmlns:controls="clr-namespace:Wabbajack.App.Controls"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Wabbajack.App.Controls.LogView"> x:Class="Wabbajack.App.Controls.LogView">
<ScrollViewer ScrollChanged="ScrollViewer_OnScrollChanged" x:Name="ScrollViewer"> <Grid RowDefinitions="Auto, *, Auto">
<TextBlock Grid.Row="0">Current Log Contents</TextBlock>
<ScrollViewer Grid.Row="1" ScrollChanged="ScrollViewer_OnScrollChanged" x:Name="ScrollViewer"
HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<ItemsControl x:Name="Messages"> <ItemsControl x:Name="Messages">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@ -19,4 +23,13 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
</ScrollViewer> </ScrollViewer>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="CopyLog">
<avalonia:MaterialIcon Kind="ContentCopy"></avalonia:MaterialIcon>
</Button>
<Button x:Name="OpenFolder">
<avalonia:MaterialIcon Kind="Folder"></avalonia:MaterialIcon>
</Button>
</StackPanel>
</Grid>
</UserControl> </UserControl>

View File

@ -17,6 +17,8 @@ public partial class LogView : ReactiveUserControl<LogViewModel>
{ {
this.OneWayBind(ViewModel, vm => vm.Messages, view => view.Messages.Items) this.OneWayBind(ViewModel, vm => vm.Messages, view => view.Messages.Items)
.DisposeWith(disposables); .DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.CopyLogFile, view => view.CopyLog)
.DisposeWith(disposables);
}); });
} }

View File

@ -1,9 +1,15 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Disposables;
using Avalonia;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Input;
using DynamicData; using DynamicData;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.App.Utilities; using Wabbajack.App.Utilities;
using Wabbajack.App.ViewModels; using Wabbajack.App.ViewModels;
@ -12,26 +18,21 @@ namespace Wabbajack.App.Controls;
public class LogViewModel : ViewModelBase, IActivatableViewModel public class LogViewModel : ViewModelBase, IActivatableViewModel
{ {
private readonly LoggerProvider _provider; private readonly LoggerProvider _provider;
public ReadOnlyObservableCollection<LoggerProvider.ILogMessage> Messages => _provider.MessageLog;
private readonly SourceCache<LoggerProvider.ILogMessage, long> _messages; [Reactive]
public ReactiveCommand<Unit, Unit> CopyLogFile { get; set; }
public readonly ReadOnlyObservableCollection<LoggerProvider.ILogMessage> _messagesFiltered;
public ReadOnlyObservableCollection<LoggerProvider.ILogMessage> Messages => _messagesFiltered;
public LogViewModel(LoggerProvider provider) public LogViewModel(LoggerProvider provider)
{ {
_messages = new SourceCache<LoggerProvider.ILogMessage, long>(m => m.MessageId);
//_messages.LimitSizeTo(100);
Activator = new ViewModelActivator(); Activator = new ViewModelActivator();
_provider = provider; _provider = provider;
_messages.Connect() CopyLogFile = ReactiveCommand.Create(() =>
.Bind(out _messagesFiltered) {
.Subscribe(); var obj = new DataObject();
obj.Set(DataFormats.FileNames, new List<string> {_provider.LogPath.ToString()});
_provider.Messages Application.Current.Clipboard.SetDataObjectAsync(obj);
.Subscribe(m => _messages.AddOrUpdate(m)); });
} }
} }

View File

@ -0,0 +1,11 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Wabbajack.App.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Wabbajack.App.Screens.LogScreenView">
<Grid RowDefinitions="*">
<controls:LogView></controls:LogView>
</Grid>
</UserControl>

View File

@ -0,0 +1,12 @@
using ReactiveUI;
using Wabbajack.App.Views;
namespace Wabbajack.App.Screens;
public partial class LogScreenView : ScreenBase<LogScreenViewModel>
{
public LogScreenView()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,21 @@
using System.Reactive;
using Avalonia;
using Avalonia.Input;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.App.Utilities;
using Wabbajack.App.ViewModels;
namespace Wabbajack.App.Screens;
public class LogScreenViewModel : ViewModelBase, IActivatableViewModel
{
private readonly LoggerProvider _provider;
public LogScreenViewModel(LoggerProvider provider)
{
_provider = provider;
Activator = new ViewModelActivator();
}
}

View File

@ -45,6 +45,7 @@ namespace Wabbajack.App
services.AddDTOSerializer(); services.AddDTOSerializer();
services.AddSingleton<ModeSelectionViewModel>(); services.AddSingleton<ModeSelectionViewModel>();
services.AddTransient<FileSelectionBoxViewModel>(); services.AddTransient<FileSelectionBoxViewModel>();
services.AddSingleton<IScreenView, LogScreenView>();
services.AddSingleton<IScreenView, ModeSelectionView>(); services.AddSingleton<IScreenView, ModeSelectionView>();
services.AddSingleton<IScreenView, InstallConfigurationView>(); services.AddSingleton<IScreenView, InstallConfigurationView>();
services.AddSingleton<IScreenView, CompilerConfigurationView>(); services.AddSingleton<IScreenView, CompilerConfigurationView>();
@ -57,6 +58,7 @@ namespace Wabbajack.App
services.AddSingleton<InstallationStateManager>(); services.AddSingleton<InstallationStateManager>();
services.AddSingleton<HttpClient>(); services.AddSingleton<HttpClient>();
services.AddSingleton<LogScreenViewModel>();
services.AddAllSingleton<IReceiverMarker, StandardInstallationViewModel>(); services.AddAllSingleton<IReceiverMarker, StandardInstallationViewModel>();
services.AddAllSingleton<IReceiverMarker, InstallConfigurationViewModel>(); services.AddAllSingleton<IReceiverMarker, InstallConfigurationViewModel>();
services.AddAllSingleton<IReceiverMarker, CompilerConfigurationViewModel>(); services.AddAllSingleton<IReceiverMarker, CompilerConfigurationViewModel>();
@ -89,7 +91,8 @@ namespace Wabbajack.App
{ {
EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"), EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"),
ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"), ModListsDownloadLocation = KnownFolders.EntryPoint.Combine("downloaded_mod_lists"),
SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings") SavedSettingsLocation = KnownFolders.WabbajackAppLocal.Combine("saved_settings"),
LogLocation = KnownFolders.EntryPoint.Combine("logs")
}); });
services.AddSingleton<SettingsManager>(); services.AddSingleton<SettingsManager>();

View File

@ -1,9 +1,16 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Text;
using System.Threading; using System.Threading;
using DynamicData;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Wabbajack.Paths;
using Wabbajack.Paths.IO;
namespace Wabbajack.App.Utilities; namespace Wabbajack.App.Utilities;
@ -12,16 +19,67 @@ public class LoggerProvider : ILoggerProvider
private Subject<ILogMessage> _messages = new(); private Subject<ILogMessage> _messages = new();
public IObservable<ILogMessage> Messages => _messages; public IObservable<ILogMessage> Messages => _messages;
private long _messageID = 0; private long _messageId = 0;
private SourceCache<ILogMessage, long> _messageLog = new(m => m.MessageId);
public long NextMessageId() public readonly ReadOnlyObservableCollection<ILogMessage> _messagesFiltered;
private readonly CompositeDisposable _disposables;
private readonly Configuration _configuration;
private readonly DateTime _startupTime;
private readonly RelativePath _appName;
public AbsolutePath LogPath { get; }
private readonly Stream _logFile;
private readonly StreamWriter _logStream;
public ReadOnlyObservableCollection<ILogMessage> MessageLog => _messagesFiltered;
public LoggerProvider(Configuration configuration)
{ {
return Interlocked.Increment(ref _messageID); _startupTime = DateTime.UtcNow;
_configuration = configuration;
_configuration.LogLocation.CreateDirectory();
_disposables = new CompositeDisposable();
Messages.Subscribe(m => _messageLog.AddOrUpdate(m))
.DisposeWith(_disposables);
Messages.Subscribe(m => LogToFile(m))
.DisposeWith(_disposables);
_messageLog.Connect()
.Bind(out _messagesFiltered)
.Subscribe()
.DisposeWith(_disposables);
_messages.DisposeWith(_disposables);
_appName = typeof(LoggerProvider).Assembly.Location.ToAbsolutePath().FileName;
LogPath = _configuration.LogLocation.Combine($"{_appName}.current.log");
_logFile = LogPath.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
_logFile.DisposeWith(_disposables);
_logStream = new StreamWriter(_logFile, Encoding.UTF8);
}
private void LogToFile(ILogMessage logMessage)
{
var line = $"[{logMessage.TimeStamp - _startupTime}] {logMessage.LongMessage}";
lock (_logStream)
{
_logStream.Write(line);
_logStream.Flush();
}
}
private long NextMessageId()
{
return Interlocked.Increment(ref _messageId);
} }
public void Dispose() public void Dispose()
{ {
_messages.Dispose(); _disposables.Dispose();
} }
public ILogger CreateLogger(string categoryName) public ILogger CreateLogger(string categoryName)
@ -43,7 +101,7 @@ public class LoggerProvider : ILoggerProvider
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{ {
_provider._messages.OnNext(new LogMessage<TState>(_provider.NextMessageId(), logLevel, eventId, state, exception, formatter)); _provider._messages.OnNext(new LogMessage<TState>(DateTime.UtcNow, _provider.NextMessageId(), logLevel, eventId, state, exception, formatter));
} }
public bool IsEnabled(LogLevel logLevel) public bool IsEnabled(LogLevel logLevel)
@ -63,10 +121,28 @@ public class LoggerProvider : ILoggerProvider
long MessageId { get; } long MessageId { get; }
string ShortMessage { get; } string ShortMessage { get; }
DateTime TimeStamp { get; }
string LongMessage { get; }
} }
record LogMessage<TState>(long MessageId, LogLevel LogLevel, EventId EventId, TState State, Exception? Exception, Func<TState, Exception?, string> Formatter) : ILogMessage record LogMessage<TState>(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId, TState State, Exception? Exception, Func<TState, Exception?, string> Formatter) : ILogMessage
{ {
public string ShortMessage => Formatter(State, Exception); public string ShortMessage => Formatter(State, Exception);
public string LongMessage
{
get
{
var sb = new StringBuilder();
sb.AppendLine(ShortMessage);
if (Exception != null)
{
sb.Append("Exception: ");
sb.Append(Exception);
}
return sb.ToString();
}
}
} }
} }

View File

@ -44,6 +44,9 @@ namespace Wabbajack.App.ViewModels
[Reactive] [Reactive]
public ReactiveCommand<Unit, Unit> SettingsButton { get; set; } public ReactiveCommand<Unit, Unit> SettingsButton { get; set; }
[Reactive]
public ReactiveCommand<Unit, Unit> LogViewButton { get; set; }
[Reactive] [Reactive]
public string ResourceStatus { get; set; } public string ResourceStatus { get; set; }
@ -77,6 +80,12 @@ namespace Wabbajack.App.ViewModels
}) })
.DisposeWith(disposables); .DisposeWith(disposables);
LogViewButton = ReactiveCommand.Create(() =>
{
Receive(new NavigateTo(typeof(LogScreenViewModel)));
})
.DisposeWith(disposables);
}); });
CurrentScreen = (Control)_screens.First(s => s.ViewModelType == typeof(ModeSelectionViewModel)); CurrentScreen = (Control)_screens.First(s => s.ViewModelType == typeof(ModeSelectionViewModel));

View File

@ -34,21 +34,25 @@
</Design.DataContext> </Design.DataContext>
<Grid RowDefinitions="40, *"> <Grid RowDefinitions="40, *">
<Grid ColumnDefinitions="40, *, 40, 40, 40"> <Grid ColumnDefinitions="40, *, 40, 40, 40, 40">
<Button Grid.Column="0" x:Name="BackButton"> <Button Grid.Column="0" x:Name="BackButton">
<i:MaterialIcon Kind="ArrowBack"> </i:MaterialIcon> <i:MaterialIcon Kind="ArrowBack"> </i:MaterialIcon>
</Button> </Button>
<TextBlock Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" x:Name="ResourceStatus"></TextBlock> <TextBlock Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" x:Name="ResourceStatus"></TextBlock>
<Button Grid.Column="2" x:Name="SettingsButton"> <Button Grid.Column="2" x:Name="LogButton">
<i:MaterialIcon Kind="ViewList"></i:MaterialIcon>
</Button>
<Button Grid.Column="3" x:Name="SettingsButton">
<i:MaterialIcon Kind="Gear"></i:MaterialIcon> <i:MaterialIcon Kind="Gear"></i:MaterialIcon>
</Button> </Button>
<Button Grid.Column="3" x:Name="MinimizeButton"> <Button Grid.Column="4" x:Name="MinimizeButton">
<i:MaterialIcon Kind="WindowMinimize" /> <i:MaterialIcon Kind="WindowMinimize" />
</Button> </Button>
<Button Grid.Column="4" x:Name="CloseButton"> <Button Grid.Column="5" x:Name="CloseButton">
<i:MaterialIcon Kind="Close"/> <i:MaterialIcon Kind="Close"/>
</Button> </Button>

View File

@ -31,6 +31,9 @@ namespace Wabbajack.App.Views
this.BindCommand(ViewModel, vm => vm.SettingsButton, view => view.SettingsButton) this.BindCommand(ViewModel, vm => vm.SettingsButton, view => view.SettingsButton)
.DisposeWith(dispose); .DisposeWith(dispose);
this.BindCommand(ViewModel, vm => vm.LogViewButton, view => view.LogButton)
.DisposeWith(dispose);
this.Bind(ViewModel, vm => vm.CurrentScreen, view => view.Contents.Content) this.Bind(ViewModel, vm => vm.CurrentScreen, view => view.Contents.Content)
.DisposeWith(dispose); .DisposeWith(dispose);