diff --git a/Selector.CLI/Command/CommandContext.cs b/Selector.CLI/Command/CommandContext.cs new file mode 100644 index 0000000..f139972 --- /dev/null +++ b/Selector.CLI/Command/CommandContext.cs @@ -0,0 +1,16 @@ +using IF.Lastfm.Core.Api; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Selector.Model; + +namespace Selector.CLI +{ + public class CommandContext + { + public RootOptions Config { get; set; } + public ILoggerFactory Logger { get; set; } + + public DbContextOptionsBuilder DatabaseConfig { get; set; } + public LastfmClient LastFmClient { get; set; } + } +} diff --git a/Selector.CLI/Command/HostCommand.cs b/Selector.CLI/Command/HostCommand.cs new file mode 100644 index 0000000..33d7601 --- /dev/null +++ b/Selector.CLI/Command/HostCommand.cs @@ -0,0 +1,144 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NLog.Extensions.Logging; +using Selector.Cache.Extensions; +using Selector.CLI.Extensions; +using Selector.CLI.Services; +using Selector.Events; +using Selector.Extensions; +using System; +using System.CommandLine; +using System.CommandLine.Invocation; + +namespace Selector.CLI +{ + public class HostRootCommand: RootCommand + { + public HostRootCommand() + { + Handler = CommandHandler.Create(() => HostCommand.Execute()); + } + } + + public class HostCommand : Command + { + public HostCommand(string name, string description = null) : base(name, description) + { + Handler = CommandHandler.Create(() => Execute()); + } + + public static int Execute() + { + try + { + CreateHostBuilder(Environment.GetCommandLineArgs(), ConfigureDefault, ConfigureDefaultNlog) + .Build() + .Run(); + } + catch(Exception ex) + { + Console.WriteLine(ex); + return 1; + } + + return 0; + } + + public static RootOptions ConfigureOptions(HostBuilderContext context, IServiceCollection services) + { + services.Configure(options => + { + OptionsHelper.ConfigureOptions(options, context.Configuration); + }); + + var config = OptionsHelper.ConfigureOptions(context.Configuration); + + services.Configure(options => + { + options.ClientId = config.ClientId; + options.ClientSecret = config.ClientSecret; + }); + + return config; + } + + public static void ConfigureDefault(HostBuilderContext context, IServiceCollection services) + { + Console.WriteLine("~~~ Selector CLI ~~~"); + Console.WriteLine(); + + Console.WriteLine("> Configuring..."); + // CONFIG + var config = ConfigureOptions(context, services); + services.AddHttpClient(); + + Console.WriteLine("> Adding Services..."); + // SERVICES + services.AddConsumerFactories(); + if (config.RedisOptions.Enabled) + { + Console.WriteLine("> Adding caching consumers..."); + services.AddCachingConsumerFactories(); + } + + services.AddWatcher() + .AddEvents() + .AddSpotify(); + + services.ConfigureLastFm(config) + .ConfigureDb(config) + .ConfigureEqual(config); + + if (config.RedisOptions.Enabled) + { + Console.WriteLine("> Adding Redis..."); + services.AddRedisServices(config.RedisOptions.ConnectionString); + + Console.WriteLine("> Adding cache event maps..."); + services.AddTransient() + .AddTransient(); + + Console.WriteLine("> Adding caching Spotify consumers..."); + services.AddCachingSpotify(); + } + + // HOSTED SERVICES + if (config.WatcherOptions.Enabled) + { + if (config.WatcherOptions.LocalEnabled) + { + Console.WriteLine("> Adding Local Watcher Service"); + services.AddHostedService(); + } + + if (config.DatabaseOptions.Enabled) + { + Console.WriteLine("> Adding Db Watcher Service"); + services.AddHostedService(); + } + } + + if (config.ScrobbleOptions.Enabled) + { + Console.WriteLine("> Adding Scrobble Monitor Service"); + + services.AddHostedService(); + } + } + + public static void ConfigureDefaultNlog(HostBuilderContext context, ILoggingBuilder builder) + { + builder.ClearProviders() + .SetMinimumLevel(LogLevel.Trace) + .AddNLog(context.Configuration); + } + + static IHostBuilder CreateHostBuilder(string[] args, Action BuildServices, Action BuildLogs) + => Host.CreateDefaultBuilder(args) + .UseWindowsService() + .UseSystemd() + .ConfigureServices((context, services) => BuildServices(context, services)) + .ConfigureLogging((context, builder) => BuildLogs(context, builder)); + } +} diff --git a/Selector.CLI/Command/Scrobble.cs b/Selector.CLI/Command/Scrobble.cs new file mode 100644 index 0000000..ff1d2ad --- /dev/null +++ b/Selector.CLI/Command/Scrobble.cs @@ -0,0 +1,114 @@ +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 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) + { + var fromOption = new Option("--from", getDefaultValue: () => DateTime.UtcNow.AddDays(-7), "Date from which to pull scrobbles"); + AddOption(fromOption); + + var toOption = new Option( "--to", getDefaultValue: () => DateTime.UtcNow.AddHours(1), "Last date for which to pull scrobbles"); + AddOption(toOption); + + var pageOption = new Option("--page", getDefaultValue: () => 100, "number of scrobbles per page"); + pageOption.AddAlias("-p"); + AddOption(pageOption); + + var delayOption = new Option("--delay", getDefaultValue: () => 200, "milliseconds to delay"); + delayOption.AddAlias("-d"); + AddOption(delayOption); + + var username = new Option("--username", "user to pulls scrobbles for"); + username.AddAlias("-u"); + AddOption(username); + + var dontAdd = new Option("--no-add", "don't add any scrobbles to the database"); + dontAdd.AddAlias("-na"); + AddOption(dontAdd); + + var dontRemove = new Option("--no-remove", "don't remove any scrobbles from the database"); + dontRemove.AddAlias("-nr"); + AddOption(dontRemove); + + Handler = CommandHandler.Create(async (DateTime from, DateTime to, int page, int delay, string username, bool noAdd, bool noRemove, CancellationToken token) => await Execute(from, to, page, delay, username, noAdd, noRemove, token)); + } + + public static async Task Execute(DateTime from, DateTime to, int page, int delay, string username, bool noAdd, bool noRemove, CancellationToken token) + { + try + { + var context = new CommandContext().WithLogger().WithDb().WithLastfmApi(); + var logger = context.Logger.CreateLogger("Scrobble"); + + var db = new ApplicationDbContext(context.DatabaseConfig.Options, context.Logger.CreateLogger()); + + logger.LogInformation("Running from {0} to {1}", from, to); + + logger.LogInformation("Searching for {0}", username); + var user = db.Users.AsNoTracking().FirstOrDefault(u => u.UserName == username); + + if (user is not null) + { + if (user.LastFmConnected()) + { + logger.LogInformation("Last.fm username found ({0}), starting...", user.LastFmUsername); + + await new ScrobbleSaver( + context.LastFmClient.User, + new ScrobbleSaverConfig() + { + User = user, + InterRequestDelay = new TimeSpan(0, 0, 0, 0, delay), + From = from, + To = to, + PageSize = page, + DontAdd = noAdd, + DontRemove = noRemove + }, + db, + context.Logger.CreateLogger()) + .Execute(token); + } + else + { + logger.LogError("{0} doesn't have a Last.fm username", username); + } + } + else + { + logger.LogError("{0} not found", username); + } + + } + catch (Exception ex) + { + Console.WriteLine(ex); + return 1; + } + + return 0; + } + } +} diff --git a/Selector.CLI/Extensions/CommandContextExtensions.cs b/Selector.CLI/Extensions/CommandContextExtensions.cs new file mode 100644 index 0000000..a656c03 --- /dev/null +++ b/Selector.CLI/Extensions/CommandContextExtensions.cs @@ -0,0 +1,62 @@ +using IF.Lastfm.Core.Api; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Selector.Model; + +namespace Selector.CLI.Extensions +{ + public static class CommandContextExtensions + { + public static CommandContext WithConfig(this CommandContext context) + { + var configBuild = new ConfigurationBuilder(); + configBuild.AddJsonFile("appsettings.json", optional: true) + .AddJsonFile("appsettings.Development.json", optional: true) + .AddJsonFile("appsettings.Production.json", optional: true); + context.Config = configBuild.Build().ConfigureOptions(); + + return context; + } + + public static CommandContext WithLogger(this CommandContext context) + { + context.Logger = LoggerFactory.Create(builder => + { + //builder.AddConsole(a => a.); + builder.AddSimpleConsole(options => + { + options.SingleLine = true; + }); + builder.SetMinimumLevel(LogLevel.Trace); + }); + + return context; + } + + public static CommandContext WithDb(this CommandContext context) + { + if (context.Config is null) + { + context.WithConfig(); + } + + context.DatabaseConfig = new DbContextOptionsBuilder(); + context.DatabaseConfig.UseNpgsql(context.Config.DatabaseOptions.ConnectionString); + + return context; + } + + public static CommandContext WithLastfmApi(this CommandContext context) + { + if (context.Config is null) + { + context.WithConfig(); + } + + context.LastFmClient = new LastfmClient(new LastAuth(context.Config.LastfmClient, context.Config.LastfmSecret)); + + return context; + } + } +} diff --git a/Selector.CLI/Extensions/ServiceExtensions.cs b/Selector.CLI/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..63e0d2d --- /dev/null +++ b/Selector.CLI/Extensions/ServiceExtensions.cs @@ -0,0 +1,71 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Selector.Cache.Extensions; +using Selector.Extensions; +using Selector.Model; +using Selector.Model.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Selector.CLI.Extensions +{ + public static class ServiceExtensions + { + public static IServiceCollection ConfigureLastFm(this IServiceCollection services, RootOptions config) + { + if (config.LastfmClient is not null) + { + Console.WriteLine("> Adding Last.fm credentials..."); + + services.AddLastFm(config.LastfmClient, config.LastfmSecret); + + if (config.RedisOptions.Enabled) + { + Console.WriteLine("> Adding caching Last.fm consumers..."); + services.AddCachingLastFm(); + } + } + else + { + Console.WriteLine("> No Last.fm credentials, skipping init..."); + } + + return services; + } + + public static IServiceCollection ConfigureDb(this IServiceCollection services, RootOptions config) + { + if (config.DatabaseOptions.Enabled) + { + Console.WriteLine("> Adding Databse Context..."); + services.AddDbContext(options => + options.UseNpgsql(config.DatabaseOptions.ConnectionString) + ); + + services.AddHostedService(); + } + + return services; + } + + public static IServiceCollection ConfigureEqual(this IServiceCollection services, RootOptions config) + { + switch (config.Equality) + { + case EqualityChecker.Uri: + Console.WriteLine("> Using Uri Equality"); + services.AddSingleton(); + break; + case EqualityChecker.String: + Console.WriteLine("> Using String Equality"); + services.AddSingleton(); + break; + } + + return services; + } + } +} diff --git a/Selector.CLI/Options.cs b/Selector.CLI/Options.cs index 862b8de..4cb4507 100644 --- a/Selector.CLI/Options.cs +++ b/Selector.CLI/Options.cs @@ -14,7 +14,7 @@ namespace Selector.CLI config.GetSection(FormatKeys( new[] { RootOptions.Key, ScrobbleMonitorOptions.Key})).Bind(options.ScrobbleOptions); } - public static RootOptions ConfigureOptions(IConfiguration config) + public static RootOptions ConfigureOptions(this IConfiguration config) { var options = config.GetSection(RootOptions.Key).Get(); ConfigureOptions(options, config); diff --git a/Selector.CLI/Program.cs b/Selector.CLI/Program.cs index 1e50147..f2c1735 100644 --- a/Selector.CLI/Program.cs +++ b/Selector.CLI/Program.cs @@ -1,177 +1,15 @@ -using System; -using System.Threading.Tasks; -using System.CommandLine; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.EntityFrameworkCore; -using NLog.Extensions.Logging; - -using Selector.Extensions; -using Selector.Model; -using Selector.Cache; -using Selector.Cache.Extensions; -using Selector.Events; -using Selector.Model.Services; -using Selector.CLI.Services; +using System.CommandLine; namespace Selector.CLI { public static class Program { - public static async Task Main(string[] args) + public static void Main(string[] args) { - var cmd = new RootCommand { - new Command("start") { + var cmd = new HostRootCommand(); + cmd.AddCommand(new ScrobbleCommand("scrobble", "Manipulate scrobbles")); - } - }; - - CreateHostBuilder(args, ConfigureDefault, ConfigureDefaultNlog).Build().Run(); + cmd.Invoke(args); } - - public static RootOptions ConfigureOptions(HostBuilderContext context, IServiceCollection services) - { - services.Configure(options => - { - OptionsHelper.ConfigureOptions(options, context.Configuration); - }); - - var config = OptionsHelper.ConfigureOptions(context.Configuration); - - services.Configure(options => - { - options.ClientId = config.ClientId; - options.ClientSecret = config.ClientSecret; - }); - - return config; - } - - public static void ConfigureLastFm(RootOptions config, IServiceCollection services) - { - if(config.LastfmClient is not null) - { - Console.WriteLine("> Adding Last.fm credentials..."); - - services.AddLastFm(config.LastfmClient, config.LastfmSecret); - - if(config.RedisOptions.Enabled) - { - Console.WriteLine("> Adding caching Last.fm consumers..."); - services.AddCachingLastFm(); - } - } - else - { - Console.WriteLine("> No Last.fm credentials, skipping init..."); - } - } - - public static void ConfigureDb(RootOptions config, IServiceCollection services) - { - if (config.DatabaseOptions.Enabled) - { - Console.WriteLine("> Adding Databse Context..."); - services.AddDbContext(options => - options.UseNpgsql(config.DatabaseOptions.ConnectionString) - ); - - services.AddHostedService(); - } - } - - public static void ConfigureEqual(RootOptions config, IServiceCollection services) - { - switch (config.Equality) - { - case EqualityChecker.Uri: - Console.WriteLine("> Using Uri Equality"); - services.AddSingleton(); - break; - case EqualityChecker.String: - Console.WriteLine("> Using String Equality"); - services.AddSingleton(); - break; - } - } - - public static void ConfigureDefault(HostBuilderContext context, IServiceCollection services) - { - Console.WriteLine("~~~ Selector CLI ~~~"); - Console.WriteLine(); - - Console.WriteLine("> Configuring..."); - // CONFIG - var config = ConfigureOptions(context, services); - services.AddHttpClient(); - - Console.WriteLine("> Adding Services..."); - // SERVICES - services.AddConsumerFactories(); - if (config.RedisOptions.Enabled) - { - Console.WriteLine("> Adding caching consumers..."); - services.AddCachingConsumerFactories(); - } - services.AddWatcher(); - - services.AddEvents(); - - services.AddSpotify(); - ConfigureLastFm(config, services); - ConfigureDb(config, services); - ConfigureEqual(config, services); - - if (config.RedisOptions.Enabled) - { - Console.WriteLine("> Adding Redis..."); - services.AddRedisServices(config.RedisOptions.ConnectionString); - - Console.WriteLine("> Adding cache event maps..."); - services.AddTransient(); - services.AddTransient(); - - Console.WriteLine("> Adding caching Spotify consumers..."); - services.AddCachingSpotify(); - } - - // HOSTED SERVICES - if (config.WatcherOptions.Enabled) - { - if(config.WatcherOptions.LocalEnabled) - { - Console.WriteLine("> Adding Local Watcher Service"); - services.AddHostedService(); - } - - if(config.DatabaseOptions.Enabled) - { - Console.WriteLine("> Adding Db Watcher Service"); - services.AddHostedService(); - } - } - - if (config.ScrobbleOptions.Enabled) - { - Console.WriteLine("> Adding Scrobble Monitor Service"); - - services.AddHostedService(); - } - } - - public static void ConfigureDefaultNlog(HostBuilderContext context, ILoggingBuilder builder) - { - builder.ClearProviders(); - builder.SetMinimumLevel(LogLevel.Trace); - builder.AddNLog(context.Configuration); - } - - static IHostBuilder CreateHostBuilder(string[] args, Action BuildServices, Action BuildLogs) - => Host.CreateDefaultBuilder(args) - .UseWindowsService() - .UseSystemd() - .ConfigureServices((context, services) => BuildServices(context, services)) - .ConfigureLogging((context, builder) => BuildLogs(context, builder)); } } diff --git a/Selector.CLI/Properties/launchSettings.json b/Selector.CLI/Properties/launchSettings.json index 171b13a..cc8dda5 100644 --- a/Selector.CLI/Properties/launchSettings.json +++ b/Selector.CLI/Properties/launchSettings.json @@ -6,6 +6,22 @@ "DOTNET_ENVIRONMENT": "Development" }, "nativeDebugging": true + }, + "Selector.CLI.Scrobble": { + "commandName": "Project", + "commandLineArgs": "scrobble save -u sarsoo --from \"2022/01/01\"", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "nativeDebugging": true + }, + "Selector.CLI.Scrobble.All": { + "commandName": "Project", + "commandLineArgs": "scrobble save -u sarsoo --from \"2017/01/01\" -p 200 -d 75 --no-remove", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "nativeDebugging": true } } } \ No newline at end of file diff --git a/Selector.CLI/ScrobbleSaver.cs b/Selector.CLI/ScrobbleSaver.cs index 00abca8..05da416 100644 --- a/Selector.CLI/ScrobbleSaver.cs +++ b/Selector.CLI/ScrobbleSaver.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Selector.Model; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,6 +20,9 @@ namespace Selector public DateTime? From { get; set; } public DateTime? To { get; set; } public int PageSize { get; set; } = 100; + public int Retries { get; set; } = 5; + public bool DontAdd { get; set; } = false; + public bool DontRemove { get; set; } = false; } public class ScrobbleSaver @@ -27,22 +31,19 @@ namespace Selector private readonly IUserApi userClient; private readonly ScrobbleSaverConfig config; - private readonly IServiceScopeFactory serviceScopeFactory; + private readonly ApplicationDbContext db; - public ScrobbleSaver(IUserApi _userClient, ScrobbleSaverConfig _config, IServiceScopeFactory _serviceScopeFactory, ILogger _logger) + public ScrobbleSaver(IUserApi _userClient, ScrobbleSaverConfig _config, ApplicationDbContext _db, ILogger _logger) { userClient = _userClient; config = _config; - serviceScopeFactory = _serviceScopeFactory; + db = _db; logger = _logger; } public async Task Execute(CancellationToken token) { - logger.LogInformation("Saving all scrobbles for {0}/{1}", config.User.UserName, config.User.LastFmUsername); - - using var scope = serviceScopeFactory.CreateScope(); - using var db = scope.ServiceProvider.GetRequiredService(); + logger.LogInformation("Saving scrobbles for {0}/{1}", config.User.UserName, config.User.LastFmUsername); var page1 = await userClient.GetRecentScrobbles(config.User.LastFmUsername, count: config.PageSize, from: config.From, to: config.To); @@ -50,9 +51,9 @@ namespace Selector { var scrobbles = page1.Content.ToList(); - if(page1.TotalPages > 1) + if (page1.TotalPages > 1) { - var tasks = await GetScrobblesFromPageNumbers(Enumerable.Range(2, page1.TotalPages - 1), token); + var tasks = await GetScrobblesFromPageNumbers(2, page1.TotalPages, token); var taskResults = await Task.WhenAll(tasks); foreach (var result in taskResults) @@ -63,24 +64,26 @@ namespace Selector } else { - logger.LogInformation("Failed to get a subset of scrobbles for {0}/{1}", config.User.UserName, config.User.LastFmUsername); + logger.LogWarning("Failed to get a subset of scrobbles for {0}/{1}", config.User.UserName, config.User.LastFmUsername); } - } + } } + logger.LogDebug("Ordering and filtering pulled scrobbles"); + var nativeScrobbles = scrobbles - .DistinctBy(s => s.TimePlayed) - .OrderBy(s => s.TimePlayed) + .DistinctBy(s => s.TimePlayed?.UtcDateTime) .Select(s => { var nativeScrobble = (UserScrobble) s; nativeScrobble.UserId = config.User.Id; return nativeScrobble; }); - + + logger.LogDebug("Pulling currently stored scrobbles"); + var currentScrobbles = db.Scrobble .AsEnumerable() - .OrderBy(s => s.Timestamp) .Where(s => s.UserId == config.User.Id); if (config.From is not null) @@ -93,11 +96,43 @@ namespace Selector currentScrobbles = currentScrobbles.Where(s => s.Timestamp < config.To); } - (var toAdd, var toRemove) = ScrobbleMatcher.IdentifyDiffsContains(currentScrobbles, nativeScrobbles); + logger.LogInformation("Completed scrobble pulling for {0}, pulled {1:n0}", config.User.UserName, nativeScrobbles.Count()); - await db.Scrobble.AddRangeAsync(toAdd.Cast()); - db.Scrobble.RemoveRange(toRemove.Cast()); + logger.LogDebug("Identifying difference sets"); + var time = Stopwatch.StartNew(); + + (var toAdd, var toRemove) = ScrobbleMatcher.IdentifyDiffs(currentScrobbles, nativeScrobbles); + + var toAddUser = toAdd.Cast().ToList(); + var toRemoveUser = toRemove.Cast().ToList(); + + time.Stop(); + logger.LogTrace("Finished diffing: {0:n}ms", time.ElapsedMilliseconds); + + var timeDbOps = Stopwatch.StartNew(); + + if(!config.DontAdd) + { + await db.Scrobble.AddRangeAsync(toAddUser); + } + else + { + logger.LogInformation("Skipping adding of {0} scrobbles", toAddUser.Count); + } + if (!config.DontRemove) + { + db.Scrobble.RemoveRange(toRemoveUser); + } + else + { + logger.LogInformation("Skipping removal of {0} scrobbles", toRemoveUser.Count); + } await db.SaveChangesAsync(); + + timeDbOps.Stop(); + logger.LogTrace("DB ops: {0:n}ms", timeDbOps.ElapsedMilliseconds); + + logger.LogInformation("Completed scrobble pulling for {0}, +{1:n0}, -{2:n0}", config.User.UserName, toAddUser.Count(), toRemoveUser.Count()); } else { @@ -105,13 +140,13 @@ namespace Selector } } - private async Task>>> GetScrobblesFromPageNumbers(IEnumerable pageNumbers, CancellationToken token) + private async Task>>> GetScrobblesFromPageNumbers(int start, int totalPages, CancellationToken token) { var tasks = new List>>(); - foreach (var pageNumber in pageNumbers) + foreach (var pageNumber in Enumerable.Range(start, totalPages - 1)) { - logger.LogInformation("Pulling page {2} for {0}/{1}", config.User.UserName, config.User.LastFmUsername, pageNumber); + logger.LogInformation("Pulling page {2:n0}/{3:n0} for {0}/{1}", config.User.UserName, config.User.LastFmUsername, pageNumber, totalPages); tasks.Add(userClient.GetRecentScrobbles(config.User.LastFmUsername, pagenumber: pageNumber, count: config.PageSize, from: config.From, to: config.To)); await Task.Delay(config.InterRequestDelay, token); diff --git a/Selector.CLI/Services/ScrobbleMonitor.cs b/Selector.CLI/Services/ScrobbleMonitor.cs index 7557e9a..d4adebf 100644 --- a/Selector.CLI/Services/ScrobbleMonitor.cs +++ b/Selector.CLI/Services/ScrobbleMonitor.cs @@ -20,7 +20,6 @@ namespace Selector.CLI.Services private readonly ScrobbleMonitorOptions config; private readonly IUserApi userApi; private readonly IServiceScopeFactory serviceScopeFactory; - private Task task; public ScrobbleMonitor(ILogger _logger, IOptions _options, IUserApi _userApi, IServiceScopeFactory _serviceScopeFactory, ILoggerFactory _loggerFactory) { @@ -43,19 +42,14 @@ namespace Selector.CLI.Services public async Task RunScrobbleSavers(ApplicationDbContext db, CancellationToken token) { + using var scope = serviceScopeFactory.CreateScope(); + foreach (var user in db.Users .AsNoTracking() .AsEnumerable() .Where(u => u.ScrobbleSavingEnabled())) { - logger.LogInformation("Starting scrobble saver for {0}/{1}", user.UserName, user.LastFmUsername); - - await new ScrobbleSaver(userApi, new ScrobbleSaverConfig() - { - User = user, - InterRequestDelay = config.InterRequestDelay, - From = DateTime.UtcNow.AddDays(-3) - }, serviceScopeFactory, loggerFactory.CreateLogger()).Execute(token); + //TODO } } diff --git a/Selector.CLI/appsettings.json b/Selector.CLI/appsettings.json index 9988ade..a72213f 100644 --- a/Selector.CLI/appsettings.json +++ b/Selector.CLI/appsettings.json @@ -4,7 +4,6 @@ "ClientSecret": "", "Equality": "uri", "Watcher": { - "enabled": false, "localenabled": false, "Instances": [ { @@ -19,7 +18,7 @@ "enabled": true }, "Redis": { - "enabled": false + "enabled": true } }, "Logging": { diff --git a/Selector.Cache/Extensions/ServiceExtensions.cs b/Selector.Cache/Extensions/ServiceExtensions.cs index 52f17f1..8005d61 100644 --- a/Selector.Cache/Extensions/ServiceExtensions.cs +++ b/Selector.Cache/Extensions/ServiceExtensions.cs @@ -9,7 +9,7 @@ namespace Selector.Cache.Extensions { public static class ServiceExtensions { - public static void AddRedisServices(this IServiceCollection services, string connectionStr) + public static IServiceCollection AddRedisServices(this IServiceCollection services, string connectionStr) { Console.WriteLine("> Configuring Redis..."); @@ -23,9 +23,11 @@ namespace Selector.Cache.Extensions services.AddSingleton(connMulti); services.AddTransient(services => services.GetService().GetDatabase()); services.AddTransient(services => services.GetService().GetSubscriber()); + + return services; } - public static void AddCachingConsumerFactories(this IServiceCollection services) + public static IServiceCollection AddCachingConsumerFactories(this IServiceCollection services) { services.AddTransient(); services.AddTransient(); @@ -36,16 +38,22 @@ namespace Selector.Cache.Extensions services.AddTransient(); services.AddTransient(); services.AddTransient(); + + return services; } - public static void AddCachingSpotify(this IServiceCollection services) + public static IServiceCollection AddCachingSpotify(this IServiceCollection services) { services.AddSingleton(); + + return services; } - public static void AddCachingLastFm(this IServiceCollection services) + public static IServiceCollection AddCachingLastFm(this IServiceCollection services) { services.AddSingleton(); + + return services; } } } diff --git a/Selector.Event/Extensions/ServiceExtensions.cs b/Selector.Event/Extensions/ServiceExtensions.cs index d23b2f2..f8b11ba 100644 --- a/Selector.Event/Extensions/ServiceExtensions.cs +++ b/Selector.Event/Extensions/ServiceExtensions.cs @@ -4,24 +4,30 @@ namespace Selector.Events { public static class ServiceExtensions { - public static void AddEvents(this IServiceCollection services) + public static IServiceCollection AddEvents(this IServiceCollection services) { services.AddEventBus(); services.AddEventMappingAgent(); services.AddTransient(); services.AddTransient(); + + return services; } - public static void AddEventBus(this IServiceCollection services) + public static IServiceCollection AddEventBus(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); + + return services; } - public static void AddEventMappingAgent(this IServiceCollection services) + public static IServiceCollection AddEventMappingAgent(this IServiceCollection services) { services.AddHostedService(); + + return services; } } } diff --git a/Selector/Extensions/ServiceExtensions.cs b/Selector/Extensions/ServiceExtensions.cs index 4edc3d3..f530398 100644 --- a/Selector/Extensions/ServiceExtensions.cs +++ b/Selector/Extensions/ServiceExtensions.cs @@ -9,7 +9,7 @@ namespace Selector.Extensions { public static class ServiceExtensions { - public static void AddConsumerFactories(this IServiceCollection services) + public static IServiceCollection AddConsumerFactories(this IServiceCollection services) { services.AddTransient(); services.AddTransient(); @@ -19,15 +19,19 @@ namespace Selector.Extensions services.AddTransient(); services.AddTransient(); + + return services; } - public static void AddSpotify(this IServiceCollection services) + public static IServiceCollection AddSpotify(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); + + return services; } - public static void AddLastFm(this IServiceCollection services, string client, string secret) + public static IServiceCollection AddLastFm(this IServiceCollection services, string client, string secret) { var lastAuth = new LastAuth(client, secret); services.AddSingleton(lastAuth); @@ -42,12 +46,16 @@ namespace Selector.Extensions services.AddTransient(sp => sp.GetService().Chart); services.AddTransient(sp => sp.GetService().Library); services.AddTransient(sp => sp.GetService().Tag); + + return services; } - public static void AddWatcher(this IServiceCollection services) + public static IServiceCollection AddWatcher(this IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); + + return services; } } } diff --git a/Selector/Scrobble/Scrobble.cs b/Selector/Scrobble/Scrobble.cs index 47cd81b..918cf05 100644 --- a/Selector/Scrobble/Scrobble.cs +++ b/Selector/Scrobble/Scrobble.cs @@ -21,5 +21,7 @@ namespace Selector AlbumName = track.AlbumName, ArtistName = track.ArtistName, }; + + public override string ToString() => $"({Timestamp}) {TrackName}, {AlbumName}, {ArtistName}"; } } diff --git a/Selector/Scrobble/ScrobbleMatcher.cs b/Selector/Scrobble/ScrobbleMatcher.cs index dbd3fee..1f389df 100644 --- a/Selector/Scrobble/ScrobbleMatcher.cs +++ b/Selector/Scrobble/ScrobbleMatcher.cs @@ -11,7 +11,7 @@ namespace Selector => serviceScrobble.TimePlayed.Equals(nativeScrobble); public static bool MatchTime(Scrobble nativeScrobble, Scrobble serviceScrobble) - => serviceScrobble.Timestamp.Equals(nativeScrobble); + => serviceScrobble.Timestamp.Equals(nativeScrobble.Timestamp); public static (IEnumerable, IEnumerable) IdentifyDiffs(IEnumerable existing, IEnumerable toApply) { @@ -22,26 +22,33 @@ namespace Selector var toAdd = new List(); var toRemove = new List(); - if(existing.Any()) + if (toApplyIter.MoveNext()) { - foreach (var currentExisting in existing) + if (existing.Any()) { - while (toApplyIter.Current.Timestamp < currentExisting.Timestamp) + foreach (var currentExisting in existing) { - toAdd.Add(toApplyIter.Current); + while (toApplyIter.Current.Timestamp < currentExisting.Timestamp) + { + toAdd.Add(toApplyIter.Current); - if (!toApplyIter.MoveNext()) break; - } + toApplyIter.MoveNext(); + } - if (!MatchTime(currentExisting, toApplyIter.Current)) - { - toRemove.Add(currentExisting); + if (MatchTime(currentExisting, toApplyIter.Current)) + { + toApplyIter.MoveNext(); + } + else + { + toRemove.Add(currentExisting); + } } } - } - else - { - toAdd.AddRange(toApply); + else + { + toAdd.AddRange(toApply); + } } return (toAdd, toRemove);