Merge pull request #2069 from tidusjar/develop

Release
pull/2073/head v3.0.3020
Jamie 7 years ago committed by GitHub
commit 7e6824c252
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,5 +1,28 @@
# Changelog # Changelog
## (unreleased)
### **Fixes**
- Small memory improvements in the Plex Sync. [Jamie]
- Fixed the sort issue on the user Management page. Also added sorting to the Movie Requests page. [tidusjar]
- Downgraded the angular2-jwt library since it has a bug in it. #2064. [tidusjar]
- Fixed an issue when Plex decideds to reuse the Plex Key for a different media item... #2038. [tidusjar]
- Fixed an issue where we might show the Imdb link when we do not have a imdbid #1797. [tidusjar]
- Fixed the issue where we can no longer select Pending Approval in the filters #2057. [tidusjar]
- Fixed the API key not working when attempting to get requests #2058. [tidusjar]
- Fixed #2056. [tidusjar]
- Experimental, set the Webpack base root to the ombi base path if we have it. This should hopefully fix the reverse proxy issues. [Jamie]
## v3.0.3000 (2018-03-09) ## v3.0.3000 (2018-03-09)
### **New Features** ### **New Features**

@ -1,4 +1,5 @@
using Ombi.Core.Rule; using System;
using Ombi.Core.Rule;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Principal; using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Identity;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Helpers;
namespace Ombi.Core.Engine.Interfaces namespace Ombi.Core.Engine.Interfaces
{ {
@ -30,6 +32,13 @@ namespace Ombi.Core.Engine.Interfaces
private OmbiUser _user; private OmbiUser _user;
protected async Task<OmbiUser> GetUser() protected async Task<OmbiUser> GetUser()
{ {
if (IsApiUser)
{
return new OmbiUser
{
UserName = Username,
};
}
return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == Username)); return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == Username));
} }
@ -40,6 +49,10 @@ namespace Ombi.Core.Engine.Interfaces
protected async Task<bool> IsInRole(string roleName) protected async Task<bool> IsInRole(string roleName)
{ {
if (IsApiUser && roleName != OmbiRoles.Disabled)
{
return true;
}
return await UserManager.IsInRoleAsync(await GetUser(), roleName); return await UserManager.IsInRoleAsync(await GetUser(), roleName);
} }
@ -59,5 +72,7 @@ namespace Ombi.Core.Engine.Interfaces
var ruleResults = await Rules.StartSpecificRules(model, rule); var ruleResults = await Rules.StartSpecificRules(model, rule);
return ruleResults; return ruleResults;
} }
private bool IsApiUser => Username.Equals("Api", StringComparison.CurrentCultureIgnoreCase);
} }
} }

