From 0ee6e696a24028440f5f5996e8b0262d34a823cd Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Thu, 11 Mar 2021 20:14:32 -0600 Subject: [PATCH] improve ffmpeg/ffprobe path validation --- .../Commands/UpdateFFmpegSettings.cs | 5 +- .../Commands/UpdateFFmpegSettingsHandler.cs | 67 +++++++++++++++++-- ErsatzTV/Pages/FFmpeg.razor | 15 ++++- 3 files changed, 77 insertions(+), 10 deletions(-) diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettings.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettings.cs index 44d65d3a3..e4b3b392d 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettings.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettings.cs @@ -1,6 +1,7 @@ -using MediatR; +using ErsatzTV.Core; +using LanguageExt; namespace ErsatzTV.Application.FFmpegProfiles.Commands { - public record UpdateFFmpegSettings(FFmpegSettingsViewModel Settings) : IRequest; + public record UpdateFFmpegSettings(FFmpegSettingsViewModel Settings) : MediatR.IRequest>; } diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs index ab4dec619..e1486dbec 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs @@ -1,21 +1,74 @@ -using System.Threading; +using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; +using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; -using MediatR; -using Unit = MediatR.Unit; namespace ErsatzTV.Application.FFmpegProfiles.Commands { - public class UpdateFFmpegSettingsHandler : IRequestHandler + public class UpdateFFmpegSettingsHandler : MediatR.IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; + private readonly ILocalFileSystem _localFileSystem; - public UpdateFFmpegSettingsHandler(IConfigElementRepository configElementRepository) => + public UpdateFFmpegSettingsHandler( + IConfigElementRepository configElementRepository, + ILocalFileSystem localFileSystem) + { _configElementRepository = configElementRepository; + _localFileSystem = localFileSystem; + } + + public Task> Handle( + UpdateFFmpegSettings request, + CancellationToken cancellationToken) => + Validate(request) + .MapT(_ => ApplyUpdate(request)) + .Bind(v => v.ToEitherAsync()); + + private async Task> Validate(UpdateFFmpegSettings request) => + (await FFmpegMustExist(request), await FFprobeMustExist(request)) + .Apply((_, _) => Unit.Default); + + private Task> FFmpegMustExist(UpdateFFmpegSettings request) => + ValidateToolPath(request.Settings.FFmpegPath, "ffmpeg"); + + private Task> FFprobeMustExist(UpdateFFmpegSettings request) => + ValidateToolPath(request.Settings.FFprobePath, "ffprobe"); + + private async Task> ValidateToolPath(string path, string name) + { + if (!_localFileSystem.FileExists(path)) + { + return BaseError.New($"{name} path does not exist"); + } + + var startInfo = new ProcessStartInfo + { + FileName = path, + Arguments = "-version", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + var test = new Process + { + StartInfo = startInfo + }; + + test.Start(); + string output = await test.StandardOutput.ReadToEndAsync(); + await test.WaitForExitAsync(); + return test.ExitCode == 0 && output.Contains($"{name} version") + ? Unit.Default + : BaseError.New($"Unable to verify {name} version"); + } - public async Task Handle(UpdateFFmpegSettings request, CancellationToken cancellationToken) + private async Task ApplyUpdate(UpdateFFmpegSettings request) { Option ffmpegPath = await _configElementRepository.Get(ConfigElementKey.FFmpegPath); Option ffprobePath = await _configElementRepository.Get(ConfigElementKey.FFprobePath); @@ -64,7 +117,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands _configElementRepository.Add(ce); }); - return Unit.Value; + return Unit.Default; } } } diff --git a/ErsatzTV/Pages/FFmpeg.razor b/ErsatzTV/Pages/FFmpeg.razor index 5c8a80447..793fb0f53 100644 --- a/ErsatzTV/Pages/FFmpeg.razor +++ b/ErsatzTV/Pages/FFmpeg.razor @@ -2,8 +2,11 @@ @using ErsatzTV.Application.FFmpegProfiles @using ErsatzTV.Application.FFmpegProfiles.Commands @using ErsatzTV.Application.FFmpegProfiles.Queries +@using Unit = LanguageExt.Unit @inject IDialogService Dialog @inject IMediator Mediator +@inject ILogger Logger +@inject ISnackbar Snackbar @@ -110,7 +113,17 @@ await LoadFFmpegProfilesAsync(); } - private Task SaveSettings() => Mediator.Send(new UpdateFFmpegSettings(_ffmpegSettings)); + private async Task SaveSettings() + { + Either result = await Mediator.Send(new UpdateFFmpegSettings(_ffmpegSettings)); + result.Match( + Left: error => + { + Snackbar.Add(error.Value, Severity.Error); + Logger.LogError("Unexpected error saving FFmpeg settings: {Error}", error.Value); + }, + Right: _ => Snackbar.Add("Successfully saved FFmpeg settings", Severity.Success)); + } private static string ValidatePathExists(string path) => !File.Exists(path) ? "Path does not exist" : null;