mirror of https://github.com/ErsatzTV/ErsatzTV.git
19 changed files with 263 additions and 243 deletions
@ -0,0 +1,9 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Libraries.Commands |
||||||
|
{ |
||||||
|
public record CreateLocalLibraryPath |
||||||
|
(int LibraryId, string Path) : IRequest<Either<BaseError, LocalLibraryPathViewModel>>; |
||||||
|
} |
||||||
@ -0,0 +1,62 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
using static LanguageExt.Prelude; |
||||||
|
using static ErsatzTV.Application.Libraries.Mapper; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Libraries.Commands |
||||||
|
{ |
||||||
|
public class CreateLocalLibraryPathHandler : IRequestHandler<CreateLocalLibraryPath, |
||||||
|
Either<BaseError, LocalLibraryPathViewModel>> |
||||||
|
{ |
||||||
|
private readonly ILibraryRepository _libraryRepository; |
||||||
|
|
||||||
|
public CreateLocalLibraryPathHandler(ILibraryRepository mediaSourceRepository) => |
||||||
|
_libraryRepository = mediaSourceRepository; |
||||||
|
|
||||||
|
public Task<Either<BaseError, LocalLibraryPathViewModel>> Handle( |
||||||
|
CreateLocalLibraryPath request, |
||||||
|
CancellationToken cancellationToken) => |
||||||
|
Validate(request).MapT(PersistLocalLibraryPath).Bind(v => v.ToEitherAsync()); |
||||||
|
|
||||||
|
private Task<LocalLibraryPathViewModel> PersistLocalLibraryPath(LibraryPath p) => |
||||||
|
_libraryRepository.Add(p).Map(ProjectToViewModel); |
||||||
|
|
||||||
|
private Task<Validation<BaseError, LibraryPath>> Validate(CreateLocalLibraryPath request) => |
||||||
|
ValidateFolder(request) |
||||||
|
.MapT( |
||||||
|
folder => |
||||||
|
new LibraryPath |
||||||
|
{ |
||||||
|
LibraryId = request.LibraryId, |
||||||
|
Path = folder |
||||||
|
}); |
||||||
|
|
||||||
|
private async Task<Validation<BaseError, string>> ValidateFolder(CreateLocalLibraryPath request) |
||||||
|
{ |
||||||
|
List<string> allPaths = await _libraryRepository.GetLocalPaths(request.LibraryId) |
||||||
|
.Map(list => list.Map(c => c.Path).ToList()); |
||||||
|
|
||||||
|
|
||||||
|
return Optional(request.Path) |
||||||
|
.Filter(folder => allPaths.ForAll(f => !AreSubPaths(f, folder))) |
||||||
|
.ToValidation<BaseError>("Path must not belong to another library path"); |
||||||
|
} |
||||||
|
|
||||||
|
private static bool AreSubPaths(string path1, string path2) |
||||||
|
{ |
||||||
|
string one = path1 + Path.DirectorySeparatorChar; |
||||||
|
string two = path2 + Path.DirectorySeparatorChar; |
||||||
|
return one == two || one.StartsWith(two, StringComparison.OrdinalIgnoreCase) || |
||||||
|
two.StartsWith(one, StringComparison.OrdinalIgnoreCase); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -1,4 +1,4 @@ |
|||||||
namespace ErsatzTV.Application.Libraries |
namespace ErsatzTV.Application.Libraries |
||||||
{ |
{ |
||||||
public record LocalLibraryPathViewModel(int Id, string Path); |
public record LocalLibraryPathViewModel(int Id, int LibraryId, string Path); |
||||||
} |
} |
||||||
|
|||||||
@ -1,4 +1,6 @@ |
|||||||
namespace ErsatzTV.Application.Libraries |
using ErsatzTV.Core.Domain; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Libraries |
||||||
{ |
{ |
||||||
public record LocalLibraryViewModel(int Id, string Name, string MediaKind); |
public record LocalLibraryViewModel(int Id, string Name, LibraryMediaKind MediaKind); |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,7 @@ |
|||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Libraries.Queries |
||||||
|
{ |
||||||
|
public record GetLocalLibraryById(int LibraryId) : IRequest<Option<LocalLibraryViewModel>>; |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
using static ErsatzTV.Application.Libraries.Mapper; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Libraries.Queries |
||||||
|
{ |
||||||
|
public class GetLocalLibraryByIdHandler : IRequestHandler<GetLocalLibraryById, Option<LocalLibraryViewModel>> |
||||||
|
{ |
||||||
|
private readonly ILibraryRepository _libraryRepository; |
||||||
|
|
||||||
|
public GetLocalLibraryByIdHandler(ILibraryRepository libraryRepository) => |
||||||
|
_libraryRepository = libraryRepository; |
||||||
|
|
||||||
|
public Task<Option<LocalLibraryViewModel>> Handle( |
||||||
|
GetLocalLibraryById request, |
||||||
|
CancellationToken cancellationToken) => |
||||||
|
_libraryRepository.GetLocal(request.LibraryId).MapT(ProjectToViewModel); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,12 +0,0 @@ |
|||||||
// using ErsatzTV.Core;
|
|
||||||
// using ErsatzTV.Core.Domain;
|
|
||||||
// using LanguageExt;
|
|
||||||
// using MediatR;
|
|
||||||
//
|
|
||||||
// namespace ErsatzTV.Application.MediaSources.Commands
|
|
||||||
// {
|
|
||||||
// public record CreateLocalMediaSource
|
|
||||||
// (string Name, MediaType MediaType, string Folder) : IRequest<Either<BaseError, MediaSourceViewModel>>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,80 +0,0 @@ |
|||||||
// using System;
|
|
||||||
// using System.Collections.Generic;
|
|
||||||
// using System.IO;
|
|
||||||
// using System.Linq;
|
|
||||||
// using System.Threading;
|
|
||||||
// using System.Threading.Tasks;
|
|
||||||
// using ErsatzTV.Core;
|
|
||||||
// using ErsatzTV.Core.Domain;
|
|
||||||
// using ErsatzTV.Core.Interfaces.Repositories;
|
|
||||||
// using LanguageExt;
|
|
||||||
// using MediatR;
|
|
||||||
// using static ErsatzTV.Application.MediaSources.Mapper;
|
|
||||||
// using static LanguageExt.Prelude;
|
|
||||||
//
|
|
||||||
// namespace ErsatzTV.Application.MediaSources.Commands
|
|
||||||
// {
|
|
||||||
// public class CreateLocalMediaSourceHandler : IRequestHandler<CreateLocalMediaSource,
|
|
||||||
// Either<BaseError, MediaSourceViewModel>>
|
|
||||||
// {
|
|
||||||
// private readonly IMediaSourceRepository _mediaSourceRepository;
|
|
||||||
//
|
|
||||||
// public CreateLocalMediaSourceHandler(IMediaSourceRepository mediaSourceRepository) =>
|
|
||||||
// _mediaSourceRepository = mediaSourceRepository;
|
|
||||||
//
|
|
||||||
// public Task<Either<BaseError, MediaSourceViewModel>> Handle(
|
|
||||||
// CreateLocalMediaSource request,
|
|
||||||
// CancellationToken cancellationToken) =>
|
|
||||||
// Validate(request).MapT(PersistLocalMediaSource).Bind(v => v.ToEitherAsync());
|
|
||||||
//
|
|
||||||
// private Task<MediaSourceViewModel> PersistLocalMediaSource(LocalMediaSource c) =>
|
|
||||||
// _mediaSourceRepository.Add(c).Map(ProjectToViewModel);
|
|
||||||
//
|
|
||||||
// private async Task<Validation<BaseError, LocalMediaSource>> Validate(CreateLocalMediaSource request) =>
|
|
||||||
// (await ValidateName(request), await ValidateFolder(request))
|
|
||||||
// .Apply(
|
|
||||||
// (name, folder) =>
|
|
||||||
// new LocalMediaSource
|
|
||||||
// {
|
|
||||||
// Name = name,
|
|
||||||
// MediaType = request.MediaType,
|
|
||||||
// Folder = folder
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// private async Task<Validation<BaseError, string>> ValidateName(CreateLocalMediaSource request)
|
|
||||||
// {
|
|
||||||
// List<string> allNames = await _mediaSourceRepository.GetAll()
|
|
||||||
// .Map(list => list.Map(c => c.Name).ToList());
|
|
||||||
//
|
|
||||||
// Validation<BaseError, string> result1 = request.NotEmpty(c => c.Name)
|
|
||||||
// .Bind(_ => request.NotLongerThan(50)(c => c.Name));
|
|
||||||
//
|
|
||||||
// var result2 = Optional(request.Name)
|
|
||||||
// .Filter(name => !allNames.Contains(name))
|
|
||||||
// .ToValidation<BaseError>("Media source name must be unique");
|
|
||||||
//
|
|
||||||
// return (result1, result2).Apply((_, _) => request.Name);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private async Task<Validation<BaseError, string>> ValidateFolder(CreateLocalMediaSource request)
|
|
||||||
// {
|
|
||||||
// List<string> allFolders = await _mediaSourceRepository.GetAll()
|
|
||||||
// .Map(list => list.OfType<LocalMediaSource>().Map(c => c.Folder).ToList());
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// return Optional(request.Folder)
|
|
||||||
// .Filter(folder => allFolders.ForAll(f => !AreSubPaths(f, folder)))
|
|
||||||
// .ToValidation<BaseError>("Folder must not belong to another media source");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static bool AreSubPaths(string path1, string path2)
|
|
||||||
// {
|
|
||||||
// string one = path1 + Path.DirectorySeparatorChar;
|
|
||||||
// string two = path2 + Path.DirectorySeparatorChar;
|
|
||||||
// return one == two || one.StartsWith(two, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
// two.StartsWith(one, StringComparison.OrdinalIgnoreCase);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,86 @@ |
|||||||
|
@page "/media/libraries/local/{Id:int}/add" |
||||||
|
@using ErsatzTV.Application.Libraries |
||||||
|
@using ErsatzTV.Application.Libraries.Commands |
||||||
|
@using ErsatzTV.Application.Libraries.Queries |
||||||
|
@using ErsatzTV.Application.MediaSources.Commands |
||||||
|
@inject NavigationManager NavigationManager |
||||||
|
@inject ILogger<LocalLibraryPathEditor> Logger |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject IEntityLocker Locker |
||||||
|
@inject ChannelWriter<IBackgroundServiceRequest> Channel |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge"> |
||||||
|
<MudText Typo="Typo.h4" Class="mb-4">@_library.Name - Add Local Library Path</MudText> |
||||||
|
<div style="max-width: 400px;"> |
||||||
|
<EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync"> |
||||||
|
<FluentValidator/> |
||||||
|
<MudCard> |
||||||
|
<MudCardContent> |
||||||
|
<MudTextField T="string" Label="Media Kind" Disabled="true" Value="@(Enum.GetName(typeof(LibraryMediaKind), _library.MediaKind))"/> |
||||||
|
@* TODO: replace this with a folder picker *@ |
||||||
|
<MudTextField Label="Path" @bind-Value="_model.Path" For="@(() => _model.Path)"/> |
||||||
|
</MudCardContent> |
||||||
|
<MudCardActions> |
||||||
|
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto"> |
||||||
|
Add Local Library Path |
||||||
|
</MudButton> |
||||||
|
</MudCardActions> |
||||||
|
</MudCard> |
||||||
|
</EditForm> |
||||||
|
</div> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int Id { get; set; } |
||||||
|
|
||||||
|
private readonly LocalLibraryPathEditViewModel _model = new(); |
||||||
|
private EditContext _editContext; |
||||||
|
private ValidationMessageStore _messageStore; |
||||||
|
|
||||||
|
private LocalLibraryViewModel _library; |
||||||
|
|
||||||
|
protected override async Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
Option<LocalLibraryViewModel> maybeLibrary = await Mediator.Send(new GetLocalLibraryById(Id)); |
||||||
|
maybeLibrary.Match( |
||||||
|
library => _library = library, |
||||||
|
() => NavigationManager.NavigateTo("404")); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void OnInitialized() |
||||||
|
{ |
||||||
|
_editContext = new EditContext(_model); |
||||||
|
_messageStore = new ValidationMessageStore(_editContext); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task HandleSubmitAsync() |
||||||
|
{ |
||||||
|
_messageStore.Clear(); |
||||||
|
if (_editContext.Validate()) |
||||||
|
{ |
||||||
|
var command = new CreateLocalLibraryPath(_library.Id, _model.Path); |
||||||
|
Either<BaseError, LocalLibraryPathViewModel> result = await Mediator.Send(command); |
||||||
|
await result.Match( |
||||||
|
Left: error => |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error saving local library path: {Error}", error.Value); |
||||||
|
return Task.CompletedTask; |
||||||
|
}, |
||||||
|
Right: async vm => |
||||||
|
{ |
||||||
|
if (Locker.LockLibrary(_library.Id)) |
||||||
|
{ |
||||||
|
await Channel.WriteAsync(new ForceScanLocalLibrary(_library.Id)); |
||||||
|
NavigationManager.NavigateTo("/media/libraries"); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
@ -1,99 +0,0 @@ |
|||||||
@* @page "/media/sources/local/add" *@ |
|
||||||
@* @using ErsatzTV.Application.MediaSources.Commands *@ |
|
||||||
@* @using ErsatzTV.Application.MediaSources *@ |
|
||||||
@* @inject NavigationManager NavigationManager *@ |
|
||||||
@* @inject ILogger<LocalMediaSourceEditor> Logger *@ |
|
||||||
@* @inject ISnackbar Snackbar *@ |
|
||||||
@* @inject IMediator Mediator *@ |
|
||||||
@* @inject IEntityLocker Locker *@ |
|
||||||
@* @inject ChannelWriter<IBackgroundServiceRequest> Channel *@ |
|
||||||
@* *@ |
|
||||||
@* <div style="max-width: 400px;"> *@ |
|
||||||
@* <MudText Typo="Typo.h4" Class="mb-4">Add Local Media Source</MudText> *@ |
|
||||||
@* *@ |
|
||||||
@* <EditForm EditContext="_editContext" OnSubmit="@HandleSubmitAsync"> *@ |
|
||||||
@* <FluentValidator/> *@ |
|
||||||
@* <MudCard> *@ |
|
||||||
@* <MudCardContent> *@ |
|
||||||
@* <MudSelect Class="mt-3" Label="Media Type" @bind-Value="_model.MediaType" For="@(() => _model.MediaType)"> *@ |
|
||||||
@* @foreach (MediaType mediaType in new[] { MediaType.TvShow, MediaType.Movie }) *@ |
|
||||||
@* { *@ |
|
||||||
@* <MudSelectItem Value="@mediaType">@mediaType</MudSelectItem> *@ |
|
||||||
@* } *@ |
|
||||||
@* </MudSelect> *@ |
|
||||||
@* $1$ TODO: replace this with a folder picker #1# *@ |
|
||||||
@* <MudTextField Label="Folder" @bind-Value="_model.Folder" For="@(() => _model.Folder)"/> *@ |
|
||||||
@* </MudCardContent> *@ |
|
||||||
@* <MudCardActions> *@ |
|
||||||
@* <MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto"> *@ |
|
||||||
@* Add Local Media Source *@ |
|
||||||
@* </MudButton> *@ |
|
||||||
@* </MudCardActions> *@ |
|
||||||
@* </MudCard> *@ |
|
||||||
@* </EditForm> *@ |
|
||||||
@* </div> *@ |
|
||||||
@* *@ |
|
||||||
@* @code { *@ |
|
||||||
@* *@ |
|
||||||
@* [Parameter] *@ |
|
||||||
@* public int Id { get; set; } *@ |
|
||||||
@* *@ |
|
||||||
@* private readonly LocalMediaSourceEditViewModel _model = new(); *@ |
|
||||||
@* private EditContext _editContext; *@ |
|
||||||
@* private ValidationMessageStore _messageStore; *@ |
|
||||||
@* *@ |
|
||||||
@* private LocalMediaSource _mediaSource; *@ |
|
||||||
@* *@ |
|
||||||
@* protected override Task OnParametersSetAsync() *@ |
|
||||||
@* { *@ |
|
||||||
@* _mediaSource = new LocalMediaSource *@ |
|
||||||
@* { *@ |
|
||||||
@* MediaType = MediaType.TvShow *@ |
|
||||||
@* }; *@ |
|
||||||
@* *@ |
|
||||||
@* _model.MediaType = _mediaSource.MediaType; *@ |
|
||||||
@* _model.Folder = _mediaSource.Folder; *@ |
|
||||||
@* *@ |
|
||||||
@* return Task.CompletedTask; *@ |
|
||||||
@* } *@ |
|
||||||
@* *@ |
|
||||||
@* protected override void OnInitialized() *@ |
|
||||||
@* { *@ |
|
||||||
@* _editContext = new EditContext(_model); *@ |
|
||||||
@* _messageStore = new ValidationMessageStore(_editContext); *@ |
|
||||||
@* } *@ |
|
||||||
@* *@ |
|
||||||
@* private async Task HandleSubmitAsync() *@ |
|
||||||
@* { *@ |
|
||||||
@* _messageStore.Clear(); *@ |
|
||||||
@* if (_editContext.Validate()) *@ |
|
||||||
@* { *@ |
|
||||||
@* var command = new CreateLocalMediaSource( *@ |
|
||||||
@* Convert.ToBase64String(Guid.NewGuid().ToByteArray()) *@ |
|
||||||
@* .TrimEnd('=') *@ |
|
||||||
@* .Replace("/", "_") *@ |
|
||||||
@* .Replace("+", "-"), *@ |
|
||||||
@* _model.MediaType, *@ |
|
||||||
@* _model.Folder); *@ |
|
||||||
@* Either<BaseError, MediaSourceViewModel> result = await Mediator.Send(command); *@ |
|
||||||
@* await result.Match( *@ |
|
||||||
@* Left: error => *@ |
|
||||||
@* { *@ |
|
||||||
@* Snackbar.Add(error.Value, Severity.Error); *@ |
|
||||||
@* Logger.LogError("Unexpected error saving collection: {Error}", error.Value); *@ |
|
||||||
@* return Task.CompletedTask; *@ |
|
||||||
@* }, *@ |
|
||||||
@* Right: async vm => *@ |
|
||||||
@* { *@ |
|
||||||
@* if (Locker.LockLibrary(vm.Id)) *@ |
|
||||||
@* { *@ |
|
||||||
@* await Channel.WriteAsync(new ForceScanLocalLibrary(vm.Id)); *@ |
|
||||||
@* NavigationManager.NavigateTo("/media/sources"); *@ |
|
||||||
@* } *@ |
|
||||||
@* }); *@ |
|
||||||
@* } *@ |
|
||||||
@* } *@ |
|
||||||
@* *@ |
|
||||||
@* *@ |
|
||||||
@* *@ |
|
||||||
@* } *@ |
|
||||||
@ -1,25 +1,25 @@ |
|||||||
@page "/media/sources" |
@* @page "/media/sources" *@ |
||||||
@page "/media/sources/{PanelIndex:int}" |
@* @page "/media/sources/{PanelIndex:int}" *@ |
||||||
@inject NavigationManager NavigationManager |
@* @inject NavigationManager NavigationManager *@ |
||||||
|
@* *@ |
||||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge"> |
@* <MudContainer MaxWidth="MaxWidth.ExtraLarge"> *@ |
||||||
<MudTabs @ref="_tabs" Elevation="1"> |
@* <MudTabs @ref="_tabs" Elevation="1"> *@ |
||||||
<MudTabPanel Text="Local" OnClick="@(() => NavigationManager.NavigateTo("/media/sources/0"))"> |
@* <MudTabPanel Text="Local" OnClick="@(() => NavigationManager.NavigateTo("/media/sources/0"))"> *@ |
||||||
<LocalMediaSources></LocalMediaSources> |
@* <LocalMediaSources></LocalMediaSources> *@ |
||||||
</MudTabPanel> |
@* </MudTabPanel> *@ |
||||||
<MudTabPanel Text="Plex" OnClick="@(() => NavigationManager.NavigateTo("/media/sources/1"))"> |
@* <MudTabPanel Text="Plex" OnClick="@(() => NavigationManager.NavigateTo("/media/sources/1"))"> *@ |
||||||
<PlexMediaSources></PlexMediaSources> |
@* <PlexMediaSources></PlexMediaSources> *@ |
||||||
</MudTabPanel> |
@* </MudTabPanel> *@ |
||||||
</MudTabs> |
@* </MudTabs> *@ |
||||||
</MudContainer> |
@* </MudContainer> *@ |
||||||
|
@* *@ |
||||||
@code { |
@* @code { *@ |
||||||
|
@* *@ |
||||||
[Parameter] |
@* [Parameter] *@ |
||||||
public int PanelIndex { get; set; } |
@* public int PanelIndex { get; set; } *@ |
||||||
|
@* *@ |
||||||
private MudTabs _tabs; |
@* private MudTabs _tabs; *@ |
||||||
|
@* *@ |
||||||
protected override void OnAfterRender(bool firstRender) => _tabs.ActivatePanel(PanelIndex); |
@* protected override void OnAfterRender(bool firstRender) => _tabs.ActivatePanel(PanelIndex); *@ |
||||||
|
@* *@ |
||||||
} |
@* } *@ |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
using System.IO; |
||||||
|
using ErsatzTV.ViewModels; |
||||||
|
using FluentValidation; |
||||||
|
|
||||||
|
namespace ErsatzTV.Validators |
||||||
|
{ |
||||||
|
public class LocalLibraryPathEditViewModelValidator : AbstractValidator<LocalLibraryPathEditViewModel> |
||||||
|
{ |
||||||
|
public LocalLibraryPathEditViewModelValidator() |
||||||
|
{ |
||||||
|
RuleFor(x => x.Path).NotEmpty(); |
||||||
|
RuleFor(x => x.Path).Must(Directory.Exists).WithMessage("Path must exist on filesystem"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -1,15 +0,0 @@ |
|||||||
using System.IO; |
|
||||||
using ErsatzTV.ViewModels; |
|
||||||
using FluentValidation; |
|
||||||
|
|
||||||
namespace ErsatzTV.Validators |
|
||||||
{ |
|
||||||
public class LocalMediaSourceEditViewModelValidator : AbstractValidator<LocalMediaSourceEditViewModel> |
|
||||||
{ |
|
||||||
public LocalMediaSourceEditViewModelValidator() |
|
||||||
{ |
|
||||||
RuleFor(x => x.Folder).NotEmpty(); |
|
||||||
RuleFor(x => x.Folder).Must(Directory.Exists).WithMessage("Folder must exist on filesystem"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,7 @@ |
|||||||
|
namespace ErsatzTV.ViewModels |
||||||
|
{ |
||||||
|
public class LocalLibraryPathEditViewModel |
||||||
|
{ |
||||||
|
public string Path { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue