예제 #1
0
        private async Task <Either <BaseError, MediaItemScanResult <PlexMovie> > > AddPlexMovie(
            TvContext context,
            PlexLibrary library,
            PlexMovie item)
        {
            try
            {
                item.LibraryPathId = library.Paths.Head().Id;

                await context.PlexMovies.AddAsync(item);

                await context.SaveChangesAsync();

                await context.Entry(item).Reference(i => i.LibraryPath).LoadAsync();

                await context.Entry(item.LibraryPath).Reference(lp => lp.Library).LoadAsync();

                return(new MediaItemScanResult <PlexMovie>(item)
                {
                    IsAdded = true
                });
            }
            catch (Exception ex)
            {
                return(BaseError.New(ex.ToString()));
            }
        }
예제 #2
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()));
        }
    }
예제 #3
0
        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)));
        }
예제 #4
0
    private async Task <bool> ShouldScanItem(
        PlexLibrary library,
        List <PlexPathReplacement> pathReplacements,
        List <PlexItemEtag> existingEpisodes,
        PlexEpisode incoming,
        bool deepScan)
    {
        // deep scan will pull every episode individually from the plex api
        if (!deepScan)
        {
            Option <PlexItemEtag> maybeExisting = existingEpisodes.Find(ie => ie.Key == incoming.Key);
            string existingTag = await maybeExisting
                                 .Map(e => e.Etag ?? string.Empty)
                                 .IfNoneAsync(string.Empty);

            MediaItemState existingState = await maybeExisting
                                           .Map(e => e.State)
                                           .IfNoneAsync(MediaItemState.Normal);

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

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

            // if media is unavailable, only scan if file now exists
            if (existingState == MediaItemState.Unavailable)
            {
                if (!_localFileSystem.FileExists(localPath))
                {
                    return(false);
                }
            }
            else if (existingTag == incoming.Etag)
            {
                if (!_localFileSystem.FileExists(localPath))
                {
                    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);
                return(false);
            }

            // _logger.LogDebug(
            //     "UPDATE: Etag has changed for episode {Episode}",
            //     $"s{season.SeasonNumber}e{incoming.EpisodeMetadata.Head().EpisodeNumber}");
        }

        return(true);
    }
예제 #5
0
        public BrowseLibrary(ContextMenu contextMenu, PlexLibrary library)
        {
            MediaObjectsData    = new ObservableCollection <IPlexMediaObject>();
            MediaObjects        = CollectionViewSource.GetDefaultView(MediaObjectsData);
            MediaObjects.Filter = MediaObjects_Filter;

            Library = library;

            InitializeComponent();
        }
예제 #6
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();
            }));
        }
예제 #7
0
        public async Task <Either <BaseError, PlexSeason> > GetOrAddPlexSeason(PlexLibrary library, PlexSeason item)
        {
            await using TvContext dbContext = _dbContextFactory.CreateDbContext();
            Option <PlexSeason> maybeExisting = await dbContext.PlexSeasons
                                                .AsNoTracking()
                                                .Include(i => i.SeasonMetadata)
                                                .ThenInclude(mm => mm.Artwork)
                                                .OrderBy(i => i.Key)
                                                .SingleOrDefaultAsync(i => i.Key == item.Key);

            return(await maybeExisting.Match(
                       plexSeason => Right <BaseError, PlexSeason>(plexSeason).AsTask(),
                       async() => await AddPlexSeason(dbContext, library, item)));
        }
예제 #8
0
        public async Task <Either <BaseError, PlexEpisode> > GetOrAddPlexEpisode(PlexLibrary library, PlexEpisode item)
        {
            await using TvContext dbContext = _dbContextFactory.CreateDbContext();
            Option <PlexEpisode> maybeExisting = await dbContext.PlexEpisodes
                                                 .AsNoTracking()
                                                 .Include(i => i.EpisodeMetadata)
                                                 .ThenInclude(mm => mm.Artwork)
                                                 .Include(i => i.MediaVersions)
                                                 .ThenInclude(mv => mv.MediaFiles)
                                                 .OrderBy(i => i.Key)
                                                 .SingleOrDefaultAsync(i => i.Key == item.Key);

            return(await maybeExisting.Match(
                       plexEpisode => Right <BaseError, PlexEpisode>(plexEpisode).AsTask(),
                       async() => await AddPlexEpisode(dbContext, library, item)));
        }
