add option to auto refresh trakt lists (#2169)

This commit is contained in:
Jason Dove
2025-07-19 14:19:07 +00:00
committed by GitHub
parent 1cbd48cea0
commit 70fbd4c746
29 changed files with 35482 additions and 6 deletions
+86
View File
@@ -0,0 +1,86 @@
@page "/media/trakt/lists/{Id:int}"
@using ErsatzTV.Application.MediaCollections
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<ScheduleEditor> Logger
@inject ISnackbar Snackbar
@inject IMediator Mediator
<MudForm @ref="_form" @bind-IsValid="@_success" Style="max-height: 100%">
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-6" OnClick="@HandleSubmitAsync" StartIcon="@Icons.Material.Filled.Save">
Save Trakt List
</MudButton>
</MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">Trakt List</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Id</MudText>
</div>
<MudTextField Value="_model.Slug" Disabled="true" />
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Automatic Refresh</MudText>
</div>
<MudCheckBox @bind-Value="@_model.AutoRefresh" For="@(() => _model.AutoRefresh)" Dense="true">
<MudText Typo="Typo.caption" Style="font-weight: normal">Update list from trakt.tv once each day</MudText>
</MudCheckBox>
</MudStack>
</MudContainer>
</div>
</MudForm>
@code {
private readonly CancellationTokenSource _cts = new();
[Parameter]
public int Id { get; set; }
private readonly TraktListEditViewModel _model = new();
private MudForm _form;
private bool _success;
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
protected override async Task OnParametersSetAsync()
{
Option<TraktListViewModel> maybeTraktList = await Mediator.Send(new GetTraktListById(Id), _cts.Token);
maybeTraktList.Match(
viewModel =>
{
_model.Id = viewModel.Id;
_model.Slug = viewModel.Slug;
_model.AutoRefresh = viewModel.AutoRefresh;
},
() => NavigationManager.NavigateTo("404"));
}
private async Task HandleSubmitAsync()
{
await _form.Validate();
if (_success)
{
var request = new UpdateTraktList(_model.Id, _model.AutoRefresh);
Option<BaseError> result = await Mediator.Send(request, _cts.Token);
foreach (BaseError error in result)
{
Snackbar.Add(error.Value, Severity.Error);
Logger.LogError("Unexpected error saving trakt list: {Error}", error.Value);
}
if (result.IsNone)
{
NavigationManager.NavigateTo("/media/trakt/lists");
}
}
}
}
+8 -2
View File
@@ -32,7 +32,7 @@
<col/>
<col/>
<col/>
<col style="width: 180px;"/>
<col style="width: 240px;"/>
</MudHidden>
</ColGroup>
<HeaderContent>
@@ -47,6 +47,12 @@
<MudTd>@context.MatchCount of @context.ItemCount</MudTd>
<MudTd>
<div style="align-items: center; display: flex;">
<MudTooltip Text="Edit Trakt List Properties">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Disabled="@Locker.IsTraktLocked()"
Href="@($"media/trakt/lists/{context.Id}")">
</MudIconButton>
</MudTooltip>
<MudTooltip Text="Search Trakt List">
<MudIconButton Icon="@Icons.Material.Filled.Search"
Disabled="@Locker.IsTraktLocked()"
@@ -160,7 +166,7 @@
DialogResult result = await dialog.Result;
if (result is { Canceled: false, Data: string url })
{
await WorkerChannel.WriteAsync(new AddTraktList(url), _cts.Token);
await WorkerChannel.WriteAsync(Application.MediaCollections.AddTraktList.FromUrl(url), _cts.Token);
}
else
{
+27
View File
@@ -125,6 +125,7 @@ public class SchedulerService : BackgroundService
await ScanJellyfinMediaSources(cancellationToken);
await ScanEmbyMediaSources(cancellationToken);
#endif
await RefreshTraktLists(cancellationToken);
await MatchTraktLists(cancellationToken);
await ReleaseMemory(cancellationToken);
@@ -320,12 +321,38 @@ public class SchedulerService : BackgroundService
}
}
private async Task RefreshTraktLists(CancellationToken cancellationToken)
{
using IServiceScope scope = _serviceScopeFactory.CreateScope();
TvContext dbContext = scope.ServiceProvider.GetRequiredService<TvContext>();
DateTime target = DateTime.UtcNow.AddDays(-1);
List<TraktList> traktLists = await dbContext.TraktLists
.Filter(tl => tl.AutoRefresh && (tl.LastUpdate == null || tl.LastUpdate <= target))
.ToListAsync(cancellationToken);
if (traktLists.Count != 0 && _entityLocker.LockTrakt())
{
TraktList last = traktLists.Last();
foreach (TraktList list in traktLists)
{
await _workerChannel.WriteAsync(
AddTraktList.Existing(list.User, list.List, list == last),
cancellationToken);
}
}
}
private async Task MatchTraktLists(CancellationToken cancellationToken)
{
using IServiceScope scope = _serviceScopeFactory.CreateScope();
TvContext dbContext = scope.ServiceProvider.GetRequiredService<TvContext>();
DateTime target = DateTime.UtcNow.AddHours(-1);
List<TraktList> traktLists = await dbContext.TraktLists
.Filter(tl => tl.LastMatch == null || tl.LastMatch <= target)
.ToListAsync(cancellationToken);
if (traktLists.Count != 0 && _entityLocker.LockTrakt())
@@ -0,0 +1,8 @@
namespace ErsatzTV.ViewModels;
public class TraktListEditViewModel
{
public int Id { get; set; }
public string Slug { get; set; }
public bool AutoRefresh { get; set; }
}