Skip to content

Commit

Permalink
Added CSV import support
Browse files Browse the repository at this point in the history
  • Loading branch information
davewalker5 committed Oct 6, 2023
1 parent 131350c commit a288edd
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace MusicCatalogue.Entities.CommandLine
public class CommandLineOption
{
public CommandLineOptionType OptionType { get; set; }
public bool IsOperation { get; set; }
public string Name { get; set; } = "";
public string ShortName { get; set; } = "";
public string Description { get; set; } = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ public enum CommandLineOptionType
{
Unknown,
Lookup,
Import,
}
}
2 changes: 1 addition & 1 deletion src/MusicCatalogue.Entities/DataExchange/FlattenedTrack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static FlattenedTrack FromCsv(string record)

// Split the duration on the ":" separator and convert to milliseconds
var durationWords = words[DurationField][..^1].Split(new string[] { ":" }, StringSplitOptions.None);
var durationMs = 1000 * (60 * int.Parse(durationWords[0]) + 1000 * int.Parse(durationWords[1]));
var durationMs = 1000 * (60 * int.Parse(durationWords[0]) + int.Parse(durationWords[1]));

// Create a new "flattened" record containing artist, album and track details
return new FlattenedTrack
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;

namespace MusicCatalogue.Entities.Exceptions
{
[Serializable]
[ExcludeFromCodeCoverage]
public class MultipleOperationsException : Exception
{
public MultipleOperationsException()
{
}

public MultipleOperationsException(string message) : base(message)
{
}

public MultipleOperationsException(string message, Exception inner) : base(message, inner)
{
}

protected MultipleOperationsException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext)
{
}

public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
}
}

5 changes: 4 additions & 1 deletion src/MusicCatalogue.Entities/Interfaces/ICsvImporter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
namespace MusicCatalogue.Entities.Interfaces
using MusicCatalogue.Entities.DataExchange;

namespace MusicCatalogue.Entities.Interfaces
{
public interface ICsvImporter
{
event EventHandler<TrackDataExchangeEventArgs>? TrackImport;
Task Import(string file);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ public interface IMusicCatalogueFactory
IArtistManager Artists { get; }
ITrackManager Tracks { get; }
IUserManager Users { get; }
ICsvImporter Importer { get; }
}
}
22 changes: 21 additions & 1 deletion src/MusicCatalogue.Logic/CommandLine/CommandLineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ public class CommandLineParser
/// Add an option to the available command line options
/// </summary>
/// <param name="optionType"></param>
/// <param name="isOperation"></param>
/// <param name="name"></param>
/// <param name="shortName"></param>
/// <param name="description"></param>
/// <param name="minimumNumberOfValues"></param>
/// <param name="maximumNumberOfValues"></param>
public void Add(CommandLineOptionType optionType, string name, string shortName, string description, int minimumNumberOfValues, int maximumNumberOfValues)
public void Add(CommandLineOptionType optionType, bool isOperation, string name, string shortName, string description, int minimumNumberOfValues, int maximumNumberOfValues)
{
// Check the option's not a duplicate
if (_options.Select(x => x.OptionType).Contains(optionType))
Expand All @@ -41,6 +42,7 @@ public void Add(CommandLineOptionType optionType, string name, string shortName,
_options.Add(new CommandLineOption
{
OptionType = optionType,
IsOperation = isOperation,
Name = name,
ShortName = shortName,
Description = description,
Expand All @@ -61,6 +63,11 @@ public void Parse(IEnumerable<string> args)

// Check that all arguments have the required number of values
CheckForMinimumValues();

// Check that there's only one argument that defines an operation. For example,
// looking up an album's details is one operation, importing a CSV file is another.
// Both can't be supplied at the same time
CheckForSingleOperation();
}

/// <summary>
Expand Down Expand Up @@ -96,6 +103,19 @@ private void CheckForMinimumValues()
}
}

/// <summary>
/// Check there's only one operation specified on the command line
/// </summary>
private void CheckForSingleOperation()
{
IEnumerable<CommandLineOption> options = _options.Where(x => _values!.ContainsKey(x.OptionType) && x.IsOperation);
if (options!.Count() > 1)
{
var message = $"Command line specifies multiple operations: {string.Join(", ", options.Select(x => x.ToString()))}";
throw new MultipleOperationsException(message);
}
}

/// <summary>
/// Build the value list from the command line
/// </summary>
Expand Down
8 changes: 5 additions & 3 deletions src/MusicCatalogue.Logic/DataExchange/CsvImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public partial class CsvImporter : ICsvImporter
{
private readonly MusicCatalogueFactory _factory;

public EventHandler<TrackDataExchangeEventArgs> RecordImport;
public event EventHandler<TrackDataExchangeEventArgs>? TrackImport;

#pragma warning disable CS8618
internal CsvImporter(MusicCatalogueFactory factory)
Expand Down Expand Up @@ -60,13 +60,15 @@ public async Task Import(string file)
throw new InvalidRecordFormatException(message);
}

// Inflate the CSV record to a track and store it in the database
// Inflate the CSV record to a track and save the artist
FlattenedTrack track = FlattenedTrack.FromCsv(line);
var artist = await _factory.Artists.AddAsync(track.ArtistName);

// See if the album exists
var album = await _factory.Albums.AddAsync(artist.Id, track.AlbumTitle, track.Released, track.Genre, track.CoverUrl);
await _factory.Tracks.AddAsync(album.Id, track.Title, track.TrackNumber, track.Duration);

RecordImport?.Invoke(this, new TrackDataExchangeEventArgs { RecordCount = count - 1, Track = track });
TrackImport?.Invoke(this, new TrackDataExchangeEventArgs { RecordCount = count - 1, Track = track });
}
}
}
Expand Down
81 changes: 81 additions & 0 deletions src/MusicCatalogue.LookupTool/Logic/AlbumLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using MusicCatalogue.Entities.Config;
using MusicCatalogue.Entities.Interfaces;
using MusicCatalogue.Logic.Api;
using MusicCatalogue.Logic.Api.TheAudioDB;
using MusicCatalogue.Logic.Collection;
using MusicCatalogue.Logic.Database;