예제 #9
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();
            }));
        }
        private void SetupDatabase()
        {
            PlexServer  plexServer  = FakeDbData.GetPlexServer().Generate();
            PlexLibrary plexLibrary = FakeDbData.GetPlexLibrary(1, 1, PlexMediaType.TvShow).Generate();

            try
            {
                _dbContext.PlexServers.Add(plexServer);
                _dbContext.PlexLibraries.Add(plexLibrary);
                _dbContext.SaveChanges();
            }
            catch (Exception e)
            {
                Log.Fatal(e);
                throw;
            }
        }
예제 #11
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()));
     }
 }
예제 #12
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()));
     }
 }
예제 #13
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()));
     }
 }
예제 #14
0
        public async Task <List <int> > RemoveMissingPlexShows(PlexLibrary library, List <string> showKeys)
        {
            List <int> ids = await _dbConnection.QueryAsync <int>(
                @"SELECT m.Id FROM MediaItem m
                INNER JOIN PlexShow ps ON ps.Id = m.Id
                INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId
                WHERE lp.LibraryId = @LibraryId AND ps.Key not in @Keys",
                new { LibraryId = library.Id, Keys = showKeys }).Map(result => result.ToList());

            await _dbConnection.ExecuteAsync(
                @"DELETE FROM MediaItem WHERE Id IN
                (SELECT m.Id FROM MediaItem m
                INNER JOIN PlexShow ps ON ps.Id = m.Id
                INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId
                WHERE lp.LibraryId = @LibraryId AND ps.Key not in @Keys)",
                new { LibraryId = library.Id, Keys = showKeys });

            return(ids);
        }
예제 #15
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()));
     }
 }
예제 #16
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()));
     }
 }
예제 #17
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();
        }
    }
예제 #18
0
        /// <summary>
        /// Refresh the <see cref="PlexLibrary"/>, by first deleting all (related) media and the re-adding the media again.
        /// </summary>
        /// <param name="plexLibrary">The <see cref="PlexLibrary"/> to refresh.</param>
        /// <returns></returns>
        private async Task <Result> RefreshPlexMovieLibrary(PlexLibrary plexLibrary)
        {
            if (plexLibrary == null)
            {
                return(ResultExtensions.IsNull(nameof(plexLibrary)));
            }

            // Update the MetaData of this library
            var updateMetaDataResult = plexLibrary.UpdateMetaData();

            if (updateMetaDataResult.IsFailed)
            {
                return(updateMetaDataResult);
            }

            var updateResult = await _mediator.Send(new UpdatePlexLibraryByIdCommand(plexLibrary));

            if (updateResult.IsFailed)
            {
                return(updateResult.ToResult());
            }

            var deleteResult = await _mediator.Send(new DeleteMediaFromPlexLibraryCommand(plexLibrary.Id));

            if (deleteResult.IsFailed)
            {
                return(deleteResult.ToResult());
            }

            var createResult = await _mediator.Send(new CreateUpdateOrDeletePlexMoviesCommand(plexLibrary));

            if (createResult.IsFailed)
            {
                return(createResult.ToResult());
            }

            await _signalRService.SendLibraryProgressUpdate(plexLibrary.Id, plexLibrary.MediaCount, plexLibrary.MediaCount);

            return(Result.Ok());
        }
