private async Task <bool> ShouldScanItem( IMediaServerMovieRepository <TLibrary, TMovie, TEtag> movieRepository, TLibrary library, List <TEtag> existingMovies, TMovie incoming, string localPath, bool deepScan) { // deep scan will always pull every movie if (deepScan) { return(true); } Option <TEtag> maybeExisting = existingMovies.Find(m => m.MediaServerItemId == MediaServerItemId(incoming)); string existingItemId = await maybeExisting.Map(e => e.MediaServerItemId).IfNoneAsync(string.Empty); MediaItemState existingState = await maybeExisting.Map(e => e.State).IfNoneAsync(MediaItemState.Normal); if (existingState == MediaItemState.Unavailable) { // skip scanning unavailable items that still don't exist locally if (!_localFileSystem.FileExists(localPath)) { return(false); } } else if (existingItemId == MediaServerItemId(incoming)) { // item is unchanged, but file does not exist // don't scan, but mark as unavailable if (!_localFileSystem.FileExists(localPath)) { foreach (int id in await movieRepository.FlagUnavailable(library, incoming)) { await _searchIndex.RebuildItems(_searchRepository, new List <int> { id }); } } return(false); } if (maybeExisting.IsNone) { _logger.LogDebug("INSERT: new movie {Movie}", incoming.MovieMetadata.Head().Title); } else { _logger.LogDebug("UPDATE: Etag has changed for movie {Movie}", incoming.MovieMetadata.Head().Title); } return(true); }
protected async Task <Either <BaseError, Unit> > ScanLibrary( IMediaServerMovieRepository <TLibrary, TMovie, TEtag> movieRepository, TConnectionParameters connectionParameters, TLibrary library, Func <TMovie, string> getLocalPath, string ffmpegPath, string ffprobePath, bool deepScan, CancellationToken cancellationToken) { try { Either <BaseError, List <TMovie> > entries = await GetMovieLibraryItems(connectionParameters, library); foreach (BaseError error in entries.LeftToSeq()) { return(error); } return(await ScanLibrary( movieRepository, connectionParameters, library, getLocalPath, ffmpegPath, ffprobePath, entries.RightToSeq().Flatten().ToList(), deepScan, cancellationToken)); } catch (Exception ex) when(ex is TaskCanceledException or OperationCanceledException) { return(new ScanCanceled()); } finally { _searchIndex.Commit(); } }
private async Task <Either <BaseError, Unit> > ScanLibrary( IMediaServerMovieRepository <TLibrary, TMovie, TEtag> movieRepository, TConnectionParameters connectionParameters, TLibrary library, Func <TMovie, string> getLocalPath, string ffmpegPath, string ffprobePath, List <TMovie> movieEntries, bool deepScan, CancellationToken cancellationToken) { List <TEtag> existingMovies = await movieRepository.GetExistingMovies(library); foreach (TMovie incoming in movieEntries) { if (cancellationToken.IsCancellationRequested) { return(new ScanCanceled()); } decimal percentCompletion = (decimal)movieEntries.IndexOf(incoming) / movieEntries.Count; await _mediator.Publish(new LibraryScanProgress(library.Id, percentCompletion), cancellationToken); string localPath = getLocalPath(incoming); if (await ShouldScanItem(movieRepository, library, existingMovies, incoming, localPath, deepScan) == false) { continue; } Either <BaseError, MediaItemScanResult <TMovie> > maybeMovie = await movieRepository .GetOrAdd(library, incoming) .MapT( result => { result.LocalPath = localPath; return(result); }) .BindT(existing => UpdateMetadata(connectionParameters, library, existing, incoming, deepScan)) .BindT(existing => UpdateStatistics(existing, incoming, ffmpegPath, ffprobePath)) .BindT(UpdateSubtitles); if (maybeMovie.IsLeft) { foreach (BaseError error in maybeMovie.LeftToSeq()) { _logger.LogWarning( "Error processing movie {Title}: {Error}", incoming.MovieMetadata.Head().Title, error.Value); } continue; } foreach (MediaItemScanResult <TMovie> result in maybeMovie.RightToSeq()) { await movieRepository.SetEtag(result.Item, MediaServerEtag(incoming)); if (_localFileSystem.FileExists(result.LocalPath)) { if (await movieRepository.FlagNormal(library, result.Item)) { result.IsUpdated = true; } } else { Option <int> flagResult = await movieRepository.FlagUnavailable(library, result.Item); if (flagResult.IsSome) { result.IsUpdated = true; } } 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 fileNotFoundItemIds = existingMovies.Map(m => m.MediaServerItemId) .Except(movieEntries.Map(MediaServerItemId)).ToList(); List <int> ids = await movieRepository.FlagFileNotFound(library, fileNotFoundItemIds); await _searchIndex.RebuildItems(_searchRepository, ids); await _mediator.Publish(new LibraryScanProgress(library.Id, 0), cancellationToken); return(Unit.Default); }