public async Task <Either <BaseError, MediaItemScanResult <PlexShow> > > GetOrAddPlexShow( PlexLibrary library, PlexShow item) { await using TvContext dbContext = _dbContextFactory.CreateDbContext(); Option <PlexShow> maybeExisting = await dbContext.PlexShows .AsNoTracking() .Include(i => i.ShowMetadata) .ThenInclude(sm => sm.Genres) .Include(i => i.ShowMetadata) .ThenInclude(sm => sm.Tags) .Include(i => i.ShowMetadata) .ThenInclude(sm => sm.Studios) .Include(i => i.ShowMetadata) .ThenInclude(sm => sm.Artwork) .Include(i => i.LibraryPath) .ThenInclude(lp => lp.Library) .OrderBy(i => i.Key) .SingleOrDefaultAsync(i => i.Key == item.Key); return(await maybeExisting.Match( plexShow => Right <BaseError, MediaItemScanResult <PlexShow> >( new MediaItemScanResult <PlexShow>(plexShow) { IsAdded = true }).AsTask(), async() => await AddPlexShow(dbContext, library, item))); }
private async Task <Either <BaseError, Unit> > ScanSeasons( PlexLibrary plexMediaSourceLibrary, PlexShow show, PlexConnection connection, PlexServerAuthToken token) { Either <BaseError, List <PlexSeason> > entries = await _plexServerApiClient.GetShowSeasons( plexMediaSourceLibrary, show, connection, token); return(await entries.Match <Task <Either <BaseError, Unit> > >( async seasonEntries => { foreach (PlexSeason incoming in seasonEntries) { incoming.ShowId = show.Id; // TODO: figure out how to rebuild playlists Either <BaseError, PlexSeason> maybeSeason = await _televisionRepository .GetOrAddPlexSeason(plexMediaSourceLibrary, incoming) .BindT(existing => UpdateArtwork(existing, incoming)); await maybeSeason.Match( async season => await ScanEpisodes(plexMediaSourceLibrary, season, connection, token), error => { _logger.LogWarning( "Error processing plex show at {Key}: {Error}", incoming.Key, error.Value); return Task.CompletedTask; }); } var seasonKeys = seasonEntries.Map(s => s.Key).ToList(); await _televisionRepository.RemoveMissingPlexSeasons(show.Key, seasonKeys); return Unit.Default; }, error => { _logger.LogWarning( "Error synchronizing plex library {Path}: {Error}", plexMediaSourceLibrary.Name, error.Value); return Left <BaseError, Unit>(error).AsTask(); })); }
public async Task <Either <BaseError, List <PlexSeason> > > GetShowSeasons( PlexLibrary library, PlexShow show, PlexConnection connection, PlexServerAuthToken token) { try { IPlexServerApi service = XmlServiceFor(connection.Uri); return(await service.GetShowChildren(show.Key.Split("/").Reverse().Skip(1).Head(), token.AuthToken) .Map(r => r.Metadata) .Map(list => list.Map(metadata => ProjectToSeason(metadata, library.MediaSourceId)).ToList())); } catch (Exception ex) { return(BaseError.New(ex.ToString())); } }
private async Task <Either <BaseError, MediaItemScanResult <PlexShow> > > UpdateArtwork( MediaItemScanResult <PlexShow> result, PlexShow incoming) { PlexShow existing = result.Item; ShowMetadata existingMetadata = existing.ShowMetadata.Head(); ShowMetadata incomingMetadata = incoming.ShowMetadata.Head(); if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) { await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Poster); await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.FanArt); await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated); } return(result); }
public Task <Unit> SetPlexEtag(PlexShow show, string etag) => throw new NotSupportedException();
public Task <Either <BaseError, MediaItemScanResult <PlexShow> > > GetOrAddPlexShow( PlexLibrary library, PlexShow item) => throw new NotSupportedException();
private async Task <Either <BaseError, Unit> > ScanSeasons( PlexLibrary library, List <PlexPathReplacement> pathReplacements, PlexShow show, PlexConnection connection, PlexServerAuthToken token, string ffmpegPath, string ffprobePath, bool deepScan, CancellationToken cancellationToken) { List <PlexItemEtag> existingSeasons = await _plexTelevisionRepository.GetExistingPlexSeasons(library, show); Either <BaseError, List <PlexSeason> > entries = await _plexServerApiClient.GetShowSeasons( library, show, connection, token); foreach (BaseError error in entries.LeftToSeq()) { return(error); } var seasonEntries = entries.RightToSeq().Flatten().ToList(); foreach (PlexSeason incoming in seasonEntries) { incoming.ShowId = show.Id; // TODO: figure out how to rebuild playlists Either <BaseError, PlexSeason> maybeSeason = await _televisionRepository .GetOrAddPlexSeason(library, incoming) .BindT(existing => UpdateMetadataAndArtwork(existing, incoming, deepScan)); foreach (BaseError error in maybeSeason.LeftToSeq()) { _logger.LogWarning( "Error processing plex season at {Key}: {Error}", incoming.Key, error.Value); return(error); } foreach (PlexSeason season in maybeSeason.RightToSeq()) { Either <BaseError, Unit> scanResult = await ScanEpisodes( library, pathReplacements, season, connection, token, ffmpegPath, ffprobePath, deepScan, cancellationToken); foreach (ScanCanceled error in scanResult.LeftToSeq().OfType <ScanCanceled>()) { return(error); } await _plexTelevisionRepository.SetPlexEtag(season, incoming.Etag); season.Show = show; // TODO: if any seasons are unavailable or not found, flag show as unavailable/not found await _searchIndex.AddItems(_searchRepository, new List <MediaItem> { season }); } } var fileNotFoundKeys = existingSeasons.Map(m => m.Key).Except(seasonEntries.Map(m => m.Key)).ToList(); List <int> ids = await _plexTelevisionRepository.FlagFileNotFoundSeasons(library, fileNotFoundKeys); await _searchIndex.RebuildItems(_searchRepository, ids); return(Unit.Default); }
private async Task <Either <BaseError, MediaItemScanResult <PlexShow> > > UpdateMetadata( MediaItemScanResult <PlexShow> result, PlexShow incoming, PlexLibrary library, PlexConnection connection, PlexServerAuthToken token, bool deepScan) { PlexShow existing = result.Item; ShowMetadata existingMetadata = existing.ShowMetadata.Head(); if (result.IsAdded || existing.Etag != incoming.Etag || deepScan) { Either <BaseError, ShowMetadata> maybeMetadata = await _plexServerApiClient.GetShowMetadata( library, incoming.Key.Replace("/children", string.Empty).Split("/").Last(), connection, token); await maybeMetadata.Match( async fullMetadata => { if (existingMetadata.MetadataKind != MetadataKind.External) { existingMetadata.MetadataKind = MetadataKind.External; await _metadataRepository.MarkAsExternal(existingMetadata); } if (existingMetadata.ContentRating != fullMetadata.ContentRating) { existingMetadata.ContentRating = fullMetadata.ContentRating; await _metadataRepository.SetContentRating(existingMetadata, fullMetadata.ContentRating); result.IsUpdated = true; } foreach (Genre genre in existingMetadata.Genres .Filter(g => fullMetadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Genres.Remove(genre); if (await _metadataRepository.RemoveGenre(genre)) { result.IsUpdated = true; } } foreach (Genre genre in fullMetadata.Genres .Filter(g => existingMetadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Genres.Add(genre); if (await _televisionRepository.AddGenre(existingMetadata, genre)) { result.IsUpdated = true; } } foreach (Studio studio in existingMetadata.Studios .Filter(s => fullMetadata.Studios.All(s2 => s2.Name != s.Name)) .ToList()) { existingMetadata.Studios.Remove(studio); if (await _metadataRepository.RemoveStudio(studio)) { result.IsUpdated = true; } } foreach (Studio studio in fullMetadata.Studios .Filter(s => existingMetadata.Studios.All(s2 => s2.Name != s.Name)) .ToList()) { existingMetadata.Studios.Add(studio); if (await _televisionRepository.AddStudio(existingMetadata, studio)) { result.IsUpdated = true; } } foreach (Actor actor in existingMetadata.Actors .Filter( a => fullMetadata.Actors.All( a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { existingMetadata.Actors.Remove(actor); if (await _metadataRepository.RemoveActor(actor)) { result.IsUpdated = true; } } foreach (Actor actor in fullMetadata.Actors .Filter(a => existingMetadata.Actors.All(a2 => a2.Name != a.Name)) .ToList()) { existingMetadata.Actors.Add(actor); if (await _televisionRepository.AddActor(existingMetadata, actor)) { result.IsUpdated = true; } } foreach (MetadataGuid guid in existingMetadata.Guids .Filter(g => fullMetadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { existingMetadata.Guids.Remove(guid); if (await _metadataRepository.RemoveGuid(guid)) { result.IsUpdated = true; } } foreach (MetadataGuid guid in fullMetadata.Guids .Filter(g => existingMetadata.Guids.All(g2 => g2.Guid != g.Guid)) .ToList()) { existingMetadata.Guids.Add(guid); if (await _metadataRepository.AddGuid(existingMetadata, guid)) { result.IsUpdated = true; } } foreach (Tag tag in existingMetadata.Tags .Filter(g => fullMetadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Tags.Remove(tag); if (await _metadataRepository.RemoveTag(tag)) { result.IsUpdated = true; } } foreach (Tag tag in fullMetadata.Tags .Filter(g => existingMetadata.Tags.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Tags.Add(tag); if (await _televisionRepository.AddTag(existingMetadata, tag)) { result.IsUpdated = true; } } if (result.IsUpdated) { await _metadataRepository.MarkAsUpdated(existingMetadata, fullMetadata.DateUpdated); } }, _ => Task.CompletedTask); } return(result); }
private async Task <Either <BaseError, MediaItemScanResult <PlexShow> > > UpdateMetadata( MediaItemScanResult <PlexShow> result, PlexShow incoming) { PlexShow existing = result.Item; ShowMetadata existingMetadata = existing.ShowMetadata.Head(); ShowMetadata incomingMetadata = incoming.ShowMetadata.Head(); // TODO: this probably doesn't work // plex doesn't seem to update genres returned by the main library call if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated) { foreach (Genre genre in existingMetadata.Genres .Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Genres.Remove(genre); if (await _metadataRepository.RemoveGenre(genre)) { result.IsUpdated = true; } } foreach (Genre genre in incomingMetadata.Genres .Filter(g => existingMetadata.Genres.All(g2 => g2.Name != g.Name)) .ToList()) { existingMetadata.Genres.Add(genre); if (await _televisionRepository.AddGenre(existingMetadata, genre)) { result.IsUpdated = true; } } foreach (Studio studio in existingMetadata.Studios .Filter(s => incomingMetadata.Studios.All(s2 => s2.Name != s.Name)) .ToList()) { existingMetadata.Studios.Remove(studio); if (await _metadataRepository.RemoveStudio(studio)) { result.IsUpdated = true; } } foreach (Studio studio in incomingMetadata.Studios .Filter(s => existingMetadata.Studios.All(s2 => s2.Name != s.Name)) .ToList()) { existingMetadata.Studios.Add(studio); if (await _televisionRepository.AddStudio(existingMetadata, studio)) { result.IsUpdated = true; } } await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated); } return(result); }