work around sequential schedule validation limit (#2655)
* remove readalltext * remove unused method * remove fileexists * remove folderexists * remove readalllines * remove fake local file system * show playlist name in playout build errors * add basic sequential schedule validator tests * work around sequential schedule validation limit
This commit is contained in:
@@ -84,6 +84,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Add disabled text color and `(D)` and `(H)` labels for disabled and hidden channels in channel list
|
||||
- Graphics engine: fix subtitle path escaping and font loading
|
||||
- Fix corrupt output (green artifacts) when decoding certain 10-bit content using AMD Polaris GPUs
|
||||
- Work around sequential schedule validation limit (1000/hr by Newtonsoft.Json.Schema library)
|
||||
- Playout builds now use JsonSchema.Net library which has no validation limit
|
||||
- Validation tool in the UI still uses Newtonsoft.Json.Schema (with 1000/hr limit) as the error output is easier to understand
|
||||
|
||||
### Changed
|
||||
- Classic schedules: `Refresh` classic playouts from playout list; do not `Reset` them
|
||||
|
||||
@@ -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<DeleteChannel, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ISearchTargets _searchTargets;
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
||||
|
||||
public DeleteChannelHandler(
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel,
|
||||
IDbContextFactory<TvContext> 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<DeleteChannel, Either<BaseEr
|
||||
|
||||
// delete channel data from channel guide cache
|
||||
string cacheFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{channel.Number}.xml");
|
||||
if (_localFileSystem.FileExists(cacheFile))
|
||||
if (_fileSystem.File.Exists(cacheFile))
|
||||
{
|
||||
File.Delete(cacheFile);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.Xml;
|
||||
using ErsatzTV.Application.Configuration;
|
||||
using ErsatzTV.Core;
|
||||
@@ -26,6 +27,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
{
|
||||
private readonly IConfigElementRepository _configElementRepository;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly ILogger<RefreshChannelDataHandler> _logger;
|
||||
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
|
||||
@@ -33,12 +35,14 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
public RefreshChannelDataHandler(
|
||||
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
IFileSystem fileSystem,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ILogger<RefreshChannelDataHandler> logger)
|
||||
{
|
||||
_recyclableMemoryStreamManager = recyclableMemoryStreamManager;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_localFileSystem = localFileSystem;
|
||||
_configElementRepository = configElementRepository;
|
||||
_logger = logger;
|
||||
@@ -886,7 +890,7 @@ public class RefreshChannelDataHandler : IRequestHandler<RefreshChannelData>
|
||||
"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<RefreshChannelData>
|
||||
"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<RefreshChannelData>
|
||||
"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<RefreshChannelData>
|
||||
"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<RefreshChannelData>
|
||||
"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<RefreshChannelData>
|
||||
{
|
||||
var result = new List<PlayoutItem>();
|
||||
|
||||
if (_localFileSystem.FileExists(path))
|
||||
if (_fileSystem.File.Exists(path))
|
||||
{
|
||||
Option<ExternalJsonChannel> maybeChannel = JsonConvert.DeserializeObject<ExternalJsonChannel>(
|
||||
await File.ReadAllTextAsync(path));
|
||||
|
||||
@@ -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<RefreshChannelList>
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly ILogger<RefreshChannelListHandler> _logger;
|
||||
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
|
||||
@@ -26,11 +28,13 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList>
|
||||
public RefreshChannelListHandler(
|
||||
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
IFileSystem fileSystem,
|
||||
ILocalFileSystem localFileSystem,
|
||||
ILogger<RefreshChannelListHandler> logger)
|
||||
{
|
||||
_recyclableMemoryStreamManager = recyclableMemoryStreamManager;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_localFileSystem = localFileSystem;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -44,13 +48,13 @@ public class RefreshChannelListHandler : IRequestHandler<RefreshChannelList>
|
||||
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",
|
||||
|
||||
@@ -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<GetChannelGuide, E
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public GetChannelGuideHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
|
||||
IFileSystem fileSystem,
|
||||
ILocalFileSystem localFileSystem)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_recyclableMemoryStreamManager = recyclableMemoryStreamManager;
|
||||
_fileSystem = fileSystem;
|
||||
_localFileSystem = localFileSystem;
|
||||
}
|
||||
|
||||
@@ -39,7 +43,7 @@ public partial class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, E
|
||||
.ToImmutableHashSet();
|
||||
|
||||
string channelsFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, "channels.xml");
|
||||
if (!_localFileSystem.FileExists(channelsFile))
|
||||
if (!_fileSystem.File.Exists(channelsFile))
|
||||
{
|
||||
return BaseError.New($"Required file {channelsFile} is missing");
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading.Channels;
|
||||
using ErsatzTV.Application.FFmpeg;
|
||||
using ErsatzTV.Application.Subtitles;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
|
||||
namespace ErsatzTV.Application.FFmpegProfiles;
|
||||
|
||||
public class UpdateFFmpegSettingsHandler(
|
||||
IConfigElementRepository configElementRepository,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
||||
: IRequestHandler<UpdateFFmpegSettings, Either<BaseError, Unit>>
|
||||
{
|
||||
@@ -35,7 +35,7 @@ public class UpdateFFmpegSettingsHandler(
|
||||
|
||||
private async Task<Validation<BaseError, Unit>> ValidateToolPath(string path, string name)
|
||||
{
|
||||
if (!localFileSystem.FileExists(path))
|
||||
if (!fileSystem.File.Exists(path))
|
||||
{
|
||||
return BaseError.New($"{name} path does not exist");
|
||||
}
|
||||
|
||||
@@ -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<TvContext> dbContextFactory,
|
||||
IFileSystem fileSystem,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IGraphicsElementLoader graphicsElementLoader,
|
||||
ILogger<RefreshGraphicsElementsHandler> 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)
|
||||
|
||||
@@ -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<CreateExternalJsonPlayout, Either<BaseError, CreatePlayoutResponse>>
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
|
||||
public CreateExternalJsonPlayoutHandler(
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
ChannelWriter<IBackgroundServiceRequest> channel,
|
||||
IDbContextFactory<TvContext> dbContextFactory)
|
||||
{
|
||||
_localFileSystem = localFileSystem;
|
||||
_fileSystem = fileSystem;
|
||||
_channel = channel;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public class CreateExternalJsonPlayoutHandler
|
||||
|
||||
private Validation<BaseError, string> ValidateExternalJsonFile(CreateExternalJsonPlayout request)
|
||||
{
|
||||
if (!_localFileSystem.FileExists(request.ScheduleFile))
|
||||
if (!_fileSystem.File.Exists(request.ScheduleFile))
|
||||
{
|
||||
return BaseError.New("External Json File does not exist!");
|
||||
}
|
||||
|
||||
@@ -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<IBackgroundServiceRequest> channel,
|
||||
IDbContextFactory<TvContext> dbContextFactory)
|
||||
: IRequestHandler<CreateScriptedPlayout, Either<BaseError, CreatePlayoutResponse>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
|
||||
public CreateScriptedPlayoutHandler(
|
||||
ILocalFileSystem localFileSystem,
|
||||
ChannelWriter<IBackgroundServiceRequest> channel,
|
||||
IDbContextFactory<TvContext> dbContextFactory)
|
||||
{
|
||||
_localFileSystem = localFileSystem;
|
||||
_channel = channel;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, CreatePlayoutResponse>> Handle(
|
||||
CreateScriptedPlayout request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
Validation<BaseError, Playout> 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!");
|
||||
}
|
||||
|
||||
@@ -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<IBackgroundServiceRequest> channel,
|
||||
IDbContextFactory<TvContext> dbContextFactory)
|
||||
: IRequestHandler<CreateSequentialPlayout, Either<BaseError, CreatePlayoutResponse>>
|
||||
{
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _channel;
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
|
||||
public CreateSequentialPlayoutHandler(
|
||||
ILocalFileSystem localFileSystem,
|
||||
ChannelWriter<IBackgroundServiceRequest> channel,
|
||||
IDbContextFactory<TvContext> dbContextFactory)
|
||||
{
|
||||
_localFileSystem = localFileSystem;
|
||||
_channel = channel;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, CreatePlayoutResponse>> Handle(
|
||||
CreateSequentialPlayout request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
Validation<BaseError, Playout> 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<BaseError, string> ValidateYamlFile(CreateSequentialPlayout request)
|
||||
{
|
||||
if (!_localFileSystem.FileExists(request.ScheduleFile))
|
||||
if (!fileSystem.File.Exists(request.ScheduleFile))
|
||||
{
|
||||
return BaseError.New("Sequential schedule does not exist!");
|
||||
}
|
||||
|
||||
@@ -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<DeletePlayout, Either<BaseError, Unit>>
|
||||
public class DeletePlayoutHandler(
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
IFileSystem fileSystem,
|
||||
IMediator mediator)
|
||||
: IRequestHandler<DeletePlayout, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
||||
|
||||
public DeletePlayoutHandler(
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IMediator mediator)
|
||||
{
|
||||
_workerChannel = workerChannel;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_localFileSystem = localFileSystem;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle(DeletePlayout request, CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||
.Include(p => p.Channel)
|
||||
@@ -44,15 +32,15 @@ public class DeletePlayoutHandler : IRequestHandler<DeletePlayout, Either<BaseEr
|
||||
|
||||
// delete channel data from channel guide cache
|
||||
string cacheFile = Path.Combine(FileSystemLayout.ChannelGuideCacheFolder, $"{playout.Channel.Number}.xml");
|
||||
if (_localFileSystem.FileExists(cacheFile))
|
||||
if (fileSystem.File.Exists(cacheFile))
|
||||
{
|
||||
File.Delete(cacheFile);
|
||||
}
|
||||
|
||||
// refresh channel list to remove channel that has no playout
|
||||
await _workerChannel.WriteAsync(new RefreshChannelList(), cancellationToken);
|
||||
await workerChannel.WriteAsync(new RefreshChannelList(), cancellationToken);
|
||||
|
||||
await _mediator.Publish(new PlayoutUpdatedNotification(playout.Id, false), cancellationToken);
|
||||
await mediator.Publish(new PlayoutUpdatedNotification(playout.Id, false), cancellationToken);
|
||||
}
|
||||
|
||||
return maybePlayout
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.CommandLine.Parsing;
|
||||
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.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -14,7 +14,7 @@ public class
|
||||
UpdateScriptedPlayoutHandler(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel,
|
||||
ILocalFileSystem localFileSystem)
|
||||
IFileSystem fileSystem)
|
||||
: IRequestHandler<UpdateScriptedPlayout,
|
||||
Either<BaseError, PlayoutNameViewModel>>
|
||||
{
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
@@ -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<StartFFmpegSession, Either<BaseError, Unit>>
|
||||
{
|
||||
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<StartFFmpegSession, Eit
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
IMediator mediator,
|
||||
IClient client,
|
||||
IFileSystem fileSystem,
|
||||
ILocalFileSystem localFileSystem,
|
||||
ILogger<StartFFmpegSessionHandler> logger,
|
||||
ILogger<HlsSessionWorker> sessionWorkerLogger,
|
||||
@@ -54,6 +57,7 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_mediator = mediator;
|
||||
_client = client;
|
||||
_fileSystem = fileSystem;
|
||||
_localFileSystem = localFileSystem;
|
||||
_logger = logger;
|
||||
_sessionWorkerLogger = sessionWorkerLogger;
|
||||
@@ -129,6 +133,7 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit
|
||||
_hlsPlaylistFilter,
|
||||
_hlsInitSegmentCache,
|
||||
_configElementRepository,
|
||||
_fileSystem,
|
||||
_localFileSystem,
|
||||
_sessionWorkerLogger,
|
||||
targetFramerate)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Pipelines;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
@@ -30,6 +31,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
private readonly IHlsInitSegmentCache _hlsInitSegmentCache;
|
||||
private readonly Dictionary<long, int> _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<HlsSessionWorker> logger,
|
||||
Option<int> 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);
|
||||
}
|
||||
@@ -679,9 +683,9 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
var generatedAtHash = new System.Collections.Generic.HashSet<long>();
|
||||
|
||||
// delete old segments
|
||||
var allSegments = Directory.GetFiles(_workingDirectory, "live*.ts")
|
||||
.Append(Directory.GetFiles(_workingDirectory, "live*.mp4"))
|
||||
.Append(Directory.GetFiles(_workingDirectory, "live*.m4s"))
|
||||
var allSegments = _fileSystem.Directory.GetFiles(_workingDirectory, "live*.ts")
|
||||
.Append(_fileSystem.Directory.GetFiles(_workingDirectory, "live*.mp4"))
|
||||
.Append(_fileSystem.Directory.GetFiles(_workingDirectory, "live*.m4s"))
|
||||
.Map(file =>
|
||||
{
|
||||
string fileName = Path.GetFileName(file);
|
||||
@@ -699,7 +703,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var allInits = Directory.GetFiles(_workingDirectory, "*init.mp4")
|
||||
var allInits = _fileSystem.Directory.GetFiles(_workingDirectory, "*init.mp4")
|
||||
.Map(file => long.TryParse(Path.GetFileName(file).Split('_')[0], out long generatedAt) && !generatedAtHash.Contains(generatedAt)
|
||||
? new Segment(file, 0, generatedAt)
|
||||
: Option<Segment>.None)
|
||||
@@ -739,7 +743,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(segment.File);
|
||||
_fileSystem.File.Delete(segment.File);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
@@ -752,12 +756,12 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
|
||||
private async Task RefreshInits()
|
||||
{
|
||||
var allSegments = Directory.GetFiles(_workingDirectory, "live*.m4s")
|
||||
var allSegments = _fileSystem.Directory.GetFiles(_workingDirectory, "live*.m4s")
|
||||
.Map(Path.GetFileName)
|
||||
.Map(s => s.Split("_")[1])
|
||||
.ToHashSet();
|
||||
|
||||
foreach (string file in Directory.GetFiles(_workingDirectory, "*init.mp4"))
|
||||
foreach (string file in _fileSystem.Directory.GetFiles(_workingDirectory, "*init.mp4"))
|
||||
{
|
||||
string key = Path.GetFileName(file).Split("_")[0];
|
||||
if (allSegments.Contains(key))
|
||||
@@ -812,9 +816,9 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
private async Task<Option<string[]>> ReadPlaylistLines(CancellationToken cancellationToken)
|
||||
{
|
||||
string fileName = PlaylistFileName();
|
||||
if (File.Exists(fileName))
|
||||
if (_fileSystem.File.Exists(fileName))
|
||||
{
|
||||
return await File.ReadAllLinesAsync(fileName, cancellationToken);
|
||||
return await _fileSystem.File.ReadAllLinesAsync(fileName, cancellationToken);
|
||||
}
|
||||
|
||||
_logger.LogDebug("Playlist does not exist at expected location {File}", fileName);
|
||||
@@ -824,7 +828,7 @@ public class HlsSessionWorker : IHlsSessionWorker
|
||||
private async Task WritePlaylist(string playlist, CancellationToken cancellationToken)
|
||||
{
|
||||
string fileName = PlaylistFileName();
|
||||
await File.WriteAllTextAsync(fileName, playlist, cancellationToken);
|
||||
await _fileSystem.File.WriteAllTextAsync(fileName, playlist, cancellationToken);
|
||||
}
|
||||
|
||||
private string PlaylistFileName() => Path.Combine(_workingDirectory, "live.m3u8");
|
||||
|
||||
@@ -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<GetLastPtsTimeHandler> 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);
|
||||
}
|
||||
|
||||
@@ -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<GetPlayoutItemProcessByChannelNumberHandler> _logger;
|
||||
@@ -47,6 +49,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
public GetPlayoutItemProcessByChannelNumberHandler(
|
||||
IDbContextFactory<TvContext> 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)
|
||||
{
|
||||
|
||||
@@ -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<TvContext> dbContextFactory,
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
ILogger<ExtractEmbeddedSubtitlesHandler> logger)
|
||||
: ExtractEmbeddedSubtitlesHandlerBase(localFileSystem, logger),
|
||||
: ExtractEmbeddedSubtitlesHandlerBase(fileSystem, logger),
|
||||
IRequestHandler<ExtractEmbeddedShowSubtitles, Option<BaseError>>
|
||||
{
|
||||
public async Task<Option<BaseError>> Handle(
|
||||
|
||||
@@ -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<TvContext> dbContextFactory,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
IEntityLocker entityLocker,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel,
|
||||
ILogger<ExtractEmbeddedSubtitlesHandler> logger)
|
||||
: base(localFileSystem, logger)
|
||||
: base(fileSystem, logger)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_entityLocker = entityLocker;
|
||||
|
||||
@@ -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<Validation<BaseError, string>> 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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Debug" Version="3.0.0" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="Testably.Abstractions.Testing" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,11 +2,11 @@ using Destructurama;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Tests.Fakes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NUnit.Framework;
|
||||
using Serilog;
|
||||
using Shouldly;
|
||||
using Testably.Abstractions.Testing;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||
|
||||
@@ -94,9 +94,10 @@ public class CustomStreamSelectorTests
|
||||
- "eng"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -123,9 +124,10 @@ public class CustomStreamSelectorTests
|
||||
- audio_language: ["und"]
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -152,9 +154,10 @@ public class CustomStreamSelectorTests
|
||||
- audio_language: ["en", "eng"]
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -187,9 +190,10 @@ public class CustomStreamSelectorTests
|
||||
disable_subtitles: true
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -217,9 +221,10 @@ public class CustomStreamSelectorTests
|
||||
- "en*"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -248,9 +253,10 @@ public class CustomStreamSelectorTests
|
||||
""";
|
||||
_audioVersion = GetTestAudioVersion("en");
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -279,9 +285,10 @@ public class CustomStreamSelectorTests
|
||||
disable_subtitles: true
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -305,9 +312,10 @@ public class CustomStreamSelectorTests
|
||||
- "eng"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -337,9 +345,10 @@ public class CustomStreamSelectorTests
|
||||
- "en*"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -378,9 +387,10 @@ public class CustomStreamSelectorTests
|
||||
}
|
||||
];
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -413,9 +423,10 @@ public class CustomStreamSelectorTests
|
||||
disable_subtitles: true
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -450,9 +461,10 @@ public class CustomStreamSelectorTests
|
||||
disable_subtitles: true
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -492,9 +504,10 @@ public class CustomStreamSelectorTests
|
||||
disable_subtitles: true
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -528,9 +541,10 @@ public class CustomStreamSelectorTests
|
||||
disable_subtitles: true
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -568,9 +582,10 @@ public class CustomStreamSelectorTests
|
||||
- "riff"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -600,9 +615,10 @@ public class CustomStreamSelectorTests
|
||||
- "movie"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -634,9 +650,10 @@ public class CustomStreamSelectorTests
|
||||
- "signs"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -668,9 +685,10 @@ public class CustomStreamSelectorTests
|
||||
- "songs"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -699,9 +717,10 @@ public class CustomStreamSelectorTests
|
||||
subtitle_condition: "forced"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -730,9 +749,10 @@ public class CustomStreamSelectorTests
|
||||
subtitle_condition: "lang like 'en%' and external"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -761,9 +781,10 @@ public class CustomStreamSelectorTests
|
||||
audio_condition: "title like '%movie%'"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -792,9 +813,10 @@ public class CustomStreamSelectorTests
|
||||
audio_condition: "channels > 2"
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -822,9 +844,10 @@ public class CustomStreamSelectorTests
|
||||
audio_title_blocklist: ["riff"]
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -853,9 +876,10 @@ public class CustomStreamSelectorTests
|
||||
subtitle_language: ["jp","en*"]
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
@@ -885,9 +909,10 @@ public class CustomStreamSelectorTests
|
||||
subtitle_language: ["es*","de*"]
|
||||
""";
|
||||
|
||||
var streamSelector = new CustomStreamSelector(
|
||||
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]),
|
||||
_logger);
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(TestFileName).Which(f => f.HasStringContent(YAML));
|
||||
var streamSelector = new CustomStreamSelector(fileSystem, _logger);
|
||||
|
||||
StreamSelectorResult result = await streamSelector.SelectStreams(
|
||||
_channel,
|
||||
|
||||
@@ -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<ILogger<ScriptEngine>>()),
|
||||
Substitute.For<IStreamSelectorRepository>(),
|
||||
Substitute.For<IConfigElementRepository>(),
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
new MockFileSystem(),
|
||||
languageCodeService,
|
||||
Substitute.For<ILogger<FFmpegStreamSelector>>());
|
||||
|
||||
@@ -123,7 +124,7 @@ public class FFmpegStreamSelectorTests
|
||||
new ScriptEngine(Substitute.For<ILogger<ScriptEngine>>()),
|
||||
Substitute.For<IStreamSelectorRepository>(),
|
||||
Substitute.For<IConfigElementRepository>(),
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
new MockFileSystem(),
|
||||
languageCodeService,
|
||||
Substitute.For<ILogger<FFmpegStreamSelector>>());
|
||||
|
||||
@@ -173,7 +174,7 @@ public class FFmpegStreamSelectorTests
|
||||
new ScriptEngine(Substitute.For<ILogger<ScriptEngine>>()),
|
||||
Substitute.For<IStreamSelectorRepository>(),
|
||||
Substitute.For<IConfigElementRepository>(),
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
new MockFileSystem(),
|
||||
languageCodeService,
|
||||
Substitute.For<ILogger<FFmpegStreamSelector>>());
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace ErsatzTV.Core.Tests.Fakes;
|
||||
|
||||
public record FakeFileEntry(string Path)
|
||||
{
|
||||
public DateTime LastWriteTime { get; set; } = SystemTime.MinValueUtc;
|
||||
|
||||
public string Contents { get; set; }
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace ErsatzTV.Core.Tests.Fakes;
|
||||
|
||||
public record FakeFolderEntry(string Path);
|
||||
@@ -1,99 +0,0 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.Fakes;
|
||||
|
||||
public class FakeLocalFileSystem : ILocalFileSystem
|
||||
{
|
||||
private readonly List<FakeFileEntry> _files;
|
||||
private readonly List<FakeFolderEntry> _folders;
|
||||
|
||||
public FakeLocalFileSystem(List<FakeFileEntry> files) : this(files, new List<FakeFolderEntry>())
|
||||
{
|
||||
}
|
||||
|
||||
public FakeLocalFileSystem(List<FakeFileEntry> files, List<FakeFolderEntry> folders)
|
||||
{
|
||||
_files = files;
|
||||
|
||||
var allFolders = new List<string>(folders.Map(f => f.Path));
|
||||
foreach (FakeFileEntry file in _files)
|
||||
{
|
||||
List<DirectoryInfo> moreFolders =
|
||||
Split(new DirectoryInfo(Path.GetDirectoryName(file.Path) ?? string.Empty));
|
||||
allFolders.AddRange(moreFolders.Map(i => i.FullName));
|
||||
}
|
||||
|
||||
_folders = allFolders.Distinct().Map(f => new FakeFolderEntry(f)).ToList();
|
||||
}
|
||||
|
||||
public Unit EnsureFolderExists(string folder) => Unit.Default;
|
||||
|
||||
public DateTime GetLastWriteTime(string path) =>
|
||||
Optional(_files.SingleOrDefault(f => f.Path == path))
|
||||
.Map(f => f.LastWriteTime)
|
||||
.IfNone(SystemTime.MinValueUtc);
|
||||
|
||||
public bool IsLibraryPathAccessible(LibraryPath libraryPath) =>
|
||||
_folders.Any(f => f.Path == libraryPath.Path);
|
||||
|
||||
public IEnumerable<string> ListSubdirectories(string folder) =>
|
||||
_folders.Map(f => f.Path).Filter(f => f.StartsWith(folder) && Directory.GetParent(f)?.FullName == folder);
|
||||
|
||||
public IEnumerable<string> ListFiles(string folder) =>
|
||||
_files.Map(f => f.Path).Filter(f => Path.GetDirectoryName(f) == folder);
|
||||
|
||||
// TODO: this isn't accurate, need to use search pattern
|
||||
public IEnumerable<string> ListFiles(string folder, string searchPattern) =>
|
||||
_files.Map(f => f.Path).Filter(f => Path.GetDirectoryName(f) == folder);
|
||||
|
||||
public IEnumerable<string> ListFiles(string folder, params string[] searchPatterns) =>
|
||||
_files.Map(f => f.Path).Filter(f => Path.GetDirectoryName(f) == folder);
|
||||
|
||||
public bool FileExists(string path) => _files.Any(f => f.Path == path);
|
||||
public bool FolderExists(string folder) => false;
|
||||
|
||||
public Task<Either<BaseError, Unit>> CopyFile(string source, string destination) =>
|
||||
Task.FromResult(Right<BaseError, Unit>(Unit.Default));
|
||||
|
||||
public Unit EmptyFolder(string folder) => Unit.Default;
|
||||
|
||||
public async Task<string> ReadAllText(string path) => await _files
|
||||
.Filter(f => f.Path == path)
|
||||
.HeadOrNone()
|
||||
.Select(f => f.Contents)
|
||||
.IfNoneAsync(string.Empty);
|
||||
|
||||
public async Task<string[]> ReadAllLines(string path) => await _files
|
||||
.Filter(f => f.Path == path)
|
||||
.HeadOrNone()
|
||||
.Select(f => f.Contents)
|
||||
.IfNoneAsync(string.Empty)
|
||||
.Map(s => s.Split(Environment.NewLine));
|
||||
|
||||
public Task<byte[]> GetHash(string path) => throw new NotSupportedException();
|
||||
|
||||
public string GetCustomOrDefaultFile(string folder, string file)
|
||||
{
|
||||
string path = Path.Combine(folder, file);
|
||||
return FileExists(path) ? path : Path.Combine(folder, $"_{file}");
|
||||
}
|
||||
|
||||
private static List<DirectoryInfo> Split(DirectoryInfo path)
|
||||
{
|
||||
var result = new List<DirectoryInfo>();
|
||||
if (path == null || string.IsNullOrWhiteSpace(path.FullName))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (path.Parent != null)
|
||||
{
|
||||
result.AddRange(Split(path.Parent));
|
||||
}
|
||||
|
||||
result.Add(path);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
var builder = new PlayoutBuilder(
|
||||
configRepo,
|
||||
@@ -850,7 +847,7 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
|
||||
televisionRepo,
|
||||
artistRepo,
|
||||
factory,
|
||||
localFileSystem,
|
||||
new MockFileSystem(),
|
||||
rerunHelper,
|
||||
Logger);
|
||||
|
||||
|
||||
@@ -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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
var builder = new PlayoutBuilder(
|
||||
configRepo,
|
||||
@@ -1939,7 +1928,7 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
|
||||
televisionRepo,
|
||||
artistRepo,
|
||||
factory,
|
||||
localFileSystem,
|
||||
new MockFileSystem(),
|
||||
rerunHelper,
|
||||
Logger);
|
||||
|
||||
|
||||
@@ -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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
var builder = new PlayoutBuilder(
|
||||
configRepo,
|
||||
@@ -190,7 +188,7 @@ public abstract class PlayoutBuilderTestBase
|
||||
televisionRepo,
|
||||
artistRepo,
|
||||
factory,
|
||||
localFileSystem,
|
||||
new MockFileSystem(),
|
||||
rerunHelper,
|
||||
Logger);
|
||||
|
||||
|
||||
@@ -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<IArtistRepository>();
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory factory =
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>();
|
||||
ILocalFileSystem localFileSystem = Substitute.For<ILocalFileSystem>();
|
||||
IRerunHelper rerunHelper = Substitute.For<IRerunHelper>();
|
||||
var builder = new PlayoutBuilder(
|
||||
configRepo,
|
||||
@@ -109,7 +108,7 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
|
||||
televisionRepo,
|
||||
artistRepo,
|
||||
factory,
|
||||
localFileSystem,
|
||||
new MockFileSystem(),
|
||||
rerunHelper,
|
||||
Logger);
|
||||
|
||||
|
||||
@@ -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<ISearchIndex>();
|
||||
await searchIndex.Initialize(
|
||||
new LocalFileSystem(
|
||||
new MockFileSystem(),
|
||||
provider.GetRequiredService<IClient>(),
|
||||
provider.GetRequiredService<ILogger<LocalFileSystem>>()),
|
||||
provider.GetRequiredService<IConfigElementRepository>(),
|
||||
@@ -125,7 +127,7 @@ public class ScheduleIntegrationTests
|
||||
new TelevisionRepository(factory, provider.GetRequiredService<ILogger<TelevisionRepository>>()),
|
||||
new ArtistRepository(factory),
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>(),
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
new MockFileSystem(),
|
||||
Substitute.For<IRerunHelper>(),
|
||||
provider.GetRequiredService<ILogger<PlayoutBuilder>>());
|
||||
|
||||
@@ -321,7 +323,7 @@ public class ScheduleIntegrationTests
|
||||
new TelevisionRepository(factory, provider.GetRequiredService<ILogger<TelevisionRepository>>()),
|
||||
new ArtistRepository(factory),
|
||||
Substitute.For<IMultiEpisodeShuffleCollectionEnumeratorFactory>(),
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
new MockFileSystem(),
|
||||
Substitute.For<IRerunHelper>(),
|
||||
provider.GetRequiredService<ILogger<PlayoutBuilder>>());
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Testably.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="TimeSpanParserUtil" Version="1.2.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="16.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Enumeration;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.FFmpeg.Selector;
|
||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NCalc;
|
||||
using YamlDotNet.Serialization;
|
||||
@@ -10,7 +10,7 @@ using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace ErsatzTV.Core.FFmpeg;
|
||||
|
||||
public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger<CustomStreamSelector> logger)
|
||||
public class CustomStreamSelector(IFileSystem fileSystem, ILogger<CustomStreamSelector> logger)
|
||||
: ICustomStreamSelector
|
||||
{
|
||||
public async Task<StreamSelectorResult> SelectStreams(
|
||||
@@ -25,7 +25,7 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger<Cust
|
||||
FileSystemLayout.ChannelStreamSelectorsFolder,
|
||||
channel.StreamSelector);
|
||||
|
||||
if (!localFileSystem.FileExists(streamSelectorFile))
|
||||
if (!fileSystem.File.Exists(streamSelectorFile))
|
||||
{
|
||||
logger.LogWarning("YAML stream selector file {File} does not exist; aborting.", channel.StreamSelector);
|
||||
return StreamSelectorResult.None;
|
||||
@@ -327,7 +327,7 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger<Cust
|
||||
{
|
||||
try
|
||||
{
|
||||
string yaml = await localFileSystem.ReadAllText(streamSelectorFile);
|
||||
string yaml = await fileSystem.File.ReadAllTextAsync(streamSelectorFile);
|
||||
|
||||
IDeserializer deserializer = new DeserializerBuilder()
|
||||
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||
|
||||
@@ -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<FFmpegStreamSelector> _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<FFmpegStreamSelector> 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<MediaStream>.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");
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Metadata;
|
||||
namespace ErsatzTV.Core.Interfaces.Metadata;
|
||||
|
||||
public interface ILocalFileSystem
|
||||
{
|
||||
Unit EnsureFolderExists(string folder);
|
||||
DateTime GetLastWriteTime(string path);
|
||||
bool IsLibraryPathAccessible(LibraryPath libraryPath);
|
||||
IEnumerable<string> ListSubdirectories(string folder);
|
||||
IEnumerable<string> ListFiles(string folder);
|
||||
IEnumerable<string> ListFiles(string folder, string searchPattern);
|
||||
IEnumerable<string> ListFiles(string folder, params string[] searchPatterns);
|
||||
bool FileExists(string path);
|
||||
bool FolderExists(string folder);
|
||||
Task<Either<BaseError, Unit>> CopyFile(string source, string destination);
|
||||
Unit EmptyFolder(string folder);
|
||||
Task<string> ReadAllText(string path);
|
||||
Task<string[]> ReadAllLines(string path);
|
||||
Task<byte[]> GetHash(string path);
|
||||
string GetCustomOrDefaultFile(string folder, string file);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO.Abstractions;
|
||||
using System.Security.Cryptography;
|
||||
using Bugsnag;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Core.Metadata;
|
||||
|
||||
public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) : ILocalFileSystem
|
||||
public class LocalFileSystem(IFileSystem fileSystem, IClient client, ILogger<LocalFileSystem> logger) : ILocalFileSystem
|
||||
{
|
||||
public Unit EnsureFolderExists(string folder)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (folder != null && !Directory.Exists(folder))
|
||||
if (folder != null && !fileSystem.Directory.Exists(folder))
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
fileSystem.Directory.CreateDirectory(folder);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -30,7 +30,7 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.GetLastWriteTimeUtc(path);
|
||||
return fileSystem.File.GetLastWriteTimeUtc(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -38,16 +38,13 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLibraryPathAccessible(LibraryPath libraryPath) =>
|
||||
Directory.Exists(libraryPath.Path);
|
||||
|
||||
public IEnumerable<string> ListSubdirectories(string folder)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
if (fileSystem.Directory.Exists(folder))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Directory.EnumerateDirectories(folder);
|
||||
return fileSystem.Directory.EnumerateDirectories(folder);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
@@ -65,11 +62,11 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
|
||||
public IEnumerable<string> ListFiles(string folder)
|
||||
{
|
||||
if (Directory.Exists(folder))
|
||||
if (fileSystem.Directory.Exists(folder))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly)
|
||||
return fileSystem.Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly)
|
||||
.Where(path => !Path.GetFileName(path).StartsWith("._", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
@@ -88,11 +85,11 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
|
||||
public IEnumerable<string> ListFiles(string folder, string searchPattern)
|
||||
{
|
||||
if (folder is not null && Directory.Exists(folder))
|
||||
if (folder is not null && fileSystem.Directory.Exists(folder))
|
||||
{
|
||||
try
|
||||
{
|
||||
return Directory.EnumerateFiles(folder, searchPattern, SearchOption.TopDirectoryOnly)
|
||||
return fileSystem.Directory.EnumerateFiles(folder, searchPattern, SearchOption.TopDirectoryOnly)
|
||||
.Where(path => !Path.GetFileName(path).StartsWith("._", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
@@ -111,14 +108,15 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
|
||||
public IEnumerable<string> ListFiles(string folder, params string[] searchPatterns)
|
||||
{
|
||||
if (folder is not null && Directory.Exists(folder))
|
||||
if (folder is not null && fileSystem.Directory.Exists(folder))
|
||||
{
|
||||
try
|
||||
{
|
||||
return searchPatterns
|
||||
.SelectMany(searchPattern =>
|
||||
Directory.EnumerateFiles(folder, searchPattern, SearchOption.TopDirectoryOnly)
|
||||
.Where(path => !Path.GetFileName(path).StartsWith("._", StringComparison.OrdinalIgnoreCase)))
|
||||
fileSystem.Directory.EnumerateFiles(folder, searchPattern, SearchOption.TopDirectoryOnly)
|
||||
.Where(path =>
|
||||
!Path.GetFileName(path).StartsWith("._", StringComparison.OrdinalIgnoreCase)))
|
||||
.Distinct();
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
@@ -135,22 +133,18 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public bool FileExists(string path) => File.Exists(path);
|
||||
|
||||
public bool FolderExists(string folder) => Directory.Exists(folder);
|
||||
|
||||
public async Task<Either<BaseError, Unit>> CopyFile(string source, string destination)
|
||||
{
|
||||
try
|
||||
{
|
||||
string directory = Path.GetDirectoryName(destination) ?? string.Empty;
|
||||
if (!Directory.Exists(directory))
|
||||
if (!fileSystem.Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
fileSystem.Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
await using FileStream sourceStream = File.OpenRead(source);
|
||||
await using FileStream destinationStream = File.Create(destination);
|
||||
await using FileSystemStream sourceStream = fileSystem.File.OpenRead(source);
|
||||
await using FileSystemStream destinationStream = fileSystem.File.Create(destination);
|
||||
await sourceStream.CopyToAsync(destinationStream);
|
||||
|
||||
return Unit.Default;
|
||||
@@ -166,14 +160,14 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(folder))
|
||||
foreach (string file in fileSystem.Directory.GetFiles(folder))
|
||||
{
|
||||
File.Delete(file);
|
||||
fileSystem.File.Delete(file);
|
||||
}
|
||||
|
||||
foreach (string directory in Directory.GetDirectories(folder))
|
||||
foreach (string directory in fileSystem.Directory.GetDirectories(folder))
|
||||
{
|
||||
Directory.Delete(directory, true);
|
||||
fileSystem.Directory.Delete(directory, true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -184,20 +178,17 @@ public class LocalFileSystem(IClient client, ILogger<LocalFileSystem> logger) :
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
public Task<string> ReadAllText(string path) => File.ReadAllTextAsync(path);
|
||||
public Task<string[]> ReadAllLines(string path) => File.ReadAllLinesAsync(path);
|
||||
|
||||
[SuppressMessage("Security", "CA5351:Do Not Use Broken Cryptographic Algorithms")]
|
||||
public async Task<byte[]> GetHash(string path)
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
await using var stream = File.OpenRead(path);
|
||||
await using var stream = fileSystem.File.OpenRead(path);
|
||||
return await md5.ComputeHashAsync(stream);
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PlayoutBuilder> _logger;
|
||||
@@ -34,7 +34,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
ITelevisionRepository televisionRepository,
|
||||
IArtistRepository artistRepository,
|
||||
IMultiEpisodeShuffleCollectionEnumeratorFactory multiEpisodeFactory,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
IRerunHelper rerunHelper,
|
||||
ILogger<PlayoutBuilder> logger)
|
||||
{
|
||||
@@ -43,7 +43,7 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
_televisionRepository = televisionRepository;
|
||||
_artistRepository = artistRepository;
|
||||
_multiEpisodeFactory = multiEpisodeFactory;
|
||||
_localFileSystem = localFileSystem;
|
||||
_fileSystem = fileSystem;
|
||||
_rerunHelper = rerunHelper;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -400,12 +400,12 @@ public class PlayoutBuilder : IPlayoutBuilder
|
||||
name =>
|
||||
{
|
||||
_logger.LogError(
|
||||
"Unable to rebuild playout; {CollectionType} {CollectionName} has no valid items!",
|
||||
"Unable to rebuild playout; {CollectionType} \"{CollectionName}\" has no valid items!",
|
||||
emptyCollection.CollectionType,
|
||||
name);
|
||||
|
||||
return BaseError.New(
|
||||
$"Unable to rebuild playout; {emptyCollection.CollectionType} {name} has no valid items!");
|
||||
$"Unable to rebuild playout; {emptyCollection.CollectionType} \"{name}\" has no valid items!");
|
||||
},
|
||||
() =>
|
||||
{
|
||||
@@ -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
|
||||
|
||||
@@ -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<ScriptedPlayoutBuilder> 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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -22,10 +22,17 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="Testably.Abstractions.Testing" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ErsatzTV.Infrastructure\ErsatzTV.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\ErsatzTV\Resources\sequential-schedule.schema.json">
|
||||
<Link>Resources/sequential-schedule.schema.json</Link>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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<IMetadataRepository>(),
|
||||
new MockFileSystem(),
|
||||
Substitute.For<ILocalFileSystem>(),
|
||||
Substitute.For<IClient>(),
|
||||
Substitute.For<IHardwareCapabilitiesFactory>(),
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
using System.Reflection;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Infrastructure.Scheduling;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
using Testably.Abstractions.Testing;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Tests.Scheduling;
|
||||
|
||||
[TestFixture]
|
||||
public class SequentialScheduleValidatorTests
|
||||
{
|
||||
private readonly string _schema;
|
||||
|
||||
public SequentialScheduleValidatorTests()
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(SequentialScheduleValidatorTests));
|
||||
assembly.ShouldNotBeNull();
|
||||
|
||||
using var stream = assembly.GetManifestResourceStream(
|
||||
"ErsatzTV.Infrastructure.Tests.Resources.sequential-schedule.schema.json");
|
||||
stream.ShouldNotBeNull();
|
||||
|
||||
using var reader = new StreamReader(stream);
|
||||
_schema = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
[CancelAfter(2_000)]
|
||||
[Test]
|
||||
public async Task ValidateSchedule_Should_Succeed_Valid_Schedule(CancellationToken cancellationToken)
|
||||
{
|
||||
const string YAML =
|
||||
"""
|
||||
content:
|
||||
- show:
|
||||
key: "SOME_SHOW"
|
||||
guids:
|
||||
- source: "imdb"
|
||||
value: "tt123456"
|
||||
order: chronological
|
||||
- search:
|
||||
key: "FILLER"
|
||||
query: "type:other_video"
|
||||
order: "shuffle"
|
||||
|
||||
reset:
|
||||
- wait_until: '8:00am'
|
||||
tomorrow: false
|
||||
rewind_on_reset: true
|
||||
|
||||
playout:
|
||||
- duration: "30 minutes"
|
||||
content: "SOME_SHOW"
|
||||
discard_attempts: 2
|
||||
offline_tail: false
|
||||
|
||||
- epg_group: true
|
||||
advance: false
|
||||
|
||||
- pad_to_next: 30
|
||||
content: "FILLER"
|
||||
filler_kind: postroll
|
||||
trim: true
|
||||
|
||||
- epg_group: false
|
||||
|
||||
- repeat: true
|
||||
""";
|
||||
|
||||
string schemaFileName = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "sequential-schedule.schema.json");
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(schemaFileName).Which(f => f.HasStringContent(_schema));
|
||||
|
||||
var validator = new SequentialScheduleValidator(
|
||||
fileSystem,
|
||||
Substitute.For<ILogger<SequentialScheduleValidator>>());
|
||||
|
||||
bool result = await validator.ValidateSchedule(YAML, false);
|
||||
result.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[CancelAfter(2_000)]
|
||||
[Test]
|
||||
public async Task ValidateSchedule_Should_Fail_Invalid_Schedule(CancellationToken cancellationToken)
|
||||
{
|
||||
const string YAML =
|
||||
"""
|
||||
content:
|
||||
- show:
|
||||
key: "SOME_SHOW"
|
||||
guids22:
|
||||
- source: "imdb"
|
||||
value: "tt123456"
|
||||
order: chronological
|
||||
- search:
|
||||
key: "FILLER"
|
||||
query: "type:other_video"
|
||||
order: "shuffle"
|
||||
|
||||
reset:
|
||||
- wait_until: '8:00am'
|
||||
tomorrow: false
|
||||
rewind_on_reset: true
|
||||
|
||||
playout:
|
||||
- duration: "30 minutes"
|
||||
content: "SOME_SHOW"
|
||||
discard_attempts: 2
|
||||
offline_tail: false
|
||||
|
||||
- epg_group: true
|
||||
advance: false
|
||||
|
||||
- pad_to_next: 30
|
||||
content: "FILLER"
|
||||
filler_kind: postroll
|
||||
trim: true
|
||||
|
||||
- epg_group: false
|
||||
|
||||
- repeat: true
|
||||
""";
|
||||
|
||||
string schemaFileName = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "sequential-schedule.schema.json");
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(schemaFileName).Which(f => f.HasStringContent(_schema));
|
||||
|
||||
var validator = new SequentialScheduleValidator(
|
||||
fileSystem,
|
||||
Substitute.For<ILogger<SequentialScheduleValidator>>());
|
||||
|
||||
bool result = await validator.ValidateSchedule(YAML, false);
|
||||
result.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[CancelAfter(2_000)]
|
||||
[Test]
|
||||
public async Task GetValidationMessages_With_Invalid_Schedule(CancellationToken cancellationToken)
|
||||
{
|
||||
const string YAML =
|
||||
"""
|
||||
content:
|
||||
- show:
|
||||
key: "SOME_SHOW"
|
||||
guids22:
|
||||
- source: "imdb"
|
||||
value: "tt123456"
|
||||
order: chronological
|
||||
- search:
|
||||
key: "FILLER"
|
||||
query: "type:other_video"
|
||||
order: "shuffle"
|
||||
|
||||
reset:
|
||||
- wait_until: '8:00am'
|
||||
tomorrow: false
|
||||
rewind_on_reset: true
|
||||
|
||||
playout:
|
||||
- duration: "30 minutes"
|
||||
content: "SOME_SHOW"
|
||||
discard_attempts: 2
|
||||
offline_tail: false
|
||||
|
||||
- epg_group: true
|
||||
advance: false
|
||||
|
||||
- pad_to_next: 30
|
||||
content: "FILLER"
|
||||
filler_kind: postroll
|
||||
trim: true
|
||||
|
||||
- epg_group: false
|
||||
|
||||
- repeat: true
|
||||
""";
|
||||
|
||||
string schemaFileName = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "sequential-schedule.schema.json");
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
fileSystem.Initialize()
|
||||
.WithFile(schemaFileName).Which(f => f.HasStringContent(_schema));
|
||||
|
||||
var validator = new SequentialScheduleValidator(
|
||||
fileSystem,
|
||||
Substitute.For<ILogger<SequentialScheduleValidator>>());
|
||||
|
||||
IList<string> result = await validator.GetValidationMessages(YAML, false);
|
||||
result.ShouldNotBeNull();
|
||||
result.Count.ShouldBe(1);
|
||||
result[0].ShouldContain("line 3");
|
||||
result[0].ShouldContain("position 5");
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,18 @@
|
||||
using Dapper;
|
||||
using System.IO.Abstractions;
|
||||
using Dapper;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Repositories;
|
||||
|
||||
public class LibraryRepository : ILibraryRepository
|
||||
public class LibraryRepository(IFileSystem fileSystem, IDbContextFactory<TvContext> dbContextFactory)
|
||||
: ILibraryRepository
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
|
||||
public LibraryRepository(ILocalFileSystem localFileSystem, IDbContextFactory<TvContext> dbContextFactory)
|
||||
{
|
||||
_localFileSystem = localFileSystem;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public async Task<LibraryPath> Add(LibraryPath libraryPath)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
await dbContext.LibraryPaths.AddAsync(libraryPath);
|
||||
await dbContext.SaveChangesAsync();
|
||||
return libraryPath;
|
||||
@@ -28,7 +20,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task<Option<Library>> GetLibrary(int libraryId)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Libraries
|
||||
.Include(l => l.Paths)
|
||||
.ThenInclude(p => p.LibraryFolders)
|
||||
@@ -40,7 +32,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task<Option<LocalLibrary>> GetLocal(int libraryId)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.LocalLibraries
|
||||
.OrderBy(l => l.Id)
|
||||
.SingleOrDefaultAsync(l => l.Id == libraryId)
|
||||
@@ -49,7 +41,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task<List<Library>> GetAll()
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Libraries
|
||||
.AsNoTracking()
|
||||
.Include(l => l.MediaSource)
|
||||
@@ -59,7 +51,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task<Unit> UpdateLastScan(Library library)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Connection.ExecuteAsync(
|
||||
"UPDATE Library SET LastScan = @LastScan WHERE Id = @Id",
|
||||
new { library.LastScan, library.Id }).ToUnit();
|
||||
@@ -67,7 +59,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task<Unit> UpdateLastScan(LibraryPath libraryPath)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Connection.ExecuteAsync(
|
||||
"UPDATE LibraryPath SET LastScan = @LastScan WHERE Id = @Id",
|
||||
new { libraryPath.LastScan, libraryPath.Id }).ToUnit();
|
||||
@@ -75,7 +67,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task<List<LibraryPath>> GetLocalPaths(int libraryId)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.LocalLibraries
|
||||
.Include(l => l.Paths)
|
||||
.OrderBy(l => l.Id)
|
||||
@@ -86,7 +78,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task<int> CountMediaItemsByPath(int libraryPathId)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
return await dbContext.Connection.QuerySingleAsync<int>(
|
||||
@"SELECT COUNT(*) FROM MediaItem WHERE LibraryPathId = @LibraryPathId",
|
||||
new { LibraryPathId = libraryPathId });
|
||||
@@ -98,7 +90,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
string path,
|
||||
string etag)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
foreach (LibraryFolder folder in knownFolder)
|
||||
{
|
||||
@@ -123,10 +115,10 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task CleanEtagsForLibraryPath(LibraryPath libraryPath)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
IOrderedEnumerable<LibraryFolder> orderedFolders = libraryPath.LibraryFolders
|
||||
.Where(f => !_localFileSystem.FolderExists(f.Path))
|
||||
.Where(f => !fileSystem.Directory.Exists(f.Path))
|
||||
.OrderByDescending(lp => lp.Path.Length);
|
||||
|
||||
foreach (LibraryFolder folder in orderedFolders)
|
||||
@@ -152,7 +144,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
return Option<int>.None;
|
||||
}
|
||||
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
return await dbContext.LibraryFolders
|
||||
.AsNoTracking()
|
||||
@@ -166,7 +158,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
Option<int> maybeParentFolder,
|
||||
string folder)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
|
||||
// load from db or create new folder
|
||||
LibraryFolder knownFolder = await libraryPath.LibraryFolders
|
||||
@@ -199,7 +191,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task UpdateLibraryFolderId(MediaFile mediaFile, int libraryFolderId)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
mediaFile.LibraryFolderId = libraryFolderId;
|
||||
await dbContext.Connection.ExecuteAsync(
|
||||
"UPDATE MediaFile SET LibraryFolderId = @LibraryFolderId WHERE Id = @Id",
|
||||
@@ -208,7 +200,7 @@ public class LibraryRepository : ILibraryRepository
|
||||
|
||||
public async Task UpdatePath(LibraryPath libraryPath, string normalizedLibraryPath)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync();
|
||||
libraryPath.Path = normalizedLibraryPath;
|
||||
await dbContext.Connection.ExecuteAsync(
|
||||
"UPDATE LibraryPath SET Path = @Path WHERE Id = @Id",
|
||||
|
||||
@@ -1087,7 +1087,10 @@ public class MediaCollectionRepository : IMediaCollectionRepository
|
||||
.AsNoTracking()
|
||||
.SelectOneAsync(a => a.Id, a => a.Id == emptyCollection.MediaItemId.Value, cancellationToken)
|
||||
.MapT(s => s.ShowMetadata.Head().Title),
|
||||
// TODO: get playlist name
|
||||
CollectionType.Playlist => await dbContext.Playlists
|
||||
.AsNoTracking()
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == emptyCollection.PlaylistId.Value, cancellationToken)
|
||||
.MapT(p => p.Name),
|
||||
_ => None
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<TvContext> dbContextFactory)
|
||||
public class TemplateDataRepository(IFileSystem fileSystem, IDbContextFactory<TvContext> dbContextFactory)
|
||||
: ITemplateDataRepository
|
||||
{
|
||||
public async Task<Option<Dictionary<string, object>>> 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<EpgProgramme> xmlProgrammes = EpgReader.FindProgrammesAt(stream, time, count);
|
||||
var result = new List<Dictionary<string, object>>();
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.2" />
|
||||
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.2.1" />
|
||||
<PackageReference Include="Jint" Version="4.4.2" />
|
||||
<PackageReference Include="JsonSchema.Net" Version="7.4.0" />
|
||||
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00017" />
|
||||
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00017" />
|
||||
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00017" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO.Abstractions;
|
||||
using System.Runtime.InteropServices;
|
||||
using CliWrap;
|
||||
using ErsatzTV.Core;
|
||||
@@ -15,6 +16,7 @@ using YamlDotNet.Serialization.NamingConventions;
|
||||
namespace ErsatzTV.Infrastructure.FFmpeg;
|
||||
|
||||
public class MpegTsScriptService(
|
||||
IFileSystem fileSystem,
|
||||
ILocalFileSystem localFileSystem,
|
||||
ITempFilePool tempFilePool,
|
||||
ILogger<MpegTsScriptService> logger) : IMpegTsScriptService
|
||||
@@ -26,9 +28,9 @@ 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<MpegTsScript> maybeScript = FromYaml(await localFileSystem.ReadAllText(definition));
|
||||
Option<MpegTsScript> maybeScript = FromYaml(await fileSystem.File.ReadAllTextAsync(definition));
|
||||
foreach (var script in maybeScript)
|
||||
{
|
||||
script.Id = Path.GetFileName(folder);
|
||||
@@ -94,7 +96,7 @@ public class MpegTsScriptService(
|
||||
string channelName,
|
||||
string ffmpegPath)
|
||||
{
|
||||
string script = await localFileSystem.ReadAllText(fileName);
|
||||
string script = await fileSystem.File.ReadAllTextAsync(fileName);
|
||||
try
|
||||
{
|
||||
var data = new Dictionary<string, string>
|
||||
|
||||
@@ -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<Either<BaseError, string>> 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<BaseError, Unit> maybeResult = await _localFileSystem.CopyFile(path, target);
|
||||
Either<BaseError, Unit> maybeResult = await localFileSystem.CopyFile(path, target);
|
||||
return maybeResult.Match<Either<BaseError, string>>(
|
||||
_ => 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))
|
||||
{
|
||||
|
||||
@@ -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<LocalStatisticsProvider> _logger;
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public LocalStatisticsProvider(
|
||||
IMetadataRepository metadataRepository,
|
||||
IFileSystem fileSystem,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IClient client,
|
||||
IHardwareCapabilitiesFactory hardwareCapabilitiesFactory,
|
||||
ILogger<LocalStatisticsProvider> 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<double>.None;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -8,30 +10,30 @@ using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Schema;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
using JsonSchemaNet = Json.Schema;
|
||||
|
||||
namespace ErsatzTV.Infrastructure.Scheduling;
|
||||
|
||||
public class SequentialScheduleValidator(ILogger<SequentialScheduleValidator> logger) : ISequentialScheduleValidator
|
||||
public class SequentialScheduleValidator(IFileSystem fileSystem, ILogger<SequentialScheduleValidator> logger)
|
||||
: ISequentialScheduleValidator
|
||||
{
|
||||
public async Task<bool> ValidateSchedule(string yaml, bool isImport)
|
||||
{
|
||||
try
|
||||
{
|
||||
string schemaFileName = Path.Combine(
|
||||
FileSystemLayout.ResourcesCacheFolder,
|
||||
isImport ? "sequential-schedule-import.schema.json" : "sequential-schedule.schema.json");
|
||||
using StreamReader sr = File.OpenText(schemaFileName);
|
||||
await using var reader = new JsonTextReader(sr);
|
||||
var schema = JSchema.Load(reader);
|
||||
string schemaFileName = GetSchemaPath(isImport);
|
||||
string schemaText = await fileSystem.File.ReadAllTextAsync(schemaFileName);
|
||||
|
||||
using var textReader = new StringReader(yaml);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(textReader);
|
||||
var schedule = JObject.Parse(Convert(yamlStream));
|
||||
JsonSchemaNet.JsonSchema schema = JsonSchemaNet.JsonSchema.FromText(schemaText);
|
||||
|
||||
if (!schedule.IsValid(schema, out IList<string> errorMessages))
|
||||
string jsonString = ConvertYamlToJsonString(yaml);
|
||||
JsonNode jsonNode = JsonNode.Parse(jsonString);
|
||||
|
||||
JsonSchemaNet.EvaluationResults result = schema.Evaluate(jsonNode);
|
||||
|
||||
if (!result.IsValid)
|
||||
{
|
||||
logger.LogWarning("Failed to validate sequential schedule definition: {ErrorMessages}", errorMessages);
|
||||
logger.LogWarning("Sequential schedule definition failed validation");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -47,30 +49,27 @@ public class SequentialScheduleValidator(ILogger<SequentialScheduleValidator> lo
|
||||
|
||||
public string ToJson(string yaml)
|
||||
{
|
||||
using var textReader = new StringReader(yaml);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(textReader);
|
||||
var schedule = JObject.Parse(Convert(yamlStream));
|
||||
string jsonString = ConvertYamlToJsonString(yaml);
|
||||
var schedule = JObject.Parse(jsonString);
|
||||
|
||||
string formatted = JsonConvert.SerializeObject(schedule, Formatting.Indented);
|
||||
string[] lines = formatted.Split('\n');
|
||||
return string.Join('\n', lines.Select((line, index) => $"{index + 1,4}: {line}"));
|
||||
}
|
||||
|
||||
// limited to 1000/hr, but only called manually from UI
|
||||
public async Task<IList<string>> GetValidationMessages(string yaml, bool isImport)
|
||||
{
|
||||
try
|
||||
{
|
||||
string schemaFileName = Path.Combine(
|
||||
FileSystemLayout.ResourcesCacheFolder,
|
||||
isImport ? "sequential-schedule-import.schema.json" : "sequential-schedule.schema.json");
|
||||
using StreamReader sr = File.OpenText(schemaFileName);
|
||||
string schemaFileName = GetSchemaPath(isImport);
|
||||
|
||||
using StreamReader sr = fileSystem.File.OpenText(schemaFileName);
|
||||
await using var reader = new JsonTextReader(sr);
|
||||
var schema = JSchema.Load(reader);
|
||||
|
||||
using var textReader = new StringReader(yaml);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(textReader);
|
||||
var schedule = JObject.Parse(Convert(yamlStream));
|
||||
string jsonString = ConvertYamlToJsonString(yaml);
|
||||
var schedule = JObject.Parse(jsonString);
|
||||
|
||||
return schedule.IsValid(schema, out IList<string> errorMessages) ? [] : errorMessages;
|
||||
}
|
||||
@@ -80,13 +79,24 @@ public class SequentialScheduleValidator(ILogger<SequentialScheduleValidator> lo
|
||||
}
|
||||
}
|
||||
|
||||
private static string Convert(YamlStream yamlStream)
|
||||
private static string ConvertYamlToJsonString(string yaml)
|
||||
{
|
||||
using var textReader = new StringReader(yaml);
|
||||
var yamlStream = new YamlStream();
|
||||
yamlStream.Load(textReader);
|
||||
|
||||
var visitor = new YamlToJsonVisitor();
|
||||
yamlStream.Accept(visitor);
|
||||
return JsonConvert.SerializeObject(JsonConvert.DeserializeObject(visitor.JsonString), Formatting.Indented);
|
||||
}
|
||||
|
||||
private static string GetSchemaPath(bool isImport)
|
||||
{
|
||||
return Path.Combine(
|
||||
FileSystemLayout.ResourcesCacheFolder,
|
||||
isImport ? "sequential-schedule-import.schema.json" : "sequential-schedule.schema.json");
|
||||
}
|
||||
|
||||
private sealed class YamlToJsonVisitor : IYamlVisitor
|
||||
{
|
||||
private readonly JsonSerializerOptions _options = new() { WriteIndented = false };
|
||||
|
||||
@@ -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<TvContext> _dbContextFactory;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILocalStatisticsProvider _localStatisticsProvider;
|
||||
private readonly ILogger<ExternalJsonPlayoutItemProvider> _logger;
|
||||
private readonly IPlexPathReplacementService _plexPathReplacementService;
|
||||
@@ -28,7 +29,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider
|
||||
|
||||
public ExternalJsonPlayoutItemProvider(
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
IPlexPathReplacementService plexPathReplacementService,
|
||||
IPlexServerApiClient plexServerApiClient,
|
||||
IPlexSecretStore plexSecretStore,
|
||||
@@ -36,7 +37,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider
|
||||
ILogger<ExternalJsonPlayoutItemProvider> 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<ExternalJsonChannel> maybeChannel = JsonConvert.DeserializeObject<ExternalJsonChannel>(
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Graphics;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Streaming;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
@@ -18,7 +18,7 @@ namespace ErsatzTV.Infrastructure.Streaming.Graphics;
|
||||
|
||||
public partial class GraphicsElementLoader(
|
||||
TemplateFunctions templateFunctions,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
ITemplateDataRepository templateDataRepository,
|
||||
ILogger<GraphicsElementLoader> logger)
|
||||
: IGraphicsElementLoader
|
||||
@@ -147,7 +147,7 @@ public partial class GraphicsElementLoader(
|
||||
{
|
||||
try
|
||||
{
|
||||
string yaml = await localFileSystem.ReadAllText(fileName);
|
||||
string yaml = await fileSystem.File.ReadAllTextAsync(fileName, cancellationToken);
|
||||
var template = Template.Parse(yaml);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
@@ -187,7 +187,7 @@ public partial class GraphicsElementLoader(
|
||||
|
||||
foreach (var reference in elementsWithEpg)
|
||||
{
|
||||
foreach (string line in await localFileSystem.ReadAllLines(reference.GraphicsElement.Path))
|
||||
foreach (string line in await fileSystem.File.ReadAllLinesAsync(reference.GraphicsElement.Path))
|
||||
{
|
||||
Match match = EpgEntriesRegex().Match(line);
|
||||
if (!match.Success || !int.TryParse(match.Groups[1].Value, out int value))
|
||||
@@ -257,7 +257,7 @@ public partial class GraphicsElementLoader(
|
||||
|
||||
private async Task<Option<string>> GetTemplatedYaml(string fileName, Dictionary<string, object> variables)
|
||||
{
|
||||
string yaml = await localFileSystem.ReadAllText(fileName);
|
||||
string yaml = await fileSystem.File.ReadAllTextAsync(fileName);
|
||||
try
|
||||
{
|
||||
var scriptObject = new ScriptObject();
|
||||
|
||||
@@ -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<IClient>(),
|
||||
LoggerFactory.CreateLogger<LocalFileSystem>());
|
||||
var fileSystem = new MockFileSystem();
|
||||
var tempFilePool = new TempFilePool();
|
||||
|
||||
ImageCache mockImageCache = Substitute.For<ImageCache>(localFileSystem, tempFilePool);
|
||||
@@ -353,7 +356,8 @@ public class TranscodingTests
|
||||
|
||||
var localStatisticsProvider = new LocalStatisticsProvider(
|
||||
metadataRepository,
|
||||
new LocalFileSystem(Substitute.For<IClient>(), LoggerFactory.CreateLogger<LocalFileSystem>()),
|
||||
fileSystem,
|
||||
localFileSystem,
|
||||
Substitute.For<IClient>(),
|
||||
Substitute.For<IHardwareCapabilitiesFactory>(),
|
||||
LoggerFactory.CreateLogger<LocalStatisticsProvider>());
|
||||
@@ -460,6 +464,12 @@ public class TranscodingTests
|
||||
// do nothing
|
||||
}
|
||||
|
||||
var localFileSystem = new LocalFileSystem(
|
||||
new MockFileSystem(),
|
||||
Substitute.For<IClient>(),
|
||||
LoggerFactory.CreateLogger<LocalFileSystem>());
|
||||
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<IClient>(), LoggerFactory.CreateLogger<LocalFileSystem>()),
|
||||
fileSystem,
|
||||
localFileSystem,
|
||||
Substitute.For<IClient>(),
|
||||
Substitute.For<IHardwareCapabilitiesFactory>(),
|
||||
LoggerFactory.CreateLogger<LocalStatisticsProvider>());
|
||||
@@ -669,9 +680,6 @@ public class TranscodingTests
|
||||
SubtitleMode = subtitleMode
|
||||
};
|
||||
|
||||
var localFileSystem = new LocalFileSystem(
|
||||
Substitute.For<IClient>(),
|
||||
LoggerFactory.CreateLogger<LocalFileSystem>());
|
||||
var tempFilePool = new TempFilePool();
|
||||
|
||||
ImageCache mockImageCache = Substitute.For<ImageCache>(localFileSystem, tempFilePool);
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
|
||||
namespace ErsatzTV.Scanner.Tests.Core.Fakes;
|
||||
|
||||
public class FakeLocalFileSystem : ILocalFileSystem
|
||||
{
|
||||
private readonly List<FakeFileEntry> _files;
|
||||
private readonly List<FakeFolderEntry> _folders;
|
||||
|
||||
public FakeLocalFileSystem(List<FakeFileEntry> files) : this(files, new List<FakeFolderEntry>())
|
||||
{
|
||||
}
|
||||
|
||||
public FakeLocalFileSystem(List<FakeFileEntry> files, List<FakeFolderEntry> folders)
|
||||
{
|
||||
_files = files;
|
||||
|
||||
var allFolders = new List<string>(folders.Map(f => f.Path));
|
||||
foreach (FakeFileEntry file in _files)
|
||||
{
|
||||
List<DirectoryInfo> moreFolders =
|
||||
Split(new DirectoryInfo(Path.GetDirectoryName(file.Path) ?? string.Empty));
|
||||
allFolders.AddRange(moreFolders.Map(i => i.FullName));
|
||||
}
|
||||
|
||||
_folders = allFolders.Distinct().Map(f => new FakeFolderEntry(f)).ToList();
|
||||
}
|
||||
|
||||
public Unit EnsureFolderExists(string folder) => Unit.Default;
|
||||
|
||||
public DateTime GetLastWriteTime(string path) =>
|
||||
Optional(_files.SingleOrDefault(f => f.Path == path))
|
||||
.Map(f => f.LastWriteTime)
|
||||
.IfNone(SystemTime.MinValueUtc);
|
||||
|
||||
public bool IsLibraryPathAccessible(LibraryPath libraryPath) =>
|
||||
_folders.Any(f => f.Path == libraryPath.Path);
|
||||
|
||||
public IEnumerable<string> ListSubdirectories(string folder) =>
|
||||
_folders.Map(f => f.Path).Filter(f => f.StartsWith(folder) && Directory.GetParent(f)?.FullName == folder);
|
||||
|
||||
public IEnumerable<string> ListFiles(string folder) =>
|
||||
_files.Map(f => f.Path).Filter(f => Path.GetDirectoryName(f) == folder);
|
||||
|
||||
// TODO: this isn't accurate, need to use search pattern
|
||||
public IEnumerable<string> ListFiles(string folder, string searchPattern) =>
|
||||
_files.Map(f => f.Path).Filter(f => Path.GetDirectoryName(f) == folder);
|
||||
|
||||
public IEnumerable<string> ListFiles(string folder, params string[] searchPatterns) =>
|
||||
_files.Map(f => f.Path).Filter(f => Path.GetDirectoryName(f) == folder);
|
||||
|
||||
public bool FileExists(string path) => _files.Any(f => f.Path == path);
|
||||
public bool FolderExists(string folder) => false;
|
||||
|
||||
public Task<Either<BaseError, Unit>> CopyFile(string source, string destination) =>
|
||||
Task.FromResult(Right<BaseError, Unit>(Unit.Default));
|
||||
|
||||
public Unit EmptyFolder(string folder) => Unit.Default;
|
||||
public Task<string> ReadAllText(string path) => throw new NotImplementedException();
|
||||
public Task<string[]> ReadAllLines(string path) => throw new NotImplementedException();
|
||||
public Task<byte[]> GetHash(string path) => throw new NotImplementedException();
|
||||
|
||||
public string GetCustomOrDefaultFile(string folder, string file)
|
||||
{
|
||||
string path = Path.Combine(folder, file);
|
||||
return FileExists(path) ? path : Path.Combine(folder, $"_{file}");
|
||||
}
|
||||
|
||||
private static List<DirectoryInfo> Split(DirectoryInfo path)
|
||||
{
|
||||
var result = new List<DirectoryInfo>();
|
||||
if (path == null || string.IsNullOrWhiteSpace(path.FullName))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (path.Parent != null)
|
||||
{
|
||||
result.AddRange(Split(path.Parent));
|
||||
}
|
||||
|
||||
result.Add(path);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
using System.Globalization;
|
||||
using Bugsnag;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using ErsatzTV.Scanner.Core.Metadata;
|
||||
using ErsatzTV.Scanner.Tests.Core.Fakes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
using Testably.Abstractions.Testing;
|
||||
using Testably.Abstractions.Testing.Initializer;
|
||||
|
||||
namespace ErsatzTV.Scanner.Tests.Core.Metadata;
|
||||
|
||||
@@ -46,10 +50,17 @@ public class LocalSubtitlesProviderTests
|
||||
new(@"/Movies/Avatar (2009)/Avatar (2009).DE.SDH.FORCED.SRT")
|
||||
};
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
IFileSystemInitializer<MockFileSystem> init = fileSystem.Initialize();
|
||||
foreach (var file in fakeFiles)
|
||||
{
|
||||
init.WithFile(file.Path);
|
||||
}
|
||||
|
||||
var provider = new LocalSubtitlesProvider(
|
||||
Substitute.For<IMediaItemRepository>(),
|
||||
Substitute.For<IMetadataRepository>(),
|
||||
new FakeLocalFileSystem(fakeFiles),
|
||||
new LocalFileSystem(fileSystem, Substitute.For<IClient>(), Substitute.For<ILogger<LocalFileSystem>>()),
|
||||
Substitute.For<ILogger<LocalSubtitlesProvider>>());
|
||||
|
||||
List<Subtitle> result = provider.LocateExternalSubtitles(
|
||||
@@ -91,10 +102,17 @@ public class LocalSubtitlesProviderTests
|
||||
new(@"/Movies/Avatar (2009)/Avatar (2009).DE.SDH.FORCED.SRT")
|
||||
};
|
||||
|
||||
var fileSystem = new MockFileSystem();
|
||||
IFileSystemInitializer<MockFileSystem> init = fileSystem.Initialize();
|
||||
foreach (var file in fakeFiles)
|
||||
{
|
||||
init.WithFile(file.Path);
|
||||
}
|
||||
|
||||
var provider = new LocalSubtitlesProvider(
|
||||
Substitute.For<IMediaItemRepository>(),
|
||||
Substitute.For<IMetadataRepository>(),
|
||||
new FakeLocalFileSystem(fakeFiles),
|
||||
new LocalFileSystem(fileSystem, Substitute.For<IClient>(), Substitute.For<ILogger<LocalFileSystem>>()),
|
||||
Substitute.For<ILogger<LocalSubtitlesProvider>>());
|
||||
|
||||
List<Subtitle> result = provider.LocateExternalSubtitles(
|
||||
|
||||
@@ -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,28 +716,19 @@ public class MovieFolderScannerTests
|
||||
await _mediaItemRepository.Received(1).FlagFileNotFound(libraryPath, oldMoviePath);
|
||||
}
|
||||
|
||||
private MovieFolderScanner GetService(params FakeFileEntry[] files) =>
|
||||
new(
|
||||
_scannerProxy,
|
||||
new FakeLocalFileSystem([..files]),
|
||||
_movieRepository,
|
||||
_localStatisticsProvider,
|
||||
Substitute.For<ILocalSubtitlesProvider>(),
|
||||
Substitute.For<ILocalChaptersProvider>(),
|
||||
_localMetadataProvider,
|
||||
Substitute.For<IMetadataRepository>(),
|
||||
_imageCache,
|
||||
_libraryRepository,
|
||||
_mediaItemRepository,
|
||||
Substitute.For<IFFmpegPngService>(),
|
||||
Substitute.For<ITempFilePool>(),
|
||||
Substitute.For<IClient>(),
|
||||
Logger);
|
||||
private MovieFolderScanner GetService(params FakeFileEntry[] files)
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
IFileSystemInitializer<MockFileSystem> init = fileSystem.Initialize();
|
||||
foreach (var file in files)
|
||||
{
|
||||
init.WithFile(file.Path).Which(f => f.File.LastWriteTime = file.LastWriteTime);
|
||||
}
|
||||
|
||||
private MovieFolderScanner GetService(params FakeFolderEntry[] folders) =>
|
||||
new(
|
||||
return new MovieFolderScanner(
|
||||
_scannerProxy,
|
||||
new FakeLocalFileSystem([], [..folders]),
|
||||
fileSystem,
|
||||
new LocalFileSystem(fileSystem, Substitute.For<IClient>(), Substitute.For<ILogger<LocalFileSystem>>()),
|
||||
_movieRepository,
|
||||
_localStatisticsProvider,
|
||||
Substitute.For<ILocalSubtitlesProvider>(),
|
||||
@@ -753,5 +742,34 @@ public class MovieFolderScannerTests
|
||||
Substitute.For<ITempFilePool>(),
|
||||
Substitute.For<IClient>(),
|
||||
Logger);
|
||||
}
|
||||
|
||||
private MovieFolderScanner GetService(params FakeFolderEntry[] folders)
|
||||
{
|
||||
var fileSystem = new MockFileSystem();
|
||||
IFileSystemInitializer<MockFileSystem> init = fileSystem.Initialize();
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
init.WithSubdirectory(folder.Path);
|
||||
}
|
||||
|
||||
return new MovieFolderScanner(
|
||||
_scannerProxy,
|
||||
fileSystem,
|
||||
new LocalFileSystem(fileSystem, Substitute.For<IClient>(), Substitute.For<ILogger<LocalFileSystem>>()),
|
||||
_movieRepository,
|
||||
_localStatisticsProvider,
|
||||
Substitute.For<ILocalSubtitlesProvider>(),
|
||||
Substitute.For<ILocalChaptersProvider>(),
|
||||
_localMetadataProvider,
|
||||
Substitute.For<IMetadataRepository>(),
|
||||
_imageCache,
|
||||
_libraryRepository,
|
||||
_mediaItemRepository,
|
||||
Substitute.For<IFFmpegPngService>(),
|
||||
Substitute.For<ITempFilePool>(),
|
||||
Substitute.For<IClient>(),
|
||||
Logger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="Testably.Abstractions.Testing" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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<EmbyMovieLibraryScanner> logger)
|
||||
: base(
|
||||
scannerProxy,
|
||||
localFileSystem,
|
||||
fileSystem,
|
||||
localChaptersProvider,
|
||||
metadataRepository,
|
||||
logger)
|
||||
|
||||
@@ -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<EmbyTelevisionLibraryScanner> logger)
|
||||
: base(
|
||||
scannerProxy,
|
||||
localFileSystem,
|
||||
fileSystem,
|
||||
localChaptersProvider,
|
||||
metadataRepository,
|
||||
logger)
|
||||
|
||||
@@ -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<JellyfinMovieLibraryScanner> logger)
|
||||
: base(
|
||||
scannerProxy,
|
||||
localFileSystem,
|
||||
fileSystem,
|
||||
localChaptersProvider,
|
||||
metadataRepository,
|
||||
logger)
|
||||
|
||||
@@ -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<JellyfinTelevisionLibraryScanner> logger)
|
||||
: base(
|
||||
scannerProxy,
|
||||
localFileSystem,
|
||||
fileSystem,
|
||||
localChaptersProvider,
|
||||
metadataRepository,
|
||||
logger)
|
||||
|
||||
@@ -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<ImageFolderScanner> _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<ImageFolderScanner> 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<int> imageIds = await FlagFileNotFound(libraryPath, path);
|
||||
|
||||
@@ -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<BaseError, bool> refreshResult =
|
||||
@@ -141,7 +142,7 @@ public abstract class LocalFolderScanner
|
||||
Option<int> attachedPicIndex,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
DateTime lastWriteTime = _localFileSystem.GetLastWriteTime(artworkFile);
|
||||
DateTime lastWriteTime = _fileSystem.File.GetLastWriteTime(artworkFile);
|
||||
|
||||
metadata.Artwork ??= new List<Artwork>();
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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<ShowMetadata> 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<ArtistMetadata> maybeMetadata = None;
|
||||
if (_localFileSystem.FileExists(nfoFileName))
|
||||
if (_fileSystem.File.Exists(nfoFileName))
|
||||
{
|
||||
maybeMetadata = await LoadArtistMetadata(nfoFileName);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace ErsatzTV.Scanner.Core.Metadata;
|
||||
|
||||
public class LocalSubtitlesProvider : ILocalSubtitlesProvider
|
||||
{
|
||||
private readonly List<CultureInfo> _languageCodes = new();
|
||||
private readonly List<CultureInfo> _languageCodes = [];
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly ILogger<LocalSubtitlesProvider> _logger;
|
||||
private readonly IMediaItemRepository _mediaItemRepository;
|
||||
|
||||
@@ -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<TConnectionParameters, TLib
|
||||
{
|
||||
private readonly ILocalChaptersProvider _localChaptersProvider;
|
||||
private readonly IScannerProxy _scannerProxy;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
|
||||
protected MediaServerMovieLibraryScanner(
|
||||
IScannerProxy scannerProxy,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
ILocalChaptersProvider localChaptersProvider,
|
||||
IMetadataRepository metadataRepository,
|
||||
ILogger logger)
|
||||
{
|
||||
_scannerProxy = scannerProxy;
|
||||
_localFileSystem = localFileSystem;
|
||||
_fileSystem = fileSystem;
|
||||
_localChaptersProvider = localChaptersProvider;
|
||||
_metadataRepository = metadataRepository;
|
||||
_logger = logger;
|
||||
@@ -167,7 +167,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
||||
{
|
||||
await movieRepository.SetEtag(result.Item, MediaServerEtag(incoming));
|
||||
|
||||
if (_localFileSystem.FileExists(result.LocalPath))
|
||||
if (_fileSystem.File.Exists(result.LocalPath))
|
||||
{
|
||||
Option<int> flagResult = await movieRepository.FlagNormal(library, result.Item);
|
||||
if (flagResult.IsSome)
|
||||
@@ -270,7 +270,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
||||
existingEtag == MediaServerEtag(incoming))
|
||||
{
|
||||
// skip scanning unavailable/file not found items that are unchanged and still don't exist locally
|
||||
if (!_localFileSystem.FileExists(localPath) && !ServerSupportsRemoteStreaming)
|
||||
if (!_fileSystem.File.Exists(localPath) && !ServerSupportsRemoteStreaming)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -279,7 +279,7 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
|
||||
{
|
||||
// item is unchanged, but file does not exist
|
||||
// don't scan, but mark as unavailable
|
||||
if (!_localFileSystem.FileExists(localPath))
|
||||
if (!_fileSystem.File.Exists(localPath))
|
||||
{
|
||||
if (ServerSupportsRemoteStreaming)
|
||||
{
|
||||
|
||||
@@ -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 MediaServerOtherVideoLibraryScanner<TConnectionParameters,
|
||||
{
|
||||
private readonly ILocalChaptersProvider _localChaptersProvider;
|
||||
private readonly IScannerProxy _scannerProxy;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
|
||||
protected MediaServerOtherVideoLibraryScanner(
|
||||
IScannerProxy scannerProxy,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
ILocalChaptersProvider localChaptersProvider,
|
||||
IMetadataRepository metadataRepository,
|
||||
ILogger logger)
|
||||
{
|
||||
_scannerProxy = scannerProxy;
|
||||
_localFileSystem = localFileSystem;
|
||||
_fileSystem = fileSystem;
|
||||
_localChaptersProvider = localChaptersProvider;
|
||||
_metadataRepository = metadataRepository;
|
||||
_logger = logger;
|
||||
@@ -174,7 +174,7 @@ public abstract class MediaServerOtherVideoLibraryScanner<TConnectionParameters,
|
||||
{
|
||||
await otherVideoRepository.SetEtag(result.Item, MediaServerEtag(incoming));
|
||||
|
||||
if (_localFileSystem.FileExists(result.LocalPath))
|
||||
if (_fileSystem.File.Exists(result.LocalPath))
|
||||
{
|
||||
Option<int> flagResult = await otherVideoRepository.FlagNormal(library, result.Item);
|
||||
if (flagResult.IsSome)
|
||||
@@ -277,7 +277,7 @@ public abstract class MediaServerOtherVideoLibraryScanner<TConnectionParameters,
|
||||
existingEtag == MediaServerEtag(incoming))
|
||||
{
|
||||
// skip scanning unavailable/file not found items that are unchanged and still don't exist locally
|
||||
if (!_localFileSystem.FileExists(localPath) && !ServerSupportsRemoteStreaming)
|
||||
if (!_fileSystem.File.Exists(localPath) && !ServerSupportsRemoteStreaming)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -286,7 +286,7 @@ public abstract class MediaServerOtherVideoLibraryScanner<TConnectionParameters,
|
||||
{
|
||||
// item is unchanged, but file does not exist
|
||||
// don't scan, but mark as unavailable
|
||||
if (!_localFileSystem.FileExists(localPath))
|
||||
if (!_fileSystem.File.Exists(localPath))
|
||||
{
|
||||
if (ServerSupportsRemoteStreaming)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using ErsatzTV.Core;
|
||||
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;
|
||||
@@ -23,19 +23,19 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
||||
{
|
||||
private readonly ILocalChaptersProvider _localChaptersProvider;
|
||||
private readonly IScannerProxy _scannerProxy;
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
|
||||
protected MediaServerTelevisionLibraryScanner(
|
||||
IScannerProxy scannerProxy,
|
||||
ILocalFileSystem localFileSystem,
|
||||
IFileSystem fileSystem,
|
||||
ILocalChaptersProvider localChaptersProvider,
|
||||
IMetadataRepository metadataRepository,
|
||||
ILogger logger)
|
||||
{
|
||||
_scannerProxy = scannerProxy;
|
||||
_localFileSystem = localFileSystem;
|
||||
_fileSystem = fileSystem;
|
||||
_localChaptersProvider = localChaptersProvider;
|
||||
_metadataRepository = metadataRepository;
|
||||
_logger = logger;
|
||||
@@ -469,7 +469,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
||||
{
|
||||
await televisionRepository.SetEtag(result.Item, MediaServerEtag(incoming), cancellationToken);
|
||||
|
||||
if (_localFileSystem.FileExists(result.LocalPath))
|
||||
if (_fileSystem.File.Exists(result.LocalPath))
|
||||
{
|
||||
Option<int> flagResult = await televisionRepository.FlagNormal(library, result.Item, cancellationToken);
|
||||
if (flagResult.IsSome)
|
||||
@@ -541,7 +541,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
||||
existingEtag == MediaServerEtag(incoming))
|
||||
{
|
||||
// skip scanning unavailable/file not found items that are unchanged and still don't exist locally
|
||||
if (!_localFileSystem.FileExists(localPath) && !ServerSupportsRemoteStreaming)
|
||||
if (!_fileSystem.File.Exists(localPath) && !ServerSupportsRemoteStreaming)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -550,7 +550,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
||||
{
|
||||
// item is unchanged, but file does not exist
|
||||
// don't scan, but mark as unavailable
|
||||
if (!_localFileSystem.FileExists(localPath))
|
||||
if (!_fileSystem.File.Exists(localPath))
|
||||
{
|
||||
if (ServerSupportsRemoteStreaming)
|
||||
{
|
||||
@@ -741,7 +741,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
|
||||
if (deepScan || result.IsAdded || MediaServerEtag(existing) != MediaServerEtag(incoming) ||
|
||||
existing.MediaVersions.Head().Streams.Count == 0)
|
||||
{
|
||||
// if (maybeMediaVersion.IsNone && _localFileSystem.FileExists(result.LocalPath))
|
||||
// if (maybeMediaVersion.IsNone && _fileSystem.File.Exists(result.LocalPath))
|
||||
// {
|
||||
// _logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", result.LocalPath);
|
||||
// Either<BaseError, bool> refreshResult =
|
||||
|
||||
@@ -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<MovieFolderScanner> 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<int> 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<string> possibleMoviePosters = ImageFileExtensions.Collect(ext =>
|
||||
new[] { $"{segment}.{ext}", Path.GetFileNameWithoutExtension(path) + $"-{segment}.{ext}" })
|
||||
.Map(f => Path.Combine(folder, f));
|
||||
Option<string> result = possibleMoviePosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone();
|
||||
Option<string> result = possibleMoviePosters.Filter(p => _fileSystem.File.Exists(p)).HeadOrNone();
|
||||
if (result.IsNone && artworkKind == ArtworkKind.Poster)
|
||||
{
|
||||
IEnumerable<string> 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;
|
||||
|
||||
@@ -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<MusicVideoFolderScanner> 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<int> musicVideoIds = await FlagFileNotFound(libraryPath, path);
|
||||
@@ -459,7 +463,7 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan
|
||||
}
|
||||
|
||||
private Option<string> 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<string> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<OtherVideoFolderScanner> 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<int> otherVideoIds = await FlagFileNotFound(libraryPath, path);
|
||||
@@ -274,7 +278,7 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan
|
||||
string path = otherVideo.MediaVersions.Head().MediaFiles.Head().Path;
|
||||
|
||||
Option<string> maybeNfoFile = new List<string> { 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RemoteStreamFolderScanner> _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<RemoteStreamFolderScanner> 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<int> remoteStreamIds = await FlagFileNotFound(libraryPath, path);
|
||||
|
||||
@@ -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<SongFolderScanner> _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<SongFolderScanner> 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<int> 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();
|
||||
}
|
||||
|
||||
@@ -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<TelevisionFolderScanner> 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<string> 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<string> 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<string> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PlexMovieLibraryScanner> logger)
|
||||
: base(
|
||||
scannerProxy,
|
||||
localFileSystem,
|
||||
fileSystem,
|
||||
localChaptersProvider,
|
||||
metadataRepository,
|
||||
logger)
|
||||
|
||||
@@ -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<PlexOtherVideoLibraryScanner> logger)
|
||||
: base(
|
||||
scannerProxy,
|
||||
localFileSystem,
|
||||
fileSystem,
|
||||
localChaptersProvider,
|
||||
metadataRepository,
|
||||
logger)
|
||||
|
||||
@@ -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<PlexTelevisionLibraryScanner> logger)
|
||||
: base(
|
||||
scannerProxy,
|
||||
localFileSystem,
|
||||
fileSystem,
|
||||
localChaptersProvider,
|
||||
metadataRepository,
|
||||
logger)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.IO.Abstractions;
|
||||
using Bugsnag;
|
||||
using Bugsnag.Payload;
|
||||
using Dapper;
|
||||
@@ -47,6 +48,7 @@ using Microsoft.IO;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact;
|
||||
using Testably.Abstractions;
|
||||
using Exception = System.Exception;
|
||||
using IConfiguration = Bugsnag.IConfiguration;
|
||||
|
||||
@@ -252,6 +254,8 @@ public class Program
|
||||
services.AddSingleton<IScannerProxy, ScannerProxy>();
|
||||
services.AddSingleton<ILanguageCodeCache, LanguageCodeCache>();
|
||||
|
||||
services.AddSingleton<IFileSystem, RealFileSystem>();
|
||||
|
||||
services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining<Worker>());
|
||||
services.AddMemoryCache();
|
||||
|
||||
|
||||
@@ -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<IFFmpegWorkerRequest> 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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
<MudForm Style="max-height: 100%">
|
||||
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
|
||||
@@ -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); });
|
||||
|
||||
@@ -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<CacheCleanerService> logger)
|
||||
: BackgroundService
|
||||
{
|
||||
private readonly ILogger<CacheCleanerService> _logger;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly SystemStartup _systemStartup;
|
||||
|
||||
public CacheCleanerService(
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
SystemStartup systemStartup,
|
||||
ILogger<CacheCleanerService> 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<TvContext>();
|
||||
ILocalFileSystem localFileSystem = scope.ServiceProvider.GetRequiredService<ILocalFileSystem>();
|
||||
IFileSystem fileSystem = scope.ServiceProvider.GetRequiredService<IFileSystem>();
|
||||
|
||||
if (localFileSystem.FolderExists(FileSystemLayout.LegacyImageCacheFolder))
|
||||
if (fileSystem.Directory.Exists(FileSystemLayout.LegacyImageCacheFolder))
|
||||
{
|
||||
_logger.LogInformation("Migrating channel logos from legacy image cache folder");
|
||||
logger.LogInformation("Migrating channel logos from legacy image cache folder");
|
||||
|
||||
List<string> 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))
|
||||
if (fileSystem.Directory.Exists(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))
|
||||
if (fileSystem.Directory.Exists(FileSystemLayout.ChannelGuideCacheFolder))
|
||||
{
|
||||
_logger.LogInformation("Cleaning channel cache");
|
||||
logger.LogInformation("Cleaning channel cache");
|
||||
|
||||
List<string> channelFiles = await dbContext.Channels
|
||||
.AsNoTracking()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@@ -99,6 +100,7 @@ using Refit;
|
||||
using Scalar.AspNetCore;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Testably.Abstractions;
|
||||
|
||||
namespace ErsatzTV;
|
||||
|
||||
@@ -692,6 +694,8 @@ public class Startup
|
||||
{
|
||||
services.AddSingleton<IEnvironmentValidator, EnvironmentValidator>();
|
||||
|
||||
services.AddSingleton<IFileSystem, RealFileSystem>();
|
||||
|
||||
services.AddSingleton<IDatabaseMigrations, DatabaseMigrations>();
|
||||
services.AddSingleton<IPlexSecretStore, PlexSecretStore>();
|
||||
services.AddSingleton<IPlexTvApiClient, PlexTvApiClient>(); // TODO: does this need to be singleton?
|
||||
|
||||
Reference in New Issue
Block a user