Skip to content

Commit

Permalink
Use the regular ILogger abstraction in MIDI detection
Browse files Browse the repository at this point in the history
This should be what we use everywhere else too, but one step at a time
  • Loading branch information
jskeet committed May 24, 2020
1 parent 9fd115b commit 7f6b139
Show file tree
Hide file tree
Showing 22 changed files with 195 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task<int> InvokeAsync(InvocationContext context)
var console = context.Console.Out;
var kit = context.ParseResult.ValueForOption<int>("kit");
var file = context.ParseResult.ValueForOption<string>("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)
{
Expand Down
53 changes: 53 additions & 0 deletions Drums/VDrumExplorer.Console/ConsoleLogger.cs
Original file line number Diff line number Diff line change
@@ -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>(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<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> 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()
{
}
}
}
}
2 changes: 1 addition & 1 deletion Drums/VDrumExplorer.Console/ImportKitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public async Task<int> InvokeAsync(InvocationContext context)
var console = context.Console.Out;
var kit = context.ParseResult.ValueForOption<int>("kit");
var file = context.ParseResult.ValueForOption<string>("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)
{
Expand Down
2 changes: 1 addition & 1 deletion Drums/VDrumExplorer.Console/ListDevicesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ListDevicesCommand : ICommandHandler
public async Task<int> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task<int> 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;
Expand Down
2 changes: 1 addition & 1 deletion Drums/VDrumExplorer.Console/TurnPagesViaNoteCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task<int> 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;
Expand Down
29 changes: 15 additions & 14 deletions Drums/VDrumExplorer.Midi/MidiDevices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -68,18 +69,18 @@ public static Task<RolandMidiClient> CreateRolandMidiClientAsync(MidiInputDevice
/// <summary>
/// Detects a single Roland MIDI client, or null if there are 0 or multiple known devices.
/// </summary>
public static async Task<RolandMidiClient?> DetectSingleRolandMidiClientAsync(Action<string> log, IEnumerable<ModuleIdentifier> knownIdentifiers)
public static async Task<RolandMidiClient?> DetectSingleRolandMidiClientAsync(ILogger logger, IEnumerable<ModuleIdentifier> 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();
Expand All @@ -88,17 +89,17 @@ public static Task<RolandMidiClient> CreateRolandMidiClientAsync(MidiInputDevice
}
}

public static async IAsyncEnumerable<RolandMidiClient> DetectRolandMidiClientsAsync(Action<string> log, IEnumerable<ModuleIdentifier> knownIdentifiers)
public static async IAsyncEnumerable<RolandMidiClient> DetectRolandMidiClientsAsync(ILogger logger, IEnumerable<ModuleIdentifier> 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))
Expand All @@ -107,36 +108,36 @@ public static async IAsyncEnumerable<RolandMidiClient> 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;
}
Expand Down
1 change: 1 addition & 0 deletions Drums/VDrumExplorer.Midi/RawMidiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private static RawMidiMessage ConvertMessage(MidiReceivedEventArgs args) =>

internal static async Task<RawMidiClient> CreateAsync(MidiInputDevice inputDevice, MidiOutputDevice outputDevice, Action<RawMidiMessage> 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);
Expand Down
1 change: 1 addition & 0 deletions Drums/VDrumExplorer.Midi/VDrumExplorer.Midi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="managed-midi" Version="1.9.13" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.4" />
<PackageReference Include="System.Linq.Async" Version="4.1.1" />
</ItemGroup>

Expand Down
37 changes: 37 additions & 0 deletions Drums/VDrumExplorer.ViewModel/Home/LogViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,6 +15,7 @@ public class LogViewModel
{
private readonly IClock clock;
public ObservableCollection<LogEntry> LogEntries { get; }
public ILogger Logger { get; }

public LogViewModel() : this(SystemClock.Instance)
{
Expand All @@ -23,6 +25,7 @@ public LogViewModel(IClock clock)
{
LogEntries = new ObservableCollection<LogEntry>();
this.clock = clock;
Logger = new LoggerImpl(this);
}

public void Log(string text)
Expand All @@ -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>(TState state) => NoOpDisposable.Instance;

public bool IsEnabled(LogLevel logLevel) => true;

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> 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()
{
}
}
}
}
}
2 changes: 1 addition & 1 deletion Drums/VDrumExplorer.ViewModel/SharedViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
</ItemGroup>

<ItemGroup>
<!-- TODO: Sort this out.-->
<PackageReference Include="NAudio" Version="1.10.0" />
<PackageReference Include="NodaTime" Version="2.4.7" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Drums/VDrumExplorer.Wpf/DataExplorer.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 13 additions & 12 deletions Drums/VDrumExplorer.Wpf/DeviceLoaderDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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
{
Expand All @@ -92,7 +93,7 @@ internal async void CopySegmentsToDeviceAsync(List<DataSegment> 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)
{
Expand All @@ -102,19 +103,19 @@ internal async void CopySegmentsToDeviceAsync(List<DataSegment> 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;
}
}
Expand All @@ -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;
}
}
Expand Down
Loading

0 comments on commit 7f6b139

Please sign in to comment.