From b134ff9fc5fada533bdd49d5dbc9128ca9272d89 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Mon, 18 Oct 2021 21:37:10 -0600 Subject: [PATCH] Log view can copy logs via the copy button --- Wabbajack.App/Configuration.cs | 2 + Wabbajack.App/Controls/LogView.axaml | 41 ++++++--- Wabbajack.App/Controls/LogView.axaml.cs | 2 + Wabbajack.App/Controls/LogViewModel.cs | 31 +++---- Wabbajack.App/Screens/LogScreenView.axaml | 30 +------ Wabbajack.App/Screens/LogScreenView.axaml.cs | 6 +- Wabbajack.App/Screens/LogScreenViewModel.cs | 12 +++ Wabbajack.App/ServiceExtensions.cs | 5 +- Wabbajack.App/Utilities/LoggerProvider.cs | 90 +++++++++++++++++-- .../ViewModels/MainWindowViewModel.cs | 15 +++- Wabbajack.App/Views/MainWindow.axaml | 12 ++- Wabbajack.App/Views/MainWindow.axaml.cs | 3 + 12 files changed, 176 insertions(+), 73 deletions(-) diff --git a/Wabbajack.App/Configuration.cs b/Wabbajack.App/Configuration.cs index 21b93e84..96f71dcf 100644 --- a/Wabbajack.App/Configuration.cs +++ b/Wabbajack.App/Configuration.cs @@ -8,5 +8,7 @@ namespace Wabbajack.App public AbsolutePath SavedSettingsLocation { get; set; } public AbsolutePath EncryptedDataLocation { get; set; } + + public AbsolutePath LogLocation { get; set; } } } \ No newline at end of file diff --git a/Wabbajack.App/Controls/LogView.axaml b/Wabbajack.App/Controls/LogView.axaml index 317b1521..5be40935 100644 --- a/Wabbajack.App/Controls/LogView.axaml +++ b/Wabbajack.App/Controls/LogView.axaml @@ -3,20 +3,33 @@ 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" + xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Controls.LogView"> - - - - - - - - - - - - - - + + Current Log Contents + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Wabbajack.App/Controls/LogView.axaml.cs b/Wabbajack.App/Controls/LogView.axaml.cs index 69620a74..d0017291 100644 --- a/Wabbajack.App/Controls/LogView.axaml.cs +++ b/Wabbajack.App/Controls/LogView.axaml.cs @@ -17,6 +17,8 @@ public partial class LogView : ReactiveUserControl { this.OneWayBind(ViewModel, vm => vm.Messages, view => view.Messages.Items) .DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.CopyLogFile, view => view.CopyLog) + .DisposeWith(disposables); }); } diff --git a/Wabbajack.App/Controls/LogViewModel.cs b/Wabbajack.App/Controls/LogViewModel.cs index f0ea35a1..c25f856e 100644 --- a/Wabbajack.App/Controls/LogViewModel.cs +++ b/Wabbajack.App/Controls/LogViewModel.cs @@ -1,9 +1,15 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Reactive; +using System.Reactive.Disposables; +using Avalonia; using Avalonia.Controls.Mixins; +using Avalonia.Input; using DynamicData; using Microsoft.Extensions.Logging; using ReactiveUI; +using ReactiveUI.Fody.Helpers; using Wabbajack.App.Utilities; using Wabbajack.App.ViewModels; @@ -12,26 +18,21 @@ namespace Wabbajack.App.Controls; public class LogViewModel : ViewModelBase, IActivatableViewModel { private readonly LoggerProvider _provider; - - private readonly SourceCache _messages; - - public readonly ReadOnlyObservableCollection _messagesFiltered; - public ReadOnlyObservableCollection Messages => _messagesFiltered; + public ReadOnlyObservableCollection Messages => _provider.MessageLog; + + [Reactive] + public ReactiveCommand CopyLogFile { get; set; } public LogViewModel(LoggerProvider provider) { - _messages = new SourceCache(m => m.MessageId); - //_messages.LimitSizeTo(100); - Activator = new ViewModelActivator(); _provider = provider; - _messages.Connect() - .Bind(out _messagesFiltered) - .Subscribe(); - - _provider.Messages - .Subscribe(m => _messages.AddOrUpdate(m)); + CopyLogFile = ReactiveCommand.Create(() => + { + var obj = new DataObject(); + obj.Set(DataFormats.FileNames, new List {_provider.LogPath.ToString()}); + Application.Current.Clipboard.SetDataObjectAsync(obj); + }); } - } \ No newline at end of file diff --git a/Wabbajack.App/Screens/LogScreenView.axaml b/Wabbajack.App/Screens/LogScreenView.axaml index 8e749b88..b3c5f05f 100644 --- a/Wabbajack.App/Screens/LogScreenView.axaml +++ b/Wabbajack.App/Screens/LogScreenView.axaml @@ -5,33 +5,7 @@ xmlns:controls="clr-namespace:Wabbajack.App.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Wabbajack.App.Screens.LogScreenView"> - - - - - - - - - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/Wabbajack.App/Screens/LogScreenView.axaml.cs b/Wabbajack.App/Screens/LogScreenView.axaml.cs index d3ed2ff0..f9c27a9a 100644 --- a/Wabbajack.App/Screens/LogScreenView.axaml.cs +++ b/Wabbajack.App/Screens/LogScreenView.axaml.cs @@ -1,8 +1,12 @@ +using ReactiveUI; using Wabbajack.App.Views; namespace Wabbajack.App.Screens; public partial class LogScreenView : ScreenBase { - + public LogScreenView() + { + InitializeComponent(); + } } \ No newline at end of file diff --git a/Wabbajack.App/Screens/LogScreenViewModel.cs b/Wabbajack.App/Screens/LogScreenViewModel.cs index a984975b..94667370 100644 --- a/Wabbajack.App/Screens/LogScreenViewModel.cs +++ b/Wabbajack.App/Screens/LogScreenViewModel.cs @@ -1,9 +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(); + + } } \ No newline at end of file diff --git a/Wabbajack.App/ServiceExtensions.cs b/Wabbajack.App/ServiceExtensions.cs index b206ced3..688d6449 100644 --- a/Wabbajack.App/ServiceExtensions.cs +++ b/Wabbajack.App/ServiceExtensions.cs @@ -45,6 +45,7 @@ namespace Wabbajack.App services.AddDTOSerializer(); services.AddSingleton(); services.AddTransient(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -57,6 +58,7 @@ namespace Wabbajack.App services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddAllSingleton(); services.AddAllSingleton(); services.AddAllSingleton(); @@ -89,7 +91,8 @@ namespace Wabbajack.App { EncryptedDataLocation = KnownFolders.WabbajackAppLocal.Combine("encrypted"), 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(); diff --git a/Wabbajack.App/Utilities/LoggerProvider.cs b/Wabbajack.App/Utilities/LoggerProvider.cs index 31e42a6f..ceabfa67 100644 --- a/Wabbajack.App/Utilities/LoggerProvider.cs +++ b/Wabbajack.App/Utilities/LoggerProvider.cs @@ -1,9 +1,16 @@ using System; using System.Collections.Immutable; +using System.Collections.ObjectModel; +using System.IO; using System.Reactive.Disposables; using System.Reactive.Subjects; +using System.Text; using System.Threading; +using DynamicData; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Wabbajack.Paths; +using Wabbajack.Paths.IO; namespace Wabbajack.App.Utilities; @@ -12,16 +19,67 @@ public class LoggerProvider : ILoggerProvider private Subject _messages = new(); public IObservable Messages => _messages; - private long _messageID = 0; + private long _messageId = 0; + private SourceCache _messageLog = new(m => m.MessageId); - public long NextMessageId() + public readonly ReadOnlyObservableCollection _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 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() { - _messages.Dispose(); + _disposables.Dispose(); } public ILogger CreateLogger(string categoryName) @@ -43,7 +101,7 @@ public class LoggerProvider : ILoggerProvider public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { - _provider._messages.OnNext(new LogMessage(_provider.NextMessageId(), logLevel, eventId, state, exception, formatter)); + _provider._messages.OnNext(new LogMessage(DateTime.UtcNow, _provider.NextMessageId(), logLevel, eventId, state, exception, formatter)); } public bool IsEnabled(LogLevel logLevel) @@ -63,10 +121,28 @@ public class LoggerProvider : ILoggerProvider long MessageId { get; } string ShortMessage { get; } + DateTime TimeStamp { get; } + string LongMessage { get; } } - record LogMessage(long MessageId, LogLevel LogLevel, EventId EventId, TState State, Exception? Exception, Func Formatter) : ILogMessage + record LogMessage(DateTime TimeStamp, long MessageId, LogLevel LogLevel, EventId EventId, TState State, Exception? Exception, Func Formatter) : ILogMessage { 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(); + } + } } } \ No newline at end of file diff --git a/Wabbajack.App/ViewModels/MainWindowViewModel.cs b/Wabbajack.App/ViewModels/MainWindowViewModel.cs index 50243983..79e3939f 100644 --- a/Wabbajack.App/ViewModels/MainWindowViewModel.cs +++ b/Wabbajack.App/ViewModels/MainWindowViewModel.cs @@ -43,6 +43,9 @@ namespace Wabbajack.App.ViewModels [Reactive] public ReactiveCommand SettingsButton { get; set; } + + [Reactive] + public ReactiveCommand LogViewButton { get; set; } [Reactive] public string ResourceStatus { get; set; } @@ -72,9 +75,15 @@ namespace Wabbajack.App.ViewModels .DisposeWith(disposables); SettingsButton = ReactiveCommand.Create(() => - { - Receive(new NavigateTo(typeof(SettingsViewModel))); - }) + { + Receive(new NavigateTo(typeof(SettingsViewModel))); + }) + .DisposeWith(disposables); + + LogViewButton = ReactiveCommand.Create(() => + { + Receive(new NavigateTo(typeof(LogScreenViewModel))); + }) .DisposeWith(disposables); }); diff --git a/Wabbajack.App/Views/MainWindow.axaml b/Wabbajack.App/Views/MainWindow.axaml index b02b577b..1c4eb209 100644 --- a/Wabbajack.App/Views/MainWindow.axaml +++ b/Wabbajack.App/Views/MainWindow.axaml @@ -34,21 +34,25 @@ - + - + + - - diff --git a/Wabbajack.App/Views/MainWindow.axaml.cs b/Wabbajack.App/Views/MainWindow.axaml.cs index b7f7f9b3..249ed22e 100644 --- a/Wabbajack.App/Views/MainWindow.axaml.cs +++ b/Wabbajack.App/Views/MainWindow.axaml.cs @@ -31,6 +31,9 @@ namespace Wabbajack.App.Views this.BindCommand(ViewModel, vm => vm.SettingsButton, view => view.SettingsButton) .DisposeWith(dispose); + this.BindCommand(ViewModel, vm => vm.LogViewButton, view => view.LogButton) + .DisposeWith(dispose); + this.Bind(ViewModel, vm => vm.CurrentScreen, view => view.Contents.Content) .DisposeWith(dispose);