feat: Plex Watchlist Error Reporting

This commit is contained in:
tidusjar 2022-08-22 21:35:48 +01:00
parent 6cb0bf58c6
commit b6d0b8ebfc
7 changed files with 139 additions and 5 deletions

View file

@ -1,9 +1,12 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Ombi.Api.Plex.Models
{
public class PlexWatchlistContainer
{
public PlexWatchlist MediaContainer { get; set; }
[JsonIgnore]
public bool AuthError { get; set; }
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@ -295,9 +296,18 @@ namespace Ombi.Api.Plex
var request = new Request("library/sections/watchlist/all", WatchlistUri, HttpMethod.Get);
await AddHeaders(request, plexToken);
var result = await Api.Request<PlexWatchlistContainer>(request, cancellationToken);
var result = await Api.Request(request, cancellationToken);
return result;
if (result.StatusCode.Equals(HttpStatusCode.Unauthorized))
{
return new PlexWatchlistContainer
{
AuthError = true
};
}
var receivedString = await result.Content.ReadAsStringAsync(cancellationToken);
return JsonConvert.DeserializeObject<PlexWatchlistContainer>(receivedString);
}
public async Task<PlexWatchlistMetadataContainer> GetWatchlistMetadata(string ratingKey, string plexToken, CancellationToken cancellationToken)

View file

@ -1,4 +1,5 @@
using Moq;
using MockQueryable.Moq;
using Moq;
using Moq.AutoMock;
using NUnit.Framework;
using Ombi.Api.Plex;
@ -8,6 +9,7 @@ using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.Requests;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Core.Tests;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
@ -32,11 +34,12 @@ namespace Ombi.Schedule.Tests
public void Setup()
{
_mocker = new AutoMocker();
var um = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", UserType = UserType.PlexUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" } });
var um = MockHelper.MockUserManager(new List<OmbiUser> { new OmbiUser { Id = "abc", UserType = UserType.PlexUser, MediaServerToken = "token1", UserName = "abc", NormalizedUserName = "ABC" } });
_mocker.Use(um);
_context = _mocker.GetMock<IJobExecutionContext>();
_context.Setup(x => x.CancellationToken).Returns(CancellationToken.None);
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
_mocker.Setup<IExternalRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>().AsQueryable().BuildMock().Object);
}
[Test]
@ -53,6 +56,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task TerminatesWhenWatchlistIsNotEnabled()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = false });
await _subject.Execute(null);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
@ -75,9 +79,74 @@ namespace Ombi.Schedule.Tests
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
}
[Test]
public async Task AuthenticationError()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer { AuthError = true });
await _subject.Execute(_context.Object);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistUserError>>(x => x.Add(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Once);
}
[Test]
public async Task FailedWatchListUser_NewToken_ShouldBeRemoved()
{
_mocker.Setup<IExternalRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>
{
new PlexWatchlistUserError
{
UserId = "abc",
MediaServerToken = "dead"
}
}.AsQueryable().BuildMock().Object);
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer { AuthError = false });
await _subject.Execute(_context.Object);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistUserError>>(x => x.Add(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistUserError>>(x => x.Delete(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Once);
}
[Test]
public async Task FailedWatchListUser_OldToken_ShouldSkip()
{
_mocker.Setup<IExternalRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>
{
new PlexWatchlistUserError
{
UserId = "abc",
MediaServerToken = "token1"
}
}.AsQueryable().BuildMock().Object);
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer { AuthError = false });
await _subject.Execute(_context.Object);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
_mocker.Verify<IMovieRequestEngine>(x => x.RequestMovie(It.IsAny<MovieRequestViewModel>()), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistUserError>>(x => x.Add(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Never);
_mocker.Verify<IExternalRepository<PlexWatchlistUserError>>(x => x.Delete(It.Is<PlexWatchlistUserError>(x => x.UserId == "abc")), Times.Never);
}
[Test]
public async Task NoPlexUsersWithToken()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
var um = MockHelper.MockUserManager(new List<OmbiUser>
{
@ -102,6 +171,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task MultipleUsers()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
var um = MockHelper.MockUserManager(new List<OmbiUser>
{
@ -125,6 +195,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task MovieRequestFromWatchList_NoGuid()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{
@ -175,6 +246,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task TvRequestFromWatchList_NoGuid()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{
@ -224,6 +296,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task MovieRequestFromWatchList_AlreadyRequested()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{
@ -273,6 +346,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task TvRequestFromWatchList_AlreadyRequested()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{
@ -322,6 +396,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task MovieRequestFromWatchList_NoTmdbGuid()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{
@ -371,6 +446,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task TvRequestFromWatchList_NoTmdbGuid()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{
@ -420,6 +496,7 @@ namespace Ombi.Schedule.Tests
[Test]
public async Task MovieRequestFromWatchList_AlreadyImported()
{
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
{

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
@ -32,10 +33,11 @@ namespace Ombi.Schedule.Jobs.Plex
private readonly IHubContext<NotificationHub> _hub;
private readonly ILogger _logger;
private readonly IExternalRepository<PlexWatchlistHistory> _watchlistRepo;
private readonly IExternalRepository<PlexWatchlistUserError> _userError;
public PlexWatchlistImport(IPlexApi plexApi, ISettingsService<PlexSettings> settings, OmbiUserManager ombiUserManager,
IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IHubContext<NotificationHub> hub,
ILogger<PlexWatchlistImport> logger, IExternalRepository<PlexWatchlistHistory> watchlistRepo)
ILogger<PlexWatchlistImport> logger, IExternalRepository<PlexWatchlistHistory> watchlistRepo, IExternalRepository<PlexWatchlistUserError> userError)
{
_plexApi = plexApi;
_settings = settings;
@ -45,6 +47,7 @@ namespace Ombi.Schedule.Jobs.Plex
_hub = hub;
_logger = logger;
_watchlistRepo = watchlistRepo;
_userError = userError;
}
public async Task Execute(IJobExecutionContext context)
@ -64,9 +67,35 @@ namespace Ombi.Schedule.Jobs.Plex
{
try
{
// Check if the user has errors and the token is the same (not refreshed)
var failedUser = await _userError.GetAll().Where(x => x.UserId == user.Id).FirstOrDefaultAsync();
if (failedUser != null)
{
if (failedUser.MediaServerToken.Equals(user.MediaServerToken))
{
_logger.LogInformation($"Skipping Plex Watchlist Import for user '{user.UserName}' as they failed previously and the token has not yet been refreshed");
continue;
}
else
{
// remove that guy
await _userError.Delete(failedUser);
failedUser = null;
}
}
_logger.LogDebug($"Starting Watchlist Import for {user.UserName} with token {user.MediaServerToken}");
var watchlist = await _plexApi.GetWatchlist(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
if (watchlist?.AuthError ?? false)
{
_logger.LogError($"Auth failed for user '{user.UserName}'. Need to re-authenticate with Ombi.");
await _userError.Add(new PlexWatchlistUserError
{
UserId = user.Id,
MediaServerToken = user.MediaServerToken,
});
continue;
}
if (watchlist == null || !(watchlist.MediaContainer?.Metadata?.Any() ?? false))
{
_logger.LogDebug($"No watchlist found for {user.UserName}");

View file

@ -28,6 +28,7 @@ namespace Ombi.Store.Context
public DbSet<PlexSeasonsContent> PlexSeasonsContent { get; set; }
public DbSet<PlexEpisode> PlexEpisode { get; set; }
public DbSet<PlexWatchlistHistory> PlexWatchlistHistory { get; set; }
public DbSet<PlexWatchlistUserError> PlexWatchListUserError { get; set; }
public DbSet<RadarrCache> RadarrCache { get; set; }
public DbSet<CouchPotatoCache> CouchPotatoCache { get; set; }
public DbSet<EmbyContent> EmbyContent { get; set; }

View file

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table(nameof(PlexWatchlistUserError))]
public class PlexWatchlistUserError : Entity
{
public string UserId { get; set; }
public string MediaServerToken { get; set; }
[ForeignKey(nameof(UserId))]
public OmbiUser User { get; set; }
}
}