Esempio n. 1
0
    public async Task <Either <BaseError, Tuple <EpisodeMetadata, MediaVersion> > > GetEpisodeMetadataAndStatistics(
        PlexLibrary library,
        string key,
        PlexConnection connection,
        PlexServerAuthToken token)
    {
        try
        {
            IPlexServerApi service = XmlServiceFor(connection.Uri);
            Option <PlexXmlVideoMetadataResponseContainer> maybeResponse = await service
                                                                           .GetVideoMetadata(key, token.AuthToken)
                                                                           .Map(Optional)
                                                                           .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0));

            return(maybeResponse.Match(
                       response =>
            {
                Option <MediaVersion> maybeVersion = ProjectToMediaVersion(response.Metadata);
                return maybeVersion.Match <Either <BaseError, Tuple <EpisodeMetadata, MediaVersion> > >(
                    version => Tuple(
                        ProjectToEpisodeMetadata(response.Metadata, library.MediaSourceId),
                        version),
                    () => BaseError.New("Unable to locate metadata"));
            },
                       () => BaseError.New("Unable to locate metadata")));
        }
        catch (Exception ex)
        {
            return(BaseError.New(ex.ToString()));
        }
    }
Esempio n. 2
0
        private async Task <Either <BaseError, PlexEpisode> > UpdateStatistics(
            PlexEpisode existing,
            PlexEpisode incoming,
            PlexConnection connection,
            PlexServerAuthToken token)
        {
            MediaVersion existingVersion = existing.MediaVersions.Head();
            MediaVersion incomingVersion = incoming.MediaVersions.Head();

            if (incomingVersion.DateUpdated > existingVersion.DateUpdated ||
                string.IsNullOrWhiteSpace(existingVersion.SampleAspectRatio))
            {
                Either <BaseError, MediaVersion> maybeStatistics =
                    await _plexServerApiClient.GetStatistics(incoming.Key.Split("/").Last(), connection, token);

                await maybeStatistics.Match(
                    async mediaVersion =>
                {
                    existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio ?? "1:1";
                    existingVersion.VideoScanKind     = mediaVersion.VideoScanKind;
                    existingVersion.DateUpdated       = incomingVersion.DateUpdated;

                    await _metadataRepository.UpdatePlexStatistics(existingVersion);
                },
                    _ => Task.CompletedTask);
            }

            return(Right <BaseError, PlexEpisode>(existing));
        }
Esempio n. 3
0
    public async Task <Either <BaseError, Unit> > ScanLibrary(
        PlexConnection connection,
        PlexServerAuthToken token,
        PlexLibrary library,
        string ffmpegPath,
        string ffprobePath,
        bool deepScan,
        CancellationToken cancellationToken)
    {
        List <PlexPathReplacement> pathReplacements =
            await _mediaSourceRepository.GetPlexPathReplacements(library.MediaSourceId);

        string GetLocalPath(PlexMovie movie)
        {
            return(_plexPathReplacementService.GetReplacementPlexPath(
                       pathReplacements,
                       movie.GetHeadVersion().MediaFiles.Head().Path,
                       false));
        }

        return(await ScanLibrary(
                   _plexMovieRepository,
                   new PlexConnectionParameters(connection, token),
                   library,
                   GetLocalPath,
                   ffmpegPath,
                   ffprobePath,
                   deepScan,
                   cancellationToken));
    }
Esempio n. 4
0
    public async Task <Either <BaseError, List <PlexLibrary> > > GetLibraries(
        PlexConnection connection,
        PlexServerAuthToken token)
    {
        try
        {
            IPlexServerApi service = RestService.For <IPlexServerApi>(
                new HttpClient
            {
                BaseAddress = new Uri(connection.Uri),
                Timeout     = TimeSpan.FromSeconds(10)
            });
            List <PlexLibraryResponse> directory =
                await service.GetLibraries(token.AuthToken).Map(r => r.MediaContainer.Directory);

            return(directory
                   // .Filter(l => l.Hidden == 0)
                   .Filter(l => l.Type.ToLowerInvariant() is "movie" or "show")
                   .Map(Project)
                   .Somes()
                   .ToList());
        }
        catch (Exception ex)
        {
            return(BaseError.New(ex.ToString()));
        }
    }
