mirror of https://github.com/ErsatzTV/ErsatzTV.git
193 changed files with 17038 additions and 1149 deletions
@ -1,4 +1,4 @@ |
|||||||
namespace ErsatzTV.Application.MediaItems |
namespace ErsatzTV.Application.MediaItems |
||||||
{ |
{ |
||||||
public record MediaItemViewModel(int Id, int MediaSourceId, string Path); |
public record MediaItemViewModel(int Id, int LibraryPathId, string Path); |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,114 @@ |
|||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Locking; |
||||||
|
using ErsatzTV.Core.Interfaces.Metadata; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
using Microsoft.Extensions.Logging; |
||||||
|
using Unit = LanguageExt.Unit; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.MediaSources.Commands |
||||||
|
{ |
||||||
|
public class ScanLocalLibraryHandler : IRequestHandler<ForceScanLocalLibrary, Either<BaseError, string>>, |
||||||
|
IRequestHandler<ScanLocalLibraryIfNeeded, Either<BaseError, string>> |
||||||
|
{ |
||||||
|
private readonly IConfigElementRepository _configElementRepository; |
||||||
|
private readonly IEntityLocker _entityLocker; |
||||||
|
private readonly ILibraryRepository _libraryRepository; |
||||||
|
private readonly ILogger<ScanLocalLibraryHandler> _logger; |
||||||
|
private readonly IMovieFolderScanner _movieFolderScanner; |
||||||
|
private readonly ITelevisionFolderScanner _televisionFolderScanner; |
||||||
|
|
||||||
|
public ScanLocalLibraryHandler( |
||||||
|
ILibraryRepository libraryRepository, |
||||||
|
IConfigElementRepository configElementRepository, |
||||||
|
IMovieFolderScanner movieFolderScanner, |
||||||
|
ITelevisionFolderScanner televisionFolderScanner, |
||||||
|
IEntityLocker entityLocker, |
||||||
|
ILogger<ScanLocalLibraryHandler> logger) |
||||||
|
{ |
||||||
|
_libraryRepository = libraryRepository; |
||||||
|
_configElementRepository = configElementRepository; |
||||||
|
_movieFolderScanner = movieFolderScanner; |
||||||
|
_televisionFolderScanner = televisionFolderScanner; |
||||||
|
_entityLocker = entityLocker; |
||||||
|
_logger = logger; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<Either<BaseError, string>> Handle( |
||||||
|
ForceScanLocalLibrary request, |
||||||
|
CancellationToken cancellationToken) => Handle(request); |
||||||
|
|
||||||
|
public Task<Either<BaseError, string>> Handle( |
||||||
|
ScanLocalLibraryIfNeeded request, |
||||||
|
CancellationToken cancellationToken) => Handle(request); |
||||||
|
|
||||||
|
private Task<Either<BaseError, string>> |
||||||
|
Handle(IScanLocalLibrary request) => |
||||||
|
Validate(request) |
||||||
|
.MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalLibrary.Name)) |
||||||
|
.Bind(v => v.ToEitherAsync()); |
||||||
|
|
||||||
|
private async Task<Unit> PerformScan(RequestParameters parameters) |
||||||
|
{ |
||||||
|
(LocalLibrary localLibrary, string ffprobePath, bool forceScan) = parameters; |
||||||
|
|
||||||
|
DateTimeOffset lastScan = localLibrary.LastScan ?? DateTime.MinValue; |
||||||
|
if (forceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) |
||||||
|
{ |
||||||
|
foreach (LibraryPath libraryPath in localLibrary.Paths) |
||||||
|
{ |
||||||
|
switch (localLibrary.MediaKind) |
||||||
|
{ |
||||||
|
case LibraryMediaKind.Movies: |
||||||
|
await _movieFolderScanner.ScanFolder(libraryPath, ffprobePath); |
||||||
|
break; |
||||||
|
case LibraryMediaKind.Shows: |
||||||
|
await _televisionFolderScanner.ScanFolder(libraryPath, ffprobePath); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
localLibrary.LastScan = DateTimeOffset.Now; |
||||||
|
await _libraryRepository.UpdateLastScan(localLibrary); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
_logger.LogDebug( |
||||||
|
"Skipping unforced scan of library {Name}", |
||||||
|
localLibrary.Name); |
||||||
|
} |
||||||
|
|
||||||
|
_entityLocker.UnlockLibrary(localLibrary.Id); |
||||||
|
return Unit.Default; |
||||||
|
} |
||||||
|
|
||||||
|
private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalLibrary request) => |
||||||
|
(await LocalLibraryMustExist(request), await ValidateFFprobePath()) |
||||||
|
.Apply( |
||||||
|
(library, ffprobePath) => new RequestParameters( |
||||||
|
library, |
||||||
|
ffprobePath, |
||||||
|
request.ForceScan)); |
||||||
|
|
||||||
|
private Task<Validation<BaseError, LocalLibrary>> LocalLibraryMustExist( |
||||||
|
IScanLocalLibrary request) => |
||||||
|
_libraryRepository.Get(request.LibraryId) |
||||||
|
.Map(maybeLibrary => maybeLibrary.Map(ms => ms as LocalLibrary)) |
||||||
|
.Map(v => v.ToValidation<BaseError>($"Local library {request.LibraryId} does not exist.")); |
||||||
|
|
||||||
|
private Task<Validation<BaseError, string>> ValidateFFprobePath() => |
||||||
|
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath) |
||||||
|
.FilterT(File.Exists) |
||||||
|
.Map( |
||||||
|
ffprobePath => |
||||||
|
ffprobePath.ToValidation<BaseError>("FFprobe path does not exist on the file system")); |
||||||
|
|
||||||
|
private record RequestParameters(LocalLibrary LocalLibrary, string FFprobePath, bool ForceScan); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,111 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.IO; |
|
||||||
using System.Threading; |
|
||||||
using System.Threading.Tasks; |
|
||||||
using ErsatzTV.Core; |
|
||||||
using ErsatzTV.Core.Domain; |
|
||||||
using ErsatzTV.Core.Interfaces.Locking; |
|
||||||
using ErsatzTV.Core.Interfaces.Metadata; |
|
||||||
using ErsatzTV.Core.Interfaces.Repositories; |
|
||||||
using LanguageExt; |
|
||||||
using MediatR; |
|
||||||
using Microsoft.Extensions.Logging; |
|
||||||
using Unit = LanguageExt.Unit; |
|
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaSources.Commands |
|
||||||
{ |
|
||||||
public class ScanLocalMediaSourceHandler : IRequestHandler<ForceScanLocalMediaSource, Either<BaseError, string>>, |
|
||||||
IRequestHandler<ScanLocalMediaSourceIfNeeded, Either<BaseError, string>> |
|
||||||
{ |
|
||||||
private readonly IConfigElementRepository _configElementRepository; |
|
||||||
private readonly IEntityLocker _entityLocker; |
|
||||||
private readonly ILogger<ScanLocalMediaSourceHandler> _logger; |
|
||||||
private readonly IMediaSourceRepository _mediaSourceRepository; |
|
||||||
private readonly IMovieFolderScanner _movieFolderScanner; |
|
||||||
private readonly ITelevisionFolderScanner _televisionFolderScanner; |
|
||||||
|
|
||||||
public ScanLocalMediaSourceHandler( |
|
||||||
IMediaSourceRepository mediaSourceRepository, |
|
||||||
IConfigElementRepository configElementRepository, |
|
||||||
IMovieFolderScanner movieFolderScanner, |
|
||||||
ITelevisionFolderScanner televisionFolderScanner, |
|
||||||
IEntityLocker entityLocker, |
|
||||||
ILogger<ScanLocalMediaSourceHandler> logger) |
|
||||||
{ |
|
||||||
_mediaSourceRepository = mediaSourceRepository; |
|
||||||
_configElementRepository = configElementRepository; |
|
||||||
_movieFolderScanner = movieFolderScanner; |
|
||||||
_televisionFolderScanner = televisionFolderScanner; |
|
||||||
_entityLocker = entityLocker; |
|
||||||
_logger = logger; |
|
||||||
} |
|
||||||
|
|
||||||
public Task<Either<BaseError, string>> Handle( |
|
||||||
ForceScanLocalMediaSource request, |
|
||||||
CancellationToken cancellationToken) => |
|
||||||
Handle((IScanLocalMediaSource) request, cancellationToken); |
|
||||||
|
|
||||||
public Task<Either<BaseError, string>> Handle( |
|
||||||
ScanLocalMediaSourceIfNeeded request, |
|
||||||
CancellationToken cancellationToken) => |
|
||||||
Handle((IScanLocalMediaSource) request, cancellationToken); |
|
||||||
|
|
||||||
private Task<Either<BaseError, string>> |
|
||||||
Handle(IScanLocalMediaSource request, CancellationToken cancellationToken) => |
|
||||||
Validate(request) |
|
||||||
.MapT(parameters => PerformScan(parameters).Map(_ => parameters.LocalMediaSource.Folder)) |
|
||||||
.Bind(v => v.ToEitherAsync()); |
|
||||||
|
|
||||||
private async Task<Unit> PerformScan(RequestParameters parameters) |
|
||||||
{ |
|
||||||
DateTimeOffset lastScan = parameters.LocalMediaSource.LastScan ?? DateTime.MinValue; |
|
||||||
if (parameters.ForceScan || lastScan < DateTimeOffset.Now - TimeSpan.FromHours(6)) |
|
||||||
{ |
|
||||||
switch (parameters.LocalMediaSource.MediaType) |
|
||||||
{ |
|
||||||
case MediaType.Movie: |
|
||||||
await _movieFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); |
|
||||||
break; |
|
||||||
case MediaType.TvShow: |
|
||||||
await _televisionFolderScanner.ScanFolder(parameters.LocalMediaSource, parameters.FFprobePath); |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
parameters.LocalMediaSource.LastScan = DateTimeOffset.Now; |
|
||||||
await _mediaSourceRepository.Update(parameters.LocalMediaSource); |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
_logger.LogDebug( |
|
||||||
"Skipping unforced scan of media source {Folder}", |
|
||||||
parameters.LocalMediaSource.Folder); |
|
||||||
} |
|
||||||
|
|
||||||
_entityLocker.UnlockMediaSource(parameters.LocalMediaSource.Id); |
|
||||||
return Unit.Default; |
|
||||||
} |
|
||||||
|
|
||||||
private async Task<Validation<BaseError, RequestParameters>> Validate(IScanLocalMediaSource request) => |
|
||||||
(await LocalMediaSourceMustExist(request), await ValidateFFprobePath()) |
|
||||||
.Apply( |
|
||||||
(localMediaSource, ffprobePath) => new RequestParameters( |
|
||||||
localMediaSource, |
|
||||||
ffprobePath, |
|
||||||
request.ForceScan)); |
|
||||||
|
|
||||||
private Task<Validation<BaseError, LocalMediaSource>> LocalMediaSourceMustExist( |
|
||||||
IScanLocalMediaSource request) => |
|
||||||
_mediaSourceRepository.Get(request.MediaSourceId) |
|
||||||
.Map(maybeMediaSource => maybeMediaSource.Map(ms => ms as LocalMediaSource)) |
|
||||||
.Map(v => v.ToValidation<BaseError>($"Local media source {request.MediaSourceId} does not exist.")); |
|
||||||
|
|
||||||
private Task<Validation<BaseError, string>> ValidateFFprobePath() => |
|
||||||
_configElementRepository.GetValue<string>(ConfigElementKey.FFprobePath) |
|
||||||
.FilterT(File.Exists) |
|
||||||
.Map( |
|
||||||
ffprobePath => |
|
||||||
ffprobePath.ToValidation<BaseError>("FFprobe path does not exist on the file system")); |
|
||||||
|
|
||||||
private record RequestParameters(LocalMediaSource LocalMediaSource, string FFprobePath, bool ForceScan); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,7 +1,5 @@ |
|||||||
using ErsatzTV.Core.Domain; |
namespace ErsatzTV.Application.MediaSources |
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaSources |
|
||||||
{ |
{ |
||||||
public record LocalMediaSourceViewModel(int Id, string Name, string Folder) |
public record LocalMediaSourceViewModel(int Id, string Name, string Folder) |
||||||
: MediaSourceViewModel(Id, Name, MediaSourceType.Local); |
: MediaSourceViewModel(Id, Name); |
||||||
} |
} |
||||||
|
|||||||
@ -1,6 +1,4 @@ |
|||||||
using ErsatzTV.Core.Domain; |
namespace ErsatzTV.Application.MediaSources |
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaSources |
|
||||||
{ |
{ |
||||||
public record MediaSourceViewModel(int Id, string Name, MediaSourceType SourceType); |
public record MediaSourceViewModel(int Id, string Name); |
||||||
} |
} |
||||||
|
|||||||
@ -1,9 +0,0 @@ |
|||||||
using ErsatzTV.Core.Domain; |
|
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaSources |
|
||||||
{ |
|
||||||
public record PlexMediaSourceViewModel(int Id, string Name, string Address) : MediaSourceViewModel( |
|
||||||
Id, |
|
||||||
Name, |
|
||||||
MediaSourceType.Plex); |
|
||||||
} |
|
||||||
@ -1,7 +1,7 @@ |
|||||||
using ErsatzTV.Core; |
using ErsatzTV.Core; |
||||||
using LanguageExt; |
using LanguageExt; |
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaSources.Commands |
namespace ErsatzTV.Application.Plex.Commands |
||||||
{ |
{ |
||||||
public record SynchronizePlexLibraries(int PlexMediaSourceId) : MediatR.IRequest<Either<BaseError, Unit>>; |
public record SynchronizePlexLibraries(int PlexMediaSourceId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||||
} |
} |
||||||
@ -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) |
||||||
|
{ |
||||||
|
DateTimeOffset lastScan = parameters.Library.LastScan ?? DateTime.MinValue; |
||||||
|
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 = DateTimeOffset.Now; |
||||||
|
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 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); |
||||||
|
} |
||||||
@ -1,7 +1,7 @@ |
|||||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||||
using MediatR; |
using MediatR; |
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaSources.Queries |
namespace ErsatzTV.Application.Plex.Queries |
||||||
{ |
{ |
||||||
public record GetAllPlexMediaSources : IRequest<List<PlexMediaSourceViewModel>>; |
public record GetAllPlexMediaSources : IRequest<List<PlexMediaSourceViewModel>>; |
||||||
} |
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class Collection |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public string Name { get; set; } |
||||||
|
public List<MediaItem> MediaItems { get; set; } |
||||||
|
public List<CollectionItem> CollectionItems { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class CollectionItem |
||||||
|
{ |
||||||
|
public int CollectionId { get; set; } |
||||||
|
public Collection Collection { get; set; } |
||||||
|
public int MediaItemId { get; set; } |
||||||
|
public MediaItem MediaItem { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public abstract class Library |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public string Name { get; set; } |
||||||
|
public LibraryMediaKind MediaKind { get; set; } |
||||||
|
public DateTimeOffset? LastScan { get; set; } |
||||||
|
|
||||||
|
public int MediaSourceId { get; set; } |
||||||
|
public MediaSource MediaSource { get; set; } |
||||||
|
|
||||||
|
public List<LibraryPath> Paths { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public enum LibraryMediaKind |
||||||
|
{ |
||||||
|
Movies = 1, |
||||||
|
Shows = 2 |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class LibraryPath |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public string Path { get; set; } |
||||||
|
|
||||||
|
public int LibraryId { get; set; } |
||||||
|
public Library Library { get; set; } |
||||||
|
|
||||||
|
public List<MediaItem> MediaItems { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class LocalLibrary : Library |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class PlexLibrary : Library |
||||||
|
{ |
||||||
|
public string Key { get; set; } |
||||||
|
public bool ShouldSyncItems { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -1,10 +0,0 @@ |
|||||||
namespace ErsatzTV.Core.Domain |
|
||||||
{ |
|
||||||
public class LocalMediaSource : MediaSource |
|
||||||
{ |
|
||||||
public LocalMediaSource() => SourceType = MediaSourceType.Local; |
|
||||||
|
|
||||||
public MediaType MediaType { get; set; } |
|
||||||
public string Folder { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,17 +0,0 @@ |
|||||||
using System; |
|
||||||
using ErsatzTV.Core.Interfaces.Domain; |
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Domain |
|
||||||
{ |
|
||||||
public class MediaItem : IHasAPoster |
|
||||||
{ |
|
||||||
public int Id { get; set; } |
|
||||||
public int MediaSourceId { get; set; } |
|
||||||
public MediaSource Source { get; set; } |
|
||||||
public MediaItemStatistics Statistics { get; set; } |
|
||||||
public DateTime? LastWriteTime { get; set; } |
|
||||||
public string Path { get; set; } |
|
||||||
public string Poster { get; set; } |
|
||||||
public DateTime? PosterLastWriteTime { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,13 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class Episode : MediaItem |
||||||
|
{ |
||||||
|
public int EpisodeNumber { get; set; } |
||||||
|
public int SeasonId { get; set; } |
||||||
|
public Season Season { get; set; } |
||||||
|
public List<EpisodeMetadata> EpisodeMetadata { get; set; } |
||||||
|
public List<MediaVersion> MediaVersions { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class MediaFile |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public string Path { get; set; } |
||||||
|
|
||||||
|
public int MediaVersionId { get; set; } |
||||||
|
public MediaVersion MediaVersion { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using ErsatzTV.Core.Interfaces.Domain; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class MediaItem : IHasAPoster |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public MediaItemStatistics Statistics { get; set; } |
||||||
|
public DateTime? LastWriteTime { get; set; } |
||||||
|
|
||||||
|
public int LibraryPathId { get; set; } |
||||||
|
public LibraryPath LibraryPath { get; set; } |
||||||
|
public string Path { get; set; } |
||||||
|
public string Poster { get; set; } |
||||||
|
public DateTime? PosterLastWriteTime { get; set; } |
||||||
|
|
||||||
|
// temporary fields to help migrations...
|
||||||
|
public int TelevisionShowId { get; set; } |
||||||
|
public int TelevisionSeasonId { get; set; } |
||||||
|
public int TelevisionEpisodeId { get; set; } |
||||||
|
|
||||||
|
public List<Collection> Collections { get; set; } |
||||||
|
public List<CollectionItem> CollectionItems { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class MediaVersion |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public string Name { get; set; } |
||||||
|
|
||||||
|
public List<MediaFile> MediaFiles { get; set; } |
||||||
|
|
||||||
|
public TimeSpan Duration { get; set; } |
||||||
|
public string SampleAspectRatio { get; set; } |
||||||
|
public string DisplayAspectRatio { get; set; } |
||||||
|
public string VideoCodec { get; set; } |
||||||
|
public string AudioCodec { get; set; } |
||||||
|
public bool IsInterlaced { get; set; } |
||||||
|
public int Width { get; set; } |
||||||
|
public int Height { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class Movie : MediaItem |
||||||
|
{ |
||||||
|
// TODO: remove MetadataId
|
||||||
|
public int MetadataId { get; set; } |
||||||
|
// TODO: remove Metadata
|
||||||
|
public MovieMetadata Metadata { get; set; } |
||||||
|
public List<NewMovieMetadata> MovieMetadata { get; set; } |
||||||
|
|
||||||
|
public List<MediaVersion> MediaVersions { get; set; } |
||||||
|
|
||||||
|
// TODO: remove SimpleMediaCollections
|
||||||
|
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class PlexMediaItemPart |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public int PlexId { get; set; } |
||||||
|
public string Key { get; set; } |
||||||
|
public int Duration { get; set; } |
||||||
|
public string File { get; set; } |
||||||
|
public int Size { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class PlexMovie : Movie |
||||||
|
{ |
||||||
|
public string Key { get; set; } |
||||||
|
public PlexMediaItemPart Part { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class Season : MediaItem |
||||||
|
{ |
||||||
|
public int SeasonNumber { get; set; } |
||||||
|
public int ShowId { get; set; } |
||||||
|
public Show Show { get; set; } |
||||||
|
|
||||||
|
public List<Episode> Episodes { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class Show : MediaItem |
||||||
|
{ |
||||||
|
public List<Season> Seasons { get; set; } |
||||||
|
public List<ShowMetadata> ShowMetadata { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -1,12 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Domain |
|
||||||
{ |
|
||||||
public abstract class MediaSource |
|
||||||
{ |
|
||||||
public int Id { get; set; } |
|
||||||
public MediaSourceType SourceType { get; set; } |
|
||||||
public string Name { get; set; } |
|
||||||
public DateTimeOffset? LastScan { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,6 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class LocalMediaSource : MediaSource |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public abstract class MediaSource |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
|
||||||
|
public List<Library> Libraries { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -1,9 +1,11 @@ |
|||||||
namespace ErsatzTV.Core.Domain |
namespace ErsatzTV.Core.Domain |
||||||
{ |
{ |
||||||
public class PlexMediaSourceConnection |
public class PlexConnection |
||||||
{ |
{ |
||||||
public int Id { get; set; } |
public int Id { get; set; } |
||||||
public bool IsActive { get; set; } |
public bool IsActive { get; set; } |
||||||
public string Uri { get; set; } |
public string Uri { get; set; } |
||||||
|
public int PlexMediaSourceId { get; set; } |
||||||
|
public PlexMediaSource PlexMediaSource { get; set; } |
||||||
} |
} |
||||||
} |
} |
||||||
@ -1,10 +0,0 @@ |
|||||||
namespace ErsatzTV.Core.Domain |
|
||||||
{ |
|
||||||
public enum MediaSourceType |
|
||||||
{ |
|
||||||
None = 0, |
|
||||||
Local = 1, |
|
||||||
Plex = 2, |
|
||||||
Jellyfin = 3 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,9 +0,0 @@ |
|||||||
namespace ErsatzTV.Core.Domain |
|
||||||
{ |
|
||||||
public enum MediaType |
|
||||||
{ |
|
||||||
Other = 0, |
|
||||||
TvShow = 1, |
|
||||||
Movie = 2 |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,11 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class EpisodeMetadata : Metadata |
||||||
|
{ |
||||||
|
public string Outline { get; set; } |
||||||
|
public string Plot { get; set; } |
||||||
|
public string Tagline { get; set; } |
||||||
|
public int EpisodeId { get; set; } |
||||||
|
public Episode Episode { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
using System; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class Metadata |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public MetadataKind MetadataKind { get; set; } |
||||||
|
public string Title { get; set; } |
||||||
|
public string OriginalTitle { get; set; } |
||||||
|
public string SortTitle { get; set; } |
||||||
|
public DateTimeOffset? ReleaseDate { get; set; } |
||||||
|
public DateTimeOffset DateAdded { get; set; } |
||||||
|
public DateTimeOffset DateUpdated { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public enum MetadataKind |
||||||
|
{ |
||||||
|
Fallback, |
||||||
|
Sidecar |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class NewMovieMetadata : Metadata |
||||||
|
{ |
||||||
|
public string Outline { get; set; } |
||||||
|
public string Plot { get; set; } |
||||||
|
public string Tagline { get; set; } |
||||||
|
public int MovieId { get; set; } |
||||||
|
public Movie Movie { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public class ShowMetadata : Metadata |
||||||
|
{ |
||||||
|
public string Outline { get; set; } |
||||||
|
public string Plot { get; set; } |
||||||
|
public string Tagline { get; set; } |
||||||
|
public int ShowId { get; set; } |
||||||
|
public Show Show { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -1,11 +0,0 @@ |
|||||||
using System.Collections.Generic; |
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Domain |
|
||||||
{ |
|
||||||
public class MovieMediaItem : MediaItem |
|
||||||
{ |
|
||||||
public int MetadataId { get; set; } |
|
||||||
public MovieMetadata Metadata { get; set; } |
|
||||||
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,10 +0,0 @@ |
|||||||
namespace ErsatzTV.Core.Domain |
|
||||||
{ |
|
||||||
public class PlexMediaSourceLibrary |
|
||||||
{ |
|
||||||
public int Id { get; set; } |
|
||||||
public string Key { get; init; } |
|
||||||
public string Name { get; init; } |
|
||||||
public MediaType MediaType { get; init; } |
|
||||||
} |
|
||||||
} |
|
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=domain_005Ccollection/@EntryIndexedValue">True</s:Boolean> |
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=domain_005Clibrary/@EntryIndexedValue">True</s:Boolean> |
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=domain_005Cmediaitem/@EntryIndexedValue">True</s:Boolean> |
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=domain_005Cmediasource/@EntryIndexedValue">True</s:Boolean> |
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=domain_005Cmediasources/@EntryIndexedValue">True</s:Boolean> |
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=domain_005Cmetadata/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Plex; |
||||||
|
using LanguageExt; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Interfaces.Plex |
||||||
|
{ |
||||||
|
public interface IPlexMovieLibraryScanner |
||||||
|
{ |
||||||
|
Task<Either<BaseError, Unit>> ScanLibrary( |
||||||
|
PlexConnection connection, |
||||||
|
PlexServerAuthToken token, |
||||||
|
PlexLibrary plexMediaSourceLibrary); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using LanguageExt; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Interfaces.Repositories |
||||||
|
{ |
||||||
|
public interface ILibraryRepository |
||||||
|
{ |
||||||
|
Task<Option<Library>> Get(int libraryId); |
||||||
|
Task<Unit> UpdateLastScan(Library library); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Plex |
||||||
|
{ |
||||||
|
public class PlexMediaEntry |
||||||
|
{ |
||||||
|
public int Id { get; set; } |
||||||
|
public int Duration { get; set; } |
||||||
|
public int Bitrate { get; set; } |
||||||
|
public int Width { get; set; } |
||||||
|
public int Height { get; set; } |
||||||
|
public double AspectRatio { get; set; } |
||||||
|
public int AudioChannels { get; set; } |
||||||
|
public string AudioCodec { get; set; } |
||||||
|
public string VideoCodec { get; set; } |
||||||
|
public string Container { get; set; } |
||||||
|
public string VideoFrameRate { get; set; } |
||||||
|
public List<PlexPartEntry> Part { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Plex |
||||||
|
{ |
||||||
|
public class PlexMetadataEntry |
||||||
|
{ |
||||||
|
public string Key { get; set; } |
||||||
|
public string Title { get; set; } |
||||||
|
public string Summary { get; set; } |
||||||
|
public int Year { get; set; } |
||||||
|
public string Tagline { get; set; } |
||||||
|
public string Thumb { get; set; } |
||||||
|
public string Art { get; set; } |
||||||
|
public string OriginallyAvailableAt { get; set; } |
||||||
|
public int AddedAt { get; set; } |
||||||
|
public int UpdatedAt { get; set; } |
||||||
|
public List<PlexMediaEntry> Media { get; set; } |
||||||
|
} |
||||||
|
} |
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue