Пример #1
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();
            }));
        }
Пример #2
0
    public async Task <Either <BaseError, Unit> > ScanFolder(
        LibraryPath libraryPath,
        string ffmpegPath,
        string ffprobePath,
        decimal progressMin,
        decimal progressMax,
        CancellationToken cancellationToken)
    {
        try
        {
            decimal progressSpread = progressMax - progressMin;

            var allArtistFolders = _localFileSystem.ListSubdirectories(libraryPath.Path)
                                   .Filter(ShouldIncludeFolder)
                                   .OrderBy(identity)
                                   .ToList();

            foreach (string artistFolder in allArtistFolders)
            {
                // _logger.LogDebug("Scanning artist folder {Folder}", artistFolder);
                if (cancellationToken.IsCancellationRequested)
                {
                    return(new ScanCanceled());
                }

                decimal percentCompletion = (decimal)allArtistFolders.IndexOf(artistFolder) / allArtistFolders.Count;
                await _mediator.Publish(
                    new LibraryScanProgress(libraryPath.LibraryId, progressMin + percentCompletion *progressSpread),
                    cancellationToken);

                Either <BaseError, MediaItemScanResult <Artist> > maybeArtist =
                    await FindOrCreateArtist(libraryPath.Id, artistFolder)
                    .BindT(artist => UpdateMetadataForArtist(artist, artistFolder))
                    .BindT(
                        artist => UpdateArtworkForArtist(
                            artist,
                            artistFolder,
                            ArtworkKind.Thumbnail,
                            cancellationToken))
                    .BindT(
                        artist => UpdateArtworkForArtist(
                            artist,
                            artistFolder,
                            ArtworkKind.FanArt,
                            cancellationToken));

                foreach (BaseError error in maybeArtist.LeftToSeq())
                {
                    _logger.LogWarning(
                        "Error processing artist in folder {Folder}: {Error}",
                        artistFolder,
                        error.Value);
                }

                foreach (MediaItemScanResult <Artist> result in maybeArtist.RightToSeq())
                {
                    Either <BaseError, Unit> scanResult = await ScanMusicVideos(
                        libraryPath,
                        ffmpegPath,
                        ffprobePath,
                        result.Item,
                        artistFolder,
                        cancellationToken);

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

                    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
                        });
                    }
                }
            }

            foreach (string path in await _musicVideoRepository.FindOrphanPaths(libraryPath))
            {
                _logger.LogInformation("Removing improperly named music video at {Path}", path);
                List <int> musicVideoIds = await _musicVideoRepository.DeleteByPath(libraryPath, path);

                await _searchIndex.RemoveItems(musicVideoIds);
            }

            foreach (string path in await _musicVideoRepository.FindMusicVideoPaths(libraryPath))
            {
                if (!_localFileSystem.FileExists(path))
                {
                    _logger.LogInformation("Flagging missing music video at {Path}", path);
                    List <int> musicVideoIds = await FlagFileNotFound(libraryPath, path);

                    await _searchIndex.RebuildItems(_searchRepository, musicVideoIds);
                }
                else if (Path.GetFileName(path).StartsWith("._"))
                {
                    _logger.LogInformation("Removing dot underscore file at {Path}", path);
                    List <int> musicVideoIds = await _musicVideoRepository.DeleteByPath(libraryPath, path);

                    await _searchIndex.RemoveItems(musicVideoIds);
                }
            }

            await _libraryRepository.CleanEtagsForLibraryPath(libraryPath);

            List <int> artistIds = await _artistRepository.DeleteEmptyArtists(libraryPath);

            await _searchIndex.RemoveItems(artistIds);

            return(Unit.Default);
        }
        catch (Exception ex) when(ex is TaskCanceledException or OperationCanceledException)
        {
            return(new ScanCanceled());
        }
        finally
        {
            _searchIndex.Commit();
        }
    }