Esempio n. 5
0
        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();
            }));
        }
Esempio n. 6
0
        private async Task <Either <BaseError, Unit> > ScanEpisodes(
            PlexLibrary plexMediaSourceLibrary,
            PlexSeason season,
            PlexConnection connection,
            PlexServerAuthToken token)
        {
            Either <BaseError, List <PlexEpisode> > entries = await _plexServerApiClient.GetSeasonEpisodes(
                plexMediaSourceLibrary,
                season,
                connection,
                token);

            return(await entries.Match <Task <Either <BaseError, Unit> > >(
                       async episodeEntries =>
            {
                foreach (PlexEpisode incoming in episodeEntries)
                {
                    incoming.SeasonId = season.Id;

                    // TODO: figure out how to rebuild playlists
                    Either <BaseError, PlexEpisode> maybeEpisode = await _televisionRepository
                                                                   .GetOrAddPlexEpisode(plexMediaSourceLibrary, incoming)
                                                                   .BindT(existing => UpdateStatistics(existing, incoming, connection, token))
                                                                   .BindT(existing => UpdateArtwork(existing, incoming));

                    maybeEpisode.IfLeft(
                        error => _logger.LogWarning(
                            "Error processing plex episode at {Key}: {Error}",
                            incoming.Key,
                            error.Value));
                }

                var episodeKeys = episodeEntries.Map(s => s.Key).ToList();
                await _televisionRepository.RemoveMissingPlexEpisodes(season.Key, episodeKeys);

                return Unit.Default;
            },
                       error =>
            {
                _logger.LogWarning(
                    "Error synchronizing plex library {Path}: {Error}",
                    plexMediaSourceLibrary.Name,
                    error.Value);

                return Left <BaseError, Unit>(error).AsTask();
            }));
        }
Esempio n. 7
0
 public async Task <Either <BaseError, List <PlexShow> > > GetShowLibraryContents(
     PlexLibrary library,
     PlexConnection connection,
     PlexServerAuthToken token)
 {
     try
     {
         IPlexServerApi service = RestService.For <IPlexServerApi>(connection.Uri);
         return(await service.GetLibrarySectionContents(library.Key, token.AuthToken)
                .Map(r => r.MediaContainer.Metadata)
                .Map(list => list.Map(metadata => ProjectToShow(metadata, library.MediaSourceId)).ToList()));
     }
     catch (Exception ex)
     {
         return(BaseError.New(ex.ToString()));
     }
 }
Esempio n. 8
0
 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()));
     }
 }
Esempio n. 9
0
 public async Task <Either <BaseError, List <PlexEpisode> > > GetSeasonEpisodes(
     PlexLibrary library,
     PlexSeason season,
     PlexConnection connection,
     PlexServerAuthToken token)
 {
     try
     {
         IPlexServerApi service = XmlServiceFor(connection.Uri);
         return(await service.GetSeasonChildren(season.Key.Split("/").Reverse().Skip(1).Head(), token.AuthToken)
                .Map(r => r.Metadata.Filter(m => m.Media.Count > 0 && m.Media[0].Part.Count > 0))
                .Map(list => list.Map(metadata => ProjectToEpisode(metadata, library.MediaSourceId)))
                .Map(ProcessMultiEpisodeFiles));
     }
     catch (Exception ex)
     {
         return(BaseError.New(ex.ToString()));
     }
 }