예제 #19
0
        /// <summary>
        /// Returns the latest version of the <see cref="PlexLibrary"/> with the included media. Id and PlexServerId are copied over from the input parameter.
        /// </summary>
        /// <param name="plexLibrary"></param>
        /// <param name="authToken">The token used to authenticate with the <see cref="PlexServer"/>.</param>
        /// <param name="plexServerBaseUrl"></param>
        /// <returns></returns>
        public async Task <PlexLibrary> GetLibraryMediaAsync(PlexLibrary plexLibrary, string authToken)
        {
            // Retrieve updated version of the PlexLibrary
            var plexLibraries = await GetLibrarySectionsAsync(authToken, plexLibrary.ServerUrl);

            if (!plexLibraries.Any())
            {
                return(null);
            }

            var updatedPlexLibrary = plexLibraries.Find(x => x.Key == plexLibrary.Key);

            updatedPlexLibrary.Id           = plexLibrary.Id;
            updatedPlexLibrary.PlexServerId = plexLibrary.PlexServerId;
            updatedPlexLibrary.CheckedAt    = DateTime.Now;

            // Retrieve the media for this library
            var result = await _plexApi.GetMetadataForLibraryAsync(authToken, plexLibrary.ServerUrl, plexLibrary.Key);

            if (result == null)
            {
                return(null);
            }

            // Determine how to map based on the Library type.
            switch (result.MediaContainer.ViewGroup)
            {
            case "movie":
                updatedPlexLibrary.Movies = _mapper.Map <List <PlexMovie> >(result.MediaContainer.Metadata);
                break;

            case "show":
                updatedPlexLibrary.TvShows = _mapper.Map <List <PlexTvShow> >(result.MediaContainer.Metadata);
                break;
            }

            return(updatedPlexLibrary);
        }
예제 #20
0
 public async Task Update(PlexLibrary plexMediaSourceLibrary)
 {
     await using TvContext context = _dbContextFactory.CreateDbContext();
     context.PlexLibraries.Update(plexMediaSourceLibrary);
     await context.SaveChangesAsync();
 }
