Browse Source

unavailable improvements (#756)

* add unavailable health check

* improve file not found health check
pull/757/head
Jason Dove 4 years ago committed by GitHub
parent
commit
558e8acf5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      ErsatzTV.Core/Health/Checks/IUnavailableHealthCheck.cs
  2. 2
      ErsatzTV.Core/Interfaces/Repositories/IPlexMovieRepository.cs
  3. 2
      ErsatzTV.Core/Interfaces/Repositories/IPlexTelevisionRepository.cs
  4. 5
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  5. 5
      ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs
  6. 24
      ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs
  7. 20
      ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs
  8. 49
      ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs
  9. 82
      ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs
  10. 2
      ErsatzTV.Infrastructure/Health/HealthCheckService.cs
  11. 1
      ErsatzTV/Startup.cs

5
ErsatzTV.Core/Health/Checks/IUnavailableHealthCheck.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
namespace ErsatzTV.Core.Health.Checks;
public interface IUnavailableHealthCheck : IHealthCheck
{
}

2
ErsatzTV.Core/Interfaces/Repositories/IPlexMovieRepository.cs

@ -5,6 +5,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories; @@ -5,6 +5,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories;
public interface IPlexMovieRepository
{
Task<bool> FlagNormal(PlexLibrary library, PlexMovie movie);
Task<bool> FlagUnavailable(PlexLibrary library, PlexMovie movie);
Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexMovie movie);
Task<List<int>> FlagFileNotFound(PlexLibrary library, List<string> plexMovieKeys);
}

2
ErsatzTV.Core/Interfaces/Repositories/IPlexTelevisionRepository.cs

@ -9,7 +9,7 @@ public interface IPlexTelevisionRepository @@ -9,7 +9,7 @@ public interface IPlexTelevisionRepository
Task<List<PlexItemEtag>> GetExistingPlexSeasons(PlexLibrary library, PlexShow show);
Task<List<PlexItemEtag>> GetExistingPlexEpisodes(PlexLibrary library, PlexSeason season);
Task<bool> FlagNormal(PlexLibrary library, PlexEpisode episode);
Task<bool> FlagUnavailable(PlexLibrary library, PlexEpisode episode);
Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexEpisode episode);
Task<List<int>> FlagFileNotFoundShows(PlexLibrary library, List<string> plexShowKeys);
Task<List<int>> FlagFileNotFoundSeasons(PlexLibrary library, List<string> plexSeasonKeys);
Task<List<int>> FlagFileNotFoundEpisodes(PlexLibrary library, List<string> plexEpisodeKeys);

5
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -229,7 +229,10 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan @@ -229,7 +229,10 @@ public class PlexMovieLibraryScanner : PlexLibraryScanner, IPlexMovieLibraryScan
{
if (!_localFileSystem.FileExists(localPath))
{
await _plexMovieRepository.FlagUnavailable(library, incoming);
foreach (int id in await _plexMovieRepository.FlagUnavailable(library, incoming))
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { id });
}
}
// _logger.LogDebug("NOOP: etag has not changed for plex movie with key {Key}", incoming.Key);

5
ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

@ -643,7 +643,10 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL @@ -643,7 +643,10 @@ public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionL
{
if (!_localFileSystem.FileExists(localPath))
{
await _plexTelevisionRepository.FlagUnavailable(library, incoming);
foreach (int id in await _plexTelevisionRepository.FlagUnavailable(library, incoming))
{
await _searchIndex.RebuildItems(_searchRepository, new List<int> { id });
}
}
// _logger.LogDebug("NOOP: etag has not changed for plex episode with key {Key}", incoming.Key);

24
ErsatzTV.Infrastructure/Data/Repositories/PlexMovieRepository.cs

@ -26,19 +26,27 @@ public class PlexMovieRepository : IPlexMovieRepository @@ -26,19 +26,27 @@ public class PlexMovieRepository : IPlexMovieRepository
new { LibraryId = library.Id, movie.Key }).Map(count => count > 0);
}
public async Task<bool> FlagUnavailable(PlexLibrary library, PlexMovie movie)
public async Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexMovie movie)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
movie.State = MediaItemState.Unavailable;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 2 WHERE Id IN
(SELECT PlexMovie.Id FROM PlexMovie
INNER JOIN MediaItem MI ON MI.Id = PlexMovie.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE PlexMovie.Key = @Key)",
new { LibraryId = library.Id, movie.Key }).Map(count => count > 0);
Option<int> maybeId = await dbContext.Connection.ExecuteScalarAsync<int>(
@"SELECT PlexMovie.Id FROM PlexMovie
INNER JOIN MediaItem MI ON MI.Id = PlexMovie.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE PlexMovie.Key = @Key",
new { LibraryId = library.Id, movie.Key });
foreach (int id in maybeId)
{
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 2 WHERE Id = @Id",
new { Id = id }).Map(count => count > 0 ? Some(id) : None);
}
return None;
}
public async Task<List<int>> FlagFileNotFound(PlexLibrary library, List<string> plexMovieKeys)