Пример #3
0
        public async Task <Either <BaseError, Unit> > ScanFolder(LibraryPath libraryPath, string ffprobePath)
        {
            if (!_localFileSystem.IsLibraryPathAccessible(libraryPath))
            {
                return(new MediaSourceInaccessible());
            }

            var folderQueue = new Queue <string>();

            foreach (string folder in _localFileSystem.ListSubdirectories(libraryPath.Path).OrderBy(identity))
            {
                folderQueue.Enqueue(folder);
            }

            while (folderQueue.Count > 0)
            {
                string movieFolder = folderQueue.Dequeue();

                var allFiles = _localFileSystem.ListFiles(movieFolder)
                               .Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f)))
                               .Filter(
                    f => !ExtraFiles.Any(
                        e => Path.GetFileNameWithoutExtension(f).EndsWith(e, StringComparison.OrdinalIgnoreCase)))
                               .ToList();

                if (allFiles.Count == 0)
                {
                    foreach (string subdirectory in _localFileSystem.ListSubdirectories(movieFolder).OrderBy(identity))
                    {
                        folderQueue.Enqueue(subdirectory);
                    }

                    continue;
                }

                foreach (string file in allFiles.OrderBy(identity))
                {
                    // TODO: figure out how to rebuild playlists
                    Either <BaseError, MediaItemScanResult <Movie> > maybeMovie = await _movieRepository
                                                                                  .GetOrAdd(libraryPath, file)
                                                                                  .BindT(movie => UpdateStatistics(movie, ffprobePath))
                                                                                  .BindT(UpdateMetadata)
                                                                                  .BindT(movie => UpdateArtwork(movie, ArtworkKind.Poster))
                                                                                  .BindT(movie => UpdateArtwork(movie, ArtworkKind.FanArt));

                    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 movie at {Path}: {Error}", file, error.Value);
                        return(Task.CompletedTask);
                    });
                }
            }

            foreach (string path in await _movieRepository.FindMoviePaths(libraryPath))
            {
                if (!_localFileSystem.FileExists(path))
                {
                    _logger.LogInformation("Removing missing movie at {Path}", path);
                    List <int> ids = await _movieRepository.DeleteByPath(libraryPath, path);

                    await _searchIndex.RemoveItems(ids);
                }
            }

            return(Unit.Default);
        }
Пример #4
0
    public async Task <Either <BaseError, Unit> > ScanFolder(
        LibraryPath libraryPath,
        string ffmpegPath,
        string ffprobePath,
        decimal progressMin,
        decimal progressMax,
        CancellationToken cancellationToken)
    {
        try
        {
            decimal progressSpread = progressMax - progressMin;

            var foldersCompleted = 0;

            var folderQueue = new Queue <string>();

            if (ShouldIncludeFolder(libraryPath.Path))
            {
                folderQueue.Enqueue(libraryPath.Path);
            }

            foreach (string folder in _localFileSystem.ListSubdirectories(libraryPath.Path)
                     .Filter(ShouldIncludeFolder)
                     .OrderBy(identity))
            {
                folderQueue.Enqueue(folder);
            }

            while (folderQueue.Count > 0)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(new ScanCanceled());
                }

                decimal percentCompletion = (decimal)foldersCompleted / (foldersCompleted + folderQueue.Count);
                await _mediator.Publish(
                    new LibraryScanProgress(libraryPath.LibraryId, progressMin + percentCompletion *progressSpread),
                    cancellationToken);

                string otherVideoFolder = folderQueue.Dequeue();
                foldersCompleted++;

                var filesForEtag = _localFileSystem.ListFiles(otherVideoFolder).ToList();

                var allFiles = filesForEtag
                               .Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f)))
                               .Filter(f => !Path.GetFileName(f).StartsWith("._"))
                               .ToList();

                foreach (string subdirectory in _localFileSystem.ListSubdirectories(otherVideoFolder)
                         .Filter(ShouldIncludeFolder)
                         .OrderBy(identity))
                {
                    folderQueue.Enqueue(subdirectory);
                }

                string etag = FolderEtag.Calculate(otherVideoFolder, _localFileSystem);
                Option <LibraryFolder> knownFolder = libraryPath.LibraryFolders
                                                     .Filter(f => f.Path == otherVideoFolder)
                                                     .HeadOrNone();

                // skip folder if etag matches
                if (!allFiles.Any() || await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) ==
                    etag)
                {
                    continue;
                }

                _logger.LogDebug(
                    "UPDATE: Etag has changed for folder {Folder}",
                    otherVideoFolder);

                foreach (string file in allFiles.OrderBy(identity))
                {
                    Either <BaseError, MediaItemScanResult <OtherVideo> > maybeVideo = await _otherVideoRepository
                                                                                       .GetOrAdd(libraryPath, file)
                                                                                       .BindT(video => UpdateStatistics(video, ffmpegPath, ffprobePath))
                                                                                       .BindT(UpdateMetadata)
                                                                                       .BindT(UpdateSubtitles)
                                                                                       .BindT(FlagNormal);

                    foreach (BaseError error in maybeVideo.LeftToSeq())
                    {
                        _logger.LogWarning("Error processing other video at {Path}: {Error}", file, error.Value);
                    }

                    foreach (MediaItemScanResult <OtherVideo> result in maybeVideo.RightToSeq())
                    {
                        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
                            });
                        }

                        await _libraryRepository.SetEtag(libraryPath, knownFolder, otherVideoFolder, etag);
                    }
                }
            }

            foreach (string path in await _otherVideoRepository.FindOtherVideoPaths(libraryPath))
            {
                if (!_localFileSystem.FileExists(path))
                {
                    _logger.LogInformation("Flagging missing other video at {Path}", path);
                    List <int> otherVideoIds = await FlagFileNotFound(libraryPath, path);

                    await _searchIndex.RebuildItems(_searchRepository, otherVideoIds);
                }
                else if (Path.GetFileName(path).StartsWith("._"))
                {
                    _logger.LogInformation("Removing dot underscore file at {Path}", path);
                    List <int> otherVideoIds = await _otherVideoRepository.DeleteByPath(libraryPath, path);

                    await _searchIndex.RemoveItems(otherVideoIds);
                }
            }

            await _libraryRepository.CleanEtagsForLibraryPath(libraryPath);

            return(Unit.Default);
        }
        catch (Exception ex) when(ex is TaskCanceledException or OperationCanceledException)
        {
            return(new ScanCanceled());
        }
        finally
        {
            _searchIndex.Commit();
        }
    }
Пример #5
0
    public async Task <Either <BaseError, Unit> > ScanLibrary(
        string address,
        string apiKey,
        JellyfinLibrary library,
        string ffmpegPath,
        string ffprobePath,
        CancellationToken cancellationToken)
    {
        try
        {
            List <JellyfinItemEtag> existingShows = await _televisionRepository.GetExistingShows(library);

            // TODO: maybe get quick list of item ids and etags from api to compare first
            // TODO: paging?

            List <JellyfinPathReplacement> pathReplacements = await _mediaSourceRepository
                                                              .GetJellyfinPathReplacements(library.MediaSourceId);

            Either <BaseError, List <JellyfinShow> > maybeShows = await _jellyfinApiClient.GetShowLibraryItems(
                address,
                apiKey,
                library.MediaSourceId,
                library.ItemId);

            foreach (BaseError error in maybeShows.LeftToSeq())
            {
                _logger.LogWarning(
                    "Error synchronizing jellyfin library {Path}: {Error}",
                    library.Name,
                    error.Value);
            }

            foreach (List <JellyfinShow> shows in maybeShows.RightToSeq())
            {
                Either <BaseError, Unit> scanResult = await ProcessShows(
                    address,
                    apiKey,
                    library,
                    ffmpegPath,
                    ffprobePath,
                    pathReplacements,
                    existingShows,
                    shows,
                    cancellationToken);

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

                foreach (Unit _ in scanResult.RightToSeq())
                {
                    var incomingShowIds = shows.Map(s => s.ItemId).ToList();
                    var showIds         = existingShows
                                          .Filter(i => !incomingShowIds.Contains(i.ItemId))
                                          .Map(m => m.ItemId)
                                          .ToList();
                    List <int> missingShowIds = await _televisionRepository.RemoveMissingShows(library, showIds);

                    await _searchIndex.RemoveItems(missingShowIds);

                    await _televisionRepository.DeleteEmptySeasons(library);

                    List <int> emptyShowIds = await _televisionRepository.DeleteEmptyShows(library);

                    await _searchIndex.RemoveItems(emptyShowIds);

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

            return(Unit.Default);
        }
        catch (Exception ex) when(ex is TaskCanceledException or OperationCanceledException)
        {
            return(new ScanCanceled());
        }
        finally
        {
            _searchIndex.Commit();
        }
    }
Пример #6
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);
        }