Esempio n. 10
0
 public async Task <Either <BaseError, ShowMetadata> > GetShowMetadata(
     PlexLibrary library,
     string key,
     PlexConnection connection,
     PlexServerAuthToken token)
 {
     try
     {
         IPlexServerApi service = XmlServiceFor(connection.Uri);
         return(await service.GetDirectoryMetadata(key, token.AuthToken)
                .Map(Optional)
                .MapT(response => ProjectToShowMetadata(response.Metadata, library.MediaSourceId))
                .Map(o => o.ToEither <BaseError>("Unable to locate metadata")));
     }
     catch (Exception ex)
     {
         return(BaseError.New(ex.ToString()));
     }
 }
Esempio n. 11
0
 public async Task <Either <BaseError, MovieMetadata> > GetMovieMetadata(
     PlexLibrary library,
     string key,
     PlexConnection connection,
     PlexServerAuthToken token)
 {
     try
     {
         IPlexServerApi service = XmlServiceFor(connection.Uri);
         return(await service.GetVideoMetadata(key, token.AuthToken)
                .Map(Optional)
                .Map(r => r.Filter(m => m.Metadata.Media.Count > 0 && m.Metadata.Media[0].Part.Count > 0))
                .MapT(response => ProjectToMovieMetadata(response.Metadata, library.MediaSourceId))
                .Map(o => o.ToEither <BaseError>("Unable to locate metadata")));
     }
     catch (Exception ex)
     {
         return(BaseError.New(ex.ToString()));
     }
 }
Esempio n. 12
0
    public async Task <Either <BaseError, Unit> > ScanLibrary(
        PlexConnection connection,
        PlexServerAuthToken token,
        PlexLibrary library,
        string ffmpegPath,
        string ffprobePath,
        bool deepScan,
        CancellationToken cancellationToken)
    {
        try
        {
            Either <BaseError, List <PlexShow> > entries = await _plexServerApiClient.GetShowLibraryContents(
                library,
                connection,
                token);

            foreach (BaseError error in entries.LeftToSeq())
            {
                return(error);
            }

            return(await ScanLibrary(
                       connection,
                       token,
                       library,
                       ffmpegPath,
                       ffprobePath,
                       deepScan,
                       entries.RightToSeq().Flatten().ToList(),
                       cancellationToken));
        }
        catch (Exception ex) when(ex is TaskCanceledException or OperationCanceledException)
        {
            return(new ScanCanceled());
        }
        finally
        {
            // always commit the search index to prevent corruption
            _searchIndex.Commit();
        }
    }
Esempio n. 13
0
    public async Task <bool> Ping(
        PlexConnection connection,
        PlexServerAuthToken token)
    {
        try
        {
            IPlexServerApi service = RestService.For <IPlexServerApi>(
                new HttpClient
            {
                BaseAddress = new Uri(connection.Uri),
                Timeout     = TimeSpan.FromSeconds(5)
            });

            await service.Ping(token.AuthToken);

            return(true);
        }
        catch (Exception)
        {
            return(false);
        }
    }
    private async Task SynchronizeServer(List <PlexMediaSource> allExisting, PlexMediaSource server)
    {
        if (server.Connections.All(c => c.Uri != LocalhostUri))
        {
            var localhost = new PlexConnection
            {
                PlexMediaSource   = server,
                PlexMediaSourceId = server.Id,
                Uri = LocalhostUri
            };

            server.Connections.Add(localhost);
        }

        Option <PlexMediaSource> maybeExisting =
            allExisting.Find(s => s.ClientIdentifier == server.ClientIdentifier);

        foreach (PlexMediaSource existing in maybeExisting)
        {
            existing.Platform        = server.Platform;
            existing.PlatformVersion = server.PlatformVersion;
            existing.ProductVersion  = server.ProductVersion;
            existing.ServerName      = server.ServerName;
            var toAdd = server.Connections
                        .Filter(connection => existing.Connections.All(c => c.Uri != connection.Uri)).ToList();
            var toRemove = existing.Connections
                           .Filter(connection => server.Connections.All(c => c.Uri != connection.Uri)).ToList();
            await _mediaSourceRepository.Update(existing, toAdd, toRemove);
            await FindConnectionToActivate(existing);
        }

        if (maybeExisting.IsNone)
        {
            await _mediaSourceRepository.Add(server);
            await FindConnectionToActivate(server);
        }
    }
