@ -56,23 +56,31 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
{
{
try
try
{
{
Either < BaseError , List < TShow > > entries = await GetShowLibraryItems ( connectionParameters , library ) ;
Either < BaseError , int > maybeCount = await CountShowLibraryItems ( connectionParameters , library ) ;
foreach ( BaseError error in maybeCount . LeftToSeq ( ) )
foreach ( BaseError error in entries . LeftToSeq ( ) )
{
{
return error ;
return error ;
}
}
return await ScanLibrary (
foreach ( int count in maybeCount . RightToSeq ( ) )
televisionRepository ,
{
connectionParameters ,
_l ogger . LogDebug ( "Library {Library} contains {Count} shows" , library . Name , count ) ;
library ,
getLocalPath ,
return await ScanLibrary (
ffmpegPath ,
televisionRepository ,
ffprobePath ,
connectionParameters ,
entries . RightToSeq ( ) . Flatten ( ) . ToList ( ) ,
library ,
deepScan ,
getLocalPath ,
cancellationToken ) ;
ffmpegPath ,
ffprobePath ,
GetShowLibraryItems ( connectionParameters , library ) ,
count ,
deepScan ,
cancellationToken ) ;
}
// this won't happen
return Unit . Default ;
}
}
catch ( Exception ex ) when ( ex is TaskCanceledException or OperationCanceledException )
catch ( Exception ex ) when ( ex is TaskCanceledException or OperationCanceledException )
{
{
@ -84,7 +92,11 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
}
}
}
}
protected abstract Task < Either < BaseError , List < TShow > > > GetShowLibraryItems (
protected abstract Task < Either < BaseError , int > > CountShowLibraryItems (
TConnectionParameters connectionParameters ,
TLibrary library ) ;
protected abstract IAsyncEnumerable < TShow > GetShowLibraryItems (
TConnectionParameters connectionParameters ,
TConnectionParameters connectionParameters ,
TLibrary library ) ;
TLibrary library ) ;
@ -102,21 +114,24 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
Func < TEpisode , string > getLocalPath ,
Func < TEpisode , string > getLocalPath ,
string ffmpegPath ,
string ffmpegPath ,
string ffprobePath ,
string ffprobePath ,
List < TShow > showEntries ,
IAsyncEnumerable < TShow > showEntries ,
int totalShowCount ,
bool deepScan ,
bool deepScan ,
CancellationToken cancellationToken )
CancellationToken cancellationToken )
{
{
var incomingItemIds = new List < string > ( ) ;
List < TEtag > existingShows = await televisionRepository . GetExistingShows ( library ) ;
List < TEtag > existingShows = await televisionRepository . GetExistingShows ( library ) ;
var sortedShows = showEntries . OrderBy ( s = > s . ShowMetadata . Head ( ) . SortTitle ) . ToList ( ) ;
await foreach ( TShow incoming in showEntries . WithCancellation ( cancellationToken ) )
foreach ( TShow incoming in showEntries )
{
{
if ( cancellationToken . IsCancellationRequested )
if ( cancellationToken . IsCancellationRequested )
{
{
return new ScanCanceled ( ) ;
return new ScanCanceled ( ) ;
}
}
decimal percentCompletion = ( decimal ) sortedShows . IndexOf ( incoming ) / sortedShows . Count ;
incomingItemIds . Add ( MediaServerItemId ( incoming ) ) ;
decimal percentCompletion = Math . Clamp ( ( decimal ) incomingItemIds . Count / totalShowCount , 0 , 1 ) ;
await _ mediator . Publish ( new LibraryScanProgress ( library . Id , percentCompletion ) , cancellationToken ) ;
await _ mediator . Publish ( new LibraryScanProgress ( library . Id , percentCompletion ) , cancellationToken ) ;
Either < BaseError , MediaItemScanResult < TShow > > maybeShow = await televisionRepository
Either < BaseError , MediaItemScanResult < TShow > > maybeShow = await televisionRepository
@ -138,16 +153,23 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
foreach ( MediaItemScanResult < TShow > result in maybeShow . RightToSeq ( ) )
foreach ( MediaItemScanResult < TShow > result in maybeShow . RightToSeq ( ) )
{
{
Either < BaseError , List < TSeason > > entries = await GetSeasonLibraryItems (
Either < BaseError , int > maybeCount = await CountSeasonLibraryItems (
library ,
connectionParameters ,
connectionParameters ,
library ,
result . Item ) ;
result . Item ) ;
foreach ( BaseError error in maybeCount . LeftToSeq ( ) )
foreach ( BaseError error in entries . LeftToSeq ( ) )
{
{
return error ;
return error ;
}
}
foreach ( int count in maybeCount . RightToSeq ( ) )
{
_l ogger . LogDebug (
"Show {Title} contains {Count} seasons" ,
result . Item . ShowMetadata . Head ( ) . Title ,
count ) ;
}
Either < BaseError , Unit > scanResult = await ScanSeasons (
Either < BaseError , Unit > scanResult = await ScanSeasons (
televisionRepository ,
televisionRepository ,
library ,
library ,
@ -156,7 +178,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
connectionParameters ,
connectionParameters ,
ffmpegPath ,
ffmpegPath ,
ffprobePath ,
ffprobePath ,
entries . RightToSeq ( ) . Flatten ( ) . ToList ( ) ,
GetSeasonLibraryItems ( library , connectionParameters , result . Item ) ,
deepScan ,
deepScan ,
cancellationToken ) ;
cancellationToken ) ;
@ -175,8 +197,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
}
}
// trash shows that are no longer present on the media server
// trash shows that are no longer present on the media server
var fileNotFoundItemIds = existingShows . Map ( s = > s . MediaServerItemId )
var fileNotFoundItemIds = existingShows . Map ( s = > s . MediaServerItemId ) . Except ( incomingItemIds ) . ToList ( ) ;
. Except ( showEntries . Map ( MediaServerItemId ) ) . ToList ( ) ;
List < int > ids = await televisionRepository . FlagFileNotFoundShows ( library , fileNotFoundItemIds ) ;
List < int > ids = await televisionRepository . FlagFileNotFoundShows ( library , fileNotFoundItemIds ) ;
await _ searchIndex . RebuildItems ( _ searchRepository , ids ) ;
await _ searchIndex . RebuildItems ( _ searchRepository , ids ) ;
@ -185,14 +206,25 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
return Unit . Default ;
return Unit . Default ;
}
}
protected abstract Task < Either < BaseError , List < TSeason > > > GetSeasonLibraryItems (
protected abstract Task < Either < BaseError , int > > CountSeasonLibraryItems (
TConnectionParameters connectionParameters ,
TLibrary library ,
TShow show ) ;
protected abstract IAsyncEnumerable < TSeason > GetSeasonLibraryItems (
TLibrary library ,
TLibrary library ,
TConnectionParameters connectionParameters ,
TConnectionParameters connectionParameters ,
TShow show ) ;
TShow show ) ;
protected abstract Task < Either < BaseError , List < TEpisode > > > GetEpisodeLibraryItems (
protected abstract Task < Either < BaseError , int > > CountEpisodeLibraryItems (
TConnectionParameters connectionParameters ,
TLibrary library ,
TSeason season ) ;
protected abstract IAsyncEnumerable < TEpisode > GetEpisodeLibraryItems (
TLibrary library ,
TLibrary library ,
TConnectionParameters connectionParameters ,
TConnectionParameters connectionParameters ,
TShow show ,
TSeason season ) ;
TSeason season ) ;
protected abstract Task < Option < ShowMetadata > > GetFullMetadata (
protected abstract Task < Option < ShowMetadata > > GetFullMetadata (
@ -236,14 +268,14 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
TConnectionParameters connectionParameters ,
TConnectionParameters connectionParameters ,
string ffmpegPath ,
string ffmpegPath ,
string ffprobePath ,
string ffprobePath ,
List < TSeason > seasonEntries ,
IAsyncEnumerable < TSeason > seasonEntries ,
bool deepScan ,
bool deepScan ,
CancellationToken cancellationToken )
CancellationToken cancellationToken )
{
{
var incomingItemIds = new List < string > ( ) ;
List < TEtag > existingSeasons = await televisionRepository . GetExistingSeasons ( library , show ) ;
List < TEtag > existingSeasons = await televisionRepository . GetExistingSeasons ( library , show ) ;
var sortedSeasons = seasonEntries . OrderBy ( s = > s . SeasonNumber ) . ToList ( ) ;
await foreach ( TSeason incoming in seasonEntries . WithCancellation ( cancellationToken ) )
foreach ( TSeason incoming in sortedSeasons )
{
{
incoming . ShowId = show . Id ;
incoming . ShowId = show . Id ;
@ -252,6 +284,8 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
return new ScanCanceled ( ) ;
return new ScanCanceled ( ) ;
}
}
incomingItemIds . Add ( MediaServerItemId ( incoming ) ) ;
Either < BaseError , MediaItemScanResult < TSeason > > maybeSeason = await televisionRepository
Either < BaseError , MediaItemScanResult < TSeason > > maybeSeason = await televisionRepository
. GetOrAdd ( library , incoming )
. GetOrAdd ( library , incoming )
. BindT ( existing = > UpdateMetadata ( connectionParameters , library , existing , incoming , deepScan ) ) ;
. BindT ( existing = > UpdateMetadata ( connectionParameters , library , existing , incoming , deepScan ) ) ;
@ -272,16 +306,24 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
foreach ( MediaItemScanResult < TSeason > result in maybeSeason . RightToSeq ( ) )
foreach ( MediaItemScanResult < TSeason > result in maybeSeason . RightToSeq ( ) )
{
{
Either < BaseError , List < TEpisode > > entries = await GetEpisodeLibraryItems (
Either < BaseError , int > maybeCount = await CountEpisodeLibraryItems (
library ,
connectionParameters ,
connectionParameters ,
library ,
result . Item ) ;
result . Item ) ;
foreach ( BaseError error in maybeCount . LeftToSeq ( ) )
foreach ( BaseError error in entries . LeftToSeq ( ) )
{
{
return error ;
return error ;
}
}
foreach ( int count in maybeCount . RightToSeq ( ) )
{
_l ogger . LogDebug (
"Show {Title} season {Season} contains {Count} episodes" ,
show . ShowMetadata . Head ( ) . Title ,
result . Item . SeasonNumber ,
count ) ;
}
Either < BaseError , Unit > scanResult = await ScanEpisodes (
Either < BaseError , Unit > scanResult = await ScanEpisodes (
televisionRepository ,
televisionRepository ,
library ,
library ,
@ -291,7 +333,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
connectionParameters ,
connectionParameters ,
ffmpegPath ,
ffmpegPath ,
ffprobePath ,
ffprobePath ,
entries . RightToSeq ( ) . Flatten ( ) . ToList ( ) ,
GetEpisodeLibraryItems ( library , connectionParameters , show , result . Item ) ,
deepScan ,
deepScan ,
cancellationToken ) ;
cancellationToken ) ;
@ -312,8 +354,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
}
}
// trash seasons that are no longer present on the media server
// trash seasons that are no longer present on the media server
var fileNotFoundItemIds = existingSeasons . Map ( s = > s . MediaServerItemId )
var fileNotFoundItemIds = existingSeasons . Map ( s = > s . MediaServerItemId ) . Except ( incomingItemIds ) . ToList ( ) ;
. Except ( seasonEntries . Map ( MediaServerItemId ) ) . ToList ( ) ;
List < int > ids = await televisionRepository . FlagFileNotFoundSeasons ( library , fileNotFoundItemIds ) ;
List < int > ids = await televisionRepository . FlagFileNotFoundSeasons ( library , fileNotFoundItemIds ) ;
await _ searchIndex . RebuildItems ( _ searchRepository , ids ) ;
await _ searchIndex . RebuildItems ( _ searchRepository , ids ) ;
@ -329,20 +370,22 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
TConnectionParameters connectionParameters ,
TConnectionParameters connectionParameters ,
string ffmpegPath ,
string ffmpegPath ,
string ffprobePath ,
string ffprobePath ,
List < TEpisode > episodeEntries ,
IAsyncEnumerable < TEpisode > episodeEntries ,
bool deepScan ,
bool deepScan ,
CancellationToken cancellationToken )
CancellationToken cancellationToken )
{
{
var incomingItemIds = new List < string > ( ) ;
List < TEtag > existingEpisodes = await televisionRepository . GetExistingEpisodes ( library , season ) ;
List < TEtag > existingEpisodes = await televisionRepository . GetExistingEpisodes ( library , season ) ;
var sortedEpisodes = episodeEntries . OrderBy ( s = > s . EpisodeMetadata . Head ( ) . EpisodeNumber ) . ToList ( ) ;
await foreach ( TEpisode incoming in episodeEntries . WithCancellation ( cancellationToken ) )
foreach ( TEpisode incoming in sortedEpisodes )
{
{
if ( cancellationToken . IsCancellationRequested )
if ( cancellationToken . IsCancellationRequested )
{
{
return new ScanCanceled ( ) ;
return new ScanCanceled ( ) ;
}
}
incomingItemIds . Add ( MediaServerItemId ( incoming ) ) ;
string localPath = getLocalPath ( incoming ) ;
string localPath = getLocalPath ( incoming ) ;
if ( await ShouldScanItem (
if ( await ShouldScanItem (
televisionRepository ,
televisionRepository ,
@ -414,8 +457,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
}
}
// trash episodes that are no longer present on the media server
// trash episodes that are no longer present on the media server
var fileNotFoundItemIds = existingEpisodes . Map ( m = > m . MediaServerItemId )
var fileNotFoundItemIds = existingEpisodes . Map ( m = > m . MediaServerItemId ) . Except ( incomingItemIds ) . ToList ( ) ;
. Except ( episodeEntries . Map ( MediaServerItemId ) ) . ToList ( ) ;
List < int > ids = await televisionRepository . FlagFileNotFoundEpisodes ( library , fileNotFoundItemIds ) ;
List < int > ids = await televisionRepository . FlagFileNotFoundEpisodes ( library , fileNotFoundItemIds ) ;
await _ searchIndex . RebuildItems ( _ searchRepository , ids ) ;
await _ searchIndex . RebuildItems ( _ searchRepository , ids ) ;