Browse Source

find working plex connection on startup (#438)

pull/440/head
Jason Dove 5 years ago committed by GitHub
parent
commit
1a68dd040a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 89
      ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs
  3. 4
      ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs
  4. 1
      ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs
  5. 55
      ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs
  6. 6
      ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs
  7. 22
      ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

3
CHANGELOG.md

@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix double scheduling; this could happen if the app was shutdown during a playout build - Fix double scheduling; this could happen if the app was shutdown during a playout build
- Fix updating Jellyfin and Emby TV seasons - Fix updating Jellyfin and Emby TV seasons
### Changed
- Automatically find working Plex address on startup
## [0.1.4-alpha] - 2021-10-14 ## [0.1.4-alpha] - 2021-10-14
### Fixed ### Fixed
- Fix error message/offline stream continuity with channels that use HLS Segmenter - Fix error message/offline stream continuity with channels that use HLS Segmenter

89
ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs

@ -8,6 +8,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Plex;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Plex;
using LanguageExt; using LanguageExt;
using MediatR; using MediatR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -23,16 +24,22 @@ namespace ErsatzTV.Application.Plex.Commands
private readonly ILogger<SynchronizePlexMediaSourcesHandler> _logger; private readonly ILogger<SynchronizePlexMediaSourcesHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IPlexTvApiClient _plexTvApiClient; private readonly IPlexTvApiClient _plexTvApiClient;
private readonly IPlexServerApiClient _plexServerApiClient;
private readonly IPlexSecretStore _plexSecretStore;
public SynchronizePlexMediaSourcesHandler( public SynchronizePlexMediaSourcesHandler(
IMediaSourceRepository mediaSourceRepository, IMediaSourceRepository mediaSourceRepository,
IPlexTvApiClient plexTvApiClient, IPlexTvApiClient plexTvApiClient,
IPlexServerApiClient plexServerApiClient,
IPlexSecretStore plexSecretStore,
ChannelWriter<IPlexBackgroundServiceRequest> channel, ChannelWriter<IPlexBackgroundServiceRequest> channel,
IEntityLocker entityLocker, IEntityLocker entityLocker,
ILogger<SynchronizePlexMediaSourcesHandler> logger) ILogger<SynchronizePlexMediaSourcesHandler> logger)
{ {
_mediaSourceRepository = mediaSourceRepository; _mediaSourceRepository = mediaSourceRepository;
_plexTvApiClient = plexTvApiClient; _plexTvApiClient = plexTvApiClient;
_plexServerApiClient = plexServerApiClient;
_plexSecretStore = plexSecretStore;
_channel = channel; _channel = channel;
_entityLocker = entityLocker; _entityLocker = entityLocker;
_logger = logger; _logger = logger;
@ -69,32 +76,76 @@ namespace ErsatzTV.Application.Plex.Commands
return allExisting; return allExisting;
} }
private Task SynchronizeServer(List<PlexMediaSource> allExisting, PlexMediaSource server) private async Task SynchronizeServer(List<PlexMediaSource> allExisting, PlexMediaSource server)
{ {
Option<PlexMediaSource> maybeExisting = Option<PlexMediaSource> maybeExisting =
allExisting.Find(s => s.ClientIdentifier == server.ClientIdentifier); allExisting.Find(s => s.ClientIdentifier == server.ClientIdentifier);
return maybeExisting.Match(
existing => foreach (PlexMediaSource existing in maybeExisting)
{ {
existing.Platform = server.Platform; existing.Platform = server.Platform;
existing.PlatformVersion = server.PlatformVersion; existing.PlatformVersion = server.PlatformVersion;
existing.ProductVersion = server.ProductVersion; existing.ProductVersion = server.ProductVersion;
existing.ServerName = server.ServerName; existing.ServerName = server.ServerName;
var toAdd = server.Connections var toAdd = server.Connections
.Filter(connection => existing.Connections.All(c => c.Uri != connection.Uri)).ToList(); .Filter(connection => existing.Connections.All(c => c.Uri != connection.Uri)).ToList();
var toRemove = existing.Connections var toRemove = existing.Connections
.Filter(connection => server.Connections.All(c => c.Uri != connection.Uri)).ToList(); .Filter(connection => server.Connections.All(c => c.Uri != connection.Uri)).ToList();
return _mediaSourceRepository.Update(existing, server.Connections, toAdd, toRemove); await _mediaSourceRepository.Update(existing, toAdd, toRemove);
}, await FindConnectionToActivate(existing);
async () => }
if (maybeExisting.IsNone)
{
await _mediaSourceRepository.Add(server);
await FindConnectionToActivate(server);
}
}
private async Task FindConnectionToActivate(PlexMediaSource server)
{
var prioritized = server.Connections.OrderBy(pc => pc.IsActive ? 0 : 1).ToList();
foreach (PlexConnection connection in server.Connections)
{
connection.IsActive = false;
}
Option<PlexServerAuthToken> maybeToken = await _plexSecretStore.GetServerAuthToken(server.ClientIdentifier);
foreach (PlexServerAuthToken token in maybeToken)
{
foreach (PlexConnection connection in prioritized)
{ {
if (server.Connections.Any()) try
{
_logger.LogDebug("Attempting to locate to Plex at {Uri}", connection.Uri);
if (await _plexServerApiClient.Ping(connection, token))
{
_logger.LogInformation("Located Plex at {Uri}", connection.Uri);
connection.IsActive = true;
break;
}
}
catch
{ {
server.Connections.Head().IsActive = true; // do nothing
} }
}
}
if (maybeToken.IsNone)
{
_logger.LogError(
"Unable to activate Plex connection for server {Server} without auth token",
server.ServerName);
}
if (server.Connections.All(c => !c.IsActive))
{
_logger.LogError("Unable to locate Plex");
server.Connections.Head().IsActive = true;
}
await _mediaSourceRepository.Add(server); await _mediaSourceRepository.Update(server, new List<PlexConnection>(), new List<PlexConnection>());
});
} }
} }
} }

