From 1a1d02a609ff26069202c6f64f6c5ecd7abdcaa1 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:12:47 -0600 Subject: [PATCH] remove fileexists --- .../Channels/Commands/DeleteChannelHandler.cs | 10 ++--- .../Commands/RefreshChannelDataHandler.cs | 16 +++++--- .../Commands/RefreshChannelListHandler.cs | 8 +++- .../Queries/GetChannelGuideHandler.cs | 6 ++- .../Commands/UpdateFFmpegSettingsHandler.cs | 6 +-- .../RefreshGraphicsElementsHandler.cs | 4 +- .../CreateExternalJsonPlayoutHandler.cs | 10 ++--- .../Commands/CreateScriptedPlayoutHandler.cs | 30 +++++--------- .../CreateSequentialPlayoutHandler.cs | 31 +++++--------- .../Playouts/Commands/DeletePlayoutHandler.cs | 36 ++++++----------- .../Commands/UpdateScriptedPlayoutHandler.cs | 6 +-- .../Commands/StartFFmpegSessionHandler.cs | 7 +++- .../Streaming/HlsSessionWorker.cs | 6 ++- .../Queries/GetLastPtsTimeHandler.cs | 8 ++-- ...layoutItemProcessByChannelNumberHandler.cs | 8 +++- .../ExtractEmbeddedShowSubtitlesHandler.cs | 6 +-- .../ExtractEmbeddedSubtitlesHandler.cs | 8 ++-- .../ExtractEmbeddedSubtitlesHandlerBase.cs | 12 +++--- .../PrepareTroubleshootingPlaybackHandler.cs | 4 +- .../FFmpeg/FFmpegStreamSelectorTests.cs | 7 ++-- .../Fakes/FakeLocalFileSystem.cs | 3 +- .../ClassicScheduling/ContinuePlayoutTests.cs | 11 ++--- .../ClassicScheduling/NewPlayoutTests.cs | 35 ++++++---------- .../PlayoutBuilderTestBase.cs | 8 ++-- .../ClassicScheduling/RefreshPlayoutTests.cs | 5 +-- .../Scheduling/ScheduleIntegrationTests.cs | 6 ++- ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs | 11 ++--- .../Interfaces/Metadata/ILocalFileSystem.cs | 1 - ErsatzTV.Core/Metadata/LocalFileSystem.cs | 10 ++--- ErsatzTV.Core/Scheduling/PlayoutBuilder.cs | 12 +++--- .../ScriptedPlayoutBuilder.cs | 6 +-- .../SequentialPlayoutBuilder.cs | 6 +-- .../ErsatzTV.Infrastructure.Tests.csproj | 1 + .../Metadata/LocalStatisticsProviderTests.cs | 2 + .../Repositories/TemplateDataRepository.cs | 8 ++-- .../FFmpeg/MpegTsScriptService.cs | 2 +- ErsatzTV.Infrastructure/Images/ImageCache.cs | 30 +++++--------- .../Metadata/LocalStatisticsProvider.cs | 6 ++- .../ExternalJsonPlayoutItemProvider.cs | 13 +++--- .../Core/FFmpeg/TranscodingTests.cs | 18 ++++++--- .../Core/Fakes/FakeLocalFileSystem.cs | 1 - .../Core/Metadata/MovieFolderScannerTests.cs | 34 ++++++++++++---- .../ErsatzTV.Scanner.Tests.csproj | 1 + .../Core/Emby/EmbyMovieLibraryScanner.cs | 8 ++-- .../Core/Emby/EmbyTelevisionLibraryScanner.cs | 8 ++-- .../Jellyfin/JellyfinMovieLibraryScanner.cs | 8 ++-- .../JellyfinTelevisionLibraryScanner.cs | 8 ++-- .../Core/Metadata/ImageFolderScanner.cs | 8 +++- .../Core/Metadata/LocalFolderScanner.cs | 13 +++--- .../Core/Metadata/LocalMetadataProvider.cs | 10 +++-- .../MediaServerMovieLibraryScanner.cs | 14 +++---- .../MediaServerOtherVideoLibraryScanner.cs | 14 +++---- .../MediaServerTelevisionLibraryScanner.cs | 18 ++++----- .../Core/Metadata/MovieFolderScanner.cs | 14 ++++--- .../Core/Metadata/MusicVideoFolderScanner.cs | 16 +++++--- .../Core/Metadata/OtherVideoFolderScanner.cs | 12 ++++-- .../Metadata/RemoteStreamFolderScanner.cs | 8 +++- .../Core/Metadata/SongFolderScanner.cs | 10 +++-- .../Core/Metadata/TelevisionFolderScanner.cs | 18 +++++---- .../Core/Plex/PlexMovieLibraryScanner.cs | 8 ++-- .../Core/Plex/PlexOtherVideoLibraryScanner.cs | 6 +-- .../Core/Plex/PlexTelevisionLibraryScanner.cs | 8 ++-- .../Controllers/Api/TroubleshootController.cs | 6 +-- .../PlaybackTroubleshooting.razor | 6 +-- .../Services/RunOnce/CacheCleanerService.cs | 40 ++++++++----------- 65 files changed, 376 insertions(+), 344 deletions(-) diff --git a/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs b/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs index b397daf5b..a91ca1706 100644 --- a/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/DeleteChannelHandler.cs @@ -1,6 +1,6 @@ +using System.IO.Abstractions; using System.Threading.Channels; using ErsatzTV.Core; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; @@ -12,19 +12,19 @@ namespace ErsatzTV.Application.Channels; public class DeleteChannelHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; - private readonly ILocalFileSystem _localFileSystem; + private readonly IFileSystem _fileSystem; private readonly ISearchTargets _searchTargets; private readonly ChannelWriter _workerChannel; public DeleteChannelHandler( ChannelWriter workerChannel, IDbContextFactory dbContextFactory, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ISearchTargets searchTargets) { _workerChannel = workerChannel; _dbContextFactory = dbContextFactory; - _localFileSystem = localFileSystem; + _fileSystem = fileSystem; _searchTargets = searchTargets; } @@ -44,7 +44,7 @@ public class DeleteChannelHandler : IRequestHandler { private readonly IConfigElementRepository _configElementRepository; private readonly IDbContextFactory _dbContextFactory; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; @@ -33,12 +35,14 @@ public class RefreshChannelDataHandler : IRequestHandler public RefreshChannelDataHandler( RecyclableMemoryStreamManager recyclableMemoryStreamManager, IDbContextFactory dbContextFactory, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, IConfigElementRepository configElementRepository, ILogger logger) { _recyclableMemoryStreamManager = recyclableMemoryStreamManager; _dbContextFactory = dbContextFactory; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _configElementRepository = configElementRepository; _logger = logger; @@ -886,7 +890,7 @@ public class RefreshChannelDataHandler : IRequestHandler "movie.sbntxt"); // fail if file doesn't exist - if (!_localFileSystem.FileExists(templateFileName)) + if (!_fileSystem.File.Exists(templateFileName)) { _logger.LogError( "Unable to generate movie XMLTV fragment without template file {File}; please restart ErsatzTV", @@ -905,7 +909,7 @@ public class RefreshChannelDataHandler : IRequestHandler "episode.sbntxt"); // fail if file doesn't exist - if (!_localFileSystem.FileExists(templateFileName)) + if (!_fileSystem.File.Exists(templateFileName)) { _logger.LogError( "Unable to generate episode XMLTV fragment without template file {File}; please restart ErsatzTV", @@ -924,7 +928,7 @@ public class RefreshChannelDataHandler : IRequestHandler "musicVideo.sbntxt"); // fail if file doesn't exist - if (!_localFileSystem.FileExists(templateFileName)) + if (!_fileSystem.File.Exists(templateFileName)) { _logger.LogError( "Unable to generate music video XMLTV fragment without template file {File}; please restart ErsatzTV", @@ -943,7 +947,7 @@ public class RefreshChannelDataHandler : IRequestHandler "song.sbntxt"); // fail if file doesn't exist - if (!_localFileSystem.FileExists(templateFileName)) + if (!_fileSystem.File.Exists(templateFileName)) { _logger.LogError( "Unable to generate song XMLTV fragment without template file {File}; please restart ErsatzTV", @@ -962,7 +966,7 @@ public class RefreshChannelDataHandler : IRequestHandler "otherVideo.sbntxt"); // fail if file doesn't exist - if (!_localFileSystem.FileExists(templateFileName)) + if (!_fileSystem.File.Exists(templateFileName)) { _logger.LogError( "Unable to generate other video XMLTV fragment without template file {File}; please restart ErsatzTV", @@ -1077,7 +1081,7 @@ public class RefreshChannelDataHandler : IRequestHandler { var result = new List(); - if (_localFileSystem.FileExists(path)) + if (_fileSystem.File.Exists(path)) { Option maybeChannel = JsonConvert.DeserializeObject( await File.ReadAllTextAsync(path)); diff --git a/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs b/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs index 92c540eca..04cce52db 100644 --- a/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/RefreshChannelListHandler.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using System.IO.Abstractions; using System.Net; using System.Xml; using Dapper; @@ -19,6 +20,7 @@ namespace ErsatzTV.Application.Channels; public class RefreshChannelListHandler : IRequestHandler { private readonly IDbContextFactory _dbContextFactory; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; @@ -26,11 +28,13 @@ public class RefreshChannelListHandler : IRequestHandler public RefreshChannelListHandler( RecyclableMemoryStreamManager recyclableMemoryStreamManager, IDbContextFactory dbContextFactory, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ILogger logger) { _recyclableMemoryStreamManager = recyclableMemoryStreamManager; _dbContextFactory = dbContextFactory; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _logger = logger; } @@ -44,13 +48,13 @@ public class RefreshChannelListHandler : IRequestHandler string templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "channel.sbntxt"); // fall back to default template - if (!_localFileSystem.FileExists(templateFileName)) + if (!_fileSystem.File.Exists(templateFileName)) { templateFileName = Path.Combine(FileSystemLayout.ChannelGuideTemplatesFolder, "_channel.sbntxt"); } // fail if file doesn't exist - if (!_localFileSystem.FileExists(templateFileName)) + if (!_fileSystem.File.Exists(templateFileName)) { _logger.LogError( "Unable to generate channel list without template file {File}; please restart ErsatzTV", diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs b/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs index d088c32d2..ed654a1c7 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelGuideHandler.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using System.Text; using System.Text.RegularExpressions; using ErsatzTV.Core; @@ -15,14 +16,17 @@ public partial class GetChannelGuideHandler : IRequestHandler _dbContextFactory; private readonly ILocalFileSystem _localFileSystem; private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager; + private readonly IFileSystem _fileSystem; public GetChannelGuideHandler( IDbContextFactory dbContextFactory, RecyclableMemoryStreamManager recyclableMemoryStreamManager, + IFileSystem fileSystem, ILocalFileSystem localFileSystem) { _dbContextFactory = dbContextFactory; _recyclableMemoryStreamManager = recyclableMemoryStreamManager; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; } @@ -39,7 +43,7 @@ public partial class GetChannelGuideHandler : IRequestHandler workerChannel) : IRequestHandler> { @@ -35,7 +35,7 @@ public class UpdateFFmpegSettingsHandler( private async Task> ValidateToolPath(string path, string name) { - if (!localFileSystem.FileExists(path)) + if (!fileSystem.File.Exists(path)) { return BaseError.New($"{name} path does not exist"); } diff --git a/ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs b/ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs index b30ebd18d..ed5a8f4d9 100644 --- a/ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs +++ b/ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; @@ -10,6 +11,7 @@ namespace ErsatzTV.Application.Graphics; public class RefreshGraphicsElementsHandler( IDbContextFactory dbContextFactory, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, IGraphicsElementLoader graphicsElementLoader, ILogger logger) @@ -24,7 +26,7 @@ public class RefreshGraphicsElementsHandler( .ToListAsync(cancellationToken); var missing = allExisting - .Where(e => !localFileSystem.FileExists(e.Path) || (Path.GetExtension(e.Path) != ".yml" && Path.GetExtension(e.Path) != ".yaml")) + .Where(e => !fileSystem.File.Exists(e.Path) || (Path.GetExtension(e.Path) != ".yml" && Path.GetExtension(e.Path) != ".yaml")) .ToList(); foreach (GraphicsElement existing in missing) diff --git a/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs index 763d473e1..82f944bac 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs @@ -1,8 +1,8 @@ +using System.IO.Abstractions; using System.Threading.Channels; using ErsatzTV.Application.Channels; using ErsatzTV.Core; using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Scheduling; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; @@ -14,16 +14,16 @@ namespace ErsatzTV.Application.Playouts; public class CreateExternalJsonPlayoutHandler : IRequestHandler> { + private readonly IFileSystem _fileSystem; private readonly ChannelWriter _channel; private readonly IDbContextFactory _dbContextFactory; - private readonly ILocalFileSystem _localFileSystem; public CreateExternalJsonPlayoutHandler( - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ChannelWriter channel, IDbContextFactory dbContextFactory) { - _localFileSystem = localFileSystem; + _fileSystem = fileSystem; _channel = channel; _dbContextFactory = dbContextFactory; } @@ -76,7 +76,7 @@ public class CreateExternalJsonPlayoutHandler private Validation ValidateExternalJsonFile(CreateExternalJsonPlayout request) { - if (!_localFileSystem.FileExists(request.ScheduleFile)) + if (!_fileSystem.File.Exists(request.ScheduleFile)) { return BaseError.New("External Json File does not exist!"); } diff --git a/ErsatzTV.Application/Playouts/Commands/CreateScriptedPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreateScriptedPlayoutHandler.cs index 9e3bf759e..88dc41e20 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreateScriptedPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreateScriptedPlayoutHandler.cs @@ -1,4 +1,5 @@ using System.CommandLine.Parsing; +using System.IO.Abstractions; using System.Threading.Channels; using ErsatzTV.Application.Channels; using ErsatzTV.Core; @@ -12,28 +13,17 @@ using Channel = ErsatzTV.Core.Domain.Channel; namespace ErsatzTV.Application.Playouts; -public class CreateScriptedPlayoutHandler +public class CreateScriptedPlayoutHandler( + IFileSystem fileSystem, + ChannelWriter channel, + IDbContextFactory dbContextFactory) : IRequestHandler> { - private readonly ChannelWriter _channel; - private readonly IDbContextFactory _dbContextFactory; - private readonly ILocalFileSystem _localFileSystem; - - public CreateScriptedPlayoutHandler( - ILocalFileSystem localFileSystem, - ChannelWriter channel, - IDbContextFactory dbContextFactory) - { - _localFileSystem = localFileSystem; - _channel = channel; - _dbContextFactory = dbContextFactory; - } - public async Task> Handle( CreateScriptedPlayout request, CancellationToken cancellationToken) { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request, cancellationToken); return await validation.Apply(playout => PersistPlayout(dbContext, playout, cancellationToken)); } @@ -45,15 +35,15 @@ public class CreateScriptedPlayoutHandler { await dbContext.Playouts.AddAsync(playout, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); - await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset), cancellationToken); + await channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset), cancellationToken); if (playout.Channel.PlayoutMode is ChannelPlayoutMode.OnDemand) { - await _channel.WriteAsync( + await channel.WriteAsync( new TimeShiftOnDemandPlayout(playout.Id, DateTimeOffset.Now, false), cancellationToken); } - await _channel.WriteAsync(new RefreshChannelList(), cancellationToken); + await channel.WriteAsync(new RefreshChannelList(), cancellationToken); return new CreatePlayoutResponse(playout.Id); } @@ -91,7 +81,7 @@ public class CreateScriptedPlayoutHandler { var args = CommandLineParser.SplitCommandLine(request.ScheduleFile).ToList(); string scriptFile = args[0]; - if (!_localFileSystem.FileExists(scriptFile)) + if (!fileSystem.File.Exists(scriptFile)) { return BaseError.New("Scripted schedule does not exist!"); } diff --git a/ErsatzTV.Application/Playouts/Commands/CreateSequentialPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreateSequentialPlayoutHandler.cs index 48e4ef31c..267a22098 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreateSequentialPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreateSequentialPlayoutHandler.cs @@ -1,8 +1,8 @@ +using System.IO.Abstractions; using System.Threading.Channels; using ErsatzTV.Application.Channels; using ErsatzTV.Core; using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Scheduling; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; @@ -11,28 +11,17 @@ using Channel = ErsatzTV.Core.Domain.Channel; namespace ErsatzTV.Application.Playouts; -public class CreateSequentialPlayoutHandler +public class CreateSequentialPlayoutHandler( + IFileSystem fileSystem, + ChannelWriter channel, + IDbContextFactory dbContextFactory) : IRequestHandler> { - private readonly ChannelWriter _channel; - private readonly IDbContextFactory _dbContextFactory; - private readonly ILocalFileSystem _localFileSystem; - - public CreateSequentialPlayoutHandler( - ILocalFileSystem localFileSystem, - ChannelWriter channel, - IDbContextFactory dbContextFactory) - { - _localFileSystem = localFileSystem; - _channel = channel; - _dbContextFactory = dbContextFactory; - } - public async Task> Handle( CreateSequentialPlayout request, CancellationToken cancellationToken) { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request, cancellationToken); return await validation.Apply(playout => PersistPlayout(dbContext, playout, cancellationToken)); } @@ -44,15 +33,15 @@ public class CreateSequentialPlayoutHandler { await dbContext.Playouts.AddAsync(playout, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); - await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset), cancellationToken); + await channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset), cancellationToken); if (playout.Channel.PlayoutMode is ChannelPlayoutMode.OnDemand) { - await _channel.WriteAsync( + await channel.WriteAsync( new TimeShiftOnDemandPlayout(playout.Id, DateTimeOffset.Now, false), cancellationToken); } - await _channel.WriteAsync(new RefreshChannelList(), cancellationToken); + await channel.WriteAsync(new RefreshChannelList(), cancellationToken); return new CreatePlayoutResponse(playout.Id); } @@ -87,7 +76,7 @@ public class CreateSequentialPlayoutHandler private Validation ValidateYamlFile(CreateSequentialPlayout request) { - if (!_localFileSystem.FileExists(request.ScheduleFile)) + if (!fileSystem.File.Exists(request.ScheduleFile)) { return BaseError.New("Sequential schedule does not exist!"); } diff --git a/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs index 2467aade2..b47193413 100644 --- a/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs @@ -1,8 +1,8 @@ -using System.Threading.Channels; +using System.IO.Abstractions; +using System.Threading.Channels; using ErsatzTV.Application.Channels; using ErsatzTV.Core; using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Notifications; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; @@ -10,28 +10,16 @@ using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Playouts; -public class DeletePlayoutHandler : IRequestHandler> +public class DeletePlayoutHandler( + ChannelWriter workerChannel, + IDbContextFactory dbContextFactory, + IFileSystem fileSystem, + IMediator mediator) + : IRequestHandler> { - private readonly IDbContextFactory _dbContextFactory; - private readonly ILocalFileSystem _localFileSystem; - private readonly IMediator _mediator; - private readonly ChannelWriter _workerChannel; - - public DeletePlayoutHandler( - ChannelWriter workerChannel, - IDbContextFactory dbContextFactory, - ILocalFileSystem localFileSystem, - IMediator mediator) - { - _workerChannel = workerChannel; - _dbContextFactory = dbContextFactory; - _localFileSystem = localFileSystem; - _mediator = mediator; - } - public async Task> Handle(DeletePlayout request, CancellationToken cancellationToken) { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); Option maybePlayout = await dbContext.Playouts .Include(p => p.Channel) @@ -44,15 +32,15 @@ public class DeletePlayoutHandler : IRequestHandler dbContextFactory, ChannelWriter workerChannel, - ILocalFileSystem localFileSystem) + IFileSystem fileSystem) : IRequestHandler> { @@ -63,7 +63,7 @@ public class { var args = CommandLineParser.SplitCommandLine(request.ScheduleFile).ToList(); string scriptFile = args[0]; - if (!localFileSystem.FileExists(scriptFile)) + if (!fileSystem.File.Exists(scriptFile)) { return BaseError.New("Scripted schedule does not exist!"); } diff --git a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs index c7b5cab98..d6ec000d3 100644 --- a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs +++ b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs @@ -1,4 +1,5 @@ -using System.Threading.Channels; +using System.IO.Abstractions; +using System.Threading.Channels; using Bugsnag; using ErsatzTV.Application.Channels; using ErsatzTV.Application.Graphics; @@ -21,6 +22,7 @@ namespace ErsatzTV.Application.Streaming; public class StartFFmpegSessionHandler : IRequestHandler> { private readonly IClient _client; + private readonly IFileSystem _fileSystem; private readonly IConfigElementRepository _configElementRepository; private readonly IFFmpegSegmenterService _ffmpegSegmenterService; private readonly IGraphicsEngine _graphicsEngine; @@ -40,6 +42,7 @@ public class StartFFmpegSessionHandler : IRequestHandler logger, ILogger sessionWorkerLogger, @@ -54,6 +57,7 @@ public class StartFFmpegSessionHandler : IRequestHandler _discontinuityMap = []; private readonly IConfigElementRepository _configElementRepository; + private readonly IFileSystem _fileSystem; private readonly IGraphicsEngine _graphicsEngine; private readonly IHlsPlaylistFilter _hlsPlaylistFilter; private readonly ILocalFileSystem _localFileSystem; @@ -60,6 +62,7 @@ public class HlsSessionWorker : IHlsSessionWorker IHlsPlaylistFilter hlsPlaylistFilter, IHlsInitSegmentCache hlsInitSegmentCache, IConfigElementRepository configElementRepository, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ILogger logger, Option targetFramerate) @@ -72,6 +75,7 @@ public class HlsSessionWorker : IHlsSessionWorker _hlsInitSegmentCache = hlsInitSegmentCache; _hlsPlaylistFilter = hlsPlaylistFilter; _configElementRepository = configElementRepository; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _logger = logger; _targetFramerate = targetFramerate; @@ -308,7 +312,7 @@ public class HlsSessionWorker : IHlsSessionWorker string playlistFileName = Path.Combine(_workingDirectory, "live.m3u8"); _logger.LogDebug("Waiting for playlist to exist"); - while (!_localFileSystem.FileExists(playlistFileName)) + while (!_fileSystem.File.Exists(playlistFileName)) { await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); } diff --git a/ErsatzTV.Application/Streaming/Queries/GetLastPtsTimeHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetLastPtsTimeHandler.cs index 3ec37c461..33e881b3e 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetLastPtsTimeHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetLastPtsTimeHandler.cs @@ -1,11 +1,11 @@ -using System.Text; +using System.IO.Abstractions; +using System.Text; using Bugsnag; using CliWrap; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -14,7 +14,7 @@ namespace ErsatzTV.Application.Streaming; public class GetLastPtsTimeHandler( IClient client, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ITempFilePool tempFilePool, IConfigElementRepository configElementRepository, ILogger logger) @@ -168,7 +168,7 @@ public class GetLastPtsTimeHandler( string playlistFileName = Path.Combine(FileSystemLayout.TranscodeFolder, channelNumber, "live.m3u8"); string playlistContents = string.Empty; - if (localFileSystem.FileExists(playlistFileName)) + if (fileSystem.File.Exists(playlistFileName)) { playlistContents = await File.ReadAllTextAsync(playlistFileName); } diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 834ebbdef..2e042f889 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -1,4 +1,5 @@ -using CliWrap; +using System.IO.Abstractions; +using CliWrap; using Dapper; using ErsatzTV.Application.Playouts; using ErsatzTV.Core; @@ -31,6 +32,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< private readonly IEmbyPathReplacementService _embyPathReplacementService; private readonly IExternalJsonPlayoutItemProvider _externalJsonPlayoutItemProvider; private readonly IFFmpegProcessService _ffmpegProcessService; + private readonly IFileSystem _fileSystem; private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; @@ -47,6 +49,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< public GetPlayoutItemProcessByChannelNumberHandler( IDbContextFactory dbContextFactory, IFFmpegProcessService ffmpegProcessService, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, IExternalJsonPlayoutItemProvider externalJsonPlayoutItemProvider, IPlexPathReplacementService plexPathReplacementService, @@ -64,6 +67,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< : base(dbContextFactory) { _ffmpegProcessService = ffmpegProcessService; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _externalJsonPlayoutItemProvider = externalJsonPlayoutItemProvider; _plexPathReplacementService = plexPathReplacementService; @@ -775,7 +779,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< } // check filesystem first - if (_localFileSystem.FileExists(path)) + if (_fileSystem.File.Exists(path)) { if (playoutItem.MediaItem is RemoteStream remoteStream) { diff --git a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedShowSubtitlesHandler.cs b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedShowSubtitlesHandler.cs index 4cc7293d7..769dca6af 100644 --- a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedShowSubtitlesHandler.cs +++ b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedShowSubtitlesHandler.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using System.Threading.Channels; using ErsatzTV.Core.Domain; using ErsatzTV.Infrastructure.Data; @@ -7,7 +8,6 @@ using Microsoft.Extensions.Logging; using Dapper; using ErsatzTV.Application.Maintenance; using ErsatzTV.Core; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; namespace ErsatzTV.Application.Subtitles; @@ -16,9 +16,9 @@ public class ExtractEmbeddedShowSubtitlesHandler( IDbContextFactory dbContextFactory, ChannelWriter workerChannel, IConfigElementRepository configElementRepository, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILogger logger) - : ExtractEmbeddedSubtitlesHandlerBase(localFileSystem, logger), + : ExtractEmbeddedSubtitlesHandlerBase(fileSystem, logger), IRequestHandler> { public async Task> Handle( diff --git a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs index 4e39a81a2..163bab787 100644 --- a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs +++ b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs @@ -1,9 +1,9 @@ -using System.Threading.Channels; +using System.IO.Abstractions; +using System.Threading.Channels; using ErsatzTV.Application.Maintenance; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Locking; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; @@ -23,12 +23,12 @@ public class ExtractEmbeddedSubtitlesHandler : ExtractEmbeddedSubtitlesHandlerBa public ExtractEmbeddedSubtitlesHandler( IDbContextFactory dbContextFactory, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, IEntityLocker entityLocker, IConfigElementRepository configElementRepository, ChannelWriter workerChannel, ILogger logger) - : base(localFileSystem, logger) + : base(fileSystem, logger) { _dbContextFactory = dbContextFactory; _entityLocker = entityLocker; diff --git a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandlerBase.cs b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandlerBase.cs index 2cf895b77..99d08b768 100644 --- a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandlerBase.cs +++ b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandlerBase.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions; using System.Security.Cryptography; using System.Text; using CliWrap; @@ -8,7 +9,6 @@ using Dapper; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; @@ -17,7 +17,7 @@ using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.Subtitles; [SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms")] -public abstract class ExtractEmbeddedSubtitlesHandlerBase(ILocalFileSystem localFileSystem, ILogger logger) +public abstract class ExtractEmbeddedSubtitlesHandlerBase(IFileSystem fileSystem, ILogger logger) { protected static Task> FFmpegPathMustExist( TvContext dbContext, @@ -133,7 +133,7 @@ public abstract class ExtractEmbeddedSubtitlesHandlerBase(ILocalFileSystem local { string fullOutputPath = Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.OutputPath); Directory.CreateDirectory(Path.GetDirectoryName(fullOutputPath)); - if (localFileSystem.FileExists(fullOutputPath)) + if (fileSystem.File.Exists(fullOutputPath)) { File.Delete(fullOutputPath); } @@ -193,7 +193,7 @@ public abstract class ExtractEmbeddedSubtitlesHandlerBase(ILocalFileSystem local } string fullOutputPath = Path.Combine(FileSystemLayout.FontsCacheFolder, fontStream.FileName); - if (localFileSystem.FileExists(fullOutputPath)) + if (fileSystem.File.Exists(fullOutputPath)) { // already extracted continue; @@ -212,7 +212,7 @@ public abstract class ExtractEmbeddedSubtitlesHandlerBase(ILocalFileSystem local // ffmpeg seems to return exit code 1 in all cases when dumping an attachment // so ignore it and check success a different way - if (localFileSystem.FileExists(fullOutputPath)) + if (fileSystem.File.Exists(fullOutputPath)) { logger.LogDebug("Successfully extracted font {Font}", fontStream.FileName); } @@ -300,7 +300,7 @@ public abstract class ExtractEmbeddedSubtitlesHandlerBase(ILocalFileSystem local { foreach (string path in GetRelativeOutputPath(mediaItemId, subtitle)) { - return !localFileSystem.FileExists(path); + return !fileSystem.File.Exists(path); } return false; diff --git a/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs b/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs index 0f5afb374..48a824da7 100644 --- a/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs +++ b/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using Dapper; using ErsatzTV.Application.Streaming; using ErsatzTV.Core; @@ -30,6 +31,7 @@ public class PrepareTroubleshootingPlaybackHandler( IJellyfinPathReplacementService jellyfinPathReplacementService, IEmbyPathReplacementService embyPathReplacementService, IFFmpegProcessService ffmpegProcessService, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ISongVideoGenerator songVideoGenerator, IWatermarkSelector watermarkSelector, @@ -471,7 +473,7 @@ public class PrepareTroubleshootingPlaybackHandler( string path = await GetLocalPath(mediaItem, cancellationToken); // check filesystem first - if (localFileSystem.FileExists(path)) + if (fileSystem.File.Exists(path)) { if (mediaItem is RemoteStream remoteStream) { diff --git a/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs b/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs index c74dc1359..07a2cba0a 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; using Shouldly; +using Testably.Abstractions.Testing; namespace ErsatzTV.Core.Tests.FFmpeg; @@ -61,7 +62,7 @@ public class FFmpegStreamSelectorTests new ScriptEngine(Substitute.For>()), Substitute.For(), Substitute.For(), - Substitute.For(), + new MockFileSystem(), languageCodeService, Substitute.For>()); @@ -123,7 +124,7 @@ public class FFmpegStreamSelectorTests new ScriptEngine(Substitute.For>()), Substitute.For(), Substitute.For(), - Substitute.For(), + new MockFileSystem(), languageCodeService, Substitute.For>()); @@ -173,7 +174,7 @@ public class FFmpegStreamSelectorTests new ScriptEngine(Substitute.For>()), Substitute.For(), Substitute.For(), - Substitute.For(), + new MockFileSystem(), languageCodeService, Substitute.For>()); diff --git a/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs b/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs index 773a52cc3..3fb148eef 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs @@ -1,5 +1,4 @@ -using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Metadata; +using ErsatzTV.Core.Interfaces.Metadata; namespace ErsatzTV.Core.Tests.Fakes; diff --git a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs index 3571032d7..9aaf4545c 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs @@ -1,6 +1,5 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Scheduling; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling; @@ -8,6 +7,7 @@ using ErsatzTV.Core.Tests.Fakes; using NSubstitute; using NUnit.Framework; using Shouldly; +using Testably.Abstractions.Testing; namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling; @@ -588,7 +588,6 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -596,7 +595,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -714,7 +713,6 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -722,7 +720,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -842,7 +840,6 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -850,7 +847,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); diff --git a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs index 17e14d416..9749e21ed 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs @@ -1,7 +1,6 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Scheduling; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling; @@ -9,6 +8,7 @@ using ErsatzTV.Core.Tests.Fakes; using NSubstitute; using NUnit.Framework; using Shouldly; +using Testably.Abstractions.Testing; namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling; @@ -597,7 +597,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -605,7 +604,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -717,7 +716,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -725,7 +723,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -888,7 +886,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -896,7 +893,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1018,7 +1015,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1026,7 +1022,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1147,7 +1143,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1155,7 +1150,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1277,7 +1272,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1285,7 +1279,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1412,7 +1406,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1420,7 +1413,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1547,7 +1540,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1555,7 +1547,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1691,7 +1683,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1699,7 +1690,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1828,7 +1819,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1836,7 +1826,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -1931,7 +1921,6 @@ public class NewPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -1939,7 +1928,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); diff --git a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs index c087ab8e9..a44657672 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs @@ -2,7 +2,6 @@ using Destructurama; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Scheduling; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling; @@ -11,6 +10,7 @@ using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; using Serilog; +using Testably.Abstractions.Testing; namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling; @@ -66,7 +66,6 @@ public abstract class PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -74,7 +73,7 @@ public abstract class PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); @@ -182,7 +181,6 @@ public abstract class PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -190,7 +188,7 @@ public abstract class PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); diff --git a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs index 0332a1e51..72e3ea490 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs @@ -1,6 +1,5 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Scheduling; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling; @@ -8,6 +7,7 @@ using ErsatzTV.Core.Tests.Fakes; using NSubstitute; using NUnit.Framework; using Shouldly; +using Testably.Abstractions.Testing; namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling; @@ -101,7 +101,6 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase IArtistRepository artistRepo = Substitute.For(); IMultiEpisodeShuffleCollectionEnumeratorFactory factory = Substitute.For(); - ILocalFileSystem localFileSystem = Substitute.For(); IRerunHelper rerunHelper = Substitute.For(); var builder = new PlayoutBuilder( configRepo, @@ -109,7 +108,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase televisionRepo, artistRepo, factory, - localFileSystem, + new MockFileSystem(), rerunHelper, Logger); diff --git a/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs b/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs index 3d3db22ce..3344494e1 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs @@ -24,6 +24,7 @@ using NUnit.Framework; using Serilog; using Serilog.Events; using Serilog.Extensions.Logging; +using MockFileSystem = Testably.Abstractions.Testing.MockFileSystem; namespace ErsatzTV.Core.Tests.Scheduling; @@ -108,6 +109,7 @@ public class ScheduleIntegrationTests ISearchIndex searchIndex = provider.GetRequiredService(); await searchIndex.Initialize( new LocalFileSystem( + new MockFileSystem(), provider.GetRequiredService(), provider.GetRequiredService>()), provider.GetRequiredService(), @@ -125,7 +127,7 @@ public class ScheduleIntegrationTests new TelevisionRepository(factory, provider.GetRequiredService>()), new ArtistRepository(factory), Substitute.For(), - Substitute.For(), + new MockFileSystem(), Substitute.For(), provider.GetRequiredService>()); @@ -321,7 +323,7 @@ public class ScheduleIntegrationTests new TelevisionRepository(factory, provider.GetRequiredService>()), new ArtistRepository(factory), Substitute.For(), - Substitute.For(), + new MockFileSystem(), Substitute.For(), provider.GetRequiredService>()); diff --git a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs index 42c571c98..401650649 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO.Abstractions; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Metadata; @@ -15,7 +16,7 @@ namespace ErsatzTV.Core.FFmpeg; public class FFmpegStreamSelector : IFFmpegStreamSelector { private readonly IConfigElementRepository _configElementRepository; - private readonly ILocalFileSystem _localFileSystem; + private readonly IFileSystem _fileSystem; private readonly ILanguageCodeService _languageCodeService; private readonly ILogger _logger; private readonly IScriptEngine _scriptEngine; @@ -25,14 +26,14 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector IScriptEngine scriptEngine, IStreamSelectorRepository streamSelectorRepository, IConfigElementRepository configElementRepository, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILanguageCodeService languageCodeService, ILogger logger) { _scriptEngine = scriptEngine; _streamSelectorRepository = streamSelectorRepository; _configElementRepository = configElementRepository; - _localFileSystem = localFileSystem; + _fileSystem = fileSystem; _languageCodeService = languageCodeService; _logger = logger; } @@ -318,7 +319,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector "js"); _logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); - if (!_localFileSystem.FileExists(jsScriptPath)) + if (!_fileSystem.File.Exists(jsScriptPath)) { _logger.LogDebug("Unable to locate episode audio stream selector script; falling back to built-in logic"); return Option.None; @@ -358,7 +359,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector "js"); _logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); - if (!_localFileSystem.FileExists(jsScriptPath)) + if (!_fileSystem.File.Exists(jsScriptPath)) { _logger.LogDebug( "Unable to locate movie audio stream selector script; falling back to built-in logic"); diff --git a/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs b/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs index 39c290488..434770c25 100644 --- a/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs +++ b/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs @@ -8,7 +8,6 @@ public interface ILocalFileSystem IEnumerable ListFiles(string folder); IEnumerable ListFiles(string folder, string searchPattern); IEnumerable ListFiles(string folder, params string[] searchPatterns); - bool FileExists(string path); bool FolderExists(string folder); Task> CopyFile(string source, string destination); Unit EmptyFolder(string folder); diff --git a/ErsatzTV.Core/Metadata/LocalFileSystem.cs b/ErsatzTV.Core/Metadata/LocalFileSystem.cs index f706987b3..6a9826434 100644 --- a/ErsatzTV.Core/Metadata/LocalFileSystem.cs +++ b/ErsatzTV.Core/Metadata/LocalFileSystem.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.IO.Abstractions; using System.Security.Cryptography; using Bugsnag; using ErsatzTV.Core.Interfaces.Metadata; @@ -6,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace ErsatzTV.Core.Metadata; -public class LocalFileSystem(IClient client, ILogger logger) : ILocalFileSystem +public class LocalFileSystem(IFileSystem fileSystem, IClient client, ILogger logger) : ILocalFileSystem { public Unit EnsureFolderExists(string folder) { @@ -114,7 +115,8 @@ public class LocalFileSystem(IClient client, ILogger logger) : return searchPatterns .SelectMany(searchPattern => Directory.EnumerateFiles(folder, searchPattern, SearchOption.TopDirectoryOnly) - .Where(path => !Path.GetFileName(path).StartsWith("._", StringComparison.OrdinalIgnoreCase))) + .Where(path => + !Path.GetFileName(path).StartsWith("._", StringComparison.OrdinalIgnoreCase))) .Distinct(); } catch (UnauthorizedAccessException) @@ -131,8 +133,6 @@ public class LocalFileSystem(IClient client, ILogger logger) : return new List(); } - public bool FileExists(string path) => File.Exists(path); - public bool FolderExists(string folder) => Directory.Exists(folder); public async Task> CopyFile(string source, string destination) @@ -193,6 +193,6 @@ public class LocalFileSystem(IClient client, ILogger logger) : public string GetCustomOrDefaultFile(string folder, string file) { string path = Path.Combine(folder, file); - return FileExists(path) ? path : Path.Combine(folder, $"_{file}"); + return fileSystem.File.Exists(path) ? path : Path.Combine(folder, $"_{file}"); } } diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index 4e7dbc8da..c11579137 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -1,9 +1,9 @@ -using System.Reflection; +using System.IO.Abstractions; +using System.Reflection; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Errors; using ErsatzTV.Core.Extensions; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling.Engine; @@ -20,10 +20,10 @@ public class PlayoutBuilder : IPlayoutBuilder { private readonly IArtistRepository _artistRepository; private readonly IConfigElementRepository _configElementRepository; - private readonly ILocalFileSystem _localFileSystem; private readonly IRerunHelper _rerunHelper; private readonly IMediaCollectionRepository _mediaCollectionRepository; private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory; + private readonly IFileSystem _fileSystem; private readonly ITelevisionRepository _televisionRepository; private Playlist _debugPlaylist; private ILogger _logger; @@ -34,7 +34,7 @@ public class PlayoutBuilder : IPlayoutBuilder ITelevisionRepository televisionRepository, IArtistRepository artistRepository, IMultiEpisodeShuffleCollectionEnumeratorFactory multiEpisodeFactory, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, IRerunHelper rerunHelper, ILogger logger) { @@ -43,7 +43,7 @@ public class PlayoutBuilder : IPlayoutBuilder _televisionRepository = televisionRepository; _artistRepository = artistRepository; _multiEpisodeFactory = multiEpisodeFactory; - _localFileSystem = localFileSystem; + _fileSystem = fileSystem; _rerunHelper = rerunHelper; _logger = logger; } @@ -1403,7 +1403,7 @@ public class PlayoutBuilder : IPlayoutBuilder guid.Guid.Replace("://", "_")), "js"); _logger.LogDebug("Checking for JS Script at {Path}", jsScriptPath); - if (_localFileSystem.FileExists(jsScriptPath)) + if (_fileSystem.File.Exists(jsScriptPath)) { _logger.LogDebug("Found JS Script at {Path}", jsScriptPath); try diff --git a/ErsatzTV.Core/Scheduling/ScriptedScheduling/ScriptedPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/ScriptedScheduling/ScriptedPlayoutBuilder.cs index 7ff714f8f..2345d5344 100644 --- a/ErsatzTV.Core/Scheduling/ScriptedScheduling/ScriptedPlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/ScriptedScheduling/ScriptedPlayoutBuilder.cs @@ -1,8 +1,8 @@ using System.CommandLine.Parsing; +using System.IO.Abstractions; using CliWrap; using CliWrap.Buffered; using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling.Engine; @@ -14,7 +14,7 @@ public class ScriptedPlayoutBuilder( IConfigElementRepository configElementRepository, IScriptedPlayoutBuilderService scriptedPlayoutBuilderService, ISchedulingEngine schedulingEngine, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILogger logger) : IScriptedPlayoutBuilder { @@ -38,7 +38,7 @@ public class ScriptedPlayoutBuilder( string scriptFile = args[0]; string[] scriptArgs = args.Skip(1).ToArray(); - if (!localFileSystem.FileExists(scriptFile)) + if (!fileSystem.File.Exists(scriptFile)) { logger.LogError( "Cannot build scripted playout; schedule file {File} does not exist", diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/SequentialPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/SequentialPlayoutBuilder.cs index adc5c0a33..232fb6c58 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/SequentialPlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/SequentialPlayoutBuilder.cs @@ -1,7 +1,7 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Scheduling; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; //using ErsatzTV.Core.Scheduling.Engine; @@ -17,7 +17,7 @@ namespace ErsatzTV.Core.Scheduling.YamlScheduling; public class SequentialPlayoutBuilder( //ISchedulingEngine schedulingEngine, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, IConfigElementRepository configElementRepository, IMediaCollectionRepository mediaCollectionRepository, IChannelRepository channelRepository, @@ -38,7 +38,7 @@ public class SequentialPlayoutBuilder( PlayoutBuildResult result = PlayoutBuildResult.Empty; - if (!localFileSystem.FileExists(playout.ScheduleFile)) + if (!fileSystem.File.Exists(playout.ScheduleFile)) { logger.LogWarning("Sequential schedule file {File} does not exist; aborting.", playout.ScheduleFile); return BaseError.New($"Sequential schedule file {playout.ScheduleFile} does not exist"); diff --git a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj index a96dfa840..142eda6d5 100644 --- a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj +++ b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj @@ -22,6 +22,7 @@ all + diff --git a/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs b/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs index 038670d68..b9c1a4999 100644 --- a/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs +++ b/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; using Shouldly; +using Testably.Abstractions.Testing; namespace ErsatzTV.Infrastructure.Tests.Metadata; @@ -21,6 +22,7 @@ public class LocalStatisticsProviderTests { var provider = new LocalStatisticsProvider( Substitute.For(), + new MockFileSystem(), Substitute.For(), Substitute.For(), Substitute.For(), diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TemplateDataRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TemplateDataRepository.cs index 8092660a7..fe5883ee5 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TemplateDataRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TemplateDataRepository.cs @@ -1,8 +1,8 @@ using System.Globalization; +using System.IO.Abstractions; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; using ErsatzTV.Infrastructure.Epg; @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Infrastructure.Data.Repositories; -public class TemplateDataRepository(ILocalFileSystem localFileSystem, IDbContextFactory dbContextFactory) +public class TemplateDataRepository(IFileSystem fileSystem, IDbContextFactory dbContextFactory) : ITemplateDataRepository { public async Task>> GetMediaItemTemplateData( @@ -65,9 +65,9 @@ public class TemplateDataRepository(ILocalFileSystem localFileSystem, IDbContext } string targetFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{channelNumber}.xml"); - if (localFileSystem.FileExists(targetFile)) + if (fileSystem.File.Exists(targetFile)) { - await using FileStream stream = File.OpenRead(targetFile); + await using FileSystemStream stream = fileSystem.File.OpenRead(targetFile); List xmlProgrammes = EpgReader.FindProgrammesAt(stream, time, count); var result = new List>(); diff --git a/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs b/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs index da00dd699..266ac289c 100644 --- a/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs +++ b/ErsatzTV.Infrastructure/FFmpeg/MpegTsScriptService.cs @@ -28,7 +28,7 @@ public class MpegTsScriptService( foreach (string folder in localFileSystem.ListSubdirectories(FileSystemLayout.MpegTsScriptsFolder)) { string definition = Path.Combine(folder, "mpegts.yml"); - if (!Scripts.ContainsKey(folder) && localFileSystem.FileExists(definition)) + if (!Scripts.ContainsKey(folder) && fileSystem.File.Exists(definition)) { Option maybeScript = FromYaml(await fileSystem.File.ReadAllTextAsync(definition)); foreach (var script in maybeScript) diff --git a/ErsatzTV.Infrastructure/Images/ImageCache.cs b/ErsatzTV.Infrastructure/Images/ImageCache.cs index db5bee074..1c46feb57 100644 --- a/ErsatzTV.Infrastructure/Images/ImageCache.cs +++ b/ErsatzTV.Infrastructure/Images/ImageCache.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO.Abstractions; using System.Security.Cryptography; using System.Text; using Blurhash.SkiaSharp; @@ -14,27 +15,18 @@ using SkiaSharp; namespace ErsatzTV.Infrastructure.Images; [SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms")] -public class ImageCache : IImageCache +public class ImageCache(IFileSystem fileSystem, ILocalFileSystem localFileSystem, ITempFilePool tempFilePool) + : IImageCache { private static readonly SHA1 Crypto; - private readonly ILocalFileSystem _localFileSystem; - private readonly ITempFilePool _tempFilePool; static ImageCache() => Crypto = SHA1.Create(); - public ImageCache( - ILocalFileSystem localFileSystem, - ITempFilePool tempFilePool) - { - _localFileSystem = localFileSystem; - _tempFilePool = tempFilePool; - } - public async Task> SaveArtworkToCache(Stream stream, ArtworkKind artworkKind) { try { - string tempFileName = _tempFilePool.GetNextTempFile(TempFileCategory.CachedArtwork); + string tempFileName = tempFilePool.GetNextTempFile(TempFileCategory.CachedArtwork); // ReSharper disable once UseAwaitUsing using (var fs = new FileStream(tempFileName, FileMode.OpenOrCreate, FileAccess.Write)) { @@ -63,7 +55,7 @@ public class ImageCache : IImageCache Directory.CreateDirectory(baseFolder); } - await _localFileSystem.CopyFile(tempFileName, target); + await localFileSystem.CopyFile(tempFileName, target); return hex; } @@ -77,7 +69,7 @@ public class ImageCache : IImageCache { try { - var filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}"; + var filenameKey = $"{path}:{localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}"; byte[] hash = Crypto.ComputeHash(Encoding.UTF8.GetBytes(filenameKey)); string hex = Convert.ToHexString(hash); string subfolder = hex[..2]; @@ -90,7 +82,7 @@ public class ImageCache : IImageCache _ => FileSystemLayout.LegacyImageCacheFolder }; string target = Path.Combine(baseFolder, hex); - Either maybeResult = await _localFileSystem.CopyFile(path, target); + Either maybeResult = await localFileSystem.CopyFile(path, target); return maybeResult.Match>( _ => hex, error => error); @@ -159,14 +151,14 @@ public class ImageCache : IImageCache { byte[] bytes = Encoding.UTF8.GetBytes(blurHash); string base64 = Convert.ToBase64String(bytes).Replace("+", "_").Replace("/", "-").Replace("=", ""); - string targetFile = GetPathForImage(base64, ArtworkKind.Poster, targetSize.Height); - if (!_localFileSystem.FileExists(targetFile)) + string targetFile = GetPathForImage(base64, ArtworkKind.Poster, targetSize.Height) ?? string.Empty; + if (!fileSystem.File.Exists(targetFile)) { string folder = Path.GetDirectoryName(targetFile); - _localFileSystem.EnsureFolderExists(folder); + localFileSystem.EnsureFolderExists(folder); // ReSharper disable once ConvertToUsingDeclaration - using (FileStream fs = File.OpenWrite(targetFile)) + using (FileSystemStream fs = fileSystem.File.OpenWrite(targetFile)) { using (SKBitmap image = Blurhasher.Decode(blurHash, targetSize.Width, targetSize.Height)) { diff --git a/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs b/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs index e89e04b83..cff4a5f58 100644 --- a/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs +++ b/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO.Abstractions; using System.Text; using System.Text.RegularExpressions; using Bugsnag; @@ -25,15 +26,18 @@ public partial class LocalStatisticsProvider : ILocalStatisticsProvider private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly IMetadataRepository _metadataRepository; + private readonly IFileSystem _fileSystem; public LocalStatisticsProvider( IMetadataRepository metadataRepository, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, IClient client, IHardwareCapabilitiesFactory hardwareCapabilitiesFactory, ILogger logger) { _metadataRepository = metadataRepository; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _client = client; _hardwareCapabilitiesFactory = hardwareCapabilitiesFactory; @@ -146,7 +150,7 @@ public partial class LocalStatisticsProvider : ILocalStatisticsProvider } if (filePath.StartsWith("http", StringComparison.OrdinalIgnoreCase) || - !_localFileSystem.FileExists(filePath)) + !_fileSystem.File.Exists(filePath)) { _logger.LogDebug("Skipping interlaced ratio check for remote content"); return Option.None; diff --git a/ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs b/ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs index f3e3412d9..7e73234c4 100644 --- a/ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs +++ b/ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.IO.Abstractions; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; @@ -19,7 +20,7 @@ namespace ErsatzTV.Infrastructure.Streaming; public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider { private readonly IDbContextFactory _dbContextFactory; - private readonly ILocalFileSystem _localFileSystem; + private readonly IFileSystem _fileSystem; private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILogger _logger; private readonly IPlexPathReplacementService _plexPathReplacementService; @@ -28,7 +29,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider public ExternalJsonPlayoutItemProvider( IDbContextFactory dbContextFactory, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, IPlexPathReplacementService plexPathReplacementService, IPlexServerApiClient plexServerApiClient, IPlexSecretStore plexSecretStore, @@ -36,7 +37,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider ILogger logger) { _dbContextFactory = dbContextFactory; - _localFileSystem = localFileSystem; + _fileSystem = fileSystem; _plexPathReplacementService = plexPathReplacementService; _plexServerApiClient = plexServerApiClient; _plexSecretStore = plexSecretStore; @@ -62,7 +63,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider if (playout.ScheduleKind == PlayoutScheduleKind.ExternalJson) { // json file must exist - if (_localFileSystem.FileExists(playout.ScheduleFile)) + if (_fileSystem.File.Exists(playout.ScheduleFile)) { return await GetExternalJsonPlayoutItem(dbContext, playout, now, ffprobePath, cancellationToken); } @@ -86,7 +87,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider CancellationToken cancellationToken) { Option maybeChannel = JsonConvert.DeserializeObject( - await File.ReadAllTextAsync(playout.ScheduleFile, cancellationToken)); + await _fileSystem.File.ReadAllTextAsync(playout.ScheduleFile, cancellationToken)); // must deserialize channel from json foreach (ExternalJsonChannel channel in maybeChannel) @@ -139,7 +140,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider program.File, cancellationToken); - if (_localFileSystem.FileExists(localPath)) + if (_fileSystem.File.Exists(localPath)) { return await StreamLocally(startTime, program, ffprobePath, localPath); } diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs index fb60b21f4..a3a0065e9 100644 --- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs @@ -34,6 +34,7 @@ using NSubstitute; using NUnit.Framework; using Serilog; using Shouldly; +using Testably.Abstractions.Testing; using MediaStream = ErsatzTV.Core.Domain.MediaStream; namespace ErsatzTV.Scanner.Tests.Core.FFmpeg; @@ -235,8 +236,10 @@ public class TranscodingTests StreamingMode streamingMode) { var localFileSystem = new LocalFileSystem( + new MockFileSystem(), Substitute.For(), LoggerFactory.CreateLogger()); + var fileSystem = new MockFileSystem(); var tempFilePool = new TempFilePool(); ImageCache mockImageCache = Substitute.For(localFileSystem, tempFilePool); @@ -353,7 +356,8 @@ public class TranscodingTests var localStatisticsProvider = new LocalStatisticsProvider( metadataRepository, - new LocalFileSystem(Substitute.For(), LoggerFactory.CreateLogger()), + fileSystem, + localFileSystem, Substitute.For(), Substitute.For(), LoggerFactory.CreateLogger()); @@ -460,6 +464,12 @@ public class TranscodingTests // do nothing } + var localFileSystem = new LocalFileSystem( + new MockFileSystem(), + Substitute.For(), + LoggerFactory.CreateLogger()); + var fileSystem = new MockFileSystem(); + string file = fileToTest; if (string.IsNullOrWhiteSpace(file)) { @@ -503,7 +513,8 @@ public class TranscodingTests var localStatisticsProvider = new LocalStatisticsProvider( metadataRepository, - new LocalFileSystem(Substitute.For(), LoggerFactory.CreateLogger()), + fileSystem, + localFileSystem, Substitute.For(), Substitute.For(), LoggerFactory.CreateLogger()); @@ -669,9 +680,6 @@ public class TranscodingTests SubtitleMode = subtitleMode }; - var localFileSystem = new LocalFileSystem( - Substitute.For(), - LoggerFactory.CreateLogger()); var tempFilePool = new TempFilePool(); ImageCache mockImageCache = Substitute.For(localFileSystem, tempFilePool); diff --git a/ErsatzTV.Scanner.Tests/Core/Fakes/FakeLocalFileSystem.cs b/ErsatzTV.Scanner.Tests/Core/Fakes/FakeLocalFileSystem.cs index 1bd6a8119..605d944e6 100644 --- a/ErsatzTV.Scanner.Tests/Core/Fakes/FakeLocalFileSystem.cs +++ b/ErsatzTV.Scanner.Tests/Core/Fakes/FakeLocalFileSystem.cs @@ -1,5 +1,4 @@ using ErsatzTV.Core; -using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; namespace ErsatzTV.Scanner.Tests.Core.Fakes; diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs index 639be8c17..f5808939d 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs @@ -17,16 +17,14 @@ using NSubstitute; using NUnit.Framework; using Serilog; using Shouldly; +using Testably.Abstractions.Testing; +using Testably.Abstractions.Testing.Initializer; namespace ErsatzTV.Scanner.Tests.Core.Metadata; [TestFixture] public class MovieFolderScannerTests { - private static readonly string BadFakeRoot = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? @"C:\Movies-That-Dont-Exist" - : @"/movies-that-dont-exist"; - private static readonly string FakeRoot = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"C:\Movies" : "/movies"; @@ -718,9 +716,18 @@ public class MovieFolderScannerTests await _mediaItemRepository.Received(1).FlagFileNotFound(libraryPath, oldMoviePath); } - private MovieFolderScanner GetService(params FakeFileEntry[] files) => - new( + private MovieFolderScanner GetService(params FakeFileEntry[] files) + { + var fileSystem = new MockFileSystem(); + IFileSystemInitializer init = fileSystem.Initialize(); + foreach (var file in files) + { + init.WithFile(file.Path).Which(f => f.File.LastWriteTime = file.LastWriteTime); + } + + return new MovieFolderScanner( _scannerProxy, + fileSystem, new FakeLocalFileSystem([..files]), _movieRepository, _localStatisticsProvider, @@ -735,10 +742,20 @@ public class MovieFolderScannerTests Substitute.For(), Substitute.For(), Logger); + } - private MovieFolderScanner GetService(params FakeFolderEntry[] folders) => - new( + private MovieFolderScanner GetService(params FakeFolderEntry[] folders) + { + var fileSystem = new MockFileSystem(); + IFileSystemInitializer init = fileSystem.Initialize(); + foreach (var folder in folders) + { + init.WithSubdirectory(folder.Path); + } + + return new MovieFolderScanner( _scannerProxy, + fileSystem, new FakeLocalFileSystem([], [..folders]), _movieRepository, _localStatisticsProvider, @@ -753,5 +770,6 @@ public class MovieFolderScannerTests Substitute.For(), Substitute.For(), Logger); + } } } diff --git a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj index 874f98463..b312911f8 100644 --- a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj +++ b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj @@ -23,6 +23,7 @@ all + diff --git a/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs index 098d9ef8c..77f142e22 100644 --- a/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Emby/EmbyMovieLibraryScanner.cs @@ -1,9 +1,9 @@ -using ErsatzTV.Core; +using System.IO.Abstractions; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Emby; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Emby; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; using ErsatzTV.Scanner.Core.Interfaces; @@ -29,13 +29,13 @@ public class EmbyMovieLibraryScanner : IMediaSourceRepository mediaSourceRepository, IEmbyMovieRepository embyMovieRepository, IEmbyPathReplacementService pathReplacementService, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalChaptersProvider localChaptersProvider, IMetadataRepository metadataRepository, ILogger logger) : base( scannerProxy, - localFileSystem, + fileSystem, localChaptersProvider, metadataRepository, logger) diff --git a/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs index 8a45e72d0..2f3a2f0eb 100644 --- a/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Emby/EmbyTelevisionLibraryScanner.cs @@ -1,10 +1,10 @@ -using ErsatzTV.Core; +using System.IO.Abstractions; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Emby; using ErsatzTV.Core.Errors; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Emby; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; using ErsatzTV.Scanner.Core.Interfaces; @@ -30,13 +30,13 @@ public class EmbyTelevisionLibraryScanner : MediaServerTelevisionLibraryScanner< IMediaSourceRepository mediaSourceRepository, IEmbyTelevisionRepository televisionRepository, IEmbyPathReplacementService pathReplacementService, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalChaptersProvider localChaptersProvider, IMetadataRepository metadataRepository, ILogger logger) : base( scannerProxy, - localFileSystem, + fileSystem, localChaptersProvider, metadataRepository, logger) diff --git a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs index 215cbfbb9..a5c3c6e9e 100644 --- a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinMovieLibraryScanner.cs @@ -1,8 +1,8 @@ -using ErsatzTV.Core; +using System.IO.Abstractions; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Jellyfin; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Jellyfin; using ErsatzTV.Core.Metadata; @@ -29,13 +29,13 @@ public class JellyfinMovieLibraryScanner : IJellyfinMovieRepository jellyfinMovieRepository, IJellyfinPathReplacementService pathReplacementService, IMediaSourceRepository mediaSourceRepository, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalChaptersProvider localChaptersProvider, IMetadataRepository metadataRepository, ILogger logger) : base( scannerProxy, - localFileSystem, + fileSystem, localChaptersProvider, metadataRepository, logger) diff --git a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs index b26255f0c..82957f505 100644 --- a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinTelevisionLibraryScanner.cs @@ -1,9 +1,9 @@ -using ErsatzTV.Core; +using System.IO.Abstractions; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Errors; using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Interfaces.Jellyfin; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Jellyfin; using ErsatzTV.Core.Metadata; @@ -31,13 +31,13 @@ public class JellyfinTelevisionLibraryScanner : MediaServerTelevisionLibraryScan IMediaSourceRepository mediaSourceRepository, IJellyfinTelevisionRepository televisionRepository, IJellyfinPathReplacementService pathReplacementService, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalChaptersProvider localChaptersProvider, IMetadataRepository metadataRepository, ILogger logger) : base( scannerProxy, - localFileSystem, + fileSystem, localChaptersProvider, metadataRepository, logger) diff --git a/ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs index 9401e6327..e6df0d782 100644 --- a/ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/ImageFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -22,6 +23,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner private readonly IImageRepository _imageRepository; private readonly ILibraryRepository _libraryRepository; private readonly IScannerProxy _scannerProxy; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILogger _logger; @@ -29,6 +31,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner public ImageFolderScanner( IScannerProxy scannerProxy, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, @@ -41,7 +44,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner ITempFilePool tempFilePool, IClient client, ILogger logger) : base( - localFileSystem, + fileSystem, localStatisticsProvider, metadataRepository, mediaItemRepository, @@ -52,6 +55,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner logger) { _scannerProxy = scannerProxy; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _localMetadataProvider = localMetadataProvider; _imageRepository = imageRepository; @@ -226,7 +230,7 @@ public class ImageFolderScanner : LocalFolderScanner, IImageFolderScanner foreach (string path in await _imageRepository.FindImagePaths(libraryPath)) { - if (!_localFileSystem.FileExists(path)) + if (!_fileSystem.File.Exists(path)) { _logger.LogInformation("Flagging missing image at {Path}", path); List imageIds = await FlagFileNotFound(libraryPath, path); diff --git a/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs index 02e9ca7ef..1823833be 100644 --- a/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/LocalFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using CliWrap; using ErsatzTV.Core; @@ -59,7 +60,7 @@ public abstract class LocalFolderScanner private readonly IImageCache _imageCache; - private readonly ILocalFileSystem _localFileSystem; + private readonly IFileSystem _fileSystem; private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILogger _logger; private readonly IMediaItemRepository _mediaItemRepository; @@ -67,7 +68,7 @@ public abstract class LocalFolderScanner private readonly ITempFilePool _tempFilePool; protected LocalFolderScanner( - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalStatisticsProvider localStatisticsProvider, IMetadataRepository metadataRepository, IMediaItemRepository mediaItemRepository, @@ -77,7 +78,7 @@ public abstract class LocalFolderScanner IClient client, ILogger logger) { - _localFileSystem = localFileSystem; + _fileSystem = fileSystem; _localStatisticsProvider = localStatisticsProvider; _metadataRepository = metadataRepository; _mediaItemRepository = mediaItemRepository; @@ -100,7 +101,7 @@ public abstract class LocalFolderScanner string path = version.MediaFiles.Head().Path; - if (version.DateUpdated != _localFileSystem.GetLastWriteTime(path) || version.Streams.Count == 0) + if (version.DateUpdated != _fileSystem.File.GetLastWriteTime(path) || version.Streams.Count == 0) { _logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", path); Either refreshResult = @@ -141,7 +142,7 @@ public abstract class LocalFolderScanner Option attachedPicIndex, CancellationToken cancellationToken) { - DateTime lastWriteTime = _localFileSystem.GetLastWriteTime(artworkFile); + DateTime lastWriteTime = _fileSystem.File.GetLastWriteTime(artworkFile); metadata.Artwork ??= new List(); @@ -311,5 +312,5 @@ public abstract class LocalFolderScanner protected bool ShouldIncludeFolder(string folder) => !string.IsNullOrWhiteSpace(folder) && !Path.GetFileName(folder).StartsWith('.') && - !_localFileSystem.FileExists(Path.Combine(folder, ".etvignore")); + !_fileSystem.File.Exists(Path.Combine(folder, ".etvignore")); } diff --git a/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs b/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs index 9f4d4a2b6..513b9fb6d 100644 --- a/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs +++ b/ErsatzTV.Scanner/Core/Metadata/LocalMetadataProvider.cs @@ -1,4 +1,5 @@ -using Bugsnag; +using System.IO.Abstractions; +using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; @@ -22,6 +23,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider private readonly IClient _client; private readonly IEpisodeNfoReader _episodeNfoReader; private readonly IFallbackMetadataProvider _fallbackMetadataProvider; + private readonly IFileSystem _fileSystem; private readonly IImageRepository _imageRepository; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalStatisticsProvider _localStatisticsProvider; @@ -49,6 +51,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider IImageRepository imageRepository, IRemoteStreamRepository remoteStreamRepository, IFallbackMetadataProvider fallbackMetadataProvider, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, IMovieNfoReader movieNfoReader, IEpisodeNfoReader episodeNfoReader, @@ -70,6 +73,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider _imageRepository = imageRepository; _remoteStreamRepository = remoteStreamRepository; _fallbackMetadataProvider = fallbackMetadataProvider; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _movieNfoReader = movieNfoReader; _episodeNfoReader = episodeNfoReader; @@ -86,7 +90,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider { string nfoFileName = Path.Combine(showFolder, "tvshow.nfo"); Option maybeMetadata = None; - if (_localFileSystem.FileExists(nfoFileName)) + if (_fileSystem.File.Exists(nfoFileName)) { maybeMetadata = await LoadTelevisionShowMetadata(nfoFileName); } @@ -106,7 +110,7 @@ public class LocalMetadataProvider : ILocalMetadataProvider { string nfoFileName = Path.Combine(artistFolder, "artist.nfo"); Option maybeMetadata = None; - if (_localFileSystem.FileExists(nfoFileName)) + if (_fileSystem.File.Exists(nfoFileName)) { maybeMetadata = await LoadArtistMetadata(nfoFileName); } diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs index 01010b402..cacedbf8a 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs @@ -1,10 +1,10 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.MediaServer; using ErsatzTV.Core.Errors; using ErsatzTV.Core.Extensions; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; using ErsatzTV.Scanner.Core.Interfaces; @@ -21,19 +21,19 @@ public abstract class MediaServerMovieLibraryScanner flagResult = await movieRepository.FlagNormal(library, result.Item); if (flagResult.IsSome) @@ -270,7 +270,7 @@ public abstract class MediaServerMovieLibraryScanner flagResult = await otherVideoRepository.FlagNormal(library, result.Item); if (flagResult.IsSome) @@ -277,7 +277,7 @@ public abstract class MediaServerOtherVideoLibraryScanner flagResult = await televisionRepository.FlagNormal(library, result.Item, cancellationToken); if (flagResult.IsSome) @@ -541,7 +541,7 @@ public abstract class MediaServerTelevisionLibraryScanner refreshResult = diff --git a/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs index ab3364d18..60d627e9f 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -23,6 +24,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner private readonly ILibraryRepository _libraryRepository; private readonly ILocalChaptersProvider _localChaptersProvider; private readonly IScannerProxy _scannerProxy; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider; @@ -32,6 +34,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner public MovieFolderScanner( IScannerProxy scannerProxy, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, IMovieRepository movieRepository, ILocalStatisticsProvider localStatisticsProvider, @@ -47,7 +50,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner IClient client, ILogger logger) : base( - localFileSystem, + fileSystem, localStatisticsProvider, metadataRepository, mediaItemRepository, @@ -58,6 +61,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner logger) { _scannerProxy = scannerProxy; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _movieRepository = movieRepository; _localSubtitlesProvider = localSubtitlesProvider; @@ -209,7 +213,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner foreach (string path in await _movieRepository.FindMoviePaths(libraryPath)) { - if (!_localFileSystem.FileExists(path)) + if (!_fileSystem.File.Exists(path)) { _logger.LogInformation("Flagging missing movie at {Path}", path); List ids = await FlagFileNotFound(libraryPath, path); @@ -362,7 +366,7 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner string movieAsNfo = Path.ChangeExtension(path, "nfo"); string movieNfo = Path.Combine(Path.GetDirectoryName(path) ?? string.Empty, "movie.nfo"); return Seq.create(movieAsNfo, movieNfo) - .Filter(s => _localFileSystem.FileExists(s)) + .Filter(s => _fileSystem.File.Exists(s)) .HeadOrNone(); } @@ -380,12 +384,12 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner IEnumerable possibleMoviePosters = ImageFileExtensions.Collect(ext => new[] { $"{segment}.{ext}", Path.GetFileNameWithoutExtension(path) + $"-{segment}.{ext}" }) .Map(f => Path.Combine(folder, f)); - Option result = possibleMoviePosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone(); + Option result = possibleMoviePosters.Filter(p => _fileSystem.File.Exists(p)).HeadOrNone(); if (result.IsNone && artworkKind == ArtworkKind.Poster) { IEnumerable possibleFolderPosters = ImageFileExtensions.Collect(ext => new[] { $"folder.{ext}" }) .Map(f => Path.Combine(folder, f)); - result = possibleFolderPosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone(); + result = possibleFolderPosters.Filter(p => _fileSystem.File.Exists(p)).HeadOrNone(); } return result; diff --git a/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs index 7aaaf441f..4969679bf 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -23,6 +24,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan private readonly ILibraryRepository _libraryRepository; private readonly ILocalChaptersProvider _localChaptersProvider; private readonly IScannerProxy _scannerProxy; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider; @@ -32,6 +34,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan public MusicVideoFolderScanner( IScannerProxy scannerProxy, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, @@ -47,7 +50,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan ITempFilePool tempFilePool, IClient client, ILogger logger) : base( - localFileSystem, + fileSystem, localStatisticsProvider, metadataRepository, mediaItemRepository, @@ -58,6 +61,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan logger) { _scannerProxy = scannerProxy; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _localMetadataProvider = localMetadataProvider; _localSubtitlesProvider = localSubtitlesProvider; @@ -173,7 +177,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan foreach (string path in await _musicVideoRepository.FindMusicVideoPaths(libraryPath)) { - if (!_localFileSystem.FileExists(path)) + if (!_fileSystem.File.Exists(path)) { _logger.LogInformation("Flagging missing music video at {Path}", path); List musicVideoIds = await FlagFileNotFound(libraryPath, path); @@ -459,7 +463,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan } private Option LocateNfoFileForArtist(string artistFolder) => - Optional(Path.Combine(artistFolder, "artist.nfo")).Filter(s => _localFileSystem.FileExists(s)); + Optional(Path.Combine(artistFolder, "artist.nfo")).Filter(s => _fileSystem.File.Exists(s)); private Option LocateArtworkForArtist(string artistFolder, ArtworkKind artworkKind) { @@ -473,7 +477,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan return ImageFileExtensions .Map(ext => $"{segment}.{ext}") .Map(f => Path.Combine(artistFolder, f)) - .Filter(s => _localFileSystem.FileExists(s)) + .Filter(s => _fileSystem.File.Exists(s)) .HeadOrNone(); } @@ -481,7 +485,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan { string path = musicVideo.MediaVersions.Head().MediaFiles.Head().Path; return Optional(Path.ChangeExtension(path, "nfo")) - .Filter(s => _localFileSystem.FileExists(s)) + .Filter(s => _fileSystem.File.Exists(s)) .HeadOrNone(); } @@ -554,7 +558,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan return ImageFileExtensions .SelectMany(ext => new[] { Path.ChangeExtension(path, ext), Path.ChangeExtension(thumbPath, ext) }) - .Filter(f => _localFileSystem.FileExists(f)) + .Filter(f => _fileSystem.File.Exists(f)) .HeadOrNone(); } } diff --git a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs index 7d42856e4..ecbacbdc3 100644 --- a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -22,6 +23,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan private readonly ILibraryRepository _libraryRepository; private readonly ILocalChaptersProvider _localChaptersProvider; private readonly IScannerProxy _scannerProxy; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider; @@ -31,6 +33,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan public OtherVideoFolderScanner( IScannerProxy scannerProxy, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, @@ -45,7 +48,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan ITempFilePool tempFilePool, IClient client, ILogger logger) : base( - localFileSystem, + fileSystem, localStatisticsProvider, metadataRepository, mediaItemRepository, @@ -56,6 +59,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan logger) { _scannerProxy = scannerProxy; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _localMetadataProvider = localMetadataProvider; _localSubtitlesProvider = localSubtitlesProvider; @@ -220,7 +224,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan foreach (string path in await _otherVideoRepository.FindOtherVideoPaths(libraryPath)) { - if (!_localFileSystem.FileExists(path)) + if (!_fileSystem.File.Exists(path)) { _logger.LogInformation("Flagging missing other video at {Path}", path); List otherVideoIds = await FlagFileNotFound(libraryPath, path); @@ -274,7 +278,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan string path = otherVideo.MediaVersions.Head().MediaFiles.Head().Path; Option maybeNfoFile = new List { Path.ChangeExtension(path, "nfo") } - .Filter(_localFileSystem.FileExists) + .Filter(_fileSystem.File.Exists) .HeadOrNone(); if (maybeNfoFile.IsNone) @@ -376,7 +380,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan string path = otherVideo.MediaVersions.Head().MediaFiles.Head().Path; return ImageFileExtensions .Map(ext => Path.ChangeExtension(path, ext)) - .Filter(f => _localFileSystem.FileExists(f)) + .Filter(f => _fileSystem.File.Exists(f)) .HeadOrNone(); } } diff --git a/ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs index e6f0fdba0..edf91b42e 100644 --- a/ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -24,6 +25,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder private readonly IClient _client; private readonly ILibraryRepository _libraryRepository; private readonly IScannerProxy _scannerProxy; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILogger _logger; @@ -32,6 +34,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder public RemoteStreamFolderScanner( IScannerProxy scannerProxy, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, @@ -44,7 +47,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder ITempFilePool tempFilePool, IClient client, ILogger logger) : base( - localFileSystem, + fileSystem, localStatisticsProvider, metadataRepository, mediaItemRepository, @@ -55,6 +58,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder logger) { _scannerProxy = scannerProxy; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _localMetadataProvider = localMetadataProvider; _remoteStreamRepository = remoteStreamRepository; @@ -213,7 +217,7 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder foreach (string path in await _remoteStreamRepository.FindRemoteStreamPaths(libraryPath, cancellationToken)) { - if (!_localFileSystem.FileExists(path)) + if (!_fileSystem.File.Exists(path)) { _logger.LogInformation("Flagging missing remote stream at {Path}", path); List remoteStreamIds = await FlagFileNotFound(libraryPath, path); diff --git a/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs index 994bab319..45b862680 100644 --- a/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -21,6 +22,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner private readonly IClient _client; private readonly ILibraryRepository _libraryRepository; private readonly IScannerProxy _scannerProxy; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILogger _logger; @@ -29,6 +31,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner public SongFolderScanner( IScannerProxy scannerProxy, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, @@ -41,7 +44,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner ITempFilePool tempFilePool, IClient client, ILogger logger) : base( - localFileSystem, + fileSystem, localStatisticsProvider, metadataRepository, mediaItemRepository, @@ -52,6 +55,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner logger) { _scannerProxy = scannerProxy; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _localMetadataProvider = localMetadataProvider; _songRepository = songRepository; @@ -201,7 +205,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner foreach (string path in await _songRepository.FindSongPaths(libraryPath)) { - if (!_localFileSystem.FileExists(path)) + if (!_fileSystem.File.Exists(path)) { _logger.LogInformation("Flagging missing song at {Path}", path); List songIds = await FlagFileNotFound(libraryPath, path); @@ -336,7 +340,7 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner string coverPath = Path.Combine(di.FullName, "cover.jpg"); return ImageFileExtensions .Map(ext => Path.ChangeExtension(coverPath, ext)) - .Filter(f => _localFileSystem.FileExists(f)) + .Filter(f => _fileSystem.File.Exists(f)) .HeadOrNone(); }).Flatten(); } diff --git a/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs index 9dcd79f3d..7bf9e73df 100644 --- a/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.IO.Abstractions; using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Core.Domain; @@ -23,6 +24,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan private readonly ILibraryRepository _libraryRepository; private readonly ILocalChaptersProvider _localChaptersProvider; private readonly IScannerProxy _scannerProxy; + private readonly IFileSystem _fileSystem; private readonly ILocalFileSystem _localFileSystem; private readonly ILocalMetadataProvider _localMetadataProvider; private readonly ILocalSubtitlesProvider _localSubtitlesProvider; @@ -33,6 +35,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan public TelevisionFolderScanner( IScannerProxy scannerProxy, + IFileSystem fileSystem, ILocalFileSystem localFileSystem, ITelevisionRepository televisionRepository, ILocalStatisticsProvider localStatisticsProvider, @@ -48,7 +51,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan IClient client, IFallbackMetadataProvider fallbackMetadataProvider, ILogger logger) : base( - localFileSystem, + fileSystem, localStatisticsProvider, metadataRepository, mediaItemRepository, @@ -59,6 +62,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan logger) { _scannerProxy = scannerProxy; + _fileSystem = fileSystem; _localFileSystem = localFileSystem; _televisionRepository = televisionRepository; _localMetadataProvider = localMetadataProvider; @@ -169,7 +173,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan foreach (string path in await _televisionRepository.FindEpisodePaths(libraryPath)) { - if (!_localFileSystem.FileExists(path)) + if (!_fileSystem.File.Exists(path)) { _logger.LogInformation("Flagging missing episode at {Path}", path); @@ -584,12 +588,12 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan } private Option LocateNfoFileForShow(string showFolder) => - Optional(Path.Combine(showFolder, "tvshow.nfo")).Filter(s => _localFileSystem.FileExists(s)); + Optional(Path.Combine(showFolder, "tvshow.nfo")).Filter(s => _fileSystem.File.Exists(s)); private Option LocateNfoFile(Episode episode) { string path = episode.MediaVersions.Head().MediaFiles.Head().Path; - return Optional(Path.ChangeExtension(path, "nfo")).Filter(s => _localFileSystem.FileExists(s)); + return Optional(Path.ChangeExtension(path, "nfo")).Filter(s => _fileSystem.File.Exists(s)); } private Option LocateArtworkForShow(string showFolder, ArtworkKind artworkKind) @@ -606,7 +610,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan .Map(ext => segments.Map(segment => $"{segment}.{ext}")) .Flatten() .Map(f => Path.Combine(showFolder, f)) - .Filter(s => _localFileSystem.FileExists(s)) + .Filter(s => _fileSystem.File.Exists(s)) .HeadOrNone(); } @@ -615,7 +619,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan string folder = Path.GetDirectoryName(seasonFolder) ?? string.Empty; return ImageFileExtensions .Map(ext => Path.Combine(folder, $"season{season.SeasonNumber:00}-poster.{ext}")) - .Filter(s => _localFileSystem.FileExists(s)) + .Filter(s => _fileSystem.File.Exists(s)) .HeadOrNone(); } @@ -626,7 +630,7 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan return ImageFileExtensions .Map(ext => Path.GetFileNameWithoutExtension(path) + $"-thumb.{ext}") .Map(f => Path.Combine(folder, f)) - .Filter(f => _localFileSystem.FileExists(f)) + .Filter(f => _fileSystem.File.Exists(f)) .HeadOrNone(); } } diff --git a/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs index d20c8bfb2..7aaa8fb77 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs @@ -1,7 +1,7 @@ -using ErsatzTV.Core; +using System.IO.Abstractions; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; @@ -33,12 +33,12 @@ public class PlexMovieLibraryScanner : IMediaSourceRepository mediaSourceRepository, IPlexMovieRepository plexMovieRepository, IPlexPathReplacementService plexPathReplacementService, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalChaptersProvider localChaptersProvider, ILogger logger) : base( scannerProxy, - localFileSystem, + fileSystem, localChaptersProvider, metadataRepository, logger) diff --git a/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs index a9052dc74..36a006a8c 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs @@ -1,7 +1,7 @@ +using System.IO.Abstractions; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Extensions; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; @@ -33,12 +33,12 @@ public class PlexOtherVideoLibraryScanner : IMediaSourceRepository mediaSourceRepository, IPlexOtherVideoRepository plexOtherVideoRepository, IPlexPathReplacementService plexPathReplacementService, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalChaptersProvider localChaptersProvider, ILogger logger) : base( scannerProxy, - localFileSystem, + fileSystem, localChaptersProvider, metadataRepository, logger) diff --git a/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs index a94180034..5f20b7c69 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs @@ -1,9 +1,9 @@ -using System.Text.RegularExpressions; +using System.IO.Abstractions; +using System.Text.RegularExpressions; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Errors; using ErsatzTV.Core.Extensions; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; @@ -37,12 +37,12 @@ public partial class PlexTelevisionLibraryScanner : IMediaSourceRepository mediaSourceRepository, IPlexPathReplacementService plexPathReplacementService, IPlexTelevisionRepository plexTelevisionRepository, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, ILocalChaptersProvider localChaptersProvider, ILogger logger) : base( scannerProxy, - localFileSystem, + fileSystem, localChaptersProvider, metadataRepository, logger) diff --git a/ErsatzTV/Controllers/Api/TroubleshootController.cs b/ErsatzTV/Controllers/Api/TroubleshootController.cs index 48eb93512..7b3360411 100644 --- a/ErsatzTV/Controllers/Api/TroubleshootController.cs +++ b/ErsatzTV/Controllers/Api/TroubleshootController.cs @@ -1,3 +1,4 @@ +using System.IO.Abstractions; using System.Threading.Channels; using ErsatzTV.Application; using ErsatzTV.Application.MediaItems; @@ -6,7 +7,6 @@ using ErsatzTV.Application.Troubleshooting.Queries; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.FFmpeg; -using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Troubleshooting; using MediatR; @@ -18,7 +18,7 @@ namespace ErsatzTV.Controllers.Api; [ApiController] public class TroubleshootController( ChannelWriter channelWriter, - ILocalFileSystem localFileSystem, + IFileSystem fileSystem, IConfigElementRepository configElementRepository, ITroubleshootingNotifier notifier, IMediator mediator) : ControllerBase @@ -104,7 +104,7 @@ public class TroubleshootController( cancellationToken); string playlistFile = Path.Combine(FileSystemLayout.TranscodeTroubleshootingFolder, "live.m3u8"); - while (!localFileSystem.FileExists(playlistFile)) + while (!fileSystem.File.Exists(playlistFile)) { await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); if (cancellationToken.IsCancellationRequested || notifier.IsFailed(sessionId)) diff --git a/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor b/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor index 5b27d6ca0..e005930ad 100644 --- a/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor +++ b/ErsatzTV/Pages/Troubleshooting/PlaybackTroubleshooting.razor @@ -1,5 +1,6 @@ @page "/system/troubleshooting/playback" @using System.Globalization +@using System.IO.Abstractions @using ErsatzTV.Application.Channels @using ErsatzTV.Application.FFmpegProfiles @using ErsatzTV.Application.Graphics @@ -7,7 +8,6 @@ @using ErsatzTV.Application.Troubleshooting @using ErsatzTV.Application.Troubleshooting.Queries @using ErsatzTV.Application.Watermarks -@using ErsatzTV.Core.Interfaces.Metadata @using ErsatzTV.Core.Notifications @using MediatR.Courier @using Microsoft.AspNetCore.WebUtilities @@ -18,7 +18,7 @@ @inject IEntityLocker Locker @inject ICourier Courier; @inject ISnackbar Snackbar; -@inject ILocalFileSystem LocalFileSystem; +@inject IFileSystem FileSystem; @@ -422,7 +422,7 @@ } string logFileName = Path.Combine(FileSystemLayout.TranscodeTroubleshootingFolder, "logs.txt"); - if (LocalFileSystem.FileExists(logFileName)) + if (FileSystem.File.Exists(logFileName)) { string text = await File.ReadAllTextAsync(logFileName); await InvokeAsync(async () => { await _logsField.SetText(text); }); diff --git a/ErsatzTV/Services/RunOnce/CacheCleanerService.cs b/ErsatzTV/Services/RunOnce/CacheCleanerService.cs index 1a4731ba4..a873e7ec6 100644 --- a/ErsatzTV/Services/RunOnce/CacheCleanerService.cs +++ b/ErsatzTV/Services/RunOnce/CacheCleanerService.cs @@ -1,4 +1,5 @@ -using ErsatzTV.Core; +using System.IO.Abstractions; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Infrastructure.Data; @@ -6,39 +7,30 @@ using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Services.RunOnce; -public class CacheCleanerService : BackgroundService +public class CacheCleanerService( + IServiceScopeFactory serviceScopeFactory, + SystemStartup systemStartup, + ILogger logger) + : BackgroundService { - private readonly ILogger _logger; - private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly SystemStartup _systemStartup; - - public CacheCleanerService( - IServiceScopeFactory serviceScopeFactory, - SystemStartup systemStartup, - ILogger logger) - { - _serviceScopeFactory = serviceScopeFactory; - _systemStartup = systemStartup; - _logger = logger; - } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await Task.Yield(); - await _systemStartup.WaitForDatabase(stoppingToken); + await systemStartup.WaitForDatabase(stoppingToken); if (stoppingToken.IsCancellationRequested) { return; } - using IServiceScope scope = _serviceScopeFactory.CreateScope(); + using IServiceScope scope = serviceScopeFactory.CreateScope(); await using TvContext dbContext = scope.ServiceProvider.GetRequiredService(); ILocalFileSystem localFileSystem = scope.ServiceProvider.GetRequiredService(); + IFileSystem fileSystem = scope.ServiceProvider.GetRequiredService(); if (localFileSystem.FolderExists(FileSystemLayout.LegacyImageCacheFolder)) { - _logger.LogInformation("Migrating channel logos from legacy image cache folder"); + logger.LogInformation("Migrating channel logos from legacy image cache folder"); List logos = await dbContext.Channels .AsNoTracking() @@ -50,7 +42,7 @@ public class CacheCleanerService : BackgroundService foreach (string logo in logos) { string legacyPath = Path.Combine(FileSystemLayout.LegacyImageCacheFolder, logo); - if (localFileSystem.FileExists(legacyPath)) + if (fileSystem.File.Exists(legacyPath)) { string subfolder = logo[..2]; string newPath = Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder, logo); @@ -58,20 +50,20 @@ public class CacheCleanerService : BackgroundService } } - _logger.LogInformation("Deleting legacy image cache folder"); + logger.LogInformation("Deleting legacy image cache folder"); Directory.Delete(FileSystemLayout.LegacyImageCacheFolder, true); } if (localFileSystem.FolderExists(FileSystemLayout.TranscodeFolder)) { - _logger.LogInformation("Emptying transcode cache folder"); + logger.LogInformation("Emptying transcode cache folder"); localFileSystem.EmptyFolder(FileSystemLayout.TranscodeFolder); - _logger.LogInformation("Done emptying transcode cache folder"); + logger.LogInformation("Done emptying transcode cache folder"); } if (localFileSystem.FolderExists(FileSystemLayout.ChannelGuideCacheFolder)) { - _logger.LogInformation("Cleaning channel cache"); + logger.LogInformation("Cleaning channel cache"); List channelFiles = await dbContext.Channels .AsNoTracking()