@ -93,217 +93,155 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
foreach (var servers in plexSettings.Servers ?? new List<PlexServers>()) foreach (var servers in plexSettings.Servers ?? new List<PlexServers>())
{ {
try
{
Logger.LogInformation("Starting to cache the content on server {0}", servers.Name);
await ProcessServer(servers);
}
catch (Exception e)
{
Logger.LogWarning(LoggingEvents.PlexContentCacher, e, "Exception thrown when attempting to cache the Plex Content in server {0}", servers.Name);
}
}
}
Logger.LogInformation("Getting all content from server {0}", servers.Name); private async Task ProcessServer(PlexServers servers)
var allContent = await GetAllContent(servers); {
Logger.LogInformation("We found {0} items", allContent.Count); Logger.LogInformation("Getting all content from server {0}", servers.Name);
var allContent = await GetAllContent(servers);
Logger.LogInformation("We found {0} items", allContent.Count);
// Let's now process this. // Let's now process this.
var contentToAdd = new List<PlexServerContent>(); var contentToAdd = new HashSet<PlexServerContent>();
foreach (var content in allContent) foreach (var content in allContent)
{
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase))
{ {
if (content.viewGroup.Equals(PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)) // Process Shows
Logger.LogInformation("Processing TV Shows");
foreach (var show in content.Metadata ?? new Metadata[] { })
{ {
// Process Shows var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri,
Logger.LogInformation("Processing TV Shows"); show.ratingKey);
foreach (var show in content.Metadata ?? new Metadata[] { }) var seasonsContent = new List<PlexSeasonsContent>();
foreach (var season in seasonList.MediaContainer.Metadata)
{ {
var seasonList = await PlexApi.GetSeasons(servers.PlexAuthToken, servers.FullUri, seasonsContent.Add(new PlexSeasonsContent
show.ratingKey);
var seasonsContent = new List<PlexSeasonsContent>();
foreach (var season in seasonList.MediaContainer.Metadata)
{ {
seasonsContent.Add(new PlexSeasonsContent ParentKey = season.parentRatingKey,
{ SeasonKey = season.ratingKey,
ParentKey = season.parentRatingKey, SeasonNumber = season.index,
SeasonKey = season.ratingKey, PlexContentId = show.ratingKey
SeasonNumber = season.index, });
PlexContentId = show.ratingKey }
});
}
// Do we already have this item? // Do we already have this item?
// Let's try and match // Let's try and match
var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title var existingContent = await Repo.GetFirstContentByCustom(x => x.Title == show.title
&& x.ReleaseYear == show.year.ToString() && x.ReleaseYear == show.year.ToString()
&& x.Type == PlexMediaTypeEntity.Show); && x.Type == PlexMediaTypeEntity.Show);
if (existingContent != null) // Just double check the rating key, since this is our unique constraint
var existingKey = await Repo.GetByKey(show.ratingKey);
if (existingKey != null)
{
// Damn son.
// Let's check if they match up
var doesMatch = show.title.Equals(existingKey.Title,
StringComparison.CurrentCulture);
if (!doesMatch)
{ {
// Just check the key // Something fucked up on Plex at somepoint... Damn, rebuild of lib maybe?
var existingKey = await Repo.GetByKey(show.ratingKey); // Lets delete the matching key
if (existingKey != null) await Repo.Delete(existingKey);
{ existingKey = null;
// The rating key is all good! }
} }
else
{
// This means the rating key has changed somehow.
// Should probably delete this and get the new one
var oldKey = existingContent.Key;
Repo.DeleteWithoutSave(existingContent);
// Because we have changed the rating key, we need to change all children too
var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey);
if (episodeToChange.Any())
{
foreach (var e in episodeToChange)
{
Repo.DeleteWithoutSave(e);
}
}
await Repo.SaveChangesAsync();
existingContent = null;
}
if (existingContent != null)
{
// Just check the key
if (existingKey != null)
{
// The rating key is all good!
} }
// The ratingKey keeps changing... else
//var existingContent = await Repo.GetByKey(show.ratingKey);
if (existingContent != null)
{ {
try // This means the rating key has changed somehow.
// Should probably delete this and get the new one
var oldKey = existingContent.Key;
Repo.DeleteWithoutSave(existingContent);
// Because we have changed the rating key, we need to change all children too
var episodeToChange = Repo.GetAllEpisodes().Where(x => x.GrandparentKey == oldKey);
if (episodeToChange.Any())
{ {
Logger.LogInformation("We already have show {0} checking for new seasons", existingContent.Title); foreach (var e in episodeToChange)
// Ok so we have it, let's check if there are any new seasons
var itemAdded = false;
foreach (var season in seasonsContent)
{ {
var seasonExists = existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey); Repo.DeleteWithoutSave(e);
if (seasonExists != null)
{
// We already have this season
continue;
}
existingContent.Seasons.Add(season);
itemAdded = true;
} }
if (itemAdded) await Repo.Update(existingContent);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new seasons to title {0}", existingContent.Title);
} }
await Repo.SaveChangesAsync();
existingContent = null;
} }
else }
// The ratingKey keeps changing...
//var existingContent = await Repo.GetByKey(show.ratingKey);
if (existingContent != null)
{
try
{ {
try Logger.LogInformation("We already have show {0} checking for new seasons",
existingContent.Title);
// Ok so we have it, let's check if there are any new seasons
var itemAdded = false;
foreach (var season in seasonsContent)
{ {
var seasonExists =
existingContent.Seasons.FirstOrDefault(x => x.SeasonKey == season.SeasonKey);
Logger.LogInformation("New show {0}, so add it", show.title); if (seasonExists != null)
// 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,
show.ratingKey);
var providerIds = PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault().guid);
var item = new PlexServerContent
{
AddedAt = DateTime.Now,
Key = show.ratingKey,
ReleaseYear = show.year.ToString(),
Type = PlexMediaTypeEntity.Show,
Title = show.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Seasons = new List<PlexSeasonsContent>()
};
if (providerIds.Type == ProviderType.ImdbId)
{
item.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{ {
item.TheMovieDbId = providerIds.TheMovieDb; // We already have this season
}
if (providerIds.Type == ProviderType.TvDbId)
{
item.TvDbId = providerIds.TheTvDb;
}
// Let's just double check to make sure we do not have it now we have some id's
var existingImdb = false;
var existingMovieDbId = false;
var existingTvDbId = false;
if (item.ImdbId.HasValue())
{
existingImdb = await Repo.GetAll().AnyAsync(x =>
x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TheMovieDbId.HasValue())
{
existingMovieDbId = await Repo.GetAll().AnyAsync(x =>
x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TvDbId.HasValue())
{
existingTvDbId = await Repo.GetAll().AnyAsync(x =>
x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (existingImdb || existingTvDbId || existingMovieDbId)
{
// We already have it!
continue; continue;
} }
item.Seasons.ToList().AddRange(seasonsContent); existingContent.Seasons.Add(season);
itemAdded = true;
contentToAdd.Add(item);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}", show.title);
} }
if (itemAdded) await Repo.Update(existingContent);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e,
"Exception when adding new seasons to title {0}", existingContent.Title);
} }
} }
} else
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
Logger.LogInformation("Processing Movies");
foreach (var movie in content?.Metadata ?? new Metadata[] { })
{ {
// Let's check if we have this movie
try try
{ {
var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title Logger.LogInformation("New show {0}, so add it", show.title);
&& x.ReleaseYear == movie.year.ToString()
&& x.Type == PlexMediaTypeEntity.Movie);
// The rating key keeps changing
//var existing = await Repo.GetByKey(movie.ratingKey);
if (existing != null)
{
Logger.LogInformation("We already have movie {0}", movie.title);
continue;
}
var hasSameKey = await Repo.GetByKey(movie.ratingKey);
if (hasSameKey != null)
{
await Repo.Delete(hasSameKey);
}
Logger.LogInformation("Adding movie {0}", movie.title); // Get the show metadata... This sucks since the `metadata` var contains all information about the show
var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, // But it does not contain the `guid` property that we need to pull out thetvdb id...
movie.ratingKey); var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata show.ratingKey);
.FirstOrDefault() var providerIds =
.guid); PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault()
.guid);
var item = new PlexServerContent var item = new PlexServerContent
{ {
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Key = movie.ratingKey, Key = show.ratingKey,
ReleaseYear = movie.year.ToString(), ReleaseYear = show.year.ToString(),
Type = PlexMediaTypeEntity.Movie, Type = PlexMediaTypeEntity.Show,
Title = movie.title, Title = show.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey), Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey),
Seasons = new List<PlexSeasonsContent>(), Seasons = new List<PlexSeasonsContent>()
Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty
}; };
if (providerIds.Type == ProviderType.ImdbId) if (providerIds.Type == ProviderType.ImdbId)
{ {
@ -317,26 +255,130 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
item.TvDbId = providerIds.TheTvDb; item.TvDbId = providerIds.TheTvDb;
} }
// Let's just double check to make sure we do not have it now we have some id's
var existingImdb = false;
var existingMovieDbId = false;
var existingTvDbId = false;
if (item.ImdbId.HasValue())
{
existingImdb = await Repo.GetAll().AnyAsync(x =>
x.ImdbId == item.ImdbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TheMovieDbId.HasValue())
{
existingMovieDbId = await Repo.GetAll().AnyAsync(x =>
x.TheMovieDbId == item.TheMovieDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (item.TvDbId.HasValue())
{
existingTvDbId = await Repo.GetAll().AnyAsync(x =>
x.TvDbId == item.TvDbId && x.Type == PlexMediaTypeEntity.Show);
}
if (existingImdb || existingTvDbId || existingMovieDbId)
{
// We already have it!
continue;
}
item.Seasons.ToList().AddRange(seasonsContent);
contentToAdd.Add(item); contentToAdd.Add(item);
} }
catch (Exception e) catch (Exception e)
{ {
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new Movie {0}", movie.title); Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding tv show {0}",
show.title);
} }
} }
if (contentToAdd.Count > 500)
{
await Repo.AddRange(contentToAdd);
contentToAdd.Clear();
}
} }
if (contentToAdd.Count > 500) }
if (content.viewGroup.Equals(PlexMediaType.Movie.ToString(), StringComparison.CurrentCultureIgnoreCase))
{
Logger.LogInformation("Processing Movies");
foreach (var movie in content?.Metadata ?? new Metadata[] { })
{ {
await Repo.AddRange(contentToAdd); // Let's check if we have this movie
contentToAdd = new List<PlexServerContent>();
try
{
var existing = await Repo.GetFirstContentByCustom(x => x.Title == movie.title
&& x.ReleaseYear == movie.year.ToString()
&& x.Type == PlexMediaTypeEntity.Movie);
// The rating key keeps changing
//var existing = await Repo.GetByKey(movie.ratingKey);
if (existing != null)
{
Logger.LogInformation("We already have movie {0}", movie.title);
continue;
}
var hasSameKey = await Repo.GetByKey(movie.ratingKey);
if (hasSameKey != null)
{
await Repo.Delete(hasSameKey);
}
Logger.LogInformation("Adding movie {0}", movie.title);
var metaData = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri,
movie.ratingKey);
var providerIds = PlexHelper.GetProviderIdFromPlexGuid(metaData.MediaContainer.Metadata
.FirstOrDefault()
.guid);
var item = new PlexServerContent
{
AddedAt = DateTime.Now,
Key = movie.ratingKey,
ReleaseYear = movie.year.ToString(),
Type = PlexMediaTypeEntity.Movie,
Title = movie.title,
Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, movie.ratingKey),
Seasons = new List<PlexSeasonsContent>(),
Quality = movie.Media?.FirstOrDefault()?.videoResolution ?? string.Empty
};
if (providerIds.Type == ProviderType.ImdbId)
{
item.ImdbId = providerIds.ImdbId;
}
if (providerIds.Type == ProviderType.TheMovieDbId)
{
item.TheMovieDbId = providerIds.TheMovieDb;
}
if (providerIds.Type == ProviderType.TvDbId)
{
item.TvDbId = providerIds.TheTvDb;
}
contentToAdd.Add(item);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.PlexContentCacher, e, "Exception when adding new Movie {0}",
movie.title);
}
if (contentToAdd.Count > 500)
{
await Repo.AddRange(contentToAdd);
contentToAdd.Clear();
}
} }
} }
if (contentToAdd.Count > 500)
if (contentToAdd.Any())
{ {
await Repo.AddRange(contentToAdd); await Repo.AddRange(contentToAdd);
contentToAdd.Clear();
} }
}
if (contentToAdd.Any())
{
await Repo.AddRange(contentToAdd);
} }
} }