Esempio n. 15
0
        public async Task <Either <BaseError, Unit> > ScanLibrary(
            PlexConnection connection,
            PlexServerAuthToken token,
            PlexLibrary plexMediaSourceLibrary)
        {
            Either <BaseError, List <PlexShow> > entries = await _plexServerApiClient.GetShowLibraryContents(
                plexMediaSourceLibrary,
                connection,
                token);

            return(await entries.Match <Task <Either <BaseError, Unit> > >(
                       async showEntries =>
            {
                foreach (PlexShow incoming in showEntries)
                {
                    // TODO: figure out how to rebuild playlists
                    Either <BaseError, MediaItemScanResult <PlexShow> > maybeShow = await _televisionRepository
                                                                                    .GetOrAddPlexShow(plexMediaSourceLibrary, incoming)
                                                                                    .BindT(existing => UpdateMetadata(existing, incoming))
                                                                                    .BindT(existing => UpdateArtwork(existing, incoming));

                    await maybeShow.Match(
                        async result =>
                    {
                        if (result.IsAdded)
                        {
                            await _searchIndex.AddItems(new List <MediaItem> {
                                result.Item
                            });
                        }
                        else if (result.IsUpdated)
                        {
                            await _searchIndex.UpdateItems(new List <MediaItem> {
                                result.Item
                            });
                        }

                        await ScanSeasons(plexMediaSourceLibrary, result.Item, connection, token);
                    },
                        error =>
                    {
                        _logger.LogWarning(
                            "Error processing plex show at {Key}: {Error}",
                            incoming.Key,
                            error.Value);
                        return Task.CompletedTask;
                    });
                }

                var showKeys = showEntries.Map(s => s.Key).ToList();
                List <int> ids =
                    await _televisionRepository.RemoveMissingPlexShows(plexMediaSourceLibrary, showKeys);
                await _searchIndex.RemoveItems(ids);

                return Unit.Default;
            },
                       error =>
            {
                _logger.LogWarning(
                    "Error synchronizing plex library {Path}: {Error}",
                    plexMediaSourceLibrary.Name,
                    error.Value);

                return Left <BaseError, Unit>(error).AsTask();
            }));
        }
Esempio n. 16
0
    private async Task <Either <BaseError, Unit> > ScanLibrary(
        PlexConnection connection,
        PlexServerAuthToken token,
        PlexLibrary library,
        string ffmpegPath,
        string ffprobePath,
        bool deepScan,
        List <PlexShow> showEntries,
        CancellationToken cancellationToken)
    {
        List <PlexItemEtag> existingShows = await _plexTelevisionRepository.GetExistingPlexShows(library);

        List <PlexPathReplacement> pathReplacements = await _mediaSourceRepository
                                                      .GetPlexPathReplacements(library.MediaSourceId);

        foreach (PlexShow incoming in showEntries)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(new ScanCanceled());
            }

            decimal percentCompletion = (decimal)showEntries.IndexOf(incoming) / showEntries.Count;
            await _mediator.Publish(new LibraryScanProgress(library.Id, percentCompletion), cancellationToken);

            // TODO: figure out how to rebuild playlists
            Either <BaseError, MediaItemScanResult <PlexShow> > maybeShow = await _televisionRepository
                                                                            .GetOrAddPlexShow(library, incoming)
                                                                            .BindT(existing => UpdateMetadata(existing, incoming, library, connection, token, deepScan))
                                                                            .BindT(existing => UpdateArtwork(existing, incoming));

            if (maybeShow.IsLeft)
            {
                foreach (BaseError error in maybeShow.LeftToSeq())
                {
                    _logger.LogWarning(
                        "Error processing plex show at {Key}: {Error}",
                        incoming.Key,
                        error.Value);
                }

                continue;
            }

            foreach (MediaItemScanResult <PlexShow> result in maybeShow.RightToSeq())
            {
                Either <BaseError, Unit> scanResult = await ScanSeasons(
                    library,
                    pathReplacements,
                    result.Item,
                    connection,
                    token,
                    ffmpegPath,
                    ffprobePath,
                    deepScan,
                    cancellationToken);

                foreach (ScanCanceled error in scanResult.LeftToSeq().OfType <ScanCanceled>())
                {
                    return(error);
                }

                await _plexTelevisionRepository.SetPlexEtag(result.Item, incoming.Etag);

                // TODO: if any seasons are unavailable or not found, flag show as unavailable/not found

                if (result.IsAdded)
                {
                    await _searchIndex.AddItems(_searchRepository, new List <MediaItem> {
                        result.Item
                    });
                }
                else if (result.IsUpdated)
                {
                    await _searchIndex.UpdateItems(
                        _searchRepository,
                        new List <MediaItem> {
                        result.Item
                    });
                }
            }
        }

        // trash items that are no longer present on the media server
        var        fileNotFoundKeys = existingShows.Map(m => m.Key).Except(showEntries.Map(m => m.Key)).ToList();
        List <int> ids = await _plexTelevisionRepository.FlagFileNotFoundShows(library, fileNotFoundKeys);

        await _searchIndex.RebuildItems(_searchRepository, ids);

        await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken);

        return(Unit.Default);
    }