예제 #21
0
        /// <summary>
        /// Retrieves all tvshow, season and episode data and stores it in the database.
        /// </summary>
        /// <param name="authToken"></param>
        /// <param name="plexLibrary"></param>
        /// <returns></returns>
        private async Task <Result> RefreshPlexTvShowLibrary(string authToken, PlexLibrary plexLibrary)
        {
            if (plexLibrary == null)
            {
                return(ResultExtensions.IsNull("plexLibrary").LogError());
            }

            if (plexLibrary.Type != PlexMediaType.TvShow)
            {
                return(Result.Fail("PlexLibrary is not of type TvShow").LogError());
            }

            if (plexLibrary.TvShows.Count == 0)
            {
                return(Result.Fail("PlexLibrary does not contain any TvShows and thus cannot request the corresponding media").LogError());
            }

            var result = await _mediator.Send(new GetPlexLibraryByIdWithServerQuery(plexLibrary.Id));

            if (result.IsFailed)
            {
                return(result);
            }

            // Request seasons and episodes for every tv show
            var plexLibraryDb = result.Value;
            var serverUrl     = plexLibraryDb.PlexServer.ServerUrl;
            await _signalRService.SendLibraryProgressUpdate(plexLibrary.Id, 0, 3);

            var timer = new Stopwatch();

            timer.Start();

            var rawSeasonDataResult = await _plexServiceApi.GetAllSeasonsAsync(authToken, serverUrl, plexLibrary.Key);

            if (rawSeasonDataResult.IsFailed)
            {
                return(rawSeasonDataResult.ToResult());
            }

            // Phase 1 of 4: Season data was retrieved successfully.
            await _signalRService.SendLibraryProgressUpdate(plexLibrary.Id, 1, 4);

            var rawEpisodesDataResult = await _plexServiceApi.GetAllEpisodesAsync(authToken, serverUrl, plexLibrary.Key);

            if (rawEpisodesDataResult.IsFailed)
            {
                return(rawEpisodesDataResult.ToResult());
            }

            // Phase 2 of 4: Episode data was retrieved successfully.
            await _signalRService.SendLibraryProgressUpdate(plexLibrary.Id, 2, 4);

            foreach (var plexTvShow in plexLibrary.TvShows)
            {
                plexTvShow.Seasons = rawSeasonDataResult.Value.FindAll(x => x.ParentKey == plexTvShow.Key);
                foreach (var plexTvShowSeason in plexTvShow.Seasons)
                {
                    plexTvShowSeason.PlexLibraryId = plexLibrary.Id;
                    plexTvShowSeason.PlexLibrary   = plexLibrary;
                    plexTvShowSeason.TvShow        = plexTvShow;
                    plexTvShowSeason.Episodes      = rawEpisodesDataResult.Value.FindAll(x => x.ParentKey == plexTvShowSeason.Key);

                    // Set libraryId in each episode
                    plexTvShowSeason.Episodes.ForEach(x => x.PlexLibraryId = plexLibrary.Id);
                    plexTvShowSeason.MediaSize = plexTvShowSeason.Episodes.Sum(x => x.MediaSize);
                }

                plexTvShow.MediaSize = plexTvShow.Seasons.Sum(x => x.MediaSize);
            }

            // Phase 3 of 4: PlexLibrary media data was parsed successfully.
            await _signalRService.SendLibraryProgressUpdate(plexLibrary.Id, 3, 4);

            Log.Debug($"Finished retrieving all media for library {plexLibraryDb.Title} in {timer.Elapsed.TotalSeconds}");
            timer.Restart();

            // Update the MetaData of this library
            var updateMetaDataResult = plexLibrary.UpdateMetaData();

            if (updateMetaDataResult.IsFailed)
            {
                return(updateMetaDataResult);
            }

            var updateResult = await _mediator.Send(new UpdatePlexLibraryByIdCommand(plexLibrary));

            if (updateResult.IsFailed)
            {
                return(updateResult.ToResult());
            }

            var deleteResult = await _mediator.Send(new DeleteMediaFromPlexLibraryCommand(plexLibrary.Id));

            if (deleteResult.IsFailed)
            {
                return(deleteResult.ToResult());
            }

            var createResult = await _mediator.Send(new CreateUpdateOrDeletePlexTvShowsCommand(plexLibrary));

            if (createResult.IsFailed)
            {
                return(createResult.ToResult());
            }

            Log.Debug($"Finished updating all media in the database for library {plexLibraryDb.Title} in {timer.Elapsed.TotalSeconds}");

            // Phase 4 of 4: Database has been successfully updated with new library data.
            await _signalRService.SendLibraryProgressUpdate(plexLibrary.Id, 4, 4);

            return(Result.Ok());
        }
예제 #22
0
 public Task <List <int> > RemoveMissingPlexShows(PlexLibrary library, List <string> showKeys) =>
 throw new NotSupportedException();
 public CreateUpdateOrDeletePlexMoviesCommand(PlexLibrary plexLibrary)
 {
     PlexLibrary = plexLibrary;
 }
예제 #24
0
 public Task <Either <BaseError, PlexSeason> > GetOrAddPlexSeason(PlexLibrary library, PlexSeason item) =>
 throw new NotSupportedException();
예제 #25
0
 public Task <Either <BaseError, MediaItemScanResult <PlexEpisode> > > GetOrAddPlexEpisode(
     PlexLibrary library,
     PlexEpisode item) =>
 throw new NotSupportedException();
예제 #26
0
 public Task <List <PlexItemEtag> > GetExistingPlexEpisodes(PlexLibrary library, PlexSeason season) =>
 throw new NotSupportedException();
 public CreateUpdateOrDeletePlexTvShowsCommand(PlexLibrary plexLibrary)
 {
     PlexLibrary = plexLibrary;
 }
 public UpdatePlexLibraryByIdCommand(PlexLibrary plexLibrary)
 {
     PlexLibrary = plexLibrary;
 }
예제 #29
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);
    }
예제 #30
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);
    }