20
ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs

@ -68,19 +68,27 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository @@ -68,19 +68,27 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
new { LibraryId = library.Id, episode.Key }).Map(count => count > 0);
}
public async Task<bool> FlagUnavailable(PlexLibrary library, PlexEpisode episode)
public async Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexEpisode episode)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
episode.State = MediaItemState.Unavailable;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 2 WHERE Id IN
(SELECT PlexEpisode.Id FROM PlexEpisode
Option<int> maybeId = await dbContext.Connection.ExecuteScalarAsync<int>(
@"SELECT PlexEpisode.Id FROM PlexEpisode
INNER JOIN MediaItem MI ON MI.Id = PlexEpisode.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE PlexEpisode.Key = @Key)",
new { LibraryId = library.Id, episode.Key }).Map(count => count > 0);
WHERE PlexEpisode.Key = @Key",
new { LibraryId = library.Id, episode.Key });
foreach (int id in maybeId)
{
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 2 WHERE Id = @Id",
new { Id = id }).Map(count => count > 0 ? Some(id) : None);
}
return None;
}
public async Task<List<int>> FlagFileNotFoundShows(PlexLibrary library, List<string> plexShowKeys)

49
ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs

@ -20,51 +20,34 @@ public class FileNotFoundHealthCheck : BaseHealthCheck, IFileNotFoundHealthCheck @@ -20,51 +20,34 @@ public class FileNotFoundHealthCheck : BaseHealthCheck, IFileNotFoundHealthCheck
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
List<Episode> episodes = await dbContext.Episodes
.Filter(e => e.State == MediaItemState.FileNotFound)
.Include(e => e.MediaVersions)
IQueryable<MediaItem> mediaItems = dbContext.MediaItems
.Filter(mi => mi.State == MediaItemState.FileNotFound)
.Include(mi => (mi as Episode).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.ToListAsync(cancellationToken);
List<Movie> movies = await dbContext.Movies
.Filter(m => m.State == MediaItemState.FileNotFound)
.Include(m => m.MediaVersions)
.Include(mi => (mi as Movie).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.ToListAsync(cancellationToken);
List<MusicVideo> musicVideos = await dbContext.MusicVideos
.Filter(mv => mv.State == MediaItemState.FileNotFound)
.Include(mv => mv.MediaVersions)
.Include(mi => (mi as MusicVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.ToListAsync(cancellationToken);
List<OtherVideo> otherVideos = await dbContext.OtherVideos
.Filter(ov => ov.State == MediaItemState.FileNotFound)
.Include(ov => ov.MediaVersions)
.Include(mi => (mi as OtherVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.ToListAsync(cancellationToken);
.Include(mi => (mi as Song).MediaVersions)
.ThenInclude(mv => mv.MediaFiles);
List<Song> songs = await dbContext.Songs
.Filter(s => s.State == MediaItemState.FileNotFound)
.Include(s => s.MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
List<MediaItem> five = await mediaItems
.OrderBy(mi => mi.Id)
.Take(5)
.ToListAsync(cancellationToken);
var all = movies.Map(m => m.MediaVersions.Head().MediaFiles.Head().Path)
.Append(episodes.Map(e => e.MediaVersions.Head().MediaFiles.Head().Path))
.Append(musicVideos.Map(mv => mv.GetHeadVersion().MediaFiles.Head().Path))
.Append(otherVideos.Map(ov => ov.GetHeadVersion().MediaFiles.Head().Path))
.Append(songs.Map(s => s.GetHeadVersion().MediaFiles.Head().Path))
.ToList();
if (all.Any())
if (five.Any())
{
var paths = all.Take(5).ToList();
IEnumerable<string> paths = five.Map(mi => mi.GetHeadVersion().MediaFiles.Head().Path);
var files = string.Join(", ", paths);
int count = await mediaItems.CountAsync(cancellationToken);
return WarningResult(
$"There are {all.Count} files that do not exist on disk, including the following: {files}",
$"There are {count} files that do not exist on disk, including the following: {files}",
"/media/trash");
}

82
ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs

@ -0,0 +1,82 @@ @@ -0,0 +1,82 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Health;
using ErsatzTV.Core.Health.Checks;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Infrastructure.Health.Checks;
public class UnavailableHealthCheck : BaseHealthCheck, IUnavailableHealthCheck
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly IEmbyPathReplacementService _embyPathReplacementService;
private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService;
private readonly IPlexPathReplacementService _plexPathReplacementService;
public UnavailableHealthCheck(
IDbContextFactory<TvContext> dbContextFactory,
IPlexPathReplacementService plexPathReplacementService,
IJellyfinPathReplacementService jellyfinPathReplacementService,
IEmbyPathReplacementService embyPathReplacementService)
{
_dbContextFactory = dbContextFactory;
_plexPathReplacementService = plexPathReplacementService;
_jellyfinPathReplacementService = jellyfinPathReplacementService;
_embyPathReplacementService = embyPathReplacementService;
}
protected override string Title => "Unavailable";
public async Task<HealthCheckResult> Check(CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
IQueryable<MediaItem> mediaItems = dbContext.MediaItems
.Filter(mi => mi.State == MediaItemState.Unavailable)
.Include(mi => (mi as Episode).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Movie).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as MusicVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as OtherVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Song).MediaVersions)
.ThenInclude(mv => mv.MediaFiles);
List<MediaItem> five = await mediaItems
.OrderBy(mi => mi.Id)
.Take(5)
.ToListAsync(cancellationToken);
if (five.Any())
{
var paths = new List<string>();
foreach (MediaItem mediaItem in five)
{
string path = await mediaItem.GetLocalPath(
_plexPathReplacementService,
_jellyfinPathReplacementService,
_embyPathReplacementService,
false);
paths.Add(path);
}
var files = string.Join(", ", paths);
int count = await mediaItems.CountAsync(cancellationToken);
return WarningResult(
$"There are {count} files that are unavailable because ErsatzTV cannot find them on disk, including the following: {files}",
"/search?query=state%3aUnavailable");
}
return OkResult();
}
}

2
ErsatzTV.Infrastructure/Health/HealthCheckService.cs

@ -15,6 +15,7 @@ public class HealthCheckService : IHealthCheckService @@ -15,6 +15,7 @@ public class HealthCheckService : IHealthCheckService
IEpisodeMetadataHealthCheck episodeMetadataHealthCheck,
IZeroDurationHealthCheck zeroDurationHealthCheck,
IFileNotFoundHealthCheck fileNotFoundHealthCheck,
IUnavailableHealthCheck unavailableHealthCheck,
IVaapiDriverHealthCheck vaapiDriverHealthCheck,
IErrorReportsHealthCheck errorReportsHealthCheck) =>
_checks = new List<IHealthCheck>
@ -26,6 +27,7 @@ public class HealthCheckService : IHealthCheckService @@ -26,6 +27,7 @@ public class HealthCheckService : IHealthCheckService
episodeMetadataHealthCheck,
zeroDurationHealthCheck,
fileNotFoundHealthCheck,
unavailableHealthCheck,
vaapiDriverHealthCheck,
errorReportsHealthCheck
};

1
ErsatzTV/Startup.cs

@ -336,6 +336,7 @@ public class Startup @@ -336,6 +336,7 @@ public class Startup
services.AddScoped<IEpisodeMetadataHealthCheck, EpisodeMetadataHealthCheck>();
services.AddScoped<IZeroDurationHealthCheck, ZeroDurationHealthCheck>();
services.AddScoped<IFileNotFoundHealthCheck, FileNotFoundHealthCheck>();
services.AddScoped<IUnavailableHealthCheck, UnavailableHealthCheck>();
services.AddScoped<IVaapiDriverHealthCheck, VaapiDriverHealthCheck>();
services.AddScoped<IErrorReportsHealthCheck, ErrorReportsHealthCheck>();
services.AddScoped<IHealthCheckService, HealthCheckService>();

Loading…
Cancel
Save