Browse Source

quickly skip missing files during plex library scan (#251)

pull/252/head
Jason Dove 5 years ago committed by GitHub
parent
commit
da5148affd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 5
      ErsatzTV.Core/Interfaces/Plex/IPlexPathReplacementService.cs
  3. 37
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  4. 23
      ErsatzTV.Core/Plex/PlexPathReplacementService.cs
  5. 61
      ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs
  6. 1
      ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs

1
CHANGELOG.md

@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- Add artists directly to schedules
- Include MPAA and VCHIP content ratings in XMLTV guide data
- Quickly skip missing files during Plex library scan
### Fixed
- Ignore unsupported plex guids (this prevented some libraries from scanning correctly)

5
ErsatzTV.Core/Interfaces/Plex/IPlexPathReplacementService.cs

@ -1,9 +1,12 @@ @@ -1,9 +1,12 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Interfaces.Plex
{
public interface IPlexPathReplacementService
{
Task<string> GetReplacementPlexPath(int libraryPathId, string path);
string GetReplacementPlexPath(List<PlexPathReplacement> pathReplacements, string path, bool log = true);
}
}

37
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -3,6 +3,7 @@ using System.Collections.Generic; @@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
@ -18,6 +19,9 @@ namespace ErsatzTV.Core.Plex @@ -18,6 +19,9 @@ namespace ErsatzTV.Core.Plex
{
private readonly ILogger<PlexMovieLibraryScanner> _logger;
private readonly IMediator _mediator;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IPlexPathReplacementService _plexPathReplacementService;
private readonly ILocalFileSystem _localFileSystem;
private readonly IMetadataRepository _metadataRepository;
private readonly IMovieRepository _movieRepository;
private readonly IPlexServerApiClient _plexServerApiClient;
@ -31,6 +35,9 @@ namespace ErsatzTV.Core.Plex @@ -31,6 +35,9 @@ namespace ErsatzTV.Core.Plex
ISearchIndex searchIndex,
ISearchRepository searchRepository,
IMediator mediator,
IMediaSourceRepository mediaSourceRepository,
IPlexPathReplacementService plexPathReplacementService,
ILocalFileSystem localFileSystem,
ILogger<PlexMovieLibraryScanner> logger)
: base(metadataRepository, logger)
{
@ -40,6 +47,9 @@ namespace ErsatzTV.Core.Plex @@ -40,6 +47,9 @@ namespace ErsatzTV.Core.Plex
_searchIndex = searchIndex;
_searchRepository = searchRepository;
_mediator = mediator;
_mediaSourceRepository = mediaSourceRepository;
_plexPathReplacementService = plexPathReplacementService;
_localFileSystem = localFileSystem;
_logger = logger;
}
@ -48,6 +58,9 @@ namespace ErsatzTV.Core.Plex @@ -48,6 +58,9 @@ namespace ErsatzTV.Core.Plex
PlexServerAuthToken token,
PlexLibrary library)
{
List<PlexPathReplacement> pathReplacements = await _mediaSourceRepository
.GetPlexPathReplacements(library.MediaSourceId);
Either<BaseError, List<PlexMovie>> entries = await _plexServerApiClient.GetMovieLibraryContents(
library,
connection,
@ -56,9 +69,27 @@ namespace ErsatzTV.Core.Plex @@ -56,9 +69,27 @@ namespace ErsatzTV.Core.Plex
await entries.Match(
async movieEntries =>
{
foreach (PlexMovie incoming in movieEntries)
var validMovies = new List<PlexMovie>();
foreach (PlexMovie movie in movieEntries.OrderBy(m => m.MovieMetadata.Head().Title))
{
decimal percentCompletion = (decimal) movieEntries.IndexOf(incoming) / movieEntries.Count;
string localPath = _plexPathReplacementService.GetReplacementPlexPath(
pathReplacements,
movie.MediaVersions.Head().MediaFiles.Head().Path,
false);
if (!_localFileSystem.FileExists(localPath))
{
_logger.LogWarning("Skipping plex movie that does not exist at {Path}", localPath);
}
else
{
validMovies.Add(movie);
}
}
foreach (PlexMovie incoming in validMovies)
{
decimal percentCompletion = (decimal) validMovies.IndexOf(incoming) / validMovies.Count;
await _mediator.Publish(new LibraryScanProgress(library.Id, percentCompletion));
// TODO: figure out how to rebuild playlists
@ -92,7 +123,7 @@ namespace ErsatzTV.Core.Plex @@ -92,7 +123,7 @@ namespace ErsatzTV.Core.Plex
});
}
var movieKeys = movieEntries.Map(s => s.Key).ToList();
var movieKeys = validMovies.Map(s => s.Key).ToList();
List<int> ids = await _movieRepository.RemoveMissingPlexMovies(library, movieKeys);
await _searchIndex.RemoveItems(ids);

23
ErsatzTV.Core/Plex/PlexPathReplacementService.cs

@ -31,7 +31,13 @@ namespace ErsatzTV.Core.Plex @@ -31,7 +31,13 @@ namespace ErsatzTV.Core.Plex
{
List<PlexPathReplacement> replacements =
await _mediaSourceRepository.GetPlexPathReplacementsByLibraryId(libraryPathId);
Option<PlexPathReplacement> maybeReplacement = replacements
return GetReplacementPlexPath(replacements, path);
}
public string GetReplacementPlexPath(List<PlexPathReplacement> pathReplacements, string path, bool log = true)
{
Option<PlexPathReplacement> maybeReplacement = pathReplacements
.SingleOrDefault(
r =>
{
@ -39,6 +45,7 @@ namespace ErsatzTV.Core.Plex @@ -39,6 +45,7 @@ namespace ErsatzTV.Core.Plex
string prefix = r.PlexPath.EndsWith(separatorChar) ? r.PlexPath : r.PlexPath + separatorChar;
return path.StartsWith(prefix);
});
return maybeReplacement.Match(
replacement =>
{
@ -52,11 +59,15 @@ namespace ErsatzTV.Core.Plex @@ -52,11 +59,15 @@ namespace ErsatzTV.Core.Plex
finalPath = finalPath.Replace(@"/", @"\");
}
_logger.LogInformation(
"Replacing plex path {PlexPath} with {LocalPath} resulting in {FinalPath}",
replacement.PlexPath,
replacement.LocalPath,
finalPath);
if (log)
{
_logger.LogInformation(
"Replacing plex path {PlexPath} with {LocalPath} resulting in {FinalPath}",
replacement.PlexPath,
replacement.LocalPath,
finalPath);
}
return finalPath;
},
() => path);

61
ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

@ -3,6 +3,7 @@ using System.Collections.Generic; @@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Search;
@ -18,9 +19,12 @@ namespace ErsatzTV.Core.Plex @@ -18,9 +19,12 @@ namespace ErsatzTV.Core.Plex
{
public class PlexTelevisionLibraryScanner : PlexLibraryScanner, IPlexTelevisionLibraryScanner
{
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<PlexTelevisionLibraryScanner> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IMediator _mediator;
private readonly IMetadataRepository _metadataRepository;
private readonly IPlexPathReplacementService _plexPathReplacementService;
private readonly IPlexServerApiClient _plexServerApiClient;
private readonly ISearchIndex _searchIndex;
private readonly ISearchRepository _searchRepository;
@ -33,6 +37,9 @@ namespace ErsatzTV.Core.Plex @@ -33,6 +37,9 @@ namespace ErsatzTV.Core.Plex
ISearchIndex searchIndex,
ISearchRepository searchRepository,
IMediator mediator,
IMediaSourceRepository mediaSourceRepository,
IPlexPathReplacementService plexPathReplacementService,
ILocalFileSystem localFileSystem,
ILogger<PlexTelevisionLibraryScanner> logger)
: base(metadataRepository, logger)
{
@ -42,6 +49,9 @@ namespace ErsatzTV.Core.Plex @@ -42,6 +49,9 @@ namespace ErsatzTV.Core.Plex
_searchIndex = searchIndex;
_searchRepository = searchRepository;
_mediator = mediator;
_mediaSourceRepository = mediaSourceRepository;
_plexPathReplacementService = plexPathReplacementService;
_localFileSystem = localFileSystem;
_logger = logger;
}
@ -50,6 +60,9 @@ namespace ErsatzTV.Core.Plex @@ -50,6 +60,9 @@ namespace ErsatzTV.Core.Plex
PlexServerAuthToken token,
PlexLibrary library)
{
List<PlexPathReplacement> pathReplacements = await _mediaSourceRepository
.GetPlexPathReplacements(library.MediaSourceId);
Either<BaseError, List<PlexShow>> entries = await _plexServerApiClient.GetShowLibraryContents(
library,
connection,
@ -72,7 +85,7 @@ namespace ErsatzTV.Core.Plex @@ -72,7 +85,7 @@ namespace ErsatzTV.Core.Plex
await maybeShow.Match(
async result =>
{
await ScanSeasons(library, result.Item, connection, token);
await ScanSeasons(library, pathReplacements, result.Item, connection, token);
if (result.IsAdded)
{
@ -271,13 +284,14 @@ namespace ErsatzTV.Core.Plex @@ -271,13 +284,14 @@ namespace ErsatzTV.Core.Plex
}
private async Task<Either<BaseError, Unit>> ScanSeasons(
PlexLibrary plexMediaSourceLibrary,
PlexLibrary library,
List<PlexPathReplacement> pathReplacements,
PlexShow show,
PlexConnection connection,
PlexServerAuthToken token)
{
Either<BaseError, List<PlexSeason>> entries = await _plexServerApiClient.GetShowSeasons(
plexMediaSourceLibrary,
library,
show,
connection,
token);
@ -291,11 +305,11 @@ namespace ErsatzTV.Core.Plex @@ -291,11 +305,11 @@ namespace ErsatzTV.Core.Plex
// TODO: figure out how to rebuild playlists
Either<BaseError, PlexSeason> maybeSeason = await _televisionRepository
.GetOrAddPlexSeason(plexMediaSourceLibrary, incoming)
.GetOrAddPlexSeason(library, incoming)
.BindT(existing => UpdateMetadataAndArtwork(existing, incoming));
await maybeSeason.Match(
async season => await ScanEpisodes(plexMediaSourceLibrary, season, connection, token),
async season => await ScanEpisodes(library, pathReplacements, season, connection, token),
error =>
{
_logger.LogWarning(
@ -315,7 +329,7 @@ namespace ErsatzTV.Core.Plex @@ -315,7 +329,7 @@ namespace ErsatzTV.Core.Plex
{
_logger.LogWarning(
"Error synchronizing plex library {Path}: {Error}",
plexMediaSourceLibrary.Name,
library.Name,
error.Value);
return Left<BaseError, Unit>(error).AsTask();
@ -355,13 +369,14 @@ namespace ErsatzTV.Core.Plex @@ -355,13 +369,14 @@ namespace ErsatzTV.Core.Plex
}
private async Task<Either<BaseError, Unit>> ScanEpisodes(
PlexLibrary plexMediaSourceLibrary,
PlexLibrary library,
List<PlexPathReplacement> pathReplacements,
PlexSeason season,
PlexConnection connection,
PlexServerAuthToken token)
{
Either<BaseError, List<PlexEpisode>> entries = await _plexServerApiClient.GetSeasonEpisodes(
plexMediaSourceLibrary,
library,
season,
connection,
token);
@ -369,19 +384,39 @@ namespace ErsatzTV.Core.Plex @@ -369,19 +384,39 @@ namespace ErsatzTV.Core.Plex
return await entries.Match<Task<Either<BaseError, Unit>>>(
async episodeEntries =>
{
foreach (PlexEpisode incoming in episodeEntries)
var validEpisodes = new List<PlexEpisode>();
foreach (PlexEpisode episode in episodeEntries)
{
string localPath = _plexPathReplacementService.GetReplacementPlexPath(
pathReplacements,
episode.MediaVersions.Head().MediaFiles.Head().Path,
false);
if (!_localFileSystem.FileExists(localPath))
{
_logger.LogWarning(
"Skipping plex episode that does not exist at {Path}",
localPath);
}
else
{
validEpisodes.Add(episode);
}
}
foreach (PlexEpisode incoming in validEpisodes)
{
incoming.SeasonId = season.Id;
// TODO: figure out how to rebuild playlists
Either<BaseError, PlexEpisode> maybeEpisode = await _televisionRepository
.GetOrAddPlexEpisode(plexMediaSourceLibrary, incoming)
.GetOrAddPlexEpisode(library, incoming)
.BindT(existing => UpdateMetadata(existing, incoming))
.BindT(
existing => UpdateStatistics(
existing,
incoming,
plexMediaSourceLibrary,
library,
connection,
token))
.BindT(existing => UpdateArtwork(existing, incoming));
@ -401,7 +436,7 @@ namespace ErsatzTV.Core.Plex @@ -401,7 +436,7 @@ namespace ErsatzTV.Core.Plex
});
}
var episodeKeys = episodeEntries.Map(s => s.Key).ToList();
var episodeKeys = validEpisodes.Map(s => s.Key).ToList();
List<int> ids = await _televisionRepository.RemoveMissingPlexEpisodes(season.Key, episodeKeys);
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();
@ -412,7 +447,7 @@ namespace ErsatzTV.Core.Plex @@ -412,7 +447,7 @@ namespace ErsatzTV.Core.Plex
{
_logger.LogWarning(
"Error synchronizing plex library {Path}: {Error}",
plexMediaSourceLibrary.Name,
library.Name,
error.Value);
return Left<BaseError, Unit>(error).AsTask();

1
ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs

@ -72,6 +72,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -72,6 +72,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
using TvContext context = _dbContextFactory.CreateDbContext();
return context.PlexPathReplacements
.Include(ppr => ppr.PlexMediaSource)
.Filter(r => r.PlexMediaSourceId == plexMediaSourceId)
.ToListAsync();
}

Loading…
Cancel
Save