namespace MusicCatalogue.LookupTool.Logic
{
internal class AlbumLookup
{
private readonly IMusicLogger _logger;
private readonly MusicApplicationSettings _settings;
private readonly IMusicCatalogueFactory _factory;

public AlbumLookup(IMusicLogger logger, IMusicCatalogueFactory factory, MusicApplicationSettings settings)
{
_logger = logger;
_settings = settings;
_factory = factory;
}

/// <summary>
/// Lookup an album given the artist name and album title
/// </summary>
/// <param name="artistName"></param>
/// <param name="albumTitle"></param>
public async Task LookupAlbum(string artistName, string albumTitle)
{
// Get the API key and the URLs for the album and track lookup endpoints
var key = _settings!.ApiServiceKeys.Find(x => x.Service == ApiServiceType.TheAudioDB)!.Key;
var albumsEndpoint = _settings.ApiEndpoints.Find(x => x.EndpointType == ApiEndpointType.Albums)!.Url;
var tracksEndpoint = _settings.ApiEndpoints.Find(x => x.EndpointType == ApiEndpointType.Tracks)!.Url;

// Convert the URL into a URI instance that will expose the host name - this is needed
// to set up the client headers
var uri = new Uri(albumsEndpoint);

// Configure an HTTP client
var client = MusicHttpClient.Instance;
client.AddHeader("X-RapidAPI-Key", key);
client.AddHeader("X-RapidAPI-Host", uri.Host);

// Configure the APIs
var albumsApi = new TheAudioDBAlbumsApi(_logger, client, albumsEndpoint);
var tracksApi = new TheAudioDBTracksApi(_logger, client, tracksEndpoint);
var lookupManager = new AlbumLookupManager(_logger, albumsApi, tracksApi, _factory);

// Lookup the album and its tracks
var album = await lookupManager.LookupAlbum(artistName, albumTitle);
if (album != null)
{
// Dump the album details
Console.WriteLine($"Title: {album.Title}");
Console.WriteLine($"Artist: {StringCleaner.Clean(artistName)}");
Console.WriteLine($"Released: {album.Released}");
Console.WriteLine($"Genre: {album.Genre}");
Console.WriteLine($"Cover: {album.CoverUrl}");
Console.WriteLine();

// Dump the track list
if ((album.Tracks != null) && (album.Tracks.Count > 0))
{
foreach (var track in album.Tracks)
{
Console.WriteLine($"{track.Number} : {track.Title}, {track.FormattedDuration()}");
}
Console.WriteLine();
}
else
{
Console.WriteLine("No tracks found");
}
}
else
{
Console.WriteLine("Album details not found");
}
}
}
}
58 changes: 58 additions & 0 deletions src/MusicCatalogue.LookupTool/Logic/DataImport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using MusicCatalogue.Entities.DataExchange;
using MusicCatalogue.Entities.Interfaces;
using MusicCatalogue.Entities.Logging;

namespace MusicCatalogue.LookupTool.Logic
{
internal class DataImport
{
private readonly IMusicLogger _logger;
private readonly IMusicCatalogueFactory _factory;

public DataImport(IMusicLogger logger, IMusicCatalogueFactory factory)
{
_logger = logger;
_factory = factory;
}

/// <summary>
/// Import the data held in the specified CSV file
/// </summary>
/// <param name="albumName"></param>
public void Import(string file)
{
_logger.LogMessage(Severity.Info, $"Importing {file} ...");

try
{
// Register a handler for the "track imported" event and import the file
_factory.Importer.TrackImport += OnTrackImported;
_factory.Importer.Import(file);
}
catch (Exception ex)
{
Console.WriteLine($"Import error: {ex.Message}");
_logger.LogMessage(Severity.Info, $"Import error: {ex.Message}");
_logger.LogException(ex);
}
finally
{
// Un-register the event handler
_factory.Importer.TrackImport -= OnTrackImported;
}
}

/// <summary>
/// Handler called when a track is imported
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void OnTrackImported(object? sender, TrackDataExchangeEventArgs e)
{
if (e.Track != null)
{
Console.WriteLine($"Imported {e.Track.ArtistName} {e.Track.AlbumTitle} {e.Track.TrackNumber} {e.Track.Title}");
}
}
}
}
Loading

0 comments on commit a288edd

Please sign in to comment.