Database redesign (#31)
* starting database redesign * set season and episode numbers * use datetimes in db (utc); update movie metadata * get movie cards from new table * copy show/episode metadata * remove old movie metadata type * rename new movie metadata type * code cleanup * start to remove old television classes * remove old television tables from database * fix playout building * fix collection views * fix show/season views * clean up movie metadata table * fix scanner tests * add libraries ui * code cleanup * fix movie scanning/metadata * add library scan button to ui * delete library path from ui * temp disable movie scanning * remove orphan media items and prevent duplicate paths * attach artwork to metadata * fix split show/season display * fix television artwork * store year distinct from release date * fix collections ui * code cleanup * add library paths from ui * fix adding to collections from ui * fix schedule items loading * schedule editing works again * remove some todos * more cleanup * fix unit tests * fix episode sorting * fix deleting show library paths * remove unused class * fix playout list in ui * fix log viewer * start to use version/file instead of statistics * clean up old columns * fix playout display (time zone) * fix playback * fix channel guide time zone * cascade more deletes * fix compiler warnings * fix adding new seasons * use artwork for channel logo * clean cache folder on startup (move channel logos, delete everything else) * log database migration * update homepage docs for libraries * fix adding new channel with logo * fix episode numbers in epg
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public record StartPlexPinFlow : IRequest<Either<BaseError, string>>;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public class StartPlexPinFlowHandler : IRequestHandler<StartPlexPinFlow, Either<BaseError, string>>
|
||||
{
|
||||
private readonly ChannelWriter<IPlexBackgroundServiceRequest> _channel;
|
||||
private readonly IPlexTvApiClient _plexTvApiClient;
|
||||
|
||||
public StartPlexPinFlowHandler(
|
||||
IPlexTvApiClient plexTvApiClient,
|
||||
ChannelWriter<IPlexBackgroundServiceRequest> channel)
|
||||
{
|
||||
_plexTvApiClient = plexTvApiClient;
|
||||
_channel = channel;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, string>> Handle(
|
||||
StartPlexPinFlow request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_plexTvApiClient.StartPinFlow().Bind(
|
||||
result => result.Match(
|
||||
Left: error => Task.FromResult(Left<BaseError, string>(error)),
|
||||
Right: async pin =>
|
||||
{
|
||||
await _channel.WriteAsync(new TryCompletePlexPinFlow(pin), cancellationToken);
|
||||
return Right<BaseError, string>(pin.Url);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public record SynchronizePlexLibraries(int PlexMediaSourceId) : MediatR.IRequest<Either<BaseError, Unit>>;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Plex;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public class
|
||||
SynchronizePlexLibrariesHandler : MediatR.IRequestHandler<SynchronizePlexLibraries, Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly ILogger<SynchronizePlexLibrariesHandler> _logger;
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
private readonly IPlexSecretStore _plexSecretStore;
|
||||
private readonly IPlexServerApiClient _plexServerApiClient;
|
||||
|
||||
public SynchronizePlexLibrariesHandler(
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IPlexSecretStore plexSecretStore,
|
||||
IPlexServerApiClient plexServerApiClient,
|
||||
ILogger<SynchronizePlexLibrariesHandler> logger)
|
||||
{
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
_plexSecretStore = plexSecretStore;
|
||||
_plexServerApiClient = plexServerApiClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, Unit>> Handle(
|
||||
SynchronizePlexLibraries request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Validate(request)
|
||||
.MapT(SynchronizeLibraries)
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private Task<Validation<BaseError, ConnectionParameters>> Validate(SynchronizePlexLibraries request) =>
|
||||
MediaSourceMustExist(request)
|
||||
.BindT(MediaSourceMustHaveActiveConnection)
|
||||
.BindT(MediaSourceMustHaveToken);
|
||||
|
||||
private Task<Validation<BaseError, PlexMediaSource>> MediaSourceMustExist(SynchronizePlexLibraries request) =>
|
||||
_mediaSourceRepository.GetPlex(request.PlexMediaSourceId)
|
||||
.Map(o => o.ToValidation<BaseError>("Plex media source does not exist."));
|
||||
|
||||
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
||||
PlexMediaSource plexMediaSource)
|
||||
{
|
||||
Option<PlexConnection> maybeConnection =
|
||||
plexMediaSource.Connections.SingleOrDefault(c => c.IsActive);
|
||||
return maybeConnection.Map(connection => new ConnectionParameters(plexMediaSource, connection))
|
||||
.ToValidation<BaseError>("Plex media source requires an active connection");
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveToken(
|
||||
ConnectionParameters connectionParameters)
|
||||
{
|
||||
Option<PlexServerAuthToken> maybeToken = await
|
||||
_plexSecretStore.GetServerAuthToken(connectionParameters.PlexMediaSource.ClientIdentifier);
|
||||
return maybeToken.Map(token => connectionParameters with { PlexServerAuthToken = token })
|
||||
.ToValidation<BaseError>("Plex media source requires a token");
|
||||
}
|
||||
|
||||
private async Task<Unit> SynchronizeLibraries(ConnectionParameters connectionParameters)
|
||||
{
|
||||
Either<BaseError, List<PlexLibrary>> maybeLibraries = await _plexServerApiClient.GetLibraries(
|
||||
connectionParameters.ActiveConnection,
|
||||
connectionParameters.PlexServerAuthToken);
|
||||
|
||||
await maybeLibraries.Match(
|
||||
libraries =>
|
||||
{
|
||||
var existing = connectionParameters.PlexMediaSource.Libraries.OfType<PlexLibrary>().ToList();
|
||||
var toAdd = libraries.Filter(library => existing.All(l => l.Key != library.Key)).ToList();
|
||||
var toRemove = existing.Filter(library => libraries.All(l => l.Key != library.Key)).ToList();
|
||||
existing.AddRange(toAdd);
|
||||
toRemove.ForEach(c => existing.Remove(c));
|
||||
|
||||
return _mediaSourceRepository.Update(connectionParameters.PlexMediaSource);
|
||||
},
|
||||
error =>
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Unable to synchronize libraries from plex server {PlexServer}: {Error}",
|
||||
connectionParameters.PlexMediaSource.ServerName,
|
||||
error.Value);
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private record ConnectionParameters(
|
||||
PlexMediaSource PlexMediaSource,
|
||||
PlexConnection ActiveConnection)
|
||||
{
|
||||
public PlexServerAuthToken PlexServerAuthToken { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public interface ISynchronizePlexLibraryById : IRequest<Either<BaseError, string>>, IBackgroundServiceRequest
|
||||
{
|
||||
int PlexMediaSourceId { get; }
|
||||
int PlexLibraryId { get; }
|
||||
bool ForceScan { get; }
|
||||
}
|
||||
|
||||
public record SynchronizePlexLibraryByIdIfNeeded
|
||||
(int PlexMediaSourceId, int PlexLibraryId) : ISynchronizePlexLibraryById
|
||||
{
|
||||
public bool ForceScan => false;
|
||||
}
|
||||
|
||||
public record ForceSynchronizePlexLibraryById
|
||||
(int PlexMediaSourceId, int PlexLibraryId) : ISynchronizePlexLibraryById
|
||||
{
|
||||
public bool ForceScan => true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Locking;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Plex;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Unit = LanguageExt.Unit;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public class
|
||||
SynchronizePlexLibraryByIdHandler : IRequestHandler<ForceSynchronizePlexLibraryById, Either<BaseError, string>>,
|
||||
IRequestHandler<SynchronizePlexLibraryByIdIfNeeded, Either<BaseError, string>>
|
||||
{
|
||||
private readonly IEntityLocker _entityLocker;
|
||||
private readonly ILogger<SynchronizePlexLibraryByIdHandler> _logger;
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
private readonly IPlexMovieLibraryScanner _plexMovieLibraryScanner;
|
||||
private readonly IPlexSecretStore _plexSecretStore;
|
||||
|
||||
public SynchronizePlexLibraryByIdHandler(
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IPlexSecretStore plexSecretStore,
|
||||
IPlexMovieLibraryScanner plexMovieLibraryScanner,
|
||||
IEntityLocker entityLocker,
|
||||
ILogger<SynchronizePlexLibraryByIdHandler> logger)
|
||||
{
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
_plexSecretStore = plexSecretStore;
|
||||
_plexMovieLibraryScanner = plexMovieLibraryScanner;
|
||||
_entityLocker = entityLocker;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, string>> Handle(
|
||||
ForceSynchronizePlexLibraryById request,
|
||||
CancellationToken cancellationToken) => Handle(request);
|
||||
|
||||
public Task<Either<BaseError, string>> Handle(
|
||||
SynchronizePlexLibraryByIdIfNeeded request,
|
||||
CancellationToken cancellationToken) => Handle(request);
|
||||
|
||||
private Task<Either<BaseError, string>>
|
||||
Handle(ISynchronizePlexLibraryById request) =>
|
||||
Validate(request)
|
||||
.MapT(parameters => Synchronize(parameters).Map(_ => parameters.Library.Name))
|
||||
.Bind(v => v.ToEitherAsync());
|
||||
|
||||
private async Task<Unit> Synchronize(RequestParameters parameters)
|
||||
{
|
||||
var lastScan = new DateTimeOffset(parameters.Library.LastScan ?? DateTime.MinValue, TimeSpan.Zero);
|
||||
if (parameters.ForceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6))
|
||||
{
|
||||
switch (parameters.Library.MediaKind)
|
||||
{
|
||||
case LibraryMediaKind.Movies:
|
||||
await _plexMovieLibraryScanner.ScanLibrary(
|
||||
parameters.ConnectionParameters.ActiveConnection,
|
||||
parameters.ConnectionParameters.PlexServerAuthToken,
|
||||
parameters.Library);
|
||||
break;
|
||||
case LibraryMediaKind.Shows:
|
||||
// TODO: plex tv scanner
|
||||
// await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath);
|
||||
break;
|
||||
}
|
||||
|
||||
parameters.Library.LastScan = DateTime.UtcNow;
|
||||
await _mediaSourceRepository.Update(parameters.Library);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Skipping unforced scan of plex media library {Name}",
|
||||
parameters.Library.Name);
|
||||
}
|
||||
|
||||
// _entityLocker.UnlockMediaSource(parameters.MediaSource.Id);
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, RequestParameters>> Validate(ISynchronizePlexLibraryById request) =>
|
||||
(await ValidateConnection(request), await PlexLibraryMustExist(request))
|
||||
.Apply(
|
||||
(connectionParameters, plexLibrary) => new RequestParameters(
|
||||
connectionParameters,
|
||||
plexLibrary,
|
||||
request.ForceScan
|
||||
));
|
||||
|
||||
private Task<Validation<BaseError, ConnectionParameters>> ValidateConnection(
|
||||
ISynchronizePlexLibraryById request) =>
|
||||
PlexMediaSourceMustExist(request)
|
||||
.BindT(MediaSourceMustHaveActiveConnection)
|
||||
.BindT(MediaSourceMustHaveToken);
|
||||
|
||||
private Task<Validation<BaseError, PlexMediaSource>> PlexMediaSourceMustExist(
|
||||
ISynchronizePlexLibraryById request) =>
|
||||
_mediaSourceRepository.GetPlex(request.PlexMediaSourceId)
|
||||
.Map(v => v.ToValidation<BaseError>($"Plex media source {request.PlexMediaSourceId} does not exist."));
|
||||
|
||||
private Validation<BaseError, ConnectionParameters> MediaSourceMustHaveActiveConnection(
|
||||
PlexMediaSource plexMediaSource)
|
||||
{
|
||||
Option<PlexConnection> maybeConnection =
|
||||
plexMediaSource.Connections.SingleOrDefault(c => c.IsActive);
|
||||
return maybeConnection.Map(connection => new ConnectionParameters(plexMediaSource, connection))
|
||||
.ToValidation<BaseError>("Plex media source requires an active connection");
|
||||
}
|
||||
|
||||
private async Task<Validation<BaseError, ConnectionParameters>> MediaSourceMustHaveToken(
|
||||
ConnectionParameters connectionParameters)
|
||||
{
|
||||
Option<PlexServerAuthToken> maybeToken = await
|
||||
_plexSecretStore.GetServerAuthToken(connectionParameters.PlexMediaSource.ClientIdentifier);
|
||||
return maybeToken.Map(token => connectionParameters with { PlexServerAuthToken = token })
|
||||
.ToValidation<BaseError>("Plex media source requires a token");
|
||||
}
|
||||
|
||||
private Task<Validation<BaseError, PlexLibrary>> PlexLibraryMustExist(
|
||||
ISynchronizePlexLibraryById request) =>
|
||||
_mediaSourceRepository.GetPlexLibrary(request.PlexLibraryId)
|
||||
.Map(v => v.ToValidation<BaseError>($"Plex library {request.PlexLibraryId} does not exist."));
|
||||
|
||||
private record RequestParameters(
|
||||
ConnectionParameters ConnectionParameters,
|
||||
PlexLibrary Library,
|
||||
bool ForceScan);
|
||||
|
||||
private record ConnectionParameters(
|
||||
PlexMediaSource PlexMediaSource,
|
||||
PlexConnection ActiveConnection)
|
||||
{
|
||||
public PlexServerAuthToken PlexServerAuthToken { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public record
|
||||
SynchronizePlexMediaSources : IRequest<Either<BaseError, List<PlexMediaSource>>>, IPlexBackgroundServiceRequest;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public class
|
||||
SynchronizePlexMediaSourcesHandler : IRequestHandler<SynchronizePlexMediaSources,
|
||||
Either<BaseError, List<PlexMediaSource>>>
|
||||
{
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
private readonly IPlexTvApiClient _plexTvApiClient;
|
||||
|
||||
public SynchronizePlexMediaSourcesHandler(
|
||||
IMediaSourceRepository mediaSourceRepository,
|
||||
IPlexTvApiClient plexTvApiClient)
|
||||
{
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
_plexTvApiClient = plexTvApiClient;
|
||||
}
|
||||
|
||||
public Task<Either<BaseError, List<PlexMediaSource>>> Handle(
|
||||
SynchronizePlexMediaSources request,
|
||||
CancellationToken cancellationToken) => _plexTvApiClient.GetServers().BindAsync(SynchronizeAllServers);
|
||||
|
||||
private async Task<Either<BaseError, List<PlexMediaSource>>> SynchronizeAllServers(
|
||||
List<PlexMediaSource> servers)
|
||||
{
|
||||
List<PlexMediaSource> allExisting = await _mediaSourceRepository.GetAllPlex();
|
||||
foreach (PlexMediaSource server in servers)
|
||||
{
|
||||
await SynchronizeServer(allExisting, server);
|
||||
}
|
||||
|
||||
return allExisting;
|
||||
}
|
||||
|
||||
private async Task SynchronizeServer(List<PlexMediaSource> allExisting, PlexMediaSource server)
|
||||
{
|
||||
Option<PlexMediaSource> maybeExisting =
|
||||
allExisting.Find(s => s.ClientIdentifier == server.ClientIdentifier);
|
||||
await maybeExisting.Match(
|
||||
existing =>
|
||||
{
|
||||
existing.ProductVersion = server.ProductVersion;
|
||||
existing.ServerName = server.ServerName;
|
||||
MergeConnections(existing.Connections, server.Connections);
|
||||
if (existing.Connections.Any() && existing.Connections.All(c => !c.IsActive))
|
||||
{
|
||||
existing.Connections.Head().IsActive = true;
|
||||
}
|
||||
|
||||
return _mediaSourceRepository.Update(existing);
|
||||
},
|
||||
async () =>
|
||||
{
|
||||
await _mediaSourceRepository.Add(server);
|
||||
if (server.Connections.Any())
|
||||
{
|
||||
server.Connections.Head().IsActive = true;
|
||||
}
|
||||
|
||||
await _mediaSourceRepository.Update(server);
|
||||
});
|
||||
}
|
||||
|
||||
private void MergeConnections(
|
||||
List<PlexConnection> existing,
|
||||
List<PlexConnection> incoming)
|
||||
{
|
||||
var toAdd = incoming.Filter(connection => existing.All(c => c.Uri != connection.Uri)).ToList();
|
||||
var toRemove = existing.Filter(connection => incoming.All(c => c.Uri != connection.Uri)).ToList();
|
||||
existing.AddRange(toAdd);
|
||||
toRemove.ForEach(c => existing.Remove(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Plex;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public record TryCompletePlexPinFlow(PlexAuthPin AuthPin) : IRequest<Either<BaseError, bool>>,
|
||||
IPlexBackgroundServiceRequest;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public class TryCompletePlexPinFlowHandler : IRequestHandler<TryCompletePlexPinFlow, Either<BaseError, bool>>
|
||||
{
|
||||
private readonly ChannelWriter<IPlexBackgroundServiceRequest> _channel;
|
||||
private readonly IPlexTvApiClient _plexTvApiClient;
|
||||
|
||||
public TryCompletePlexPinFlowHandler(
|
||||
IPlexTvApiClient plexTvApiClient,
|
||||
ChannelWriter<IPlexBackgroundServiceRequest> channel)
|
||||
{
|
||||
_plexTvApiClient = plexTvApiClient;
|
||||
_channel = channel;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, bool>>
|
||||
Handle(TryCompletePlexPinFlow request, CancellationToken cancellationToken)
|
||||
{
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
|
||||
CancellationToken token = cts.Token;
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
bool result = await _plexTvApiClient.TryCompletePinFlow(request.AuthPin);
|
||||
if (result)
|
||||
{
|
||||
await _channel.WriteAsync(new SynchronizePlexMediaSources(), cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), token);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using ErsatzTV.Core;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public record UpdatePlexLibraryPreferences
|
||||
(List<PlexLibraryPreference> Preferences) : MediatR.IRequest<Either<BaseError, Unit>>;
|
||||
|
||||
public record PlexLibraryPreference(int Id, bool ShouldSyncItems);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Commands
|
||||
{
|
||||
public class
|
||||
UpdatePlexLibraryPreferencesHandler : MediatR.IRequestHandler<UpdatePlexLibraryPreferences,
|
||||
Either<BaseError, Unit>>
|
||||
{
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
|
||||
public UpdatePlexLibraryPreferencesHandler(IMediaSourceRepository mediaSourceRepository) =>
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle(
|
||||
UpdatePlexLibraryPreferences request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<int> toDisable = request.Preferences.Filter(p => p.ShouldSyncItems == false).Map(p => p.Id);
|
||||
await _mediaSourceRepository.DisablePlexLibrarySync(toDisable);
|
||||
|
||||
IEnumerable<int> toEnable = request.Preferences.Filter(p => p.ShouldSyncItems).Map(p => p.Id);
|
||||
await _mediaSourceRepository.EnablePlexLibrarySync(toEnable);
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Linq;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Application.Plex
|
||||
{
|
||||
internal static class Mapper
|
||||
{
|
||||
internal static PlexMediaSourceViewModel ProjectToViewModel(PlexMediaSource plexMediaSource) =>
|
||||
new(
|
||||
plexMediaSource.Id,
|
||||
plexMediaSource.ServerName,
|
||||
Optional(plexMediaSource.Connections.SingleOrDefault(c => c.IsActive)).Match(c => c.Uri, string.Empty));
|
||||
|
||||
internal static PlexLibraryViewModel ProjectToViewModel(PlexLibrary library) =>
|
||||
new(library.Id, library.Name, library.MediaKind, library.ShouldSyncItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Application.Plex
|
||||
{
|
||||
public record PlexLibraryViewModel(int Id, string Name, LibraryMediaKind MediaKind, bool ShouldSyncItems);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Application.MediaSources;
|
||||
|
||||
namespace ErsatzTV.Application.Plex
|
||||
{
|
||||
public record PlexMediaSourceViewModel(int Id, string Name, string Address) : MediaSourceViewModel(Id, Name);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Queries
|
||||
{
|
||||
public record GetAllPlexMediaSources : IRequest<List<PlexMediaSourceViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Plex.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Queries
|
||||
{
|
||||
public class GetAllPlexMediaSourcesHandler : IRequestHandler<GetAllPlexMediaSources, List<PlexMediaSourceViewModel>>
|
||||
{
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
|
||||
public GetAllPlexMediaSourcesHandler(IMediaSourceRepository mediaSourceRepository) =>
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
|
||||
public Task<List<PlexMediaSourceViewModel>> Handle(
|
||||
GetAllPlexMediaSources request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_mediaSourceRepository.GetAllPlex().Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Queries
|
||||
{
|
||||
public record GetPlexLibrariesBySourceId(int PlexMediaSourceId) : IRequest<List<PlexLibraryViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Plex.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Queries
|
||||
{
|
||||
public class
|
||||
GetPlexLibrariesBySourceIdHandler : IRequestHandler<GetPlexLibrariesBySourceId,
|
||||
List<PlexLibraryViewModel>>
|
||||
{
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
|
||||
public GetPlexLibrariesBySourceIdHandler(IMediaSourceRepository mediaSourceRepository) =>
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
|
||||
public Task<List<PlexLibraryViewModel>> Handle(
|
||||
GetPlexLibrariesBySourceId request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_mediaSourceRepository.GetPlexLibraries(request.PlexMediaSourceId)
|
||||
.Map(list => list.Map(ProjectToViewModel).ToList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Queries
|
||||
{
|
||||
public record GetPlexMediaSourceById(int PlexMediaSourceId) : IRequest<Option<PlexMediaSourceViewModel>>;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using LanguageExt;
|
||||
using MediatR;
|
||||
using static ErsatzTV.Application.Plex.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.Plex.Queries
|
||||
{
|
||||
public class
|
||||
GetPlexMediaSourceByIdHandler : IRequestHandler<GetPlexMediaSourceById, Option<PlexMediaSourceViewModel>>
|
||||
{
|
||||
private readonly IMediaSourceRepository _mediaSourceRepository;
|
||||
|
||||
public GetPlexMediaSourceByIdHandler(IMediaSourceRepository mediaSourceRepository) =>
|
||||
_mediaSourceRepository = mediaSourceRepository;
|
||||
|
||||
public Task<Option<PlexMediaSourceViewModel>> Handle(
|
||||
GetPlexMediaSourceById request,
|
||||
CancellationToken cancellationToken) =>
|
||||
_mediaSourceRepository.GetPlex(request.PlexMediaSourceId).MapT(ProjectToViewModel);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user