From 7f6b13920568cb175da8fd7c820d3b5e951102da Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 18 May 2020 18:48:22 +0100 Subject: [PATCH] Use the regular ILogger abstraction in MIDI detection This should be what we use everywhere else too, but one step at a time --- .../CheckInstrumentDefaultsCommand.cs | 2 +- Drums/VDrumExplorer.Console/ConsoleLogger.cs | 53 +++++++++++++++++++ .../VDrumExplorer.Console/ImportKitCommand.cs | 2 +- .../ListDevicesCommand.cs | 2 +- .../TurnPagesViaKitChangeCommand.cs | 2 +- .../TurnPagesViaNoteCommand.cs | 2 +- Drums/VDrumExplorer.Midi/MidiDevices.cs | 29 +++++----- Drums/VDrumExplorer.Midi/RawMidiClient.cs | 1 + .../VDrumExplorer.Midi.csproj | 1 + .../Home/LogViewModel.cs | 37 +++++++++++++ .../SharedViewModel.cs | 2 +- .../VDrumExplorer.ViewModel.csproj | 1 - Drums/VDrumExplorer.Wpf/DataExplorer.xaml.cs | 2 +- .../DeviceLoaderDialog.xaml.cs | 25 ++++----- Drums/VDrumExplorer.Wpf/ILogger.cs | 14 ----- .../InstrumentAudioExplorer.xaml.cs | 5 +- Drums/VDrumExplorer.Wpf/KitExplorer.cs | 1 + Drums/VDrumExplorer.Wpf/ModuleExplorer.cs | 7 +-- Drums/VDrumExplorer.Wpf/ModuleLoader.xaml.cs | 29 +++++----- .../SoundRecorderDialog.xaml.cs | 27 +++++----- Drums/VDrumExplorer.Wpf/TextBlockLogger.cs | 31 ++++++++++- Drums/VDrumExplorer.Wpf/TextConversions.cs | 3 +- 22 files changed, 195 insertions(+), 83 deletions(-) create mode 100644 Drums/VDrumExplorer.Console/ConsoleLogger.cs delete mode 100644 Drums/VDrumExplorer.Wpf/ILogger.cs diff --git a/Drums/VDrumExplorer.Console/CheckInstrumentDefaultsCommand.cs b/Drums/VDrumExplorer.Console/CheckInstrumentDefaultsCommand.cs index d31e3e0c..4db8f243 100644 --- a/Drums/VDrumExplorer.Console/CheckInstrumentDefaultsCommand.cs +++ b/Drums/VDrumExplorer.Console/CheckInstrumentDefaultsCommand.cs @@ -30,7 +30,7 @@ public async Task InvokeAsync(InvocationContext context) var console = context.Console.Out; var kit = context.ParseResult.ValueForOption("kit"); var file = context.ParseResult.ValueForOption("file"); - var client = await MidiDevices.DetectSingleRolandMidiClientAsync(console.WriteLine, SchemaRegistry.KnownSchemas.Keys); + var client = await MidiDevices.DetectSingleRolandMidiClientAsync(new ConsoleLogger(console), SchemaRegistry.KnownSchemas.Keys); if (client == null) { diff --git a/Drums/VDrumExplorer.Console/ConsoleLogger.cs b/Drums/VDrumExplorer.Console/ConsoleLogger.cs new file mode 100644 index 00000000..b7346968 --- /dev/null +++ b/Drums/VDrumExplorer.Console/ConsoleLogger.cs @@ -0,0 +1,53 @@ +// Copyright 2020 Jon Skeet. All rights reserved. +// Use of this source code is governed by the Apache License 2.0, +// as found in the LICENSE.txt file. + +using Microsoft.Extensions.Logging; +using System; +using System.CommandLine.IO; + +namespace VDrumExplorer.Console +{ + internal sealed class ConsoleLogger : ILogger + { + private readonly IStandardStreamWriter writer; + + internal ConsoleLogger(IStandardStreamWriter writer) => + this.writer = writer; + + public IDisposable BeginScope(TState state) => NoOpDisposable.Instance; + + public bool IsEnabled(LogLevel logLevel) => true; + + private void Log(string text) => + writer.WriteLine(text); + + private void Log(string message, Exception e) + { + // TODO: Aggregate exception etc. + Log($"{message}: {e}"); + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + var message = formatter(state, exception); + if (exception is null) + { + Log(message); + } + else + { + Log(message, exception); + } + } + + private class NoOpDisposable : IDisposable + { + internal static NoOpDisposable Instance { get; } = new NoOpDisposable(); + + public void Dispose() + { + } + } + } +} diff --git a/Drums/VDrumExplorer.Console/ImportKitCommand.cs b/Drums/VDrumExplorer.Console/ImportKitCommand.cs index 44bf5058..97a5ee39 100644 --- a/Drums/VDrumExplorer.Console/ImportKitCommand.cs +++ b/Drums/VDrumExplorer.Console/ImportKitCommand.cs @@ -32,7 +32,7 @@ public async Task InvokeAsync(InvocationContext context) var console = context.Console.Out; var kit = context.ParseResult.ValueForOption("kit"); var file = context.ParseResult.ValueForOption("file"); - var client = await MidiDevices.DetectSingleRolandMidiClientAsync(console.WriteLine, SchemaRegistry.KnownSchemas.Keys); + var client = await MidiDevices.DetectSingleRolandMidiClientAsync(new ConsoleLogger(console), SchemaRegistry.KnownSchemas.Keys); if (client == null) { diff --git a/Drums/VDrumExplorer.Console/ListDevicesCommand.cs b/Drums/VDrumExplorer.Console/ListDevicesCommand.cs index 3e54fb8b..efe1f484 100644 --- a/Drums/VDrumExplorer.Console/ListDevicesCommand.cs +++ b/Drums/VDrumExplorer.Console/ListDevicesCommand.cs @@ -24,7 +24,7 @@ class ListDevicesCommand : ICommandHandler public async Task InvokeAsync(InvocationContext context) { var console = context.Console.Out; - var clients = await MidiDevices.DetectRolandMidiClientsAsync(console.WriteLine, SchemaRegistry.KnownSchemas.Keys).ToListAsync(); + var clients = await MidiDevices.DetectRolandMidiClientsAsync(new ConsoleLogger(console), SchemaRegistry.KnownSchemas.Keys).ToListAsync(); foreach (var client in clients) { client.Dispose(); diff --git a/Drums/VDrumExplorer.Console/TurnPagesViaKitChangeCommand.cs b/Drums/VDrumExplorer.Console/TurnPagesViaKitChangeCommand.cs index e4a48d5e..2a5d70b9 100644 --- a/Drums/VDrumExplorer.Console/TurnPagesViaKitChangeCommand.cs +++ b/Drums/VDrumExplorer.Console/TurnPagesViaKitChangeCommand.cs @@ -47,7 +47,7 @@ public async Task InvokeAsync(InvocationContext context) return 1; } - var client = await MidiDevices.DetectSingleRolandMidiClientAsync(console.WriteLine, SchemaRegistry.KnownSchemas.Keys); + var client = await MidiDevices.DetectSingleRolandMidiClientAsync(new ConsoleLogger(console), SchemaRegistry.KnownSchemas.Keys); if (client is null) { return 1; diff --git a/Drums/VDrumExplorer.Console/TurnPagesViaNoteCommand.cs b/Drums/VDrumExplorer.Console/TurnPagesViaNoteCommand.cs index 58621323..0f4a59d5 100644 --- a/Drums/VDrumExplorer.Console/TurnPagesViaNoteCommand.cs +++ b/Drums/VDrumExplorer.Console/TurnPagesViaNoteCommand.cs @@ -46,7 +46,7 @@ public async Task InvokeAsync(InvocationContext context) return 1; } - var client = await MidiDevices.DetectSingleRolandMidiClientAsync(console.WriteLine, SchemaRegistry.KnownSchemas.Keys); + var client = await MidiDevices.DetectSingleRolandMidiClientAsync(new ConsoleLogger(console), SchemaRegistry.KnownSchemas.Keys); if (client is null) { return 1; diff --git a/Drums/VDrumExplorer.Midi/MidiDevices.cs b/Drums/VDrumExplorer.Midi/MidiDevices.cs index 1a0cce5f..d83a1746 100644 --- a/Drums/VDrumExplorer.Midi/MidiDevices.cs +++ b/Drums/VDrumExplorer.Midi/MidiDevices.cs @@ -3,6 +3,7 @@ // as found in the LICENSE.txt file. using Commons.Music.Midi; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; @@ -68,18 +69,18 @@ public static Task CreateRolandMidiClientAsync(MidiInputDevice /// /// Detects a single Roland MIDI client, or null if there are 0 or multiple known devices. /// - public static async Task DetectSingleRolandMidiClientAsync(Action log, IEnumerable knownIdentifiers) + public static async Task DetectSingleRolandMidiClientAsync(ILogger logger, IEnumerable knownIdentifiers) { - var clients = await DetectRolandMidiClientsAsync(log, knownIdentifiers).ToListAsync(); + var clients = await DetectRolandMidiClientsAsync(logger, knownIdentifiers).ToListAsync(); switch (clients.Count) { case 0: - log("No known modules detected. Aborting"); + logger.LogWarning("No known modules detected. Aborting"); return null; case 1: return clients[0]; default: - log($"Multiple known modules detected: {string.Join(", ", clients.Select(c => c.Identifier.Name))}. Aborting."); + logger.LogWarning($"Multiple known modules detected: {string.Join(", ", clients.Select(c => c.Identifier.Name))}. Aborting."); foreach (var client in clients) { client.Dispose(); @@ -88,17 +89,17 @@ public static Task CreateRolandMidiClientAsync(MidiInputDevice } } - public static async IAsyncEnumerable DetectRolandMidiClientsAsync(Action log, IEnumerable knownIdentifiers) + public static async IAsyncEnumerable DetectRolandMidiClientsAsync(ILogger logger, IEnumerable knownIdentifiers) { var inputDevices = ListInputDevices(); foreach (var device in inputDevices) { - log($"Input device: '{device.Name}'"); + logger.LogInformation($"Input device: '{device.Name}'"); } var outputDevices = ListOutputDevices(); foreach (var device in outputDevices) { - log($"Output device: '{device.Name}'"); + logger.LogInformation($"Output device: '{device.Name}'"); } var commonNames = inputDevices.Select(input => input.Name) .Intersect(outputDevices.Select(output => output.Name)) @@ -107,36 +108,36 @@ public static async IAsyncEnumerable DetectRolandMidiClientsAs if (commonNames.Count == 0) { - log("No input and output MIDI ports with the same name detected."); + logger.LogWarning("No input and output MIDI ports with the same name detected."); yield break; } foreach (var name in commonNames) { - log($"Detecting devices for MIDI ports with name '{name}'"); + logger.LogInformation($"Detecting devices for MIDI ports with name '{name}'"); var matchedInputs = inputDevices.Where(input => input.Name == name).ToList(); var matchedOutputs = outputDevices.Where(output => output.Name == name).ToList(); if (matchedInputs.Count != 1 || matchedOutputs.Count != 1) { - log($" Error: Name {name} matches multiple input or output MIDI ports. Skipping."); + logger.LogWarning($" Error: Name {name} matches multiple input or output MIDI ports. Skipping."); continue; } var identities = await ListDeviceIdentities(matchedInputs[0], matchedOutputs[0], TimeSpan.FromSeconds(1)); if (identities.Count != 1) { - log($" {(identities.Count == 0 ? "No" : "Multiple")} devices detected for MIDI port '{name}'. Skipping."); + logger.LogWarning($" {(identities.Count == 0 ? "No" : "Multiple")} devices detected for MIDI port '{name}'. Skipping."); continue; } var identity = identities[0]; - log($" Detected single Roland device identity: {identity}"); + logger.LogInformation($" Detected single Roland device identity: {identity}"); var matchingKeys = knownIdentifiers.Where(sk => sk.FamilyCode == identity.FamilyCode && sk.FamilyNumberCode == identity.FamilyNumberCode).ToList(); if (matchingKeys.Count != 1) { - log($" {(matchingKeys.Count == 0 ? "No" : "Multiple")} known V-Drums schemas detected for MIDI device. Skipping."); + logger.LogWarning($" {(matchingKeys.Count == 0 ? "No" : "Multiple")} known V-Drums schemas detected for MIDI device. Skipping."); continue; } - log($" Identity matches known schema {matchingKeys[0].Name}."); + logger.LogInformation($" Identity matches known schema {matchingKeys[0].Name}."); var client = await CreateRolandMidiClientAsync(matchedInputs[0], matchedOutputs[0], identity, matchingKeys[0]); yield return client; } diff --git a/Drums/VDrumExplorer.Midi/RawMidiClient.cs b/Drums/VDrumExplorer.Midi/RawMidiClient.cs index 4538fb70..a9fc4ba1 100644 --- a/Drums/VDrumExplorer.Midi/RawMidiClient.cs +++ b/Drums/VDrumExplorer.Midi/RawMidiClient.cs @@ -36,6 +36,7 @@ private static RawMidiMessage ConvertMessage(MidiReceivedEventArgs args) => internal static async Task CreateAsync(MidiInputDevice inputDevice, MidiOutputDevice outputDevice, Action messageHandler) { + // TODO: Retry this; sometimes it doesn't work first time. var input = await MidiAccessManager.Default.OpenInputAsync(inputDevice.SystemDeviceId); var output = await MidiAccessManager.Default.OpenOutputAsync(outputDevice.SystemDeviceId); return new RawMidiClient(input, inputDevice.Name, output, outputDevice.Name, messageHandler); diff --git a/Drums/VDrumExplorer.Midi/VDrumExplorer.Midi.csproj b/Drums/VDrumExplorer.Midi/VDrumExplorer.Midi.csproj index 0d5c80cb..93390aa0 100644 --- a/Drums/VDrumExplorer.Midi/VDrumExplorer.Midi.csproj +++ b/Drums/VDrumExplorer.Midi/VDrumExplorer.Midi.csproj @@ -9,6 +9,7 @@ + diff --git a/Drums/VDrumExplorer.ViewModel/Home/LogViewModel.cs b/Drums/VDrumExplorer.ViewModel/Home/LogViewModel.cs index b0475fa0..5ecadbb1 100644 --- a/Drums/VDrumExplorer.ViewModel/Home/LogViewModel.cs +++ b/Drums/VDrumExplorer.ViewModel/Home/LogViewModel.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using NodaTime; using System; using System.Collections.ObjectModel; @@ -14,6 +15,7 @@ public class LogViewModel { private readonly IClock clock; public ObservableCollection LogEntries { get; } + public ILogger Logger { get; } public LogViewModel() : this(SystemClock.Instance) { @@ -23,6 +25,7 @@ public LogViewModel(IClock clock) { LogEntries = new ObservableCollection(); this.clock = clock; + Logger = new LoggerImpl(this); } public void Log(string text) @@ -44,5 +47,39 @@ public void Clear() public void Save(string file) => File.WriteAllLines(file, LogEntries.Select(entry => $"{entry.Timestamp:yyyy-MM-ddTHH:mm:ss} {entry.Text}")); + + private class LoggerImpl : ILogger + { + private readonly LogViewModel viewModel; + + internal LoggerImpl(LogViewModel viewModel) => + this.viewModel = viewModel; + + public IDisposable BeginScope(TState state) => NoOpDisposable.Instance; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + var message = formatter(state, exception); + if (exception is null) + { + viewModel.Log(message); + } + else + { + viewModel.Log(message, exception); + } + } + + private class NoOpDisposable : IDisposable + { + internal static NoOpDisposable Instance { get; } = new NoOpDisposable(); + + public void Dispose() + { + } + } + } } } diff --git a/Drums/VDrumExplorer.ViewModel/SharedViewModel.cs b/Drums/VDrumExplorer.ViewModel/SharedViewModel.cs index c5863977..d3980434 100644 --- a/Drums/VDrumExplorer.ViewModel/SharedViewModel.cs +++ b/Drums/VDrumExplorer.ViewModel/SharedViewModel.cs @@ -70,7 +70,7 @@ public void LogVersion(Type type) public async Task DetectModule() { - ConnectedDevice = await MidiDevices.DetectSingleRolandMidiClientAsync(Log, ModuleSchema.KnownSchemas.Keys); + ConnectedDevice = await MidiDevices.DetectSingleRolandMidiClientAsync(LogViewModel.Logger, ModuleSchema.KnownSchemas.Keys); ConnectedDeviceSchema = ConnectedDevice is null ? null : ModuleSchema.KnownSchemas[ConnectedDevice.Identifier].Value; RaisePropertyChanged(nameof(ConnectedDevice)); RaisePropertyChanged(nameof(ConnectedDeviceSchema)); diff --git a/Drums/VDrumExplorer.ViewModel/VDrumExplorer.ViewModel.csproj b/Drums/VDrumExplorer.ViewModel/VDrumExplorer.ViewModel.csproj index 5a867c53..fc533147 100644 --- a/Drums/VDrumExplorer.ViewModel/VDrumExplorer.ViewModel.csproj +++ b/Drums/VDrumExplorer.ViewModel/VDrumExplorer.ViewModel.csproj @@ -18,7 +18,6 @@ - diff --git a/Drums/VDrumExplorer.Wpf/DataExplorer.xaml.cs b/Drums/VDrumExplorer.Wpf/DataExplorer.xaml.cs index 623c76f9..be75bec5 100644 --- a/Drums/VDrumExplorer.Wpf/DataExplorer.xaml.cs +++ b/Drums/VDrumExplorer.Wpf/DataExplorer.xaml.cs @@ -2,12 +2,12 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using Microsoft.Win32; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; diff --git a/Drums/VDrumExplorer.Wpf/DeviceLoaderDialog.xaml.cs b/Drums/VDrumExplorer.Wpf/DeviceLoaderDialog.xaml.cs index 05731b2f..30421318 100644 --- a/Drums/VDrumExplorer.Wpf/DeviceLoaderDialog.xaml.cs +++ b/Drums/VDrumExplorer.Wpf/DeviceLoaderDialog.xaml.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; @@ -49,7 +50,7 @@ internal async void LoadDeviceData(FixedContainer root) { Stopwatch sw = Stopwatch.StartNew(); var containers = root.AnnotateDescendantsAndSelf().Where(c => c.Container.Loadable).ToList(); - logger.Log($"Loading {containers.Count} containers from device {schema.Identifier.Name}"); + logger.LogInformation($"Loading {containers.Count} containers from device {schema.Identifier.Name}"); progress.Maximum = containers.Count; int loaded = 0; foreach (var container in containers) @@ -62,16 +63,16 @@ internal async void LoadDeviceData(FixedContainer root) } Data = data; DialogResult = true; - logger.Log($"Finished loading in {(int) sw.Elapsed.TotalSeconds} seconds"); + logger.LogInformation($"Finished loading in {(int) sw.Elapsed.TotalSeconds} seconds"); } catch (OperationCanceledException) { - logger.Log("Data loading from device was cancelled"); + logger.LogWarning("Data loading from device was cancelled"); DialogResult = false; } catch (Exception ex) { - logger.Log("Error loading data from device", ex); + logger.LogError("Error loading data from device", ex); } finally { @@ -92,7 +93,7 @@ internal async void CopySegmentsToDeviceAsync(List segments) int written = 0; try { - logger.Log($"Writing {segments.Count} segments to the device."); + logger.LogInformation($"Writing {segments.Count} segments to the device."); progress.Maximum = segments.Count; foreach (var segment in segments) { @@ -102,19 +103,19 @@ internal async void CopySegmentsToDeviceAsync(List segments) written++; progress.Value = written; } - logger.Log($"Finished writing segments to the device {(int) sw.Elapsed.TotalSeconds} seconds."); + logger.LogInformation($"Finished writing segments to the device {(int) sw.Elapsed.TotalSeconds} seconds."); DialogResult = true; } catch (OperationCanceledException) { - logger.Log($"Data copying to device was cancelled. Warning: module may now have inconsistent data."); + logger.LogWarning($"Data copying to device was cancelled. Warning: module may now have inconsistent data."); DialogResult = false; } catch (Exception e) { - logger.Log("Failed while writing data to the device."); - logger.Log($"Segments successfully written: {written}"); - logger.Log($"Error: {e}"); + logger.LogError("Failed while writing data to the device."); + logger.LogInformation($"Segments successfully written: {written}"); + logger.LogError($"Error: {e}"); DialogResult = false; } } @@ -130,11 +131,11 @@ private async Task PopulateSegment(ModuleData data, AnnotatedContainer annotated } catch (OperationCanceledException) when (timerToken.IsCancellationRequested) { - logger.Log($"Device didn't respond for container {annotatedContainer.Path}; skipping."); + logger.LogWarning($"Device didn't respond for container {annotatedContainer.Path}; skipping."); } catch { - logger.Log($"Failure while loading {annotatedContainer.Path}"); + logger.LogError($"Failure while loading {annotatedContainer.Path}"); throw; } } diff --git a/Drums/VDrumExplorer.Wpf/ILogger.cs b/Drums/VDrumExplorer.Wpf/ILogger.cs deleted file mode 100644 index 685d6034..00000000 --- a/Drums/VDrumExplorer.Wpf/ILogger.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2019 Jon Skeet. All rights reserved. -// Use of this source code is governed by the Apache License 2.0, -// as found in the LICENSE.txt file. - -using System; - -namespace VDrumExplorer.Wpf -{ - internal interface ILogger - { - void Log(string text); - void Log(string message, Exception e); - } -} diff --git a/Drums/VDrumExplorer.Wpf/InstrumentAudioExplorer.xaml.cs b/Drums/VDrumExplorer.Wpf/InstrumentAudioExplorer.xaml.cs index 97e9b4ab..0aa16bab 100644 --- a/Drums/VDrumExplorer.Wpf/InstrumentAudioExplorer.xaml.cs +++ b/Drums/VDrumExplorer.Wpf/InstrumentAudioExplorer.xaml.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using System.Linq; using System.Threading; using System.Windows; @@ -103,7 +104,7 @@ private async void PlaySample(object sender, RoutedEventArgs args) } catch (Exception e) { - logger.Log("Error playing sample", e); + logger.LogError("Error playing sample", e); } } diff --git a/Drums/VDrumExplorer.Wpf/KitExplorer.cs b/Drums/VDrumExplorer.Wpf/KitExplorer.cs index 6188f6fd..f19f3130 100644 --- a/Drums/VDrumExplorer.Wpf/KitExplorer.cs +++ b/Drums/VDrumExplorer.Wpf/KitExplorer.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using System.IO; using System.Windows; using VDrumExplorer.Data; diff --git a/Drums/VDrumExplorer.Wpf/ModuleExplorer.cs b/Drums/VDrumExplorer.Wpf/ModuleExplorer.cs index 048152a4..a8b252b6 100644 --- a/Drums/VDrumExplorer.Wpf/ModuleExplorer.cs +++ b/Drums/VDrumExplorer.Wpf/ModuleExplorer.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using Microsoft.Win32; using System; using System.Collections.Generic; @@ -128,17 +129,17 @@ private void ImportKitFromFile(object sender, ExecutedRoutedEventArgs e) } catch (Exception ex) { - Logger.Log($"Error loading {dialog.FileName}", ex); + Logger.LogError($"Error loading {dialog.FileName}", ex); return; } if (!(loaded is Kit kit)) { - Logger.Log("Loaded file was not a kit"); + Logger.LogError("Loaded file was not a kit"); return; } if (!kit.Schema.Identifier.Equals(Schema.Identifier)) { - Logger.Log($"Kit was from {kit.Schema.Identifier.Name}; this module is {Schema.Identifier.Name}"); + Logger.LogError($"Kit was from {kit.Schema.Identifier.Name}; this module is {Schema.Identifier.Name}"); return; } var clonedData = kit.KitRoot.Context.CloneData(kit.Data, targetKitNode.Context.Address); diff --git a/Drums/VDrumExplorer.Wpf/ModuleLoader.xaml.cs b/Drums/VDrumExplorer.Wpf/ModuleLoader.xaml.cs index 49aaade1..63057924 100644 --- a/Drums/VDrumExplorer.Wpf/ModuleLoader.xaml.cs +++ b/Drums/VDrumExplorer.Wpf/ModuleLoader.xaml.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using Microsoft.Win32; using System; using System.Collections.Concurrent; @@ -40,38 +41,38 @@ private void LogVersion() var version = typeof(ModuleLoader).Assembly.GetCustomAttributes().OfType().FirstOrDefault(); if (version != null) { - logger.Log($"V-Drum Explorer version {version.InformationalVersion}"); + logger.LogInformation($"V-Drum Explorer version {version.InformationalVersion}"); } else { - logger.Log($"Version attribute not found."); + logger.LogWarning($"Version attribute not found."); } } private async void OnLoaded(object sender, RoutedEventArgs e) { await LoadSchemaRegistry(); - logger.Log("Detecting connected V-Drums modules"); + logger.LogInformation("Detecting connected V-Drums modules"); detectedMidi = null; - var midiDevice = await MidiDevices.DetectSingleRolandMidiClientAsync(logger.Log, SchemaRegistry.KnownSchemas.Keys); + var midiDevice = await MidiDevices.DetectSingleRolandMidiClientAsync(logger, SchemaRegistry.KnownSchemas.Keys); if (midiDevice != null) { detectedMidi = (midiDevice, SchemaRegistry.KnownSchemas[midiDevice.Identifier].Value); } midiPanel.IsEnabled = midiDevice is object; - logger.Log($"Device detection result: {(midiDevice is object ? $"{midiDevice.Identifier.Name} detected" : "no compatible modules (or multiple modules) detected")}"); - logger.Log("-----------------"); + logger.LogInformation($"Device detection result: {(midiDevice is object ? $"{midiDevice.Identifier.Name} detected" : "no compatible modules (or multiple modules) detected")}"); + logger.LogInformation("-----------------"); } private async Task LoadSchemaRegistry() { - logger.Log($"Loading known schemas"); + logger.LogInformation($"Loading known schemas"); foreach (var pair in SchemaRegistry.KnownSchemas) { - logger.Log($"Loading schema for {pair.Key.Name}"); + logger.LogInformation($"Loading schema for {pair.Key.Name}"); await Task.Run(() => pair.Value.Value); } - logger.Log($"Finished loading schemas"); + logger.LogInformation($"Finished loading schemas"); } private void OnClosed(object sender, EventArgs e) @@ -96,7 +97,7 @@ private void LoadFile(object sender, RoutedEventArgs e) } catch (Exception ex) { - logger.Log($"Error loading {dialog.FileName}", ex); + logger.LogError($"Error loading {dialog.FileName}", ex); return; } // TODO: Potentially declare an IDrumData interface with the Schema property and Validate method. @@ -122,19 +123,19 @@ private void LoadFile(object sender, RoutedEventArgs e) break; } default: - logger.Log($"Unknown file data type"); + logger.LogError($"Unknown file data type"); break; } void Validate(Func validationAction) { - logger.Log($"Validating fields"); + logger.LogInformation($"Validating fields"); var validationResult = validationAction(); foreach (var error in validationResult.Errors) { - logger.Log($"Field {error.Path} error: {error.Message}"); + logger.LogError($"Field {error.Path} error: {error.Message}"); } - logger.Log($"Validation complete. Total fields: {validationResult.TotalFields}. Errors: {validationResult.Errors.Count}"); + logger.LogInformation($"Validation complete. Total fields: {validationResult.TotalFields}. Errors: {validationResult.Errors.Count}"); } } diff --git a/Drums/VDrumExplorer.Wpf/SoundRecorderDialog.xaml.cs b/Drums/VDrumExplorer.Wpf/SoundRecorderDialog.xaml.cs index af125709..155f1e7d 100644 --- a/Drums/VDrumExplorer.Wpf/SoundRecorderDialog.xaml.cs +++ b/Drums/VDrumExplorer.Wpf/SoundRecorderDialog.xaml.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using Microsoft.Win32; using System; using System.Collections.Generic; @@ -76,7 +77,7 @@ private async void StartRecording(object sender, RoutedEventArgs args) var instrumentRoot = config.KitRoot.DescendantNodesAndSelf().FirstOrDefault(node => node.InstrumentNumber == 1); if (instrumentRoot == null) { - logger.Log($"No instrument root available. Please email a bug report to skeet@pobox.com"); + logger.LogError($"No instrument root available. Please email a bug report to skeet@pobox.com"); return; } @@ -84,13 +85,13 @@ private async void StartRecording(object sender, RoutedEventArgs args) var midiNoteChain = instrumentRoot.MidiNoteField; if (midiNoteChain == null) { - logger.Log($"No midi field available. Please email a bug report to skeet@pobox.com"); + logger.LogError($"No midi field available. Please email a bug report to skeet@pobox.com"); return; } - logger.Log($"Starting recording process"); + logger.LogInformation($"Starting recording process"); var midiNoteContext = midiNoteChain.GetFinalContext(instrumentRoot.Context); - logger.Log($"Loading existing data to restore after recording"); + logger.LogInformation($"Loading existing data to restore after recording"); List instrumentContainers; try { @@ -108,7 +109,7 @@ private async void StartRecording(object sender, RoutedEventArgs args) } catch (Exception e) { - logger.Log($"Error loading data for recording", e); + logger.LogError($"Error loading data for recording", e); return; } @@ -122,14 +123,14 @@ where field is InstrumentField select (ct, (InstrumentField) field)).FirstOrDefault(); if (instrumentFieldContext == null) { - logger.Log($"No instrument field available. Please email a bug report to skeet@pobox.com"); + logger.LogError($"No instrument field available. Please email a bug report to skeet@pobox.com"); return; } var midiNote = midiNoteChain.FinalField.GetMidiNote(midiNoteContext, data); if (midiNote == null) { - logger.Log($"No midi note for instrument 1. Please email a bug report to skeet@pobox.com"); + logger.LogError($"No midi note for instrument 1. Please email a bug report to skeet@pobox.com"); } var presetInstrumentsToRecord = schema.PresetInstruments @@ -137,7 +138,7 @@ where field is InstrumentField .ToList(); progress.Maximum = presetInstrumentsToRecord.Count + config.UserSamples; - logger.Log($"Starting recording process"); + logger.LogInformation($"Starting recording process"); try { var captures = new List(); @@ -157,15 +158,15 @@ where field is InstrumentField { moduleAudio.Save(output); } - logger.Log($"Saved instrument sounds to {config.OutputFile}."); + logger.LogInformation($"Saved instrument sounds to {config.OutputFile}."); } catch (OperationCanceledException) { - logger.Log("Cancelled recording"); + logger.LogWarning("Cancelled recording"); } catch (Exception e) { - logger.Log($"Error recording data", e); + logger.LogError($"Error recording data", e); } finally { @@ -201,7 +202,7 @@ async Task RecordInstrument(Instrument instrument) private async Task RestoreData(ModuleData data) { - logger.Log("Restoring original data"); + logger.LogInformation("Restoring original data"); try { foreach (var segment in data.GetSegments()) @@ -213,7 +214,7 @@ private async Task RestoreData(ModuleData data) } catch (Exception e) { - logger.Log($"Error restoring data", e); + logger.LogError($"Error restoring data", e); } } diff --git a/Drums/VDrumExplorer.Wpf/TextBlockLogger.cs b/Drums/VDrumExplorer.Wpf/TextBlockLogger.cs index 0a19ce97..9473b3f5 100644 --- a/Drums/VDrumExplorer.Wpf/TextBlockLogger.cs +++ b/Drums/VDrumExplorer.Wpf/TextBlockLogger.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using System; using System.IO; using System.Windows.Controls; @@ -15,15 +16,41 @@ internal class TextBlockLogger : ILogger internal TextBlockLogger(TextBlock block) => this.block = block; - public void Log(string text) => + public IDisposable BeginScope(TState state) => NoOpDisposable.Instance; + + public bool IsEnabled(LogLevel logLevel) => true; + + private void Log(string text) => block.Text += $"{DateTime.Now:HH:mm:ss.fff} {text}\r\n"; - public void Log(string message, Exception e) + private void Log(string message, Exception e) { // TODO: Aggregate exception etc. Log($"{message}: {e}"); } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + var message = formatter(state, exception); + if (exception is null) + { + Log(message); + } + else + { + Log(message, exception); + } + } + public void SaveLog(string file) => File.WriteAllText(file, block.Text); + + private class NoOpDisposable : IDisposable + { + internal static NoOpDisposable Instance { get; } = new NoOpDisposable(); + + public void Dispose() + { + } + } } } diff --git a/Drums/VDrumExplorer.Wpf/TextConversions.cs b/Drums/VDrumExplorer.Wpf/TextConversions.cs index ce056a5d..674d5945 100644 --- a/Drums/VDrumExplorer.Wpf/TextConversions.cs +++ b/Drums/VDrumExplorer.Wpf/TextConversions.cs @@ -2,6 +2,7 @@ // Use of this source code is governed by the Apache License 2.0, // as found in the LICENSE.txt file. +using Microsoft.Extensions.Logging; using System.Globalization; using System.Linq; using System.Windows.Input; @@ -23,7 +24,7 @@ internal static bool TryGetKitRoot(string text, ModuleSchema schema, ILogger log if (!TryParseInt32(text, out var kitNumber) || !schema.KitRoots.TryGetValue(kitNumber, out kitRoot)) { - logger?.Log($"Invalid kit number: {text}"); + logger?.LogError($"Invalid kit number: {text}"); kitRoot = null; return false; }