4
ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs

@ -9,6 +9,10 @@ namespace ErsatzTV.Core.Interfaces.Plex
{ {
public interface IPlexServerApiClient public interface IPlexServerApiClient
{ {
Task<bool> Ping(
PlexConnection connection,
PlexServerAuthToken token);
Task<Either<BaseError, List<PlexLibrary>>> GetLibraries( Task<Either<BaseError, List<PlexLibrary>>> GetLibraries(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token); PlexServerAuthToken token);

1
ErsatzTV.Core/Interfaces/Repositories/IMediaSourceRepository.cs

@ -18,7 +18,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task Update( Task Update(
PlexMediaSource plexMediaSource, PlexMediaSource plexMediaSource,
List<PlexConnection> prioritizedConnections,
List<PlexConnection> toAdd, List<PlexConnection> toAdd,
List<PlexConnection> toDelete); List<PlexConnection> toDelete);

55
ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs

@ -113,55 +113,30 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task Update( public async Task Update(
PlexMediaSource plexMediaSource, PlexMediaSource plexMediaSource,
List<PlexConnection> sortedConnections,
List<PlexConnection> toAdd, List<PlexConnection> toAdd,
List<PlexConnection> toDelete) List<PlexConnection> toDelete)
{ {
await _dbConnection.ExecuteAsync( await using TvContext dbContext = _dbContextFactory.CreateDbContext();
@"UPDATE PlexMediaSource SET
ProductVersion = @ProductVersion,
Platform = @Platform,
PlatformVersion = @PlatformVersion,
ServerName = @ServerName
WHERE Id = @Id",
new
{
plexMediaSource.ProductVersion,
plexMediaSource.Platform,
plexMediaSource.PlatformVersion,
plexMediaSource.ServerName,
plexMediaSource.Id
});
foreach (PlexConnection add in toAdd) dbContext.Entry(plexMediaSource).State = EntityState.Modified;
{
await _dbConnection.ExecuteAsync(
@"INSERT INTO PlexConnection (IsActive, Uri, PlexMediaSourceId)
VALUES (0, @Uri, @PlexMediaSourceId)",
new { add.Uri, PlexMediaSourceId = plexMediaSource.Id });
}
foreach (PlexConnection delete in toDelete) if (toAdd.Any() || toDelete.Any())
{ {
await _dbConnection.ExecuteAsync( plexMediaSource.Connections.Clear();
@"DELETE FROM PlexConnection WHERE Id = @Id", await dbContext.Entry(plexMediaSource).Collection(pms => pms.Connections).LoadAsync();
new { delete.Id });
}
int activeCount = await _dbConnection.QuerySingleAsync<int>( plexMediaSource.Connections.AddRange(toAdd);
@"SELECT COUNT(*) FROM PlexConnection WHERE IsActive = 1 AND PlexMediaSourceId = @PlexMediaSourceId", plexMediaSource.Connections.RemoveAll(toDelete.Contains);
new { PlexMediaSourceId = plexMediaSource.Id }); }
if (activeCount == 0) else
{ {
Option<PlexConnection> toActivate = foreach (PlexConnection connection in plexMediaSource.Connections)
sortedConnections.FirstOrDefault(c => toDelete.All(d => d.Id != c.Id)); {
dbContext.Entry(connection).State = EntityState.Modified;
// update on uri because connections from Plex API don't have our local ids }
await toActivate.IfSomeAsync(
async c => await _dbConnection.ExecuteAsync(
@"UPDATE PlexConnection SET IsActive = 1 WHERE Uri = @Uri",
new { c.Uri }));
} }
await dbContext.SaveChangesAsync();
} }
public async Task<List<int>> UpdateLibraries( public async Task<List<int>> UpdateLibraries(

6
ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs

@ -6,6 +6,12 @@ namespace ErsatzTV.Infrastructure.Plex
{ {
public interface IPlexServerApi public interface IPlexServerApi
{ {
[Get("/")]
[Headers("Accept: application/json")]
public Task Ping(
[Query] [AliasAs("X-Plex-Token")]
string token);
[Get("/library/sections")] [Get("/library/sections")]
[Headers("Accept: application/json")] [Headers("Accept: application/json")]
public Task<PlexMediaContainerResponse<PlexMediaContainerDirectoryContent<PlexLibraryResponse>>> GetLibraries( public Task<PlexMediaContainerResponse<PlexMediaContainerDirectoryContent<PlexLibraryResponse>>> GetLibraries(

22
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -30,6 +30,28 @@ namespace ErsatzTV.Infrastructure.Plex
_logger = logger; _logger = logger;
} }
public async Task<bool> Ping(
PlexConnection connection,
PlexServerAuthToken token)
{
try
{
IPlexServerApi service = RestService.For<IPlexServerApi>(
new HttpClient
{
BaseAddress = new Uri(connection.Uri),
Timeout = TimeSpan.FromSeconds(5)
});
await service.Ping(token.AuthToken);
return true;
}
catch (Exception)
{
return false;
}
}
public async Task<Either<BaseError, List<PlexLibrary>>> GetLibraries( public async Task<Either<BaseError, List<PlexLibrary>>> GetLibraries(
PlexConnection connection, PlexConnection connection,
PlexServerAuthToken token) PlexServerAuthToken token)

Loading…
Cancel
Save