Browse Source

properly restore all local library items from trash during scans (#1660)

pull/1661/head
Jason Dove 1 year ago committed by GitHub
parent
commit
7702999b9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 1
      ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs
  3. 36
      ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs
  4. 2
      ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs
  5. 24
      ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs
  6. 24
      ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs
  7. 46
      ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs
  8. 24
      ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs
  9. 17
      ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs

1
CHANGELOG.md

@ -55,6 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix bug where replacing files in Plex would be missed by subsequent ETV library scans - Fix bug where replacing files in Plex would be missed by subsequent ETV library scans
- This fix will require a one-time re-scan of each Plex library in full - This fix will require a one-time re-scan of each Plex library in full
- After the initial full scan, incremental scans will behave as normal - After the initial full scan, incremental scans will behave as normal
- Fix edge case where some local episodes, music videos, other videos, songs, images would not automatically be restored from trash
### Changed ### Changed
- Log search index updates under scanner category at debug level, to indicate a potential cause for the UI being out of date - Log search index updates under scanner category at debug level, to indicate a potential cause for the UI being out of date

1
ErsatzTV.Core/Interfaces/Repositories/ILibraryRepository.cs

@ -18,5 +18,4 @@ public interface ILibraryRepository
Task<LibraryFolder> GetOrAddFolder(LibraryPath libraryPath, Option<int> maybeParentFolder, string folder); Task<LibraryFolder> GetOrAddFolder(LibraryPath libraryPath, Option<int> maybeParentFolder, string folder);
Task UpdateLibraryFolderId(MediaFile mediaFile, int libraryFolderId); Task UpdateLibraryFolderId(MediaFile mediaFile, int libraryFolderId);
Task UpdatePath(LibraryPath libraryPath, string normalizedLibraryPath); Task UpdatePath(LibraryPath libraryPath, string normalizedLibraryPath);
Task<System.Collections.Generic.HashSet<string>> FindAllMissingFiles(LibraryPath libraryPath);
} }

36
ErsatzTV.Infrastructure/Data/Repositories/LibraryRepository.cs

