fix editorconfig and run code cleanup (#2324)

* fix formatting rules

* reformat ersatztv

* reformat ersatztv.application

* reformat ersatztv.core

* refactor ersatztv.core.tests

* reformat ersatztv.ffmpeg

* reformat ersatztv.ffmpeg.tests

* reformat ersatztv.infrastructure

* cleanup infra mysql

* cleanup infra sqlite

* cleanup infra tests

* cleanup ersatztv.scanner

* cleanup ersatztv.scanner.tests

* sln cleanup

* update dependencies
This commit is contained in:
Jason Dove
2025-08-16 14:44:48 +00:00
committed by GitHub
parent 6d32dac51b
commit 5d081ceeff
331 changed files with 4908 additions and 4271 deletions
@@ -3,4 +3,4 @@ using ErsatzTV.Core;
namespace ErsatzTV.Scanner.Application.Emby;
public record SynchronizeEmbyShowById(int EmbyLibraryId, int ShowId, bool DeepScan)
: IRequest<Either<BaseError, string>>;
: IRequest<Either<BaseError, string>>;
@@ -11,9 +11,9 @@ public class SynchronizeEmbyShowByIdHandler : IRequestHandler<SynchronizeEmbySho
{
private readonly IEmbySecretStore _embySecretStore;
private readonly IEmbyTelevisionLibraryScanner _embyTelevisionLibraryScanner;
private readonly IEmbyTelevisionRepository _embyTelevisionRepository;
private readonly ILogger<SynchronizeEmbyShowByIdHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IEmbyTelevisionRepository _embyTelevisionRepository;
public SynchronizeEmbyShowByIdHandler(
IMediaSourceRepository mediaSourceRepository,
@@ -119,7 +119,8 @@ public class SynchronizeEmbyShowByIdHandler : IRequestHandler<SynchronizeEmbySho
private Task<Validation<BaseError, EmbyShowTitleItemIdResult>> EmbyShowMustExist(
SynchronizeEmbyShowById request) =>
_embyTelevisionRepository.GetShowTitleItemId(request.EmbyLibraryId, request.ShowId)
.Map(v => v.ToValidation<BaseError>($"Jellyfin show {request.ShowId} does not exist in library {request.EmbyLibraryId}."));
.Map(v => v.ToValidation<BaseError>(
$"Jellyfin show {request.ShowId} does not exist in library {request.EmbyLibraryId}."));
private record RequestParameters(
ConnectionParameters ConnectionParameters,
@@ -132,4 +133,4 @@ public class SynchronizeEmbyShowByIdHandler : IRequestHandler<SynchronizeEmbySho
{
public string? ApiKey { get; init; }
}
}
}
@@ -3,4 +3,4 @@ using ErsatzTV.Core;
namespace ErsatzTV.Scanner.Application.Jellyfin;
public record SynchronizeJellyfinShowById(int JellyfinLibraryId, int ShowId, bool DeepScan)
: IRequest<Either<BaseError, string>>;
: IRequest<Either<BaseError, string>>;
@@ -7,13 +7,14 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.Scanner.Application.Jellyfin;
public class SynchronizeJellyfinShowByIdHandler : IRequestHandler<SynchronizeJellyfinShowById, Either<BaseError, string>>
public class
SynchronizeJellyfinShowByIdHandler : IRequestHandler<SynchronizeJellyfinShowById, Either<BaseError, string>>
{
private readonly IJellyfinSecretStore _jellyfinSecretStore;
private readonly IJellyfinTelevisionLibraryScanner _jellyfinTelevisionLibraryScanner;
private readonly IJellyfinTelevisionRepository _jellyfinTelevisionRepository;
private readonly ILogger<SynchronizeJellyfinShowByIdHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IJellyfinTelevisionRepository _jellyfinTelevisionRepository;
public SynchronizeJellyfinShowByIdHandler(
IMediaSourceRepository mediaSourceRepository,
@@ -71,7 +72,8 @@ public class SynchronizeJellyfinShowByIdHandler : IRequestHandler<SynchronizeJel
}
private async Task<Validation<BaseError, RequestParameters>> Validate(SynchronizeJellyfinShowById request) =>
(await ValidateConnection(request), await JellyfinLibraryMustExist(request), await JellyfinShowMustExist(request))
(await ValidateConnection(request), await JellyfinLibraryMustExist(request),
await JellyfinShowMustExist(request))
.Apply((connectionParameters, jellyfinLibrary, showTitleItemId) =>
new RequestParameters(
connectionParameters,
@@ -119,7 +121,8 @@ public class SynchronizeJellyfinShowByIdHandler : IRequestHandler<SynchronizeJel
private Task<Validation<BaseError, JellyfinShowTitleItemIdResult>> JellyfinShowMustExist(
SynchronizeJellyfinShowById request) =>
_jellyfinTelevisionRepository.GetShowTitleItemId(request.JellyfinLibraryId, request.ShowId)
.Map(v => v.ToValidation<BaseError>($"Jellyfin show {request.ShowId} does not exist in library {request.JellyfinLibraryId}."));
.Map(v => v.ToValidation<BaseError>(
$"Jellyfin show {request.ShowId} does not exist in library {request.JellyfinLibraryId}."));
private record RequestParameters(
ConnectionParameters ConnectionParameters,
@@ -132,4 +135,4 @@ public class SynchronizeJellyfinShowByIdHandler : IRequestHandler<SynchronizeJel
{
public string? ApiKey { get; init; }
}
}
}
@@ -13,13 +13,13 @@ public class ScanLocalLibraryHandler : IRequestHandler<ScanLocalLibrary, Either<
{
private readonly IConfigElementRepository _configElementRepository;
private readonly IImageFolderScanner _imageFolderScanner;
private readonly IRemoteStreamFolderScanner _remoteStreamFolderScanner;
private readonly ILibraryRepository _libraryRepository;
private readonly ILogger<ScanLocalLibraryHandler> _logger;
private readonly IMediator _mediator;
private readonly IMovieFolderScanner _movieFolderScanner;
private readonly IMusicVideoFolderScanner _musicVideoFolderScanner;
private readonly IOtherVideoFolderScanner _otherVideoFolderScanner;
private readonly IRemoteStreamFolderScanner _remoteStreamFolderScanner;
private readonly ISongFolderScanner _songFolderScanner;
private readonly ITelevisionFolderScanner _televisionFolderScanner;
@@ -3,4 +3,4 @@ using ErsatzTV.Core;
namespace ErsatzTV.Scanner.Application.Plex;
public record SynchronizePlexShowById(int PlexLibraryId, int ShowId, bool DeepScan)
: IRequest<Either<BaseError, string>>;
: IRequest<Either<BaseError, string>>;
@@ -10,10 +10,10 @@ namespace ErsatzTV.Scanner.Application.Plex;
public class SynchronizePlexShowByIdHandler : IRequestHandler<SynchronizePlexShowById, Either<BaseError, string>>
{
private readonly ILogger<SynchronizePlexShowByIdHandler> _logger;
private readonly IPlexTelevisionRepository _plexTelevisionRepository;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IPlexSecretStore _plexSecretStore;
private readonly IPlexTelevisionLibraryScanner _plexTelevisionLibraryScanner;
private readonly IPlexTelevisionRepository _plexTelevisionRepository;
public SynchronizePlexShowByIdHandler(
IPlexTelevisionRepository plexTelevisionRepository,
@@ -119,7 +119,8 @@ public class SynchronizePlexShowByIdHandler : IRequestHandler<SynchronizePlexSho
private Task<Validation<BaseError, PlexShowTitleKeyResult>> PlexShowMustExist(
SynchronizePlexShowById request) =>
_plexTelevisionRepository.GetShowTitleKey(request.PlexLibraryId, request.ShowId)
.Map(v => v.ToValidation<BaseError>($"Plex show {request.ShowId} does not exist in library {request.PlexLibraryId}."));
.Map(v => v.ToValidation<BaseError>(
$"Plex show {request.ShowId} does not exist in library {request.PlexLibraryId}."));
private record RequestParameters(
ConnectionParameters ConnectionParameters,
@@ -132,4 +133,4 @@ public class SynchronizePlexShowByIdHandler : IRequestHandler<SynchronizePlexSho
{
public PlexServerAuthToken? PlexServerAuthToken { get; set; }
}
}
}
@@ -5,8 +5,8 @@ using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Scanner.Core.Metadata;
using Microsoft.Extensions.Logging;
@@ -6,8 +6,8 @@ using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Emby;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Scanner.Core.Metadata;
using Microsoft.Extensions.Logging;
@@ -76,6 +76,60 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
cancellationToken);
}
public async Task<Either<BaseError, Unit>> ScanSingleShow(
string address,
string apiKey,
EmbyLibrary library,
string showId,
string showTitle,
bool deepScan,
CancellationToken cancellationToken)
{
List<EmbyPathReplacement> pathReplacements =
await _mediaSourceRepository.GetEmbyPathReplacements(library.MediaSourceId);
string GetLocalPath(EmbyEpisode episode)
{
return _pathReplacementService.GetReplacementEmbyPath(
pathReplacements,
episode.GetHeadVersion().MediaFiles.Head().Path,
false);
}
// Search for the specific show
Either<BaseError, Option<EmbyShow>> searchResult = await _embyApiClient.GetSingleShow(
address,
apiKey,
library,
showId);
return await searchResult.Match(
async maybeShow =>
{
foreach (EmbyShow show in maybeShow)
{
_logger.LogInformation(
"Found show '{ShowTitle}' with id {ShowId}, starting targeted scan",
showTitle,
show.ItemId);
return await ScanSingleShowInternal(
_televisionRepository,
new EmbyConnectionParameters(address, apiKey),
library,
show,
GetLocalPath,
deepScan,
cancellationToken);
}
_logger.LogWarning("No show found with id {ShowId} in library {LibraryName}", showId, library.Name);
return Right<BaseError, Unit>(Unit.Default);
},
error => Task.FromResult<Either<BaseError, Unit>>(error));
}
protected override IAsyncEnumerable<Tuple<EmbyShow, int>> GetShowLibraryItems(
EmbyConnectionParameters connectionParameters,
EmbyLibrary library) =>
@@ -186,58 +240,6 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner<
EpisodeMetadata fullMetadata) =>
Task.FromResult<Either<BaseError, MediaItemScanResult<EmbyEpisode>>>(result);
public async Task<Either<BaseError, Unit>> ScanSingleShow(
string address,
string apiKey,
EmbyLibrary library,
string showId,
string showTitle,
bool deepScan,
CancellationToken cancellationToken)
{
List<EmbyPathReplacement> pathReplacements =
await _mediaSourceRepository.GetEmbyPathReplacements(library.MediaSourceId);
string GetLocalPath(EmbyEpisode episode)
{
return _pathReplacementService.GetReplacementEmbyPath(
pathReplacements,
episode.GetHeadVersion().MediaFiles.Head().Path,
false);
}
// Search for the specific show
Either<BaseError, Option<EmbyShow>> searchResult = await _embyApiClient.GetSingleShow(
address,
apiKey,
library,
showId);
return await searchResult.Match(
async maybeShow =>
{
foreach (var show in maybeShow)
{
_logger.LogInformation("Found show '{ShowTitle}' with id {ShowId}, starting targeted scan",
showTitle, show.ItemId);
return await ScanSingleShowInternal(
_televisionRepository,
new EmbyConnectionParameters(address, apiKey),
library,
show,
GetLocalPath,
deepScan,
cancellationToken);
}
_logger.LogWarning("No show found with id {ShowId} in library {LibraryName}", showId, library.Name);
return Right<BaseError, Unit>(Unit.Default);
},
error => Task.FromResult<Either<BaseError, Unit>>(error));
}
private async Task<Either<BaseError, Unit>> ScanSingleShowInternal(
IEmbyTelevisionRepository televisionRepository,
EmbyConnectionParameters connectionParameters,
@@ -5,4 +5,4 @@ namespace ErsatzTV.Scanner.Core.Interfaces.Metadata;
public interface ILocalChaptersProvider : IDisposable
{
Task<bool> UpdateChapters(MediaItem mediaItem, Option<string> localPath);
}
}
@@ -4,9 +4,9 @@ using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.Jellyfin;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Scanner.Core.Metadata;
using Microsoft.Extensions.Logging;
@@ -5,9 +5,9 @@ using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Jellyfin;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.Jellyfin;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Scanner.Core.Metadata;
using Microsoft.Extensions.Logging;
@@ -77,6 +77,60 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
cancellationToken);
}
public async Task<Either<BaseError, Unit>> ScanSingleShow(
string address,
string apiKey,
JellyfinLibrary library,
string showId,
string showTitle,
bool deepScan,
CancellationToken cancellationToken)
{
List<JellyfinPathReplacement> pathReplacements =
await _mediaSourceRepository.GetJellyfinPathReplacements(library.MediaSourceId);
string GetLocalPath(JellyfinEpisode episode)
{
return _pathReplacementService.GetReplacementJellyfinPath(
pathReplacements,
episode.GetHeadVersion().MediaFiles.Head().Path,
false);
}
// Search for the specific show
Either<BaseError, Option<JellyfinShow>> searchResult = await _jellyfinApiClient.GetSingleShow(
address,
apiKey,
library,
showId);
return await searchResult.Match(
async maybeShow =>
{
foreach (JellyfinShow show in maybeShow)
{
_logger.LogInformation(
"Found show '{ShowTitle}' with id {ShowId}, starting targeted scan",
showTitle,
show.ItemId);
return await ScanSingleShowInternal(
_televisionRepository,
new JellyfinConnectionParameters(address, apiKey, library.MediaSourceId),
library,
show,
GetLocalPath,
deepScan,
cancellationToken);
}
_logger.LogWarning("No show found with id {ShowId} in library {LibraryName}", showId, library.Name);
return Right<BaseError, Unit>(Unit.Default);
},
error => Task.FromResult<Either<BaseError, Unit>>(error));
}
protected override IAsyncEnumerable<Tuple<JellyfinShow, int>> GetShowLibraryItems(
JellyfinConnectionParameters connectionParameters,
JellyfinLibrary library) =>
@@ -185,58 +239,6 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan
EpisodeMetadata fullMetadata) =>
Task.FromResult<Either<BaseError, MediaItemScanResult<JellyfinEpisode>>>(result);
public async Task<Either<BaseError, Unit>> ScanSingleShow(
string address,
string apiKey,
JellyfinLibrary library,
string showId,
string showTitle,
bool deepScan,
CancellationToken cancellationToken)
{
List<JellyfinPathReplacement> pathReplacements =
await _mediaSourceRepository.GetJellyfinPathReplacements(library.MediaSourceId);
string GetLocalPath(JellyfinEpisode episode)
{
return _pathReplacementService.GetReplacementJellyfinPath(
pathReplacements,
episode.GetHeadVersion().MediaFiles.Head().Path,
false);
}
// Search for the specific show
Either<BaseError, Option<JellyfinShow>> searchResult = await _jellyfinApiClient.GetSingleShow(
address,
apiKey,
library,
showId);
return await searchResult.Match(
async maybeShow =>
{
foreach (var show in maybeShow)
{
_logger.LogInformation("Found show '{ShowTitle}' with id {ShowId}, starting targeted scan",
showTitle, show.ItemId);
return await ScanSingleShowInternal(
_televisionRepository,
new JellyfinConnectionParameters(address, apiKey, library.MediaSourceId),
library,
show,
GetLocalPath,
deepScan,
cancellationToken);
}
_logger.LogWarning("No show found with id {ShowId} in library {LibraryName}", showId, library.Name);
return Right<BaseError, Unit>(Unit.Default);
},
error => Task.FromResult<Either<BaseError, Unit>>(error));
}
private async Task<Either<BaseError, Unit>> ScanSingleShowInternal(
IJellyfinTelevisionRepository televisionRepository,
JellyfinConnectionParameters connectionParameters,
@@ -1,11 +1,11 @@
using System.Text.RegularExpressions;
using System.Xml;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;
using System.Xml;
namespace ErsatzTV.Scanner.Core.Metadata;
@@ -51,6 +51,12 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public List<MediaChapter> LocateExternalChapters(string mediaItemPath)
{
var result = new List<MediaChapter>();
@@ -124,7 +130,7 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
var chapters = new List<MediaChapter>();
XmlNodeList? chapterAtoms = chaptersNode.SelectNodes(".//ChapterAtom") ??
chaptersNode.SelectNodes(".//chapteratom");
chaptersNode.SelectNodes(".//chapteratom");
if (chapterAtoms == null)
{
@@ -134,7 +140,7 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
long chapterId = 0;
foreach (XmlNode chapterAtom in chapterAtoms)
{
var chapter = ParseChapterAtom(chapterAtom, chapterId++);
MediaChapter? chapter = ParseChapterAtom(chapterAtom, chapterId++);
if (chapter != null)
{
chapters.Add(chapter);
@@ -143,7 +149,7 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
chapters.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
for (int i = 0; i < chapters.Count; i++)
for (var i = 0; i < chapters.Count; i++)
{
chapters[i].ChapterId = i;
}
@@ -154,7 +160,7 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
private static MediaChapter? ParseChapterAtom(XmlNode chapterAtom, long chapterId)
{
XmlNode? startNode = chapterAtom.SelectSingleNode(".//ChapterTimeStart") ??
chapterAtom.SelectSingleNode(".//chaptertimestart");
chapterAtom.SelectSingleNode(".//chaptertimestart");
if (startNode?.InnerText == null)
{
@@ -168,7 +174,7 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
TimeSpan endTime = TimeSpan.Zero;
XmlNode? endNode = chapterAtom.SelectSingleNode(".//ChapterTimeEnd") ??
chapterAtom.SelectSingleNode(".//chaptertimeend");
chapterAtom.SelectSingleNode(".//chaptertimeend");
if (endNode?.InnerText != null)
{
@@ -177,9 +183,9 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
string title = string.Empty;
XmlNode? titleNode = chapterAtom.SelectSingleNode(".//ChapterString") ??
chapterAtom.SelectSingleNode(".//ChapString") ??
chapterAtom.SelectSingleNode(".//chapterstring") ??
chapterAtom.SelectSingleNode(".//chapstring");
chapterAtom.SelectSingleNode(".//ChapString") ??
chapterAtom.SelectSingleNode(".//chapterstring") ??
chapterAtom.SelectSingleNode(".//chapstring");
if (titleNode?.InnerText != null)
{
@@ -212,7 +218,7 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
}
// Handle time format HH:MM:SS.mmm or HH:MM:SS,mmm
var timeFormats = new Regex[]
var timeFormats = new[]
{
GetParseFullTimeCodeRegex(),
GetParseTimeCodeNoMilliRegex()
@@ -220,14 +226,14 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
foreach (Regex pattern in timeFormats)
{
var match = pattern.Match(timeString);
Match match = pattern.Match(timeString);
if (match.Success)
{
if (int.TryParse(match.Groups[1].Value, out int hours) &&
int.TryParse(match.Groups[2].Value, out int minutes) &&
int.TryParse(match.Groups[3].Value, out int seconds))
{
int milliseconds = 0;
var milliseconds = 0;
if (match.Groups.Count > 4 && !int.TryParse(match.Groups[4].Value, out milliseconds))
{
milliseconds = 0;
@@ -242,12 +248,6 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
return false;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
@@ -258,6 +258,7 @@ public partial class LocalChaptersProvider : ILocalChaptersProvider
[GeneratedRegex(@"^(\d{1,2}):(\d{2}):(\d{2})[\.,](\d{3})$")]
private static partial Regex GetParseFullTimeCodeRegex();
[GeneratedRegex(@"^(\d{1,2}):(\d{2}):(\d{2})$")]
private static partial Regex GetParseTimeCodeNoMilliRegex();
}
}
@@ -23,7 +23,6 @@ public class LocalMetadataProvider : ILocalMetadataProvider
private readonly IEpisodeNfoReader _episodeNfoReader;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly IImageRepository _imageRepository;
private readonly IRemoteStreamRepository _remoteStreamRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalStatisticsProvider _localStatisticsProvider;
private readonly ILogger<LocalMetadataProvider> _logger;
@@ -34,6 +33,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
private readonly IMusicVideoRepository _musicVideoRepository;
private readonly IOtherVideoNfoReader _otherVideoNfoReader;
private readonly IOtherVideoRepository _otherVideoRepository;
private readonly IRemoteStreamRepository _remoteStreamRepository;
private readonly IShowNfoReader _showNfoReader;
private readonly ISongRepository _songRepository;
private readonly ITelevisionRepository _televisionRepository;
@@ -189,7 +189,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider
string diff = Path.GetRelativePath(parent, folder);
var tags = diff.Split(Path.DirectorySeparatorChar)
.Filter(t => metadata.Tags.Any(mt => mt.Name == t) == false)
.Filter(t => metadata.Tags.All(mt => mt.Name != t))
.Map(t => new Tag { Name = t })
.ToList();
@@ -224,16 +224,13 @@ public class LocalMetadataProvider : ILocalMetadataProvider
return await RefreshFallbackMetadata(image);
}
public async Task<bool> RefreshTagMetadata(RemoteStream remoteStream)
{
public async Task<bool> RefreshTagMetadata(RemoteStream remoteStream) =>
// Option<RemoteStreamMetadata> maybeMetadata = LoadRemoteStreamMetadata(remoteStream);
// foreach (RemoteStreamMetadata metadata in maybeMetadata)
// {
// return await ApplyMetadataUpdate(remoteStream, metadata);
// }
return await RefreshFallbackMetadata(remoteStream);
}
await RefreshFallbackMetadata(remoteStream);
public Task<bool> RefreshFallbackMetadata(Movie movie) =>
ApplyMetadataUpdate(movie, _fallbackMetadataProvider.GetFallbackMetadata(movie));
@@ -1471,11 +1468,12 @@ public class LocalMetadataProvider : ILocalMetadataProvider
var tags = nfo.Tags.Map(t => new Tag { Name = t }).ToList();
foreach (string country in nfo.Countries)
{
tags.Add(new Tag
{
Name = country,
ExternalTypeId = Tag.NfoCountryTypeId
});
tags.Add(
new Tag
{
Name = country,
ExternalTypeId = Tag.NfoCountryTypeId
});
}
return new MovieMetadata
@@ -19,8 +19,8 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
where TMovie : Movie
where TEtag : MediaServerItemEtag
{
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger _logger;
private readonly IMediator _mediator;
private readonly IMetadataRepository _metadataRepository;
@@ -101,7 +101,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
string localPath = getLocalPath(incoming);
if (await ShouldScanItem(movieRepository, library, existingMovies, incoming, localPath, deepScan) == false)
if (!await ShouldScanItem(movieRepository, library, existingMovies, incoming, localPath, deepScan))
{
continue;
}
@@ -6,9 +6,9 @@ using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.MediaSources;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Scanner.Core.Metadata;
@@ -19,8 +19,8 @@ public abstract class MediaServerOtherVideoLibraryScanner<TConnectionParameters,
where TOtherVideo : OtherVideo
where TEtag : MediaServerItemEtag
{
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger _logger;
private readonly IMediator _mediator;
private readonly IMetadataRepository _metadataRepository;
@@ -102,13 +102,13 @@ public abstract class MediaServerOtherVideoLibraryScanner<TConnectionParameters,
string localPath = getLocalPath(incoming);
if (await ShouldScanItem(
if (!await ShouldScanItem(
otherVideoRepository,
library,
existingOtherVideos,
incoming,
localPath,
deepScan) == false)
deepScan))
{
continue;
}
@@ -5,9 +5,9 @@ using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.MediaSources;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Scanner.Core.Metadata;
@@ -21,8 +21,8 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
where TEpisode : Episode
where TEtag : MediaServerItemEtag
{
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger _logger;
private readonly IMediator _mediator;
private readonly IMetadataRepository _metadataRepository;
@@ -198,18 +198,16 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
Func<TEpisode, string> getLocalPath,
IAsyncEnumerable<Tuple<TShow, int>> showEntries,
bool deepScan,
CancellationToken cancellationToken)
{
return await InternalScanLibrary(
CancellationToken cancellationToken) =>
await InternalScanLibrary(
televisionRepository,
connectionParameters,
library,
getLocalPath,
showEntries,
deepScan,
cleanupFileNotFoundItems: true,
true,
cancellationToken);
}
protected async Task<Either<BaseError, Unit>> ScanLibraryWithoutCleanup(
IMediaServerTelevisionRepository<TLibrary, TShow, TSeason, TEpisode, TEtag> televisionRepository,
@@ -218,18 +216,16 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
Func<TEpisode, string> getLocalPath,
IAsyncEnumerable<Tuple<TShow, int>> showEntries,
bool deepScan,
CancellationToken cancellationToken)
{
return await InternalScanLibrary(
CancellationToken cancellationToken) =>
await InternalScanLibrary(
televisionRepository,
connectionParameters,
library,
getLocalPath,
showEntries,
deepScan,
cleanupFileNotFoundItems: false,
false,
cancellationToken);
}
protected abstract IAsyncEnumerable<Tuple<TSeason, int>> GetSeasonLibraryItems(
TLibrary library,
@@ -408,7 +404,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
incomingItemIds.Add(MediaServerItemId(incoming));
string localPath = getLocalPath(incoming);
if (await ShouldScanItem(
if (!await ShouldScanItem(
televisionRepository,
library,
show,
@@ -416,7 +412,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
existingEpisodes,
incoming,
localPath,
deepScan) == false)
deepScan))
{
continue;
}
@@ -21,10 +21,10 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner
{
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILogger<MovieFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
@@ -21,10 +21,10 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
private readonly IArtistRepository _artistRepository;
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILogger<MusicVideoFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
@@ -49,7 +49,11 @@ public abstract class NfoReader<T> : NfoReaderBase
}
catch (XmlException ex)
{
_logger.LogWarning(ex, "Error reading string content from NFO {ElementName} file {FileName}", reader.Name, fileName);
_logger.LogWarning(
ex,
"Error reading string content from NFO {ElementName} file {FileName}",
reader.Name,
fileName);
}
}
@@ -64,7 +68,11 @@ public abstract class NfoReader<T> : NfoReaderBase
}
catch (XmlException ex)
{
_logger.LogWarning(ex, "Error reading int content from NFO {ElementName} file {FileName}", reader.Name, fileName);
_logger.LogWarning(
ex,
"Error reading int content from NFO {ElementName} file {FileName}",
reader.Name,
fileName);
}
}
@@ -81,7 +89,11 @@ public abstract class NfoReader<T> : NfoReaderBase
}
catch (XmlException ex)
{
_logger.LogWarning(ex, "Error reading date content from NFO {ElementName} file {FileName}", reader.Name, fileName);
_logger.LogWarning(
ex,
"Error reading date content from NFO {ElementName} file {FileName}",
reader.Name,
fileName);
}
}
@@ -123,7 +135,11 @@ public abstract class NfoReader<T> : NfoReaderBase
}
catch (XmlException ex)
{
_logger.LogWarning(ex, "Error reading actor content from NFO {ElementName} file {FileName}", reader.Name, fileName);
_logger.LogWarning(
ex,
"Error reading actor content from NFO {ElementName} file {FileName}",
reader.Name,
fileName);
}
}
@@ -146,7 +162,11 @@ public abstract class NfoReader<T> : NfoReaderBase
}
catch (XmlException ex)
{
_logger.LogWarning(ex, "Error reading uniqueid content from NFO {ElementName} file {FileName}", reader.Name, fileName);
_logger.LogWarning(
ex,
"Error reading uniqueid content from NFO {ElementName} file {FileName}",
reader.Name,
fileName);
}
}
}
@@ -83,7 +83,8 @@ public class OtherVideoNfoReader : NfoReader<OtherVideoNfo>, IOtherVideoNfoReade
await ReadStringContent(
reader,
nfo,
(movie, contentRating) => movie.ContentRating = contentRating, fileName);
(movie, contentRating) => movie.ContentRating = contentRating,
fileName);
break;
case "premiered":
await ReadDateTimeContent(
@@ -20,10 +20,10 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
{
private readonly IClient _client;
private readonly ILibraryRepository _libraryRepository;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILogger<OtherVideoFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
@@ -22,13 +22,13 @@ namespace ErsatzTV.Scanner.Core.Metadata;
public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolderScanner
{
private readonly IClient _client;
private readonly IRemoteStreamRepository _remoteStreamRepository;
private readonly ILibraryRepository _libraryRepository;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILogger<RemoteStreamFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
private readonly IRemoteStreamRepository _remoteStreamRepository;
public RemoteStreamFolderScanner(
ILocalFileSystem localFileSystem,
@@ -126,7 +126,8 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder
cancellationToken);
string remoteStreamFolder = folderQueue.Dequeue();
Option<int> maybeParentFolder = await _libraryRepository.GetParentFolderId(libraryPath, remoteStreamFolder);
Option<int> maybeParentFolder =
await _libraryRepository.GetParentFolderId(libraryPath, remoteStreamFolder);
foldersCompleted++;
@@ -155,7 +156,9 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder
{
if (allFiles.Any(allTrashedItems.Contains))
{
_logger.LogDebug("Previously trashed items are now present in folder {Folder}", remoteStreamFolder);
_logger.LogDebug(
"Previously trashed items are now present in folder {Folder}",
remoteStreamFolder);
}
else
{
@@ -281,10 +284,10 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder
YamlRemoteStreamDefinition definition = deserializer.Deserialize<YamlRemoteStreamDefinition>(yaml);
if (!definition.IsLive.HasValue)
{
return BaseError.New($"Remote stream definition is missing required `is_live` property");
return BaseError.New("Remote stream definition is missing required `is_live` property");
}
bool updated = false;
var updated = false;
if (remoteStream.IsLive != definition.IsLive.Value)
{
remoteStream.IsLive = definition.IsLive.Value;
@@ -21,10 +21,10 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan
private readonly IClient _client;
private readonly IFallbackMetadataProvider _fallbackMetadataProvider;
private readonly ILibraryRepository _libraryRepository;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILocalMetadataProvider _localMetadataProvider;
private readonly ILocalSubtitlesProvider _localSubtitlesProvider;
private readonly ILocalChaptersProvider _localChaptersProvider;
private readonly ILogger<TelevisionFolderScanner> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMediator _mediator;
@@ -4,9 +4,9 @@ using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Core.Plex;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Scanner.Core.Metadata;
using Microsoft.Extensions.Logging;
@@ -6,9 +6,9 @@ using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Core.Plex;
using ErsatzTV.Scanner.Core.Interfaces.Metadata;
using ErsatzTV.Scanner.Core.Metadata;
using Microsoft.Extensions.Logging;
@@ -120,9 +120,10 @@ public partial class PlexTelevisionLibraryScanner :
return await showResult.Match(
async maybeShow =>
{
foreach (var show in maybeShow)
foreach (PlexShow show in maybeShow)
{
_logger.LogInformation("Found show '{ShowTitle}' with key {ShowKey}, starting targeted scan",
_logger.LogInformation(
"Found show '{ShowTitle}' with key {ShowKey}, starting targeted scan",
showTitle,
show.Key);
@@ -144,7 +145,8 @@ public partial class PlexTelevisionLibraryScanner :
}
private async Task<Either<BaseError, Unit>> ScanSingleShowInternal(
IMediaServerTelevisionRepository<PlexLibrary, PlexShow, PlexSeason, PlexEpisode, PlexItemEtag> televisionRepository,
IMediaServerTelevisionRepository<PlexLibrary, PlexShow, PlexSeason, PlexEpisode, PlexItemEtag>
televisionRepository,
PlexConnectionParameters connectionParameters,
PlexLibrary library,
PlexShow targetShow,
+2 -2
View File
@@ -33,7 +33,7 @@
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta6.25358.103" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta7.25380.108" />
</ItemGroup>
<ItemGroup>
@@ -42,4 +42,4 @@
</AssemblyAttribute>
</ItemGroup>
</Project>
</Project>
-2
View File
@@ -13,7 +13,6 @@ using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Interfaces.Repositories.Caching;
using ErsatzTV.Core.Interfaces.Search;
using ErsatzTV.Core.Interfaces.Streaming;
using ErsatzTV.Core.Jellyfin;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Core.Plex;
@@ -30,7 +29,6 @@ using ErsatzTV.Infrastructure.Plex;
using ErsatzTV.Infrastructure.Runtime;
using ErsatzTV.Infrastructure.Search;
using ErsatzTV.Infrastructure.Sqlite.Data;
using ErsatzTV.Infrastructure.Streaming;
using ErsatzTV.Scanner.Core.Emby;
using ErsatzTV.Scanner.Core.FFmpeg;
using ErsatzTV.Scanner.Core.Interfaces.FFmpeg;
+4 -2
View File
@@ -34,7 +34,7 @@ public class Worker : BackgroundService
string[] arguments = Environment.GetCommandLineArgs().Skip(1).ToArray();
ParseResult parseResult = rootCommand.Parse(arguments);
await parseResult.InvokeAsync(stoppingToken);
await parseResult.InvokeAsync(cancellationToken: stoppingToken);
_appLifetime.StopApplication();
}
@@ -117,7 +117,9 @@ public class Worker : BackgroundService
scanEmbyShowCommand.Arguments.Add(showIdArgument);
scanEmbyShowCommand.Options.Add(deepOption);
var scanJellyfinShowCommand = new Command("scan-jellyfin-show", "Scan a specific TV show in a Jellyfin library");
var scanJellyfinShowCommand = new Command(
"scan-jellyfin-show",
"Scan a specific TV show in a Jellyfin library");
scanJellyfinShowCommand.Arguments.Add(libraryIdArgument);
scanJellyfinShowCommand.Arguments.Add(showIdArgument);
scanJellyfinShowCommand.Options.Add(deepOption);