adding scrobble saving to ui, adding full scrobble save

This commit is contained in:
andy 2022-02-26 23:42:29 +00:00
parent 64b7105514
commit a2f874ec27
6 changed files with 181 additions and 103 deletions

View File

@ -55,23 +55,48 @@ namespace Selector.CLI.Extensions
tp.MaxConcurrency = 5; tp.MaxConcurrency = 5;
}); });
var scrobbleKey = new JobKey("scrobble-watcher", "scrobble"); if (config.JobOptions.Scrobble.Enabled)
{
Console.WriteLine("> Adding Scrobble Jobs...");
var scrobbleKey = new JobKey("scrobble-watcher-agile", "scrobble");
options.AddJob<ScrobbleWatcherJob>(j => j options.AddJob<ScrobbleWatcherJob>(j => j
.WithDescription("Watch recent scrobbles and mirror to database") .WithDescription("Watch recent scrobbles and mirror to database")
.WithIdentity(scrobbleKey) .WithIdentity(scrobbleKey)
.UsingJobData("IsFull", false)
); );
options.AddTrigger(t => t options.AddTrigger(t => t
.WithIdentity("scrobble-watcher-trigger") .WithIdentity("scrobble-watcher-agile-trigger")
.ForJob(scrobbleKey) .ForJob(scrobbleKey)
.StartNow() .StartNow()
.WithSimpleSchedule(x => x.WithInterval(config.JobOptions.Scrobble.InterJobDelay).RepeatForever()) .WithSimpleSchedule(x => x.WithInterval(config.JobOptions.Scrobble.InterJobDelay).RepeatForever())
.WithDescription("Periodic trigger for scrobble watcher") .WithDescription("Periodic trigger for scrobble watcher")
); );
var fullScrobbleKey = new JobKey("scrobble-watcher-full", "scrobble");
options.AddJob<ScrobbleWatcherJob>(j => j
.WithDescription("Check all scrobbles and mirror to database")
.WithIdentity(fullScrobbleKey)
.UsingJobData("IsFull", true)
);
options.AddTrigger(t => t
.WithIdentity("scrobble-watcher-full-trigger")
.ForJob(fullScrobbleKey)
.WithCronSchedule(config.JobOptions.Scrobble.FullScrobbleCron)
.WithDescription("Periodic trigger for scrobble watcher")
);
}
else
{
Console.WriteLine("> Skipping Scrobble Jobs...");
}
}); });
services.AddQuartzHostedService(options =>{ services.AddQuartzHostedService(options => {
options.WaitForJobsToComplete = true; options.WaitForJobsToComplete = true;
}); });

View File