@ -1,6 +1,5 @@
using Dapper; using Dapper;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Infrastructure.Extensions; using ErsatzTV.Infrastructure.Extensions;
@ -212,41 +211,6 @@ public class LibraryRepository : ILibraryRepository
new { Path = normalizedLibraryPath, libraryPath.Id }); new { Path = normalizedLibraryPath, libraryPath.Id });
} }
public async Task<System.Collections.Generic.HashSet<string>> FindAllMissingFiles(LibraryPath libraryPath)
{
var result = new System.Collections.Generic.HashSet<string>();
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
IAsyncEnumerable<MediaItem> items = dbContext.MediaItems
.AsNoTracking()
.Filter(mi => mi.LibraryPathId == libraryPath.Id)
.Filter(mi => mi.State == MediaItemState.FileNotFound)
.Include(mi => (mi as Movie).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Episode).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Song).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as MusicVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as OtherVideo).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.Include(mi => (mi as Image).MediaVersions)
.ThenInclude(mv => mv.MediaFiles)
.AsAsyncEnumerable();
await foreach (MediaItem item in items)
{
MediaVersion version = item.GetHeadVersion();
foreach (MediaFile file in version.MediaFiles)
{
result.Add(file.Path);
}
}
return result;
}
private static LibraryFolder CreateNewFolder(LibraryPath libraryPath, Option<int> maybeParentFolder, string folder) private static LibraryFolder CreateNewFolder(LibraryPath libraryPath, Option<int> maybeParentFolder, string folder)
{ {
int? parentId = null; int? parentId = null;

2
ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs

@ -91,7 +91,7 @@ public class MediaItemRepository : IMediaItemRepository
return await dbContext.Connection.QueryAsync<string>( return await dbContext.Connection.QueryAsync<string>(
@"SELECT MF.Path @"SELECT MF.Path
FROM MediaItem M FROM MediaItem M
INNER JOIN MediaVersion MV on M.Id = COALESCE(MovieId, MusicVideoId, OtherVideoId, SongId, EpisodeId) INNER JOIN MediaVersion MV on M.Id = COALESCE(MovieId, MusicVideoId, OtherVideoId, SongId, EpisodeId, ImageId)
INNER JOIN MediaFile MF on MV.Id = MF.MediaVersionId INNER JOIN MediaFile MF on MV.Id = MF.MediaVersionId
WHERE M.State IN (1,2) AND M.LibraryPathId = @LibraryPathId", WHERE M.State IN (1,2) AND M.LibraryPathId = @LibraryPathId",
new { LibraryPathId = libraryPath.Id }) new { LibraryPathId = libraryPath.Id })

24
ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs

@ -1,4 +1,5 @@
using Bugsnag; using System.Collections.Immutable;
using Bugsnag;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors; using ErsatzTV.Core.Errors;
@ -20,6 +21,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner
private readonly IClient _client; private readonly IClient _client;
private readonly IImageRepository _imageRepository; private readonly IImageRepository _imageRepository;
private readonly ILibraryRepository _libraryRepository; private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<ImageFolderScanner> _logger; private readonly ILogger<ImageFolderScanner> _logger;
@ -54,6 +56,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner
_mediator = mediator; _mediator = mediator;
_imageRepository = imageRepository; _imageRepository = imageRepository;
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
_client = client; _client = client;
_logger = logger; _logger = logger;
} }
@ -83,6 +86,8 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner
await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath); await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath);
} }
ImmutableHashSet<string> allTrashedItems = await _mediaItemRepository.GetAllTrashedItems(libraryPath);
if (ShouldIncludeFolder(libraryPath.Path) && allFolders.Add(libraryPath.Path)) if (ShouldIncludeFolder(libraryPath.Path) && allFolders.Add(libraryPath.Path))
{ {
folderQueue.Enqueue(libraryPath.Path); folderQueue.Enqueue(libraryPath.Path);
@ -139,11 +144,24 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner
maybeParentFolder, maybeParentFolder,
imageFolder); imageFolder);
// skip folder if etag matches if (knownFolder.Etag == etag)
if (allFiles.Count == 0 || knownFolder.Etag == etag) {
if (allFiles.Any(allTrashedItems.Contains))
{
_logger.LogDebug("Previously trashed items are now present in folder {Folder}", imageFolder);
}
else
{ {
// etag matches and no trashed items are now present, continue to next folder
continue; continue;
} }
}
else
{
_logger.LogDebug(
"UPDATE: Etag has changed for folder {Folder}",
imageFolder);
}
// walk up to get duration, if needed // walk up to get duration, if needed
LibraryFolder? currentFolder = knownFolder; LibraryFolder? currentFolder = knownFolder;

24
ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs

@ -1,4 +1,5 @@
using Bugsnag; using System.Collections.Immutable;
using Bugsnag;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors; using ErsatzTV.Core.Errors;
@ -20,6 +21,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
private readonly IArtistRepository _artistRepository; private readonly IArtistRepository _artistRepository;
private readonly IClient _client; private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository; private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
@ -59,6 +61,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
_artistRepository = artistRepository; _artistRepository = artistRepository;
_musicVideoRepository = musicVideoRepository; _musicVideoRepository = musicVideoRepository;
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
_mediator = mediator; _mediator = mediator;
_client = client; _client = client;
_logger = logger; _logger = logger;
@ -84,6 +87,8 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath); await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath);
} }
ImmutableHashSet<string> allTrashedItems = await _mediaItemRepository.GetAllTrashedItems(libraryPath);
var allArtistFolders = _localFileSystem.ListSubdirectories(libraryPath.Path) var allArtistFolders = _localFileSystem.ListSubdirectories(libraryPath.Path)
.Filter(ShouldIncludeFolder) .Filter(ShouldIncludeFolder)
.OrderBy(identity) .OrderBy(identity)
@ -151,6 +156,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
ffprobePath, ffprobePath,
result.Item, result.Item,
artistFolder, artistFolder,
allTrashedItems,
cancellationToken); cancellationToken);
foreach (ScanCanceled error in scanResult.LeftToSeq().OfType<ScanCanceled>()) foreach (ScanCanceled error in scanResult.LeftToSeq().OfType<ScanCanceled>())
@ -312,6 +318,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
string ffprobePath, string ffprobePath,
Artist artist, Artist artist,
string artistFolder, string artistFolder,
ImmutableHashSet<string> allTrashedItems,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var folderQueue = new Queue<string>(); var folderQueue = new Queue<string>();
@ -345,11 +352,24 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
maybeParentFolder, maybeParentFolder,
musicVideoFolder); musicVideoFolder);
// skip folder if etag matches
if (knownFolder.Etag == etag) if (knownFolder.Etag == etag)
{ {
if (allFiles.Any(allTrashedItems.Contains))
{
_logger.LogDebug("Previously trashed items are now present in folder {Folder}", musicVideoFolder);
}
else
{
// etag matches and no trashed items are now present, continue to next folder
continue; continue;
} }
}
else
{
_logger.LogDebug(
"UPDATE: Etag has changed for folder {Folder}",
musicVideoFolder);
}
var hasErrors = false; var hasErrors = false;

