#865 Added support for multiple plex servers

This commit is contained in:
Jamie.Rees 2017-05-15 15:30:12 +01:00
parent a278917dcd
commit 3879fc04de
11 changed files with 270 additions and 177 deletions

View file

@ -73,7 +73,8 @@ namespace Ombi.Schedule.Jobs
StartTheCache(plexSettings).Wait();
}
catch (Exception e) {
catch (Exception e)
{
Logger.LogWarning(LoggingEvents.CacherException, e, "Exception thrown when attempting to cache the Plex Content");
}
}
@ -119,87 +120,92 @@ namespace Ombi.Schedule.Jobs
private async Task StartTheCache(PlexSettings plexSettings)
{
var allContent = GetAllContent(plexSettings);
// Let's now process this.
var contentToAdd = new List<PlexContent>();
foreach (var content in allContent)
foreach (var servers in plexSettings.Servers ?? new List<PlexServers>())
{
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
var allContent = GetAllContent(servers);
// Let's now process this.
var contentToAdd = new List<PlexContent>();
foreach (var content in allContent)
{
// Process Shows
foreach (var metadata in content.Metadata)
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
var seasonList = await PlexApi.GetSeasons(plexSettings.PlexAuthToken, plexSettings.FullUri,
metadata.ratingKey);
var seasonsContent = new List<SeasonsContent>();
foreach (var season in seasonList.MediaContainer.Metadata)
// Process Shows
foreach (var metadata in content.Metadata)
{
seasonsContent.Add(new SeasonsContent
var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri,
metadata.ratingKey);
var seasonsContent = new List<SeasonsContent>();
foreach (var season in seasonList.MediaContainer.Metadata)
{
ParentKey = int.Parse(season.parentRatingKey),
SeasonKey = int.Parse(season.ratingKey),
SeasonNumber = season.index
});
}
// Do we already have this item?
var existingContent = await Repo.GetByKey(metadata.key);
if (existingContent != null)
{
// Ok so we have it, let's check if there are any new seasons
var seasonDifference = seasonsContent.Except(existingContent.Seasons).ToList();
if (seasonDifference.Any())
{
// We have new seasons on Plex, let's add them back into the entity
existingContent.Seasons.AddRange(seasonDifference);
await Repo.Update(existingContent);
continue;
seasonsContent.Add(new SeasonsContent
{
ParentKey = int.Parse(season.parentRatingKey),
SeasonKey = int.Parse(season.ratingKey),
SeasonNumber = season.index
});
}
else
// Do we already have this item?
var existingContent = await Repo.GetByKey(metadata.key);
if (existingContent != null)
{
// No changes, no need to do anything
continue;
// Ok so we have it, let's check if there are any new seasons
var seasonDifference = seasonsContent.Except(existingContent.Seasons).ToList();
if (seasonDifference.Any())
{
// We have new seasons on Plex, let's add them back into the entity
existingContent.Seasons.AddRange(seasonDifference);
await Repo.Update(existingContent);
continue;
}
else
{
// No changes, no need to do anything
continue;
}
}
// Get the show metadata... This sucks since the `metadata` var contains all information about the show
// But it does not contain the `guid` property that we need to pull out thetvdb id...
var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
metadata.ratingKey);
var item = new PlexContent
{
AddedAt = DateTime.Now,
Key = metadata.ratingKey,
ProviderId = PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata
.FirstOrDefault()
.guid),
ReleaseYear = metadata.year.ToString(),
Type = PlexMediaTypeEntity.Show,
Title = metadata.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, metadata.ratingKey),
Seasons = new List<SeasonsContent>()
};
item.Seasons.AddRange(seasonsContent);
contentToAdd.Add(item);
}
// Get the show metadata... This sucks since the `metadata` var contains all information about the show
// But it does not contain the `guid` property that we need to pull out thetvdb id...
var showMetadata = await PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri,
metadata.ratingKey);
var item = new PlexContent
{
AddedAt = DateTime.Now,
Key = metadata.ratingKey,
ProviderId = PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata
.FirstOrDefault()
.guid),
ReleaseYear = metadata.year.ToString(),
Type = PlexMediaTypeEntity.Show,
Title = metadata.title,
Url = PlexHelper.GetPlexMediaUrl(plexSettings.MachineIdentifier, metadata.ratingKey),
Seasons = new List<SeasonsContent>()
};
item.Seasons.AddRange(seasonsContent);
contentToAdd.Add(item);
}
}
}
if (contentToAdd.Any())
{
await Repo.AddRange(contentToAdd);
if (contentToAdd.Any())
{
await Repo.AddRange(contentToAdd);
}
}
}
private List<Mediacontainer> GetAllContent(PlexSettings plexSettings)
private List<Mediacontainer> GetAllContent(PlexServers plexSettings)
{
var sections = PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri).Result;
var libs = new List<Mediacontainer>();
if (sections != null)
{
@ -232,9 +238,12 @@ namespace Ombi.Schedule.Jobs
{
if (plex.Enable)
{
if (string.IsNullOrEmpty(plex?.Ip) || string.IsNullOrEmpty(plex?.PlexAuthToken))
foreach (var server in plex.Servers ?? new List<PlexServers>())
{
return false;
if (string.IsNullOrEmpty(server?.Ip) || string.IsNullOrEmpty(server?.PlexAuthToken))
{
return false;
}
}
}
return plex.Enable;

View file

@ -2,18 +2,23 @@
namespace Ombi.Core.Settings.Models.External
{
public sealed class PlexSettings : ExternalSettings
public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings
{
public bool Enable { get; set; }
public List<PlexServers> Servers { get; set; }
}
public class PlexServers : ExternalSettings
{
public string Name { get; set; }
public bool EnableEpisodeSearching { get; set; }
public string PlexAuthToken { get; set; }
public string MachineIdentifier { get; set; }
public List<PlexSelectedLibraries> PlexSelectedLibraries { get; set; }
public List<PlexSelectedLibraries> PlexSelectedLibraries { get; set; }
}
public class PlexSelectedLibraries
{
public int Key { get; set; }

View file

@ -1,4 +1,6 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -28,29 +30,42 @@ namespace Ombi.Controllers.External
{
// Do we already have settings?
var settings = await PlexSettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(settings?.PlexAuthToken)) return null;
if (!settings.Servers?.Any() ?? false) return null;
var result = await PlexApi.SignIn(request);
if (!string.IsNullOrEmpty(result.user?.authentication_token))
{
var server = await PlexApi.GetServer(result.user.authentication_token);
var firstServer = server.Server.FirstOrDefault();
await PlexSettings.SaveSettingsAsync(new PlexSettings
var servers = server.Server;
settings.Servers = new List<PlexServers>();
var serverNumber = 0;
foreach (var s in servers)
{
Enable = true,
PlexAuthToken = result.user.authentication_token,
Ip = firstServer.LocalAddresses,
MachineIdentifier = firstServer.MachineIdentifier,
Port = int.Parse(firstServer.Port),
Ssl = firstServer.Scheme != "http",
});
if (string.IsNullOrEmpty(s.LocalAddresses) || string.IsNullOrEmpty(s.Port))
{
continue;
}
settings.Servers.Add(new PlexServers
{
PlexAuthToken = result.user.authentication_token,
Id = new Random().Next(),
Ip = s.LocalAddresses.Split(new []{','}, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(),
MachineIdentifier = s.MachineIdentifier,
Port = int.Parse(s.Port),
Ssl = s.Scheme != "http",
Name = $"Server{serverNumber++}"
});
}
await PlexSettings.SaveSettingsAsync(settings);
}
return result;
}
[HttpPost("Libraries")]
public async Task<PlexLibraries> GetPlexLibraries([FromBody] PlexSettings settings)
public async Task<PlexLibraries> GetPlexLibraries([FromBody] PlexServers settings)
{
var libs = await PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri);

View file

@ -35,6 +35,7 @@ var paths = {
'@angular/forms',
'@angular/platform-browser/animations',
'@angular/material',
'@ng-bootstrap/ng-bootstrap',
'ngx-infinite-scroll'
],
dest: './lib'

View file

@ -15,6 +15,7 @@
"@angular/platform-browser-dynamic": "^4.1.0",
"@angular/platform-server": "^4.1.0",
"@angular/router": "^4.1.0",
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.25",
"@types/jquery": "^2.0.33",
"@types/systemjs": "^0.20.2",
"angular2-jwt": "^0.2.0",
@ -27,7 +28,7 @@
"gulp-clean-css": "^3.0.4",
"gulp-filter": "^5.0.0",
"gulp-if": "^2.0.2",
"gulp-rename": "^1.2.2",
"gulp-rename": "^1.2.2",
"gulp-run": "^1.7.1",
"gulp-sass": "^2.3.2",
"gulp-sourcemaps": "^1.9.0",

View file

@ -2,7 +2,8 @@ import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { MdButtonModule} from '@angular/material';
import { MdButtonModule } from '@angular/material';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AppComponent } from './app.component';
@ -75,7 +76,8 @@ const routes: Routes = [
AuthModule,
WizardModule,
DialogModule,
MdButtonModule
MdButtonModule,
NgbModule.forRoot(),
],
declarations: [
AppComponent,

View file

@ -4,7 +4,6 @@
export interface IExternalSettings extends ISettings {
ssl: boolean,
enable:boolean,
subDir: string,
ip: string,
port:number,
@ -20,15 +19,23 @@ export interface IOmbiSettings extends ISettings {
export interface IEmbySettings extends IExternalSettings {
apiKey: string,
enable: boolean,
administratorId: string,
enableEpisodeSearching:boolean,
}
export interface IPlexSettings extends IExternalSettings {
export interface IPlexSettings extends ISettings {
enable: boolean,
servers : IPlexServer[]
}
export interface IPlexServer extends IExternalSettings {
name:string,
enableEpisodeSearching: boolean,
plexAuthToken: string,
machineIdentifier: string,
plexSelectedLibraries : IPlexLibraries[],
plexSelectedLibraries: IPlexLibraries[],
}
export interface IPlexLibraries {
@ -39,6 +46,7 @@ export interface IPlexLibraries {
export interface ISonarrSettings extends IExternalSettings {
apiKey: string,
enable: boolean,
qualityProfile: string,
seasonFolders: boolean,
rootPath: string,

View file

@ -2,7 +2,7 @@
<h1>Login</h1>
<div>
<p>
@UI.UserLogin_Paragraph <span title="@UI.UserLogin_Paragraph_SpanHover"><i class="fa fa-question-circle"></i></span>
Hey! Welcome, login with your credentails below!
</p>
</div>
<form method="POST" id="loginForm">

View file

@ -10,100 +10,130 @@
<label for="enable">Enable</label>
</div>
</div>
<div class="form-group">
<label for="Ip" class="control-label">Hostname or IP</label>
<div>
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" [(ngModel)]="settings.ip" value="{{settings.ip}}">
</div>
<div style="float: right;">
<button type="submit" (click)="addTab()" class="btn btn-success-outline">Add Server</button>
</div>
<div class="form-group">
<label for="portNumber" class="control-label">Port</label>
<div>
<input type="text" [(ngModel)]="settings.port" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="{{settings.port}}">
</div>
</div>
<ngb-tabset>
<div *ngFor="let server of settings.servers">
<ngb-tab [id]="server.id" [title]="server.name">
<ng-template ngbTabContent>
<br/>
<br/>
<div style="float: right;">
<button type="submit" (click)="removeServer(server)" class="btn btn-danger-outline">Remove Server</button>
</div>
<br/>
<br/>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="ssl" [(ngModel)]="settings.ssl" ng-checked="settings.ssl">
<label for="ssl">SSL</label>
</div>
</div>
<div class="form-group">
<label for="name" class="control-label">Server name</label>
<div>
<input type="text" class="form-control form-control-custom " id="name" name="name" placeholder="Server" [(ngModel)]="server.name" value="{{server.name}}">
</div>
</div>
<div class="form-group">
<div class="checkbox">
<div class="form-group">
<label for="Ip" class="control-label">Hostname or IP</label>
<div>
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" [(ngModel)]="server.ip" value="{{server.ip}}">
</div>
</div>
<div class="form-group">
<label for="portNumber" class="control-label">Port</label>
<div>
<input type="text" [(ngModel)]="settings.port" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="{{server.port}}">
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="ssl" [(ngModel)]="server.ssl" ng-checked="server.ssl">
<label for="ssl">SSL</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="EnableTvEpisodeSearching" [(ngModel)]="settings.enableEpisodeSearching" ng-checked="settings.enableEpisodeSearching">
<label for="EnableTvEpisodeSearching">Enable Episode Searching</label>
<input type="checkbox" id="EnableTvEpisodeSearching" [(ngModel)]="server.enableEpisodeSearching" ng-checked="server.enableEpisodeSearching">
<label for="EnableTvEpisodeSearching">Enable Episode Searching</label>
</div>
<small>
If enabled then we will lookup all episodes on your Plex server and store them in the local database. This will stop episode requests that already exist on Plex (that might not be in Sonarr).
Please be aware that this is a very resource intensive process and while the Plex Episode Cacher job is running the application may appear slow (Depending on the size of your Plex library).
</small>
</div>
</div>
<small>
If enabled then we will lookup all episodes on your Plex server and store them in the local database. This will stop episode requests that already exist on Plex (that might not be in Sonarr).
Please be aware that this is a very resource intensive process and while the Plex Episode Cacher job is running the application may appear slow (Depending on the size of your Plex library).
</small>
</div>
<div class="form-group">
<label for="authToken" class="control-label">Plex Authorization Token</label>
<div class="">
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="settings.plexAuthToken" placeholder="Plex Auth Token" value="{{settings.plexAuthToken}}">
</div>
</div>
<div class="form-group">
<label for="authToken" class="control-label">Plex Authorization Token</label>
<div class="">
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="server.plexAuthToken" placeholder="Plex Auth Token" value="{{server.plexAuthToken}}">
</div>
</div>
<div class="form-group">
<label for="MachineIdentifier" class="control-label">Machine Identifier</label>
<div class="">
<input type="text" class="form-control-custom form-control" id="MachineIdentifier" name="MachineIdentifier" [(ngModel)]="settings.machineIdentifier" value="{{settings.machineIdentifier}}">
</div>
</div>
<div class="form-group">
<label for="MachineIdentifier" class="control-label">Machine Identifier</label>
<div class="">
<input type="text" class="form-control-custom form-control" id="MachineIdentifier" name="MachineIdentifier" [(ngModel)]="server.machineIdentifier" value="{{server.machineIdentifier}}">
</div>
</div>
<div class="form-group">
<label for="username" class="control-label">Username and Password</label>
<div>
<input type="text" class="form-control form-control-custom" id="username" [(ngModel)]="username" placeholder="username">
</div>
<br />
<div>
<input type="password" class="form-control form-control-custom" id="password" [(ngModel)]="password" placeholder="Password">
</div>
</div>
<div class="form-group">
<div class="">
<button id="requestToken" (click)="requestToken()" class="btn btn-primary-outline">Request Token <i class="fa fa-key"></i></button>
</div>
</div>
<label>Please select the libraries you want Ombi to look in for content</label>
<div class="form-group">
<div>
<button (click)="loadLibraries()" class="btn btn-primary-outline">Load Libraries <i class="fa fa-film"></i></button>
</div>
</div>
<div *ngIf="settings.plexSelectedLibraries">
<div *ngFor="let lib of settings.plexSelectedLibraries">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="{{lib.title}}" [(ngModel)]="lib.enabled" ng-checked="lib.enabled">
<label for="{{lib.title}}">{{lib.title}}</label>
</div>
<div class="form-group">
<label for="username" class="control-label">Username and Password</label>
<div>
<input type="text" class="form-control form-control-custom" id="username" [(ngModel)]="username" placeholder="username">
</div>
<br/>
<div>
<input type="password" class="form-control form-control-custom" id="password" [(ngModel)]="password" placeholder="Password">
</div>
</div>
<div class="form-group">
<div class="">
<button id="requestToken" (click)="requestToken()" class="btn btn-primary-outline">Request Token <i class="fa fa-key"></i></button>
</div>
</div>
<label>Please select the libraries you want Ombi to look in for content</label>
<div class="form-group">
<div>
<button (click)="loadLibraries()" class="btn btn-primary-outline">Load Libraries <i class="fa fa-film"></i></button>
</div>
</div>
<div *ngIf="server.plexSelectedLibraries">
<div *ngFor="let lib of server.plexSelectedLibraries">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="{{lib.title}}" [(ngModel)]="lib.enabled" ng-checked="lib.enabled">
<label for="{{lib.title}}">{{lib.title}}</label>
</div>
</div>
</div>
</div>
<div class="form-group">
<div>
<button id="testPlex" type="submit" (click)="testPlex()" class="btn btn-primary-outline">
Test Connectivity
<div id="spinner"></div>
</button>
</div>
</div>
</ng-template>
</ngb-tab>
</div>
</ngb-tabset>
</div>
</div>
<div class="form-group">
<div>
<button id="testPlex" type="submit" (click)="testPlex()" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
</div>
</div>
<div class="form-group">
<div>
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>

View file

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { IPlexSettings, IPlexLibraries } from '../../interfaces/ISettings'
import { IPlexSettings, IPlexLibraries, IPlexServer } from '../../interfaces/ISettings'
import { SettingsService } from '../../services/settings.service';
@ -21,7 +21,10 @@ export class PlexComponent implements OnInit {
password: string;
ngOnInit(): void {
this.settingsService.getPlex().subscribe(x => this.settings = x);
this.settingsService.getPlex().subscribe(x => {
this.settings = x;
}
);
}
requestToken() {
@ -32,23 +35,40 @@ export class PlexComponent implements OnInit {
// TODO Plex Service
}
loadLibraries() {
addTab() {
//this.settings.servers.push(<IPlexServer>{ name: "New*", id: Math.floor(Math.random() * (99999 - 0 + 1) + 1) });
this.notificationService.warning("Disabled", "This feature is currently disabled");
}
removeServer(server: IPlexServer) {
this.notificationService.warning("Disabled", "This feature is currently disabled");
//var index = this.settings.servers.indexOf(server, 0);
//if (index > -1) {
// this.settings.servers.splice(index, 1);
//}
}
loadLibraries(server:IPlexServer) {
this.plexService.getLibraries(this.settings).subscribe(x => {
this.settings.plexSelectedLibraries = [];
server.plexSelectedLibraries = [];
x.mediaContainer.directory.forEach((item, index) => {
var lib: IPlexLibraries = {
key: item.key,
title: item.title,
enabled: false
};
this.settings.plexSelectedLibraries.push(lib);
server.plexSelectedLibraries.push(lib);
});
});
}
save() {
var filtered = this.settings.servers.filter(x => x.name !== "");
this.settings.servers = filtered;
this.settingsService.savePlex(this.settings).subscribe(x => {
if (x) {
this.notificationService.success("Settings Saved", "Successfully saved Plex settings");

View file

@ -2,6 +2,7 @@
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AuthService } from '../auth/auth.service';
import { AuthGuard } from '../auth/auth.guard';
@ -34,7 +35,8 @@ const routes: Routes = [
MenuModule,
InputSwitchModule,
InputTextModule,
AuthModule
AuthModule,
NgbModule
],
declarations: [
SettingsMenuComponent,