@ -23,6 +23,10 @@ namespace Selector.CLI.Jobs
private readonly ApplicationDbContext db; private readonly ApplicationDbContext db;
private readonly ScrobbleWatcherJobOptions options; private readonly ScrobbleWatcherJobOptions options;
private static object databaseLock = new();
public bool IsFull { get; set; }
public ScrobbleWatcherJob( public ScrobbleWatcherJob(
IUserApi _userApi, IUserApi _userApi,
IScrobbleRepository _scrobbleRepo, IScrobbleRepository _scrobbleRepo,
@ -41,6 +45,8 @@ namespace Selector.CLI.Jobs
} }
public async Task Execute(IJobExecutionContext context) public async Task Execute(IJobExecutionContext context)
{
try
{ {
logger.LogInformation("Starting scrobble watching job"); logger.LogInformation("Starting scrobble watching job");
@ -53,14 +59,10 @@ namespace Selector.CLI.Jobs
{ {
logger.LogInformation("Saving scrobbles for {}/{}", user.UserName, user.LastFmUsername); logger.LogInformation("Saving scrobbles for {}/{}", user.UserName, user.LastFmUsername);
if (options.From is not null && options.From.Value.Kind != DateTimeKind.Utc) DateTime? from = null;
if (options.From is not null && !IsFull)
{ {
options.From = options.From.Value.ToUniversalTime(); from = options.From.Value.ToUniversalTime();
}
if (options.To is not null && options.To.Value.Kind != DateTimeKind.Utc)
{
options.To = options.To.Value.ToUniversalTime();
} }
var saver = new ScrobbleSaver( var saver = new ScrobbleSaver(
@ -69,8 +71,7 @@ namespace Selector.CLI.Jobs
{ {
User = user, User = user,
InterRequestDelay = options.InterRequestDelay, InterRequestDelay = options.InterRequestDelay,
From = options.From, From = from,
To = options.To,
PageSize = options.PageSize, PageSize = options.PageSize,
DontAdd = false, DontAdd = false,
DontRemove = false, DontRemove = false,
@ -78,12 +79,18 @@ namespace Selector.CLI.Jobs
}, },
scrobbleRepo, scrobbleRepo,
loggerFactory.CreateLogger<ScrobbleSaver>(), loggerFactory.CreateLogger<ScrobbleSaver>(),
loggerFactory); loggerFactory,
databaseLock);
await saver.Execute(context.CancellationToken); await saver.Execute(context.CancellationToken);
logger.LogInformation("Finished scrobbles for {}/{}", user.UserName, user.LastFmUsername); logger.LogInformation("Finished scrobbles for {}/{}", user.UserName, user.LastFmUsername);
} }
} }
catch (Exception ex)
{
logger.LogError(ex, "Error occured while saving scrobbles");
}
}
} }
} }

View File

@ -124,10 +124,10 @@ namespace Selector.CLI
public const string Key = "Scrobble"; public const string Key = "Scrobble";
public bool Enabled { get; set; } = true; public bool Enabled { get; set; } = true;
public string FullScrobbleCron { get; set; } = "0 2 * * * ?";
public TimeSpan InterJobDelay { get; set; } = TimeSpan.FromMinutes(5); public TimeSpan InterJobDelay { get; set; } = TimeSpan.FromMinutes(5);
public TimeSpan InterRequestDelay { get; set; } = TimeSpan.FromMilliseconds(100); public TimeSpan InterRequestDelay { get; set; } = TimeSpan.FromMilliseconds(100);
public DateTime? From { get; set; } = DateTime.UtcNow.AddDays(-14); public DateTime? From { get; set; } = DateTime.UtcNow.AddDays(-14);
public DateTime? To { get; set; }
public int PageSize { get; set; } = 200; public int PageSize { get; set; } = 200;
public int Simultaneous { get; set; } = 3; public int Simultaneous { get; set; } = 3;
} }

View File