Esempio n. 17
0
    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);
    }
Esempio n. 18
0
    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);
    }
Esempio n. 19
0
    private async Task <Either <BaseError, Unit> > ScanEpisodes(
        PlexLibrary library,
        List <PlexPathReplacement> pathReplacements,
        PlexSeason season,
        PlexConnection connection,
        PlexServerAuthToken token,
        string ffmpegPath,
        string ffprobePath,
        bool deepScan,
        CancellationToken cancellationToken)
    {
        List <PlexItemEtag> existingEpisodes = await _plexTelevisionRepository.GetExistingPlexEpisodes(library, season);

        Either <BaseError, List <PlexEpisode> > entries = await _plexServerApiClient.GetSeasonEpisodes(
            library,
            season,
            connection,
            token);

        foreach (BaseError error in entries.LeftToSeq())
        {
            return(error);
        }

        var episodeEntries = entries.RightToSeq().Flatten().ToList();

        foreach (PlexEpisode incoming in episodeEntries)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(new ScanCanceled());
            }

            if (await ShouldScanItem(library, pathReplacements, existingEpisodes, incoming, deepScan) == false)
            {
                continue;
            }

            incoming.SeasonId = season.Id;

            // TODO: figure out how to rebuild playlists
            Either <BaseError, MediaItemScanResult <PlexEpisode> > maybeEpisode = await _televisionRepository
                                                                                  .GetOrAddPlexEpisode(library, incoming)
                                                                                  .BindT(existing => UpdateMetadata(existing, incoming))
                                                                                  .BindT(
                existing => UpdateStatistics(
                    pathReplacements,
                    existing,
                    incoming,
                    library,
                    connection,
                    token,
                    ffmpegPath,
                    ffprobePath,
                    deepScan))
                                                                                  .BindT(existing => UpdateSubtitles(pathReplacements, existing, incoming))
                                                                                  .BindT(existing => UpdateArtwork(existing, incoming));

            foreach (BaseError error in maybeEpisode.LeftToSeq())
            {
                switch (error)
                {
                case ScanCanceled:
                    return(error);

                default:
                    _logger.LogWarning(
                        "Error processing plex episode at {Key}: {Error}",
                        incoming.Key,
                        error.Value);
                    break;
                }
            }

            foreach (MediaItemScanResult <PlexEpisode> result in maybeEpisode.RightToSeq())
            {
                await _plexTelevisionRepository.SetPlexEtag(result.Item, incoming.Etag);

                string plexPath = incoming.MediaVersions.Head().MediaFiles.Head().Path;

                string localPath = _plexPathReplacementService.GetReplacementPlexPath(
                    pathReplacements,
                    plexPath,
                    false);

                if (_localFileSystem.FileExists(localPath))
                {
                    await _plexTelevisionRepository.FlagNormal(library, result.Item);
                }
                else
                {
                    await _plexTelevisionRepository.FlagUnavailable(library, result.Item);
                }

                if (result.IsAdded)
                {
                    await _searchIndex.AddItems(_searchRepository, new List <MediaItem> {
                        result.Item
                    });
                }
                else
                {
                    await _searchIndex.UpdateItems(_searchRepository, new List <MediaItem> {
                        result.Item
                    });
                }
            }
        }

        var        fileNotFoundKeys = existingEpisodes.Map(m => m.Key).Except(episodeEntries.Map(m => m.Key)).ToList();
        List <int> ids = await _plexTelevisionRepository.FlagFileNotFoundEpisodes(library, fileNotFoundKeys);

        await _searchIndex.RebuildItems(_searchRepository, ids);

        _searchIndex.Commit();

        return(Unit.Default);
    }
