public async Task <Either <BaseError, Unit> > ScanFolder(LibraryPath libraryPath, string ffprobePath)
        {
            if (!_localFileSystem.IsLibraryPathAccessible(libraryPath))
            {
                return(new MediaSourceInaccessible());
            }

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

            foreach (string showFolder in allShowFolders)
            {
                Either <BaseError, MediaItemScanResult <Show> > maybeShow =
                    await FindOrCreateShow(libraryPath.Id, showFolder)
                    .BindT(show => UpdateMetadataForShow(show, showFolder))
                    .BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Poster))
                    .BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt));

                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(libraryPath, ffprobePath, result.Item, showFolder);
                },
                    _ => Task.FromResult(Unit.Default));
            }

            foreach (string path in await _televisionRepository.FindEpisodePaths(libraryPath))
            {
                if (!_localFileSystem.FileExists(path))
                {
                    _logger.LogInformation("Removing missing episode at {Path}", path);
                    await _televisionRepository.DeleteByPath(libraryPath, path);
                }
            }

            await _televisionRepository.DeleteEmptySeasons(libraryPath);

            List <int> ids = await _televisionRepository.DeleteEmptyShows(libraryPath);

            await _searchIndex.RemoveItems(ids);

            return(Unit.Default);
        }
    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 allShowFolders = _localFileSystem.ListSubdirectories(libraryPath.Path)
                                 .Filter(ShouldIncludeFolder)
                                 .OrderBy(identity)
                                 .ToList();

            foreach (string showFolder in allShowFolders)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return(new ScanCanceled());
                }

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

                Either <BaseError, MediaItemScanResult <Show> > maybeShow =
                    await FindOrCreateShow(libraryPath.Id, showFolder)
                    .BindT(show => UpdateMetadataForShow(show, showFolder))
                    .BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Poster, cancellationToken))
                    .BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt, cancellationToken))
                    .BindT(
                        show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Thumbnail, cancellationToken));

                foreach (BaseError error in maybeShow.LeftToSeq())
                {
                    _logger.LogWarning(
                        "Error processing show in folder {Folder}: {Error}",
                        showFolder,
                        error.Value);
                }

                foreach (MediaItemScanResult <Show> result in maybeShow.RightToSeq())
                {
                    Either <BaseError, Unit> scanResult = await ScanSeasons(
                        libraryPath,
                        ffmpegPath,
                        ffprobePath,
                        result.Item,
                        showFolder,
                        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 _televisionRepository.FindEpisodePaths(libraryPath))
            {
                if (!_localFileSystem.FileExists(path))
                {
                    _logger.LogInformation("Flagging missing episode at {Path}", path);
                    List <int> episodeIds = await FlagFileNotFound(libraryPath, path);

                    await _searchIndex.RebuildItems(_searchRepository, episodeIds);
                }
                else if (Path.GetFileName(path).StartsWith("._"))
                {
                    _logger.LogInformation("Removing dot underscore file at {Path}", path);
                    await _televisionRepository.DeleteByPath(libraryPath, path);
                }
            }

            await _libraryRepository.CleanEtagsForLibraryPath(libraryPath);

            await _televisionRepository.DeleteEmptySeasons(libraryPath);

            List <int> ids = await _televisionRepository.DeleteEmptyShows(libraryPath);

            await _searchIndex.RemoveItems(ids);

            return(Unit.Default);
        }
        catch (Exception ex) when(ex is TaskCanceledException or OperationCanceledException)
        {
            return(new ScanCanceled());
        }
        finally
        {
            _searchIndex.Commit();
        }
    }