@ -36,18 +36,21 @@ namespace Selector
private readonly IUserApi userClient; private readonly IUserApi userClient;
private readonly ScrobbleSaverConfig config; private readonly ScrobbleSaverConfig config;
private Task batchTask;
private BatchingOperation<ScrobbleRequest> batchOperation; private BatchingOperation<ScrobbleRequest> batchOperation;
private readonly IScrobbleRepository scrobbleRepo; private readonly IScrobbleRepository scrobbleRepo;
public ScrobbleSaver(IUserApi _userClient, ScrobbleSaverConfig _config, IScrobbleRepository _scrobbleRepository, ILogger<ScrobbleSaver> _logger, ILoggerFactory _loggerFactory = null) private readonly object dbLock;
public ScrobbleSaver(IUserApi _userClient, ScrobbleSaverConfig _config, IScrobbleRepository _scrobbleRepository, ILogger<ScrobbleSaver> _logger, ILoggerFactory _loggerFactory = null, object _dbLock = null)
{ {
userClient = _userClient; userClient = _userClient;
config = _config; config = _config;
scrobbleRepo = _scrobbleRepository; scrobbleRepo = _scrobbleRepository;
logger = _logger; logger = _logger;
loggerFactory = _loggerFactory; loggerFactory = _loggerFactory;
dbLock = _dbLock ?? new();
} }
public async Task Execute(CancellationToken token) public async Task Execute(CancellationToken token)
@ -74,17 +77,7 @@ namespace Selector
GetRequestsFromPageNumbers(2, page1.TotalPages) GetRequestsFromPageNumbers(2, page1.TotalPages)
); );
batchTask = batchOperation.TriggerRequests(token); await batchOperation.TriggerRequests(token);
}
logger.LogDebug("Pulling currently stored scrobbles");
var currentScrobbles = scrobbleRepo.GetAll(userId: config.User.Id, from: config.From, to: config.To);
if(batchTask is not null)
{
await batchTask;
} }
IEnumerable<LastTrack> scrobbles; IEnumerable<LastTrack> scrobbles;
@ -108,7 +101,13 @@ namespace Selector
.Select(s => (UserScrobble) s) .Select(s => (UserScrobble) s)
.ToArray(); .ToArray();
logger.LogInformation("Completed database scrobble pulling for {}, pulled {:n0}", config.User.UserName, nativeScrobbles.Length); logger.LogInformation("Completed network scrobble pulling for {}, pulled {:n0}", config.User.UserName, nativeScrobbles.Length);
logger.LogDebug("Pulling currently stored scrobbles");
lock (dbLock)
{
var currentScrobbles = scrobbleRepo.GetAll(userId: config.User.Id, from: config.From, to: config.To);
logger.LogDebug("Identifying difference sets"); logger.LogDebug("Identifying difference sets");
var time = Stopwatch.StartNew(); var time = Stopwatch.StartNew();
@ -120,11 +119,11 @@ namespace Selector
var timeDbOps = Stopwatch.StartNew(); var timeDbOps = Stopwatch.StartNew();
if(!config.DontAdd) if (!config.DontAdd)
{ {
foreach(var add in toAdd) foreach (var add in toAdd)
{ {
var scrobble = (UserScrobble) add; var scrobble = (UserScrobble)add;
scrobble.UserId = config.User.Id; scrobble.UserId = config.User.Id;
scrobbleRepo.Add(scrobble); scrobbleRepo.Add(scrobble);
} }
@ -137,7 +136,7 @@ namespace Selector
{ {
foreach (var remove in toRemove) foreach (var remove in toRemove)
{ {
var scrobble = (UserScrobble) remove; var scrobble = (UserScrobble)remove;
scrobble.UserId = config.User.Id; scrobble.UserId = config.User.Id;
scrobbleRepo.Remove(scrobble); scrobbleRepo.Remove(scrobble);
} }
@ -146,13 +145,14 @@ namespace Selector
{ {
logger.LogInformation("Skipping removal of {} scrobbles", toRemove.Count()); logger.LogInformation("Skipping removal of {} scrobbles", toRemove.Count());
} }
await scrobbleRepo.Save(); _ = scrobbleRepo.Save().Result;
timeDbOps.Stop(); timeDbOps.Stop();
logger.LogTrace("DB ops: {:n}ms", timeDbOps.ElapsedMilliseconds); logger.LogTrace("DB ops: {:n}ms", timeDbOps.ElapsedMilliseconds);
logger.LogInformation("Completed scrobble pulling for {}, +{:n0}, -{:n0}", config.User.UserName, toAdd.Count(), toRemove.Count()); logger.LogInformation("Completed scrobble pulling for {}, +{:n0}, -{:n0}", config.User.UserName, toAdd.Count(), toRemove.Count());
} }
}
else else
{ {
logger.LogError("Failed to pull first scrobble page for {}/{}", config.User.UserName, config.User.LastFmUsername); logger.LogError("Failed to pull first scrobble page for {}/{}", config.User.UserName, config.User.LastFmUsername);

View File

@ -16,7 +16,12 @@
<input asp-for="Input.Username" class="form-control" /> <input asp-for="Input.Username" class="form-control" />
<span asp-validation-for="Input.Username" class="text-danger"></span> <span asp-validation-for="Input.Username" class="text-danger"></span>
</div> </div>
<button id="change-username-button" type="submit" asp-page-handler="ChangeUsername" class="btn btn-primary form-element">Change Username</button> <div class="form-group form-element">
<label asp-for="Input.ScrobbleSaving"></label>
<input asp-for="Input.ScrobbleSaving" />
<span asp-validation-for="Input.ScrobbleSaving" class="text-danger"></span>
</div>
<button id="save-button" type="submit" asp-page-handler="Save" class="btn btn-primary form-element">Save</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -13,6 +13,7 @@ using Microsoft.AspNetCore.WebUtilities;
using Selector.Model; using Selector.Model;
using Selector.Events; using Selector.Events;
using Microsoft.Extensions.Logging;
namespace Selector.Web.Areas.Identity.Pages.Account.Manage namespace Selector.Web.Areas.Identity.Pages.Account.Manage
{ {
@ -21,12 +22,17 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly UserEventBus UserEvent; private readonly UserEventBus UserEvent;
private readonly ILogger<LastFmModel> logger;
public LastFmModel( public LastFmModel(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
UserEventBus userEvent) UserEventBus userEvent,
ILogger<LastFmModel> _logger)
{ {
_userManager = userManager; _userManager = userManager;
UserEvent = userEvent; UserEvent = userEvent;
logger = _logger;
} }
[TempData] [TempData]
@ -39,6 +45,9 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
{ {
[Display(Name = "Username")] [Display(Name = "Username")]
public string Username { get; set; } public string Username { get; set; }
[Display(Name = "Scrobble Saving")]
public bool ScrobbleSaving { get; set; }
} }
private Task LoadAsync(ApplicationUser user) private Task LoadAsync(ApplicationUser user)
@ -46,6 +55,7 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
Input = new InputModel Input = new InputModel
{ {
Username = user.LastFmUsername, Username = user.LastFmUsername,
ScrobbleSaving = user.SaveScrobbles
}; };
return Task.CompletedTask; return Task.CompletedTask;
@ -63,7 +73,7 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
return Page(); return Page();
} }
public async Task<IActionResult> OnPostChangeUsernameAsync() public async Task<IActionResult> OnPostSaveAsync()
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
if (user == null) if (user == null)
@ -77,23 +87,54 @@ namespace Selector.Web.Areas.Identity.Pages.Account.Manage
return Page(); return Page();
} }
var changed = false;
if (Input.Username != user.LastFmUsername) if (Input.Username != user.LastFmUsername)
{ {
var oldUsername = user.LastFmUsername; var oldUsername = user.LastFmUsername;
user.LastFmUsername = Input.Username?.Trim(); user.LastFmUsername = Input.Username?.Trim();
await _userManager.UpdateAsync(user); changed = true;
UserEvent.OnLastfmCredChange(this, new LastfmChange { UserEvent.OnLastfmCredChange(this, new LastfmChange {
UserId = user.Id, UserId = user.Id,
PreviousUsername = oldUsername, PreviousUsername = oldUsername,
NewUsername = user.LastFmUsername NewUsername = user.LastFmUsername
}); });
logger.LogInformation("Changing username from {} to {}", oldUsername, user.LastFmUsername);
StatusMessage = "Username changed"; StatusMessage = "Username changed";
return RedirectToPage();
} }
StatusMessage = "Username unchanged"; if (Input.ScrobbleSaving != user.SaveScrobbles)
{
user.SaveScrobbles = Input.ScrobbleSaving;
logger.LogInformation("Changing scrobble saving from {} to {}", !Input.ScrobbleSaving, Input.ScrobbleSaving);
if (changed)
{
StatusMessage += ", scrobble saving updated";
}
else
{
StatusMessage = "Scrobble saving updated";
changed = true;
}
}
if (changed)
{
logger.LogInformation("Saving Last.fm settings for {}", user.LastFmUsername);
await _userManager.UpdateAsync(user);
}
else
{
StatusMessage = "Settings unchanged";
}
return RedirectToPage(); return RedirectToPage();
} }
} }