Esempio n. 20
0
    private async Task <Either <BaseError, MediaItemScanResult <PlexEpisode> > > UpdateStatistics(
        List <PlexPathReplacement> pathReplacements,
        MediaItemScanResult <PlexEpisode> result,
        PlexEpisode incoming,
        PlexLibrary library,
        PlexConnection connection,
        PlexServerAuthToken token,
        string ffmpegPath,
        string ffprobePath,
        bool deepScan)
    {
        PlexEpisode  existing        = result.Item;
        MediaVersion existingVersion = existing.MediaVersions.Head();
        MediaVersion incomingVersion = incoming.MediaVersions.Head();

        if (result.IsAdded || existing.Etag != incoming.Etag || deepScan || existingVersion.Streams.Count == 0)
        {
            foreach (MediaFile incomingFile in incomingVersion.MediaFiles.HeadOrNone())
            {
                foreach (MediaFile existingFile in existingVersion.MediaFiles.HeadOrNone())
                {
                    if (incomingFile.Path != existingFile.Path)
                    {
                        _logger.LogDebug(
                            "Plex episode has moved from {OldPath} to {NewPath}",
                            existingFile.Path,
                            incomingFile.Path);

                        existingFile.Path = incomingFile.Path;

                        await _televisionRepository.UpdatePath(existingFile.Id, incomingFile.Path);
                    }
                }
            }

            Either <BaseError, bool> refreshResult = true;

            string localPath = _plexPathReplacementService.GetReplacementPlexPath(
                pathReplacements,
                incoming.MediaVersions.Head().MediaFiles.Head().Path,
                false);

            if ((existing.Etag != incoming.Etag || existingVersion.Streams.Count == 0) &&
                _localFileSystem.FileExists(localPath))
            {
                _logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", localPath);
                refreshResult = await _localStatisticsProvider.RefreshStatistics(
                    ffmpegPath,
                    ffprobePath,
                    existing,
                    localPath);
            }

            foreach (BaseError error in refreshResult.LeftToSeq())
            {
                _logger.LogWarning(
                    "Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
                    "Statistics",
                    localPath,
                    error.Value);
            }

            foreach (var _ in refreshResult.RightToSeq())
            {
                foreach (MediaItem updated in await _searchRepository.GetItemToIndex(incoming.Id))
                {
                    await _searchIndex.UpdateItems(
                        _searchRepository,
                        new List <MediaItem> {
                        updated
                    });
                }

                Either <BaseError, Tuple <EpisodeMetadata, MediaVersion> > maybeStatistics =
                    await _plexServerApiClient.GetEpisodeMetadataAndStatistics(
                        library,
                        incoming.Key.Split("/").Last(),
                        connection,
                        token);

                foreach (Tuple <EpisodeMetadata, MediaVersion> tuple in maybeStatistics.RightToSeq())
                {
                    (EpisodeMetadata incomingMetadata, MediaVersion mediaVersion) = tuple;

                    Option <EpisodeMetadata> maybeExisting = existing.EpisodeMetadata
                                                             .Find(em => em.EpisodeNumber == incomingMetadata.EpisodeNumber);
                    foreach (EpisodeMetadata existingMetadata in maybeExisting)
                    {
                        foreach (MetadataGuid guid in existingMetadata.Guids
                                 .Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
                                 .ToList())
                        {
                            existingMetadata.Guids.Remove(guid);
                            await _metadataRepository.RemoveGuid(guid);
                        }

                        foreach (MetadataGuid guid in incomingMetadata.Guids
                                 .Filter(g => existingMetadata.Guids.All(g2 => g2.Guid != g.Guid))
                                 .ToList())
                        {
                            existingMetadata.Guids.Add(guid);
                            await _metadataRepository.AddGuid(existingMetadata, guid);
                        }

                        foreach (Tag tag in existingMetadata.Tags
                                 .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name))
                                 .ToList())
                        {
                            existingMetadata.Tags.Remove(tag);
                            await _metadataRepository.RemoveTag(tag);
                        }

                        foreach (Tag tag in incomingMetadata.Tags
                                 .Filter(g => existingMetadata.Tags.All(g2 => g2.Name != g.Name))
                                 .ToList())
                        {
                            existingMetadata.Tags.Add(tag);
                            await _televisionRepository.AddTag(existingMetadata, tag);
                        }
                    }

                    existingVersion.SampleAspectRatio = mediaVersion.SampleAspectRatio;
                    existingVersion.VideoScanKind     = mediaVersion.VideoScanKind;
                    existingVersion.DateUpdated       = mediaVersion.DateUpdated;

                    await _metadataRepository.UpdatePlexStatistics(existingVersion.Id, mediaVersion);
                }
            }
        }

        return(result);
    }