46
ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs

@ -1,4 +1,5 @@
using Bugsnag; using System.Collections.Immutable;
using Bugsnag;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors; using ErsatzTV.Core.Errors;
@ -19,6 +20,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{ {
private readonly IClient _client; private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository; private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
@ -57,6 +59,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
_mediator = mediator; _mediator = mediator;
_otherVideoRepository = otherVideoRepository; _otherVideoRepository = otherVideoRepository;
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
_client = client; _client = client;
_logger = logger; _logger = logger;
} }
@ -78,16 +81,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
var allFolders = new System.Collections.Generic.HashSet<string>(); var allFolders = new System.Collections.Generic.HashSet<string>();
var folderQueue = new Queue<string>(); var folderQueue = new Queue<string>();
System.Collections.Generic.HashSet<string> allMissingFiles = ImmutableHashSet<string> allTrashedItems = await _mediaItemRepository.GetAllTrashedItems(libraryPath);
await _libraryRepository.FindAllMissingFiles(libraryPath);
if (allMissingFiles.Count > 0)
{
_logger.LogDebug(
"Library path {Path} has {Count} missing files",
libraryPath.Path,
allMissingFiles.Count);
}
string normalizedLibraryPath = libraryPath.Path.TrimEnd( string normalizedLibraryPath = libraryPath.Path.TrimEnd(
Path.DirectorySeparatorChar, Path.DirectorySeparatorChar,
@ -160,34 +154,24 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
maybeParentFolder, maybeParentFolder,
otherVideoFolder); otherVideoFolder);
bool hasMissingFiles = allFiles.Any(allMissingFiles.Contains); if (knownFolder.Etag == etag)
bool isSameEtag = !hasMissingFiles && knownFolder.Etag == etag;
_logger.LogDebug(
"Scanning other video folder {Folder}; file count: {Count}, etag: {LastEtag} => {Etag}; has missing files: {HasMissingFiles}",
otherVideoFolder,
allFiles.Count,
etag,
knownFolder.Etag,
hasMissingFiles);
// skip empty folder
if (allFiles.Count == 0)
{ {
_logger.LogDebug("Skipping empty other videos folder"); if (allFiles.Any(allTrashedItems.Contains))
continue; {
_logger.LogDebug("Previously trashed items are now present in folder {Folder}", otherVideoFolder);
} }
else
// skip folder if etag matches
if (isSameEtag)
{ {
_logger.LogDebug("Skipping unchanged other videos folder, that contains no missing items"); // etag matches and no trashed items are now present, continue to next folder
continue; continue;
} }
}
else
{
_logger.LogDebug( _logger.LogDebug(
"UPDATE: Etag has changed for folder {Folder}", "UPDATE: Etag has changed for folder {Folder}",
otherVideoFolder); otherVideoFolder);
}
var hasErrors = false; var hasErrors = false;

24
ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs

@ -1,4 +1,5 @@
using Bugsnag; using System.Collections.Immutable;
using Bugsnag;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors; using ErsatzTV.Core.Errors;
@ -19,6 +20,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
{ {
private readonly IClient _client; private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository; private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<SongFolderScanner> _logger; private readonly ILogger<SongFolderScanner> _logger;
@ -54,6 +56,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
_mediator = mediator; _mediator = mediator;
_songRepository = songRepository; _songRepository = songRepository;
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
_client = client; _client = client;
_logger = logger; _logger = logger;
} }
@ -82,6 +85,8 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath); await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath);
} }
ImmutableHashSet<string> allTrashedItems = await _mediaItemRepository.GetAllTrashedItems(libraryPath);
if (ShouldIncludeFolder(libraryPath.Path)) if (ShouldIncludeFolder(libraryPath.Path))
{ {
folderQueue.Enqueue(libraryPath.Path); folderQueue.Enqueue(libraryPath.Path);
@ -136,11 +141,24 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner
maybeParentFolder, maybeParentFolder,
songFolder); songFolder);
// skip folder if etag matches if (knownFolder.Etag == etag)
if (allFiles.Count == 0 || knownFolder.Etag == etag) {
if (allFiles.Any(allTrashedItems.Contains))
{ {
_logger.LogDebug("Previously trashed items are now present in folder {Folder}", songFolder);
}
else
{
// etag matches and no trashed items are now present, continue to next folder
continue; continue;
} }
}
else
{
_logger.LogDebug(
"UPDATE: Etag has changed for folder {Folder}",
songFolder);
}
_logger.LogDebug( _logger.LogDebug(
"UPDATE: Etag has changed for folder {Folder}", "UPDATE: Etag has changed for folder {Folder}",

17
ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs

@ -1,4 +1,5 @@
using Bugsnag; using System.Collections.Immutable;
using Bugsnag;
using ErsatzTV.Core; using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Errors; using ErsatzTV.Core.Errors;
@ -20,6 +21,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private readonly IClient _client; private readonly IClient _client;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider; private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILibraryRepository _libraryRepository; private readonly ILibraryRepository _libraryRepository;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
@ -60,6 +62,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
_localSubtitlesProvider = localSubtitlesProvider; _localSubtitlesProvider = localSubtitlesProvider;
_metadataRepository = metadataRepository; _metadataRepository = metadataRepository;
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
_mediaItemRepository = mediaItemRepository;
_mediator = mediator; _mediator = mediator;
_client = client; _client = client;
_fallbackMetadataProvider = fallbackMetadataProvider; _fallbackMetadataProvider = fallbackMetadataProvider;
@ -86,6 +89,8 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath); await _libraryRepository.UpdatePath(libraryPath, normalizedLibraryPath);
} }
ImmutableHashSet<string> allTrashedItems = await _mediaItemRepository.GetAllTrashedItems(libraryPath);
var allShowFolders = _localFileSystem.ListSubdirectories(libraryPath.Path) var allShowFolders = _localFileSystem.ListSubdirectories(libraryPath.Path)
.Filter(ShouldIncludeFolder) .Filter(ShouldIncludeFolder)
.OrderBy(identity) .OrderBy(identity)
@ -153,6 +158,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
ffprobePath, ffprobePath,
result.Item, result.Item,
showFolder, showFolder,
allTrashedItems,
cancellationToken); cancellationToken);
foreach (ScanCanceled error in scanResult.LeftToSeq().OfType<ScanCanceled>()) foreach (ScanCanceled error in scanResult.LeftToSeq().OfType<ScanCanceled>())
@ -227,6 +233,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
string ffprobePath, string ffprobePath,
Show show, Show show,
string showFolder, string showFolder,
ImmutableHashSet<string> allTrashedItems,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
foreach (string seasonFolder in _localFileSystem.ListSubdirectories(showFolder).Filter(ShouldIncludeFolder) foreach (string seasonFolder in _localFileSystem.ListSubdirectories(showFolder).Filter(ShouldIncludeFolder)
@ -248,8 +255,16 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
// skip folder if etag matches // skip folder if etag matches
if (knownFolder.Etag == etag) if (knownFolder.Etag == etag)
{ {
if (allTrashedItems.Any(f => f.StartsWith(seasonFolder, StringComparison.OrdinalIgnoreCase)))
{
_logger.LogDebug("Previously trashed items are now present in folder {Folder}", seasonFolder);
}
else
{
// etag matches and no trashed items are now present, continue to next folder
continue; continue;
} }
}
Option<int> maybeSeasonNumber = _fallbackMetadataProvider.GetSeasonNumberForFolder(seasonFolder); Option<int> maybeSeasonNumber = _fallbackMetadataProvider.GetSeasonNumberForFolder(seasonFolder);
foreach (int seasonNumber in maybeSeasonNumber) foreach (int seasonNumber in maybeSeasonNumber)

Loading…
Cancel
Save