Browse Source

find working plex connection on startup (#438)

pull/440/head
Jason Dove 4 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/). @@ -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 updating Jellyfin and Emby TV seasons
### Changed
- Automatically find working Plex address on startup
## [0.1.4-alpha] - 2021-10-14
### Fixed
- 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; @@ -8,6 +8,7 @@ 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;
@ -23,16 +24,22 @@ namespace ErsatzTV.Application.Plex.Commands @@ -23,16 +24,22 @@ namespace ErsatzTV.Application.Plex.Commands
private readonly ILogger<SynchronizePlexMediaSourcesHandler> _logger;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IPlexTvApiClient _plexTvApiClient;
private readonly IPlexServerApiClient _plexServerApiClient;
private readonly IPlexSecretStore _plexSecretStore;
public SynchronizePlexMediaSourcesHandler(
IMediaSourceRepository mediaSourceRepository,
IPlexTvApiClient plexTvApiClient,
IPlexServerApiClient plexServerApiClient,
IPlexSecretStore plexSecretStore,
ChannelWriter<IPlexBackgroundServiceRequest> channel,
IEntityLocker entityLocker,
ILogger<SynchronizePlexMediaSourcesHandler> logger)
{
_mediaSourceRepository = mediaSourceRepository;
_plexTvApiClient = plexTvApiClient;
_plexServerApiClient = plexServerApiClient;
_plexSecretStore = plexSecretStore;
_channel = channel;
_entityLocker = entityLocker;
_logger = logger;
@ -69,32 +76,76 @@ namespace ErsatzTV.Application.Plex.Commands @@ -69,32 +76,76 @@ namespace ErsatzTV.Application.Plex.Commands
return allExisting;
}
private Task SynchronizeServer(List<PlexMediaSource> allExisting, PlexMediaSource server)
private async Task SynchronizeServer(List<PlexMediaSource> allExisting, PlexMediaSource server)
{
Option<PlexMediaSource> maybeExisting =
allExisting.Find(s => s.ClientIdentifier == server.ClientIdentifier);
return maybeExisting.Match(
existing =>
{
existing.Platform = server.Platform;
existing.PlatformVersion = server.PlatformVersion;
existing.ProductVersion = server.ProductVersion;
existing.ServerName = server.ServerName;
var toAdd = server.Connections
.Filter(connection => existing.Connections.All(c => c.Uri != connection.Uri)).ToList();
var toRemove = existing.Connections
.Filter(connection => server.Connections.All(c => c.Uri != connection.Uri)).ToList();
return _mediaSourceRepository.Update(existing, server.Connections, toAdd, toRemove);
},
async () =>
foreach (PlexMediaSource existing in maybeExisting)
{
existing.Platform = server.Platform;
existing.PlatformVersion = server.PlatformVersion;
existing.ProductVersion = server.ProductVersion;
existing.ServerName = server.ServerName;
var toAdd = server.Connections
.Filter(connection => existing.Connections.All(c => c.Uri != connection.Uri)).ToList();
var toRemove = existing.Connections
.Filter(connection => server.Connections.All(c => c.Uri != connection.Uri)).ToList();
await _mediaSourceRepository.Update(existing, toAdd, toRemove);
await FindConnectionToActivate(existing);
}
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 @@ -9,6 +9,10 @@ namespace ErsatzTV.Core.Interfaces.Plex
{
public interface IPlexServerApiClient
{
Task<bool> Ping(
PlexConnection connection,
PlexServerAuthToken token);
Task<Either<BaseError, List<PlexLibrary>>> GetLibraries(
PlexConnection connection,
PlexServerAuthToken token);

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

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

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

@ -113,55 +113,30 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -113,55 +113,30 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task Update(
PlexMediaSource plexMediaSource,
List<PlexConnection> sortedConnections,
List<PlexConnection> toAdd,
List<PlexConnection> toDelete)
{
await _dbConnection.ExecuteAsync(
@"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
});
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
foreach (PlexConnection add in toAdd)
{
await _dbConnection.ExecuteAsync(
@"INSERT INTO PlexConnection (IsActive, Uri, PlexMediaSourceId)
VALUES (0, @Uri, @PlexMediaSourceId)",
new { add.Uri, PlexMediaSourceId = plexMediaSource.Id });
}
dbContext.Entry(plexMediaSource).State = EntityState.Modified;
foreach (PlexConnection delete in toDelete)
if (toAdd.Any() || toDelete.Any())
{
await _dbConnection.ExecuteAsync(
@"DELETE FROM PlexConnection WHERE Id = @Id",
new { delete.Id });
}
plexMediaSource.Connections.Clear();
await dbContext.Entry(plexMediaSource).Collection(pms => pms.Connections).LoadAsync();
int activeCount = await _dbConnection.QuerySingleAsync<int>(
@"SELECT COUNT(*) FROM PlexConnection WHERE IsActive = 1 AND PlexMediaSourceId = @PlexMediaSourceId",
new { PlexMediaSourceId = plexMediaSource.Id });
if (activeCount == 0)
plexMediaSource.Connections.AddRange(toAdd);
plexMediaSource.Connections.RemoveAll(toDelete.Contains);
}
else
{
Option<PlexConnection> toActivate =
sortedConnections.FirstOrDefault(c => toDelete.All(d => d.Id != c.Id));
// 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 }));
foreach (PlexConnection connection in plexMediaSource.Connections)
{
dbContext.Entry(connection).State = EntityState.Modified;
}
}
await dbContext.SaveChangesAsync();
}
public async Task<List<int>> UpdateLibraries(

6
ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs

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

22
ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs

@ -30,6 +30,28 @@ namespace ErsatzTV.Infrastructure.Plex @@ -30,6 +30,28 @@ namespace ErsatzTV.Infrastructure.Plex
_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(
PlexConnection connection,
PlexServerAuthToken token)

Loading…
Cancel
Save