Esempio n. 21
0
        public async Task <Either <BaseError, Unit> > ScanLibrary(
            PlexConnection connection,
            PlexServerAuthToken token,
            PlexLibrary plexMediaSourceLibrary)
        {
            Either <BaseError, List <PlexMovie> > entries = await _plexServerApiClient.GetMovieLibraryContents(
                plexMediaSourceLibrary,
                connection,
                token);

            await entries.Match(
                async movieEntries =>
            {
                foreach (PlexMovie incoming in movieEntries)
                {
                    // TODO: figure out how to rebuild playlists
                    Either <BaseError, MediaItemScanResult <PlexMovie> > maybeMovie = await _movieRepository
                                                                                      .GetOrAdd(plexMediaSourceLibrary, incoming)
                                                                                      .BindT(existing => UpdateStatistics(existing, incoming, connection, token))
                                                                                      .BindT(existing => UpdateMetadata(existing, incoming))
                                                                                      .BindT(existing => UpdateArtwork(existing, incoming));

                    await maybeMovie.Match(
                        async result =>
                    {
                        if (result.IsAdded)
                        {
                            await _searchIndex.AddItems(new List <MediaItem> {
                                result.Item
                            });
                        }
                        else if (result.IsUpdated)
                        {
                            await _searchIndex.UpdateItems(new List <MediaItem> {
                                result.Item
                            });
                        }
                    },
                        error =>
                    {
                        _logger.LogWarning(
                            "Error processing plex movie at {Key}: {Error}",
                            incoming.Key,
                            error.Value);
                        return(Task.CompletedTask);
                    });
                }

                var movieKeys  = movieEntries.Map(s => s.Key).ToList();
                List <int> ids = await _movieRepository.RemoveMissingPlexMovies(plexMediaSourceLibrary, movieKeys);
                await _searchIndex.RemoveItems(ids);
            },
                error =>
            {
                _logger.LogWarning(
                    "Error synchronizing plex library {Path}: {Error}",
                    plexMediaSourceLibrary.Name,
                    error.Value);

                return(Task.CompletedTask);
            });

            return(Unit.Default);
        }