@ -62,7 +62,6 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
if (!Validate(settings)) if (!Validate(settings))
{ {
_log.LogWarning("Validation failed"); _log.LogWarning("Validation failed");
return; return;
} }
@ -101,21 +100,25 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
var currentPosition = 0; var currentPosition = 0;
var resultCount = settings.EpisodeBatchSize == 0 ? 150 : settings.EpisodeBatchSize; var resultCount = settings.EpisodeBatchSize == 0 ? 150 : settings.EpisodeBatchSize;
var currentEpisodes = _repo.GetAllEpisodes();
var episodes = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, resultCount); var episodes = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, resultCount);
_log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Total Epsiodes found for {episodes.MediaContainer.librarySectionTitle} = {episodes.MediaContainer.totalSize}"); _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Total Epsiodes found for {episodes.MediaContainer.librarySectionTitle} = {episodes.MediaContainer.totalSize}");
// Delete all the episodes because we cannot uniquly match an episode to series every time, // Delete all the episodes because we cannot uniquly match an episode to series every time,
// see comment below. // see comment below.
await _repo.ExecuteSql("DELETE FROM PlexEpisode");
await ProcessEpsiodes(episodes); // 12.03.2017 - I think we should be able to match them now
//await _repo.ExecuteSql("DELETE FROM PlexEpisode");
await ProcessEpsiodes(episodes, currentEpisodes);
currentPosition += resultCount; currentPosition += resultCount;
while (currentPosition < episodes.MediaContainer.totalSize) while (currentPosition < episodes.MediaContainer.totalSize)
{ {
var ep = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, var ep = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition,
resultCount); resultCount);
await ProcessEpsiodes(ep);
await ProcessEpsiodes(ep, currentEpisodes);
_log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Processed {resultCount} more episodes. Total Remaining {episodes.MediaContainer.totalSize - currentPosition}"); _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Processed {resultCount} more episodes. Total Remaining {episodes.MediaContainer.totalSize - currentPosition}");
currentPosition += resultCount; currentPosition += resultCount;
} }
@ -125,58 +128,56 @@ namespace Ombi.Schedule.Jobs.Plex
await _repo.SaveChangesAsync(); await _repo.SaveChangesAsync();
} }
private async Task ProcessEpsiodes(PlexContainer episodes) private async Task ProcessEpsiodes(PlexContainer episodes, IQueryable<PlexEpisode> currentEpisodes)
{ {
var ep = new HashSet<PlexEpisode>(); var ep = new HashSet<PlexEpisode>();
try try
{ {
foreach (var episode in episodes?.MediaContainer?.Metadata ?? new Metadata[] { })
foreach (var episode in episodes?.MediaContainer?.Metadata ?? new Metadata[]{})
{
// I don't think we need to get the metadata, we only need to get the metadata if we need the provider id (TheTvDbid). Why do we need it for episodes?
// We have the parent and grandparent rating keys to link up to the season and series
//var metadata = _api.GetEpisodeMetaData(server.PlexAuthToken, server.FullUri, episode.ratingKey);
// This does seem to work, it looks like we can somehow get different rating, grandparent and parent keys with episodes. Not sure how.
//var epExists = currentEpisodes.Any(x => episode.ratingKey == x.Key &&
// episode.grandparentRatingKey == x.GrandparentKey);
//if (epExists)
//{
// continue;
//}
// Let's check if we have the parent
var seriesExists = await _repo.GetByKey(episode.grandparentRatingKey);
if (seriesExists == null)
{ {
// Ok let's try and match it to a title. TODO (This is experimental) // I don't think we need to get the metadata, we only need to get the metadata if we need the provider id (TheTvDbid). Why do we need it for episodes?
var seriesMatch = await _repo.GetAll().FirstOrDefaultAsync(x => // We have the parent and grandparent rating keys to link up to the season and series
x.Title.Equals(episode.grandparentTitle, StringComparison.CurrentCultureIgnoreCase)); //var metadata = _api.GetEpisodeMetaData(server.PlexAuthToken, server.FullUri, episode.ratingKey);
if (seriesMatch == null)
// This does seem to work, it looks like we can somehow get different rating, grandparent and parent keys with episodes. Not sure how.
var epExists = currentEpisodes.Any(x => episode.ratingKey == x.Key &&
episode.grandparentRatingKey == x.GrandparentKey);
if (epExists)
{ {
_log.LogWarning(
"The episode title {0} we cannot find the parent series. The episode grandparentKey = {1}, grandparentTitle = {2}",
episode.title, episode.grandparentRatingKey, episode.grandparentTitle);
continue; continue;
} }
// Set the rating key to the correct one // Let's check if we have the parent
episode.grandparentRatingKey = seriesMatch.Key; var seriesExists = await _repo.GetByKey(episode.grandparentRatingKey);
} if (seriesExists == null)
{
// Ok let's try and match it to a title. TODO (This is experimental)
seriesExists = await _repo.GetAll().FirstOrDefaultAsync(x =>
x.Title.Equals(episode.grandparentTitle, StringComparison.CurrentCultureIgnoreCase));
if (seriesExists == null)
{
_log.LogWarning(
"The episode title {0} we cannot find the parent series. The episode grandparentKey = {1}, grandparentTitle = {2}",
episode.title, episode.grandparentRatingKey, episode.grandparentTitle);
continue;
}
ep.Add(new PlexEpisode // Set the rating key to the correct one
{ episode.grandparentRatingKey = seriesExists.Key;
EpisodeNumber = episode.index, }
SeasonNumber = episode.parentIndex,
GrandparentKey = episode.grandparentRatingKey, ep.Add(new PlexEpisode
ParentKey = episode.parentRatingKey, {
Key = episode.ratingKey, EpisodeNumber = episode.index,
Title = episode.title SeasonNumber = episode.parentIndex,
}); GrandparentKey = episode.grandparentRatingKey,
} ParentKey = episode.parentRatingKey,
Key = episode.ratingKey,
Title = episode.title
});
}
await _repo.AddRange(ep); await _repo.AddRange(ep);
} }
catch (Exception e) catch (Exception e)
{ {
@ -189,7 +190,7 @@ namespace Ombi.Schedule.Jobs.Plex
{ {
if (string.IsNullOrEmpty(settings.PlexAuthToken)) if (string.IsNullOrEmpty(settings.PlexAuthToken))
{ {
return false ; return false;
} }
return true; return true;

@ -1,4 +1,5 @@
import { Component, OnInit } from "@angular/core"; import { PlatformLocation } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { NavigationStart, Router } from "@angular/router"; import { NavigationStart, Router } from "@angular/router";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { AuthService } from "./auth/auth.service"; import { AuthService } from "./auth/auth.service";
@ -32,7 +33,14 @@ export class AppComponent implements OnInit {
private readonly settingsService: SettingsService, private readonly settingsService: SettingsService,
private readonly jobService: JobService, private readonly jobService: JobService,
public readonly translate: TranslateService, public readonly translate: TranslateService,
private readonly identityService: IdentityService) { private readonly identityService: IdentityService,
private readonly platformLocation: PlatformLocation) {
const base = this.platformLocation.getBaseHrefFromDOM();
if (base.length > 1) {
__webpack_public_path__ = base + "/dist/";
}
this.translate.addLangs(["en", "de", "fr","da","es","it","nl","sv","no"]); this.translate.addLangs(["en", "de", "fr","da","es","it","nl","sv","no"]);
// this language will be used as a fallback when a translation isn't found in the current language // this language will be used as a fallback when a translation isn't found in the current language
this.translate.setDefaultLang("en"); this.translate.setDefaultLang("en");

@ -13,15 +13,15 @@
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>
<th (click)="setOrder('issue.status')"> <th (click)="setOrder('status')">
<a [translate]="'Issues.Status'"></a> <a [translate]="'Issues.Status'"></a>
<span *ngIf="order === 'issue.status'"> <span *ngIf="order === 'status'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>
<th (click)="setOrder('issue.reportedUser')"> <th (click)="setOrder('reportedUser')">
<a [translate]="'Issues.ReportedBy'"></a> <a [translate]="'Issues.ReportedBy'"></a>
<span *ngIf="order === 'issue.reportedUser'"> <span *ngIf="order === 'reportedUser'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>

@ -4,9 +4,49 @@
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)"> <input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
<span class="input-group-btn"> <span class="input-group-btn">
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay" > <button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay" >
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}</button> <i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}
<!-- <button id="filterBtn" class="btn btn-sm btn-warning-outline" (click)="sortDisplay = !sortDisplay" > </button>
<i class="fa fa-sort"></i> {{ 'Requests.Sort' | translate }}</button> -->
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-sort"></i> {{ 'Requests.Sort' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
<li>
<a (click)="setOrder('requestedDate')">{{ 'Requests.SortRequestDate' | translate }}
<span *ngIf="order === 'requestedDate'">
<span [hidden]="reverse"><small><i class="fa fa-arrow-down" aria-hidden="true"></i></small></span>
<span [hidden]="!reverse"><small><i class="fa fa-arrow-up" aria-hidden="true"></i></small></span>
</span>
</a>
<a (click)="setOrder('title')">{{ 'Requests.SortTitle' | translate}}
<span *ngIf="order === 'title'">
<span [hidden]="reverse"><small><i class="fa fa-arrow-down" aria-hidden="true"></i></small></span>
<span [hidden]="!reverse"><small><i class="fa fa-arrow-up" aria-hidden="true"></i></small></span>
</span>
</a>
<a (click)="setOrder('releaseDate')">{{ 'Requests.TheatricalReleaseSort' | translate }}
<span *ngIf="order === 'releaseDate'">
<span [hidden]="reverse"><small><i class="fa fa-arrow-down" aria-hidden="true"></i></small></span>
<span [hidden]="!reverse"><small><i class="fa fa-arrow-up" aria-hidden="true"></i></small></span>
</span>
</a>
<a (click)="setOrder('requestedUser.userAlias')">{{ 'Requests.SortRequestedBy' | translate }}
<span *ngIf="order === 'requestedUser.userAlias'">
<span [hidden]="reverse"><small><i class="fa fa-arrow-down" aria-hidden="true"></i></small></span>
<span [hidden]="!reverse"><small><i class="fa fa-arrow-up" aria-hidden="true"></i></small></span>
</span>
</a>
<a (click)="setOrder('status')">{{ 'Requests.SortStatus' | translate }}
<span *ngIf="order === 'status'">
<span [hidden]="reverse"><small><i class="fa fa-arrow-down" aria-hidden="true"></i></small></span>
<span [hidden]="!reverse"><small><i class="fa fa-arrow-up" aria-hidden="true"></i></small></span>
</span>
</a>
</li>
</ul>
</span> </span>
</div> </div>
@ -19,7 +59,7 @@
<div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="100" (scrolled)="loadMore()"> <div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="100" (scrolled)="loadMore()">
<div *ngFor="let request of movieRequests"> <div *ngFor="let request of movieRequests | orderBy: order : reverse : 'case-insensitive'">
<div class="row"> <div class="row">
@ -203,8 +243,8 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="radio"> <div class="radio">
<input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.PendingApproval)"> <input type="radio" id="pendingApproval" name="Status" (click)="filterStatus(filterType.PendingApproval, $event)">
<label for="approved">{{ 'Filter.PendingApproval' | translate }}</label> <label for="pendingApproval">{{ 'Filter.PendingApproval' | translate }}</label>
</div> </div>
</div> </div>

@ -36,7 +36,8 @@ export class MovieRequestsComponent implements OnInit {
public filter: IFilter; public filter: IFilter;
public filterType = FilterType; public filterType = FilterType;
public sortDisplay: boolean; public order: string = "requestedDate";
public reverse = false;
private currentlyLoaded: number; private currentlyLoaded: number;
private amountToLoad: number; private amountToLoad: number;
@ -174,6 +175,14 @@ export class MovieRequestsComponent implements OnInit {
}); });
} }
public setOrder(value: string) {
if (this.order === value) {
this.reverse = !this.reverse;
}
this.order = value;
}
private loadRequests(amountToLoad: number, currentlyLoaded: number) { private loadRequests(amountToLoad: number, currentlyLoaded: number) {
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1) this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
.subscribe(x => { .subscribe(x => {

@ -2,6 +2,7 @@
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { OrderModule } from "ngx-order-pipe";
import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { InfiniteScrollModule } from "ngx-infinite-scroll";
@ -34,6 +35,7 @@ const routes: Routes = [
TreeTableModule, TreeTableModule,
SharedModule, SharedModule,
SidebarModule, SidebarModule,
OrderModule,
], ],
declarations: [ declarations: [
RequestComponent, RequestComponent,

@ -62,7 +62,7 @@
<div class="col-sm-8 small-padding"> <div class="col-sm-8 small-padding">
<div> <div>
<a href="http://www.imdb.com/title/{{node.data.imdbId}}/" target="_blank"> <a *ngIf="node.data.imdbId" href="http://www.imdb.com/title/{{node.data.imdbId}}/" target="_blank">
<h4>{{node.data.title}} ({{node.data.firstAired | date: 'yyyy'}})</h4> <h4>{{node.data.title}} ({{node.data.firstAired | date: 'yyyy'}})</h4>
</a> </a>

@ -16,44 +16,45 @@
</td> </td>
</a> </a>
</th> </th>
<th (click)="setOrder('u.userName')"> <th (click)="setOrder('userName')">
<a> <a>
Username Username
</a> </a>
<span *ngIf="order === 'u.userName'"> <span *ngIf="order === 'userName'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>
<th (click)="setOrder('u.alias')"> <th (click)="setOrder('alias')">
<a> <a>
Alias Alias
</a> </a>
<span *ngIf="order === 'u.alias'"> <span *ngIf="order === 'alias'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>
<th (click)="setOrder('u.emailAddress')"> <th (click)="setOrder('emailAddress')">
<a> <a>
Email Email
</a> </a>
<span *ngIf="order === 'u.emailAddress'"> <span *ngIf="order === 'emailAddress'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span>
<span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>
<th> <th>
Roles Roles
</th> </th>
<th (click)="setOrder('u.lastLoggedIn')"> <th (click)="setOrder('lastLoggedIn')">
<a> Last Logged In</a> <a> Last Logged In</a>
<span *ngIf="order === 'u.lastLoggedIn'"> <span *ngIf="order === 'lastLoggedIn'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>
<th (click)="setOrder('u.userType')"> <th (click)="setOrder('userType')">
<a> <a>
User Type User Type
</a> </a>
<span *ngIf="order === 'u.userType'"> <span *ngIf="order === 'userType'">
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span> <span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
</span> </span>
</th> </th>

@ -13,7 +13,7 @@ export class UserManagementComponent implements OnInit {
public emailSettings: IEmailNotificationSettings; public emailSettings: IEmailNotificationSettings;
public customizationSettings: ICustomizationSettings; public customizationSettings: ICustomizationSettings;
public order: string = "u.userName"; public order: string = "userName";
public reverse = false; public reverse = false;
public showBulkEdit = false; public showBulkEdit = false;

@ -4972,9 +4972,9 @@
"integrity": "sha512-7lASze8zHSDdAAFO3VNop1TY60rs8A7sm8DzQfU33VNcJI27F6mtxwjILIH339s7m6HVC08AS7I64HBjBMw/QQ==" "integrity": "sha512-7lASze8zHSDdAAFO3VNop1TY60rs8A7sm8DzQfU33VNcJI27F6mtxwjILIH339s7m6HVC08AS7I64HBjBMw/QQ=="
}, },
"ngx-order-pipe": { "ngx-order-pipe": {
"version": "1.1.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/ngx-order-pipe/-/ngx-order-pipe-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ngx-order-pipe/-/ngx-order-pipe-2.0.1.tgz",
"integrity": "sha512-hIfdUONbKG14/S5zEyGjr1ukAd2XdUUnUsvA80ct3pyoBCh5aQ7XhBz7N9jCsVzMGTGUPK6R59KYkEPB3n5hbQ==" "integrity": "sha512-t0IUqoNs3705yZQeohmhUQvpiRTj5RX7AhFXkx3PMfq7G6h7GNNrR3x27XbXEsjKgBo5hPgfEfW5OljRYa1VVw=="
}, },
"ngx-window-token": { "ngx-window-token": {
"version": "0.0.4", "version": "0.0.4",

@ -22,7 +22,7 @@
"@angular/platform-browser-dynamic": "^5.1.2", "@angular/platform-browser-dynamic": "^5.1.2",
"@angular/platform-server": "5.0.0", "@angular/platform-server": "5.0.0",
"@angular/router": "^5.1.2", "@angular/router": "^5.1.2",
"@auth0/angular-jwt": "^1.0.0-beta.9", "@auth0/angular-jwt": "1.0.0-beta.9",
"@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.8", "@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.8",
"@ngx-translate/core": "^8.0.0", "@ngx-translate/core": "^8.0.0",
"@ngx-translate/http-loader": "^2.0.1", "@ngx-translate/http-loader": "^2.0.1",
@ -55,7 +55,7 @@
"ng2-cookies": "^1.0.12", "ng2-cookies": "^1.0.12",
"ngx-clipboard": "8.1.1", "ngx-clipboard": "8.1.1",
"ngx-infinite-scroll": "^0.6.1", "ngx-infinite-scroll": "^0.6.1",
"ngx-order-pipe": "^1.1.1", "ngx-order-pipe": "^2.0.1",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
"npm": "^5.6.0", "npm": "^5.6.0",
"pace-progress": "^1.0.2", "pace-progress": "^1.0.2",

@ -5,6 +5,8 @@ declare var require: any;
declare var localStorage: any; declare var localStorage: any;
declare var introJs: any; declare var introJs: any;
declare var __webpack_public_path__: any;
declare module "pace-progress"; declare module "pace-progress";
declare module "webpack-bundle-analyzer"; declare module "webpack-bundle-analyzer";
declare module "uglifyjs-webpack-plugin"; declare module "uglifyjs-webpack-plugin";

@ -117,6 +117,7 @@
"RequestStatus": "Request status:", "RequestStatus": "Request status:",
"Denied": " Denied:", "Denied": " Denied:",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"TheatricalReleaseSort": "Theatrical Release",
"DigitalRelease": "Digital Release: {{date}}", "DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Request Date:", "RequestDate": "Request Date:",
"QualityOverride": "Quality Override:", "QualityOverride": "Quality Override:",
@ -134,7 +135,11 @@
"ReportIssue":"Report Issue", "ReportIssue":"Report Issue",
"Filter":"Filter", "Filter":"Filter",
"Sort":"Sort", "Sort":"Sort",
"SeasonNumberHeading":"Season: {seasonNumber}" "SeasonNumberHeading":"Season: {seasonNumber}",
"SortTitle":"Title",
"SortRequestDate": "Request Date",
"SortRequestedBy":"Requested By",
"SortStatus":"Status"
}, },
"Issues":{ "Issues":{
"Title":"Issues", "Title":"Issues",

Loading…
Cancel
Save