diff --git a/Selector.CLI/Command/Scrobble/Scrobble.cs b/Selector.CLI/Command/Scrobble/Scrobble.cs new file mode 100644 index 0000000..47a6d2c --- /dev/null +++ b/Selector.CLI/Command/Scrobble/Scrobble.cs @@ -0,0 +1,16 @@ +using System.CommandLine; + +namespace Selector.CLI +{ + public class ScrobbleCommand : Command + { + public ScrobbleCommand(string name, string description = null) : base(name, description) + { + var saveCommand = new ScrobbleSaveCommand("save", "save scrobbles to database"); + AddCommand(saveCommand); + + var clearCommand = new ScrobbleClearCommand("clear", "clear user scrobbles"); + AddCommand(clearCommand); + } + } +} diff --git a/Selector.CLI/Command/Scrobble/ScrobbleClear.cs b/Selector.CLI/Command/Scrobble/ScrobbleClear.cs new file mode 100644 index 0000000..390b327 --- /dev/null +++ b/Selector.CLI/Command/Scrobble/ScrobbleClear.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Selector.CLI.Extensions; +using Selector.Model; +using Selector.Model.Extensions; +using System; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Selector.CLI +{ + public class ScrobbleClearCommand : Command + { + public ScrobbleClearCommand(string name, string description = null) : base(name, description) + { + var fromOption = new Option("--from", "Date from which to pull scrobbles"); + AddOption(fromOption); + + var toOption = new Option( "--to", "Last date for which to pull scrobbles"); + AddOption(toOption); + + var username = new Option("--username", "user to pulls scrobbles for"); + username.AddAlias("-u"); + AddOption(username); + + Handler = CommandHandler.Create(async (DateTime? from, DateTime? to, string username, CancellationToken token) => await Execute(from, to, username, token)); + } + + public static async Task Execute(DateTime? from, DateTime? to, string username, CancellationToken token) + { + try + { + var context = new CommandContext().WithLogger().WithDb(); + var logger = context.Logger.CreateLogger("Scrobble"); + + var db = new ApplicationDbContext(context.DatabaseConfig.Options, context.Logger.CreateLogger()); + + logger.LogInformation("Searching for {0}", username); + var user = db.Users + .Include(u => u.Scrobbles) + .FirstOrDefault(u => u.UserName == username); + + if (user is not null) + { + user.Scrobbles = user.Scrobbles + .Where(s => s.Timestamp < (from ?? DateTime.MinValue) + && s.Timestamp > (to ?? DateTime.MaxValue)) + .ToList(); + + await db.SaveChangesAsync(); + } + else + { + logger.LogError("{0} not found", username); + } + + } + catch (Exception ex) + { + Console.WriteLine(ex); + return 1; + } + + return 0; + } + } +} diff --git a/Selector.CLI/Command/Scrobble.cs b/Selector.CLI/Command/Scrobble/ScrobbleSave.cs similarity index 93% rename from Selector.CLI/Command/Scrobble.cs rename to Selector.CLI/Command/Scrobble/ScrobbleSave.cs index ff1d2ad..1e60475 100644 --- a/Selector.CLI/Command/Scrobble.cs +++ b/Selector.CLI/Command/Scrobble/ScrobbleSave.cs @@ -12,16 +12,6 @@ using System.Threading.Tasks; namespace Selector.CLI { - public class ScrobbleCommand : Command - { - public ScrobbleCommand(string name, string description = null) : base(name, description) - { - - var saveCommand = new ScrobbleSaveCommand("save", "save scrobbles to"); - AddCommand(saveCommand); - } - } - public class ScrobbleSaveCommand : Command { public ScrobbleSaveCommand(string name, string description = null) : base(name, description) diff --git a/Selector.CLI/Properties/launchSettings.json b/Selector.CLI/Properties/launchSettings.json index cc8dda5..3751a5a 100644 --- a/Selector.CLI/Properties/launchSettings.json +++ b/Selector.CLI/Properties/launchSettings.json @@ -9,7 +9,7 @@ }, "Selector.CLI.Scrobble": { "commandName": "Project", - "commandLineArgs": "scrobble save -u sarsoo --from \"2022/01/01\"", + "commandLineArgs": "scrobble save -u sarsoo --from \"2022/01/01\" -nr -na", "environmentVariables": { "DOTNET_ENVIRONMENT": "Development" }, @@ -17,7 +17,15 @@ }, "Selector.CLI.Scrobble.All": { "commandName": "Project", - "commandLineArgs": "scrobble save -u sarsoo --from \"2017/01/01\" -p 200 -d 75 --no-remove", + "commandLineArgs": "scrobble save -u sarsoo --from \"2017/01/01\" -p 200 -d 75 -na -nr", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "nativeDebugging": true + }, + "Selector.CLI.Scrobble.Clear": { + "commandName": "Project", + "commandLineArgs": "scrobble clear -u sarsoo --from \"2022/01/01\"", "environmentVariables": { "DOTNET_ENVIRONMENT": "Development" }, diff --git a/Selector.CLI/ScrobbleSaver.cs b/Selector.CLI/ScrobbleSaver.cs index 05da416..2598252 100644 --- a/Selector.CLI/ScrobbleSaver.cs +++ b/Selector.CLI/ScrobbleSaver.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -69,6 +70,8 @@ namespace Selector } } + IdentifyDuplicates(scrobbles); + logger.LogDebug("Ordering and filtering pulled scrobbles"); var nativeScrobbles = scrobbles @@ -154,5 +157,34 @@ namespace Selector return tasks; } + + private void IdentifyDuplicates(IEnumerable tracks) + { + logger.LogDebug("Identifying duplicates"); + + var duplicates = tracks + .GroupBy(t => t.TimePlayed?.UtcDateTime) + .Where(g => g.Count() > 1); + + foreach(var dupe in duplicates) + { + var dupeString = new StringBuilder(); + + foreach(var scrobble in dupe) + { + dupeString.Append("("); + dupeString.Append(scrobble.Name); + dupeString.Append(", "); + dupeString.Append(scrobble.AlbumName); + dupeString.Append(", "); + dupeString.Append(scrobble.ArtistName); + dupeString.Append(")"); + + dupeString.Append(" "); + } + + logger.LogInformation("Duplicate at {0}: {1}", dupe.Key, dupeString.ToString()); + } + } } } diff --git a/Selector/Scrobble/ScrobbleMatcher.cs b/Selector/Scrobble/ScrobbleMatcher.cs index 1f389df..168efd7 100644 --- a/Selector/Scrobble/ScrobbleMatcher.cs +++ b/Selector/Scrobble/ScrobbleMatcher.cs @@ -1,4 +1,5 @@ using IF.Lastfm.Core.Objects; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -13,7 +14,7 @@ namespace Selector public static bool MatchTime(Scrobble nativeScrobble, Scrobble serviceScrobble) => serviceScrobble.Timestamp.Equals(nativeScrobble.Timestamp); - public static (IEnumerable, IEnumerable) IdentifyDiffs(IEnumerable existing, IEnumerable toApply) + public static (IEnumerable, IEnumerable) IdentifyDiffs(IEnumerable existing, IEnumerable toApply, bool matchContents = true) { existing = existing.OrderBy(s => s.Timestamp); toApply = toApply.OrderBy(s => s.Timestamp); @@ -37,6 +38,11 @@ namespace Selector if (MatchTime(currentExisting, toApplyIter.Current)) { + if (matchContents) + { + MatchData(currentExisting, toApplyIter.Current); + } + toApplyIter.MoveNext(); } else @@ -54,6 +60,24 @@ namespace Selector return (toAdd, toRemove); } + public static void MatchData(Scrobble currentExisting, Scrobble toApply) + { + if (!currentExisting.TrackName.Equals(toApply.TrackName, StringComparison.InvariantCultureIgnoreCase)) + { + currentExisting.TrackName = toApply.TrackName; + } + + if (!currentExisting.AlbumName.Equals(toApply.AlbumName, StringComparison.InvariantCultureIgnoreCase)) + { + currentExisting.AlbumName = toApply.AlbumName; + } + + if (!currentExisting.ArtistName.Equals(toApply.ArtistName, StringComparison.InvariantCultureIgnoreCase)) + { + currentExisting.ArtistName = toApply.ArtistName; + } + } + public static (IEnumerable, IEnumerable) IdentifyDiffsContains(IEnumerable existing, IEnumerable toApply) { var toAdd = toApply.Where(s => !existing.Contains(s, new ScrobbleComp()));