Some errors fixed and some ui improvements #865

Also reworked sonarr and how episodes work
pull/1551/head
tidusjar 7 years ago
parent c56af9afe8
commit 95e3c62e30

@ -34,7 +34,6 @@ namespace Ombi.Api.Sonarr.Models
public string Validate() public string Validate()
{ {
var errors = new List<string>();
var sb = new StringBuilder(); var sb = new StringBuilder();
if(this.tvdbId == 0) if(this.tvdbId == 0)
{ {

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search; using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
@ -20,12 +21,31 @@ namespace Ombi.Core.Rule.Rules.Search
{ {
if (obj.Type == RequestType.TvShow) if (obj.Type == RequestType.TvShow)
{ {
var vm = (SearchTvShowViewModel) obj;
// Check if it's in Radarr // Check if it's in Radarr
var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == obj.Id); var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == vm.Id);
if (result != null) if (result != null)
{ {
obj.Approved = vm.Approved = true;
true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
if (vm.SeasonRequests.Any())
{
var sonarrEpisodes = _ctx.SonarrEpisodeCache;
foreach (var season in vm.SeasonRequests)
{
foreach (var ep in season.Episodes)
{
// Check if we have it
var monitoredInSonarr = sonarrEpisodes.Any(x =>
x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == season.SeasonNumber
&& x.TvDbId == vm.Id);
if (monitoredInSonarr)
{
ep.Approved = true;
}
}
}
}
} }
} }
return Success(); return Success();

@ -36,6 +36,10 @@ namespace Ombi.Core.Senders
public async Task<NewSeries> SendToSonarr(ChildRequests model, string qualityId = null) public async Task<NewSeries> SendToSonarr(ChildRequests model, string qualityId = null)
{ {
var s = await Settings.GetSettingsAsync(); var s = await Settings.GetSettingsAsync();
if (!s.Enabled)
{
return null;
}
if(string.IsNullOrEmpty(s.ApiKey)) if(string.IsNullOrEmpty(s.ApiKey))
{ {
return null; return null;
@ -86,136 +90,126 @@ namespace Ombi.Core.Senders
// Montitor the correct seasons, // Montitor the correct seasons,
// If we have that season in the model then it's monitored! // If we have that season in the model then it's monitored!
var seasonsToAdd = new List<Season>(); var seasonsToAdd = new List<Season>();
for (int i = 1; i < model.ParentRequest.TotalSeasons + 1; i++) for (var i = 1; i < model.ParentRequest.TotalSeasons + 1; i++)
{ {
var index = i;
var season = new Season var season = new Season
{ {
seasonNumber = i, seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == i) monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index)
}; };
seasonsToAdd.Add(season); seasonsToAdd.Add(season);
} }
newSeries.seasons = seasonsToAdd; newSeries.seasons = seasonsToAdd;
var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri); var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri);
existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri);
await SendToSonarr(model, existingSeries, s);
}
else
{
await SendToSonarr(model, existingSeries, s);
}
// Ok, now let's sort out the episodes. return new NewSeries
var sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri); {
while (!sonarrEpisodes.Any()) id = existingSeries.id,
{ seasons = existingSeries.seasons.ToList(),
// It could be that the series metadata is not ready yet. So wait cleanTitle = existingSeries.cleanTitle,
sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri); title = existingSeries.title,
await Task.Delay(300); tvdbId = existingSeries.tvdbId
} };
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.SonarrSender, e, "Exception thrown when attempting to send series over to Sonarr");
throw;
}
}
var episodesToUpdate = new List<Episode>(); private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s)
foreach (var req in model.SeasonRequests) {
{ var episodesToUpdate = new List<Episode>();
foreach (var ep in req.Episodes) // Ok, now let's sort out the episodes.
{
var sonarrEp = sonarrEpisodes.FirstOrDefault(x => x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber);
if (sonarrEp != null)
{
sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp);
}
}
}
// Now update the episodes that need updating var sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri);
foreach (var epToUpdate in episodesToUpdate) var sonarrEpList = sonarrEpisodes.ToList() ?? new List<Episode>();
{ while (!sonarrEpList.Any())
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); {
} // It could be that the series metadata is not ready yet. So wait
sonarrEpList = (await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri)).ToList();
await Task.Delay(500);
}
// TODO possibly update the season as it might be unmonitored due to the clash with the AddOptions
if (!s.AddOnly) foreach (var req in model.SeasonRequests)
{
foreach (var ep in req.Episodes)
{
var sonarrEp = sonarrEpList.FirstOrDefault(x =>
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber);
if (sonarrEp != null)
{ {
foreach (var season in model.SeasonRequests) sonarrEp.monitored = true;
{ episodesToUpdate.Add(sonarrEp);
var sonarrSeason = sonarrEpisodes.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count();
var ourRequestCount = season.Episodes.Count();
if (sonarrEpCount == ourRequestCount)
{
// We have the same amount of requests as all of the episodes in the season.
// Do a season search
await SonarrApi.SeasonSearch(result.id, season.SeasonNumber, s.ApiKey, s.FullUri);
}
else
{
// There is a miss-match, let's search the episodes indiviaully
await SonarrApi.EpisodeSearch(episodesToUpdate.Select(x => x.id).ToArray(), s.ApiKey, s.FullUri);
}
}
} }
}
}
var seriesChanges = false;
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count();
var ourRequestCount = season.Episodes.Count;
return result; if (sonarrEpCount == ourRequestCount)
{
// We have the same amount of requests as all of the episodes in the season.
var existingSeason =
result.seasons.First(x => x.seasonNumber == season.SeasonNumber);
existingSeason.monitored = true;
seriesChanges = true;
} }
else else
{ {
var sonarrEpisodes = await SonarrApi.GetEpisodes(existingSeries.id, s.ApiKey, s.FullUri);
var episodesToUpdate = new List<Episode>();
foreach (var req in model.SeasonRequests)
{
foreach (var ep in req.Episodes)
{
var sonarrEp = sonarrEpisodes.FirstOrDefault(x => x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == ep.Season.SeasonNumber);
if (sonarrEp != null)
{
sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp);
}
}
}
// Now update the episodes that need updating // Now update the episodes that need updating
foreach (var epToUpdate in episodesToUpdate) foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber))
{ {
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
} }
// TODO possibly update the season as it might be unmonitored due to the clash with the AddOptions
if (!s.AddOnly)
{
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpisodes.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count();
var ourRequestCount = season.Episodes.Count;
if (sonarrEpCount == ourRequestCount)
{
// We have the same amount of requests as all of the episodes in the season.
// Do a season search
await SonarrApi.SeasonSearch(existingSeries.id, season.SeasonNumber, s.ApiKey, s.FullUri);
}
else
{
// There is a miss-match, let's search the episodes indiviaully
await SonarrApi.EpisodeSearch(episodesToUpdate.Select(x => x.id).ToArray(), s.ApiKey, s.FullUri);
}
}
}
} }
}
if (seriesChanges)
{
await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri);
}
return new NewSeries
{ if (!s.AddOnly)
id = existingSeries.id, {
seasons = existingSeries.seasons.ToList(), await SearchForRequest(model, sonarrEpList, result, s, episodesToUpdate);
cleanTitle = existingSeries.cleanTitle,
title = existingSeries.title,
tvdbId = existingSeries.tvdbId
};
} }
catch (Exception e) }
private async Task SearchForRequest(ChildRequests model, IEnumerable<Episode> sonarrEpList, SonarrSeries existingSeries, SonarrSettings s,
IReadOnlyCollection<Episode> episodesToUpdate)
{
foreach (var season in model.SeasonRequests)
{ {
Logger.LogError(LoggingEvents.SonarrSender, e, "Exception thrown when attempting to send series over to Sonarr"); var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber);
throw; var sonarrEpCount = sonarrSeason.Count();
var ourRequestCount = season.Episodes.Count;
if (sonarrEpCount == ourRequestCount)
{
// We have the same amount of requests as all of the episodes in the season.
// Do a season search
await SonarrApi.SeasonSearch(existingSeries.id, season.SeasonNumber, s.ApiKey, s.FullUri);
}
else
{
// There is a miss-match, let's search the episodes indiviaully
await SonarrApi.EpisodeSearch(episodesToUpdate.Select(x => x.id).ToArray(), s.ApiKey, s.FullUri);
}
} }
} }

@ -35,7 +35,7 @@ namespace Ombi.Schedule
{ {
RecurringJob.AddOrUpdate(() => PlexContentCacher.CacheContent(), Cron.Hourly(20)); RecurringJob.AddOrUpdate(() => PlexContentCacher.CacheContent(), Cron.Hourly(20));
RecurringJob.AddOrUpdate(() => EmbyContentCacher.Start(), Cron.Hourly(5)); RecurringJob.AddOrUpdate(() => EmbyContentCacher.Start(), Cron.Hourly(5));
RecurringJob.AddOrUpdate(() => RadarrCacher.CacheContent(), Cron.Hourly(10)); RecurringJob.AddOrUpdate(() => SonarrCacher.Start(), Cron.Hourly(10));
RecurringJob.AddOrUpdate(() => RadarrCacher.CacheContent(), Cron.Hourly(15)); RecurringJob.AddOrUpdate(() => RadarrCacher.CacheContent(), Cron.Hourly(15));
RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), Cron.Daily(5)); RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), Cron.Daily(5));
RecurringJob.AddOrUpdate(() => EmbyUserImporter.Start(), Cron.Daily); RecurringJob.AddOrUpdate(() => EmbyUserImporter.Start(), Cron.Daily);

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Sonarr; using Ombi.Api.Sonarr;
using Ombi.Api.Sonarr.Models;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
using Ombi.Helpers; using Ombi.Helpers;
@ -21,6 +23,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
_settings = s; _settings = s;
_api = api; _api = api;
_log = l; _log = l;
_ctx = ctx;
} }
private readonly ISettingsService<SonarrSettings> _settings; private readonly ISettingsService<SonarrSettings> _settings;
@ -42,12 +45,28 @@ namespace Ombi.Schedule.Jobs.Sonarr
var series = await _api.GetSeries(settings.ApiKey, settings.FullUri); var series = await _api.GetSeries(settings.ApiKey, settings.FullUri);
if (series != null) if (series != null)
{ {
var ids = series.Select(x => x.tvdbId); var sonarrSeries = series as IList<SonarrSeries> ?? series.ToList();
var ids = sonarrSeries.Select(x => x.tvdbId);
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache");
var entites = ids.Select(id => new SonarrCache {TvDbId = id}).ToList(); var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToList();
await _ctx.SonarrCache.AddRangeAsync(entites); await _ctx.SonarrCache.AddRangeAsync(entites);
var episodesToAdd = new List<SonarrEpisodeCache>();
foreach (var s in sonarrSeries)
{
var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri);
var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile);
episodesToAdd.AddRange(monitoredEpisodes.Select(episode => new SonarrEpisodeCache
{
EpisodeNumber = episode.episodeNumber,
SeasonNumber = episode.seasonNumber,
TvDbId = s.tvdbId
}));
}
await _ctx.SonarrEpisodeCache.AddRangeAsync(episodesToAdd);
await _ctx.SaveChangesAsync(); await _ctx.SaveChangesAsync();
} }
} }

@ -34,6 +34,7 @@ namespace Ombi.Store.Context
DbSet<TvIssues> TvIssues { get; set; } DbSet<TvIssues> TvIssues { get; set; }
DbSet<Tokens> Tokens { get; set; } DbSet<Tokens> Tokens { get; set; }
DbSet<SonarrCache> SonarrCache { get; set; } DbSet<SonarrCache> SonarrCache { get; set; }
DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
EntityEntry Update(object entity); EntityEntry Update(object entity);
EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class; EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class;
} }

@ -38,12 +38,17 @@ namespace Ombi.Store.Context
public DbSet<Audit> Audit { get; set; } public DbSet<Audit> Audit { get; set; }
public DbSet<Tokens> Tokens { get; set; } public DbSet<Tokens> Tokens { get; set; }
public DbSet<SonarrCache> SonarrCache { get; set; } public DbSet<SonarrCache> SonarrCache { get; set; }
public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
public DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; } public DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
var i = StoragePathSingleton.Instance; var i = StoragePathSingleton.Instance;
if (string.IsNullOrEmpty(i.StoragePath))
{
i.StoragePath = string.Empty;
}
optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath,"Ombi.db")}"); optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath,"Ombi.db")}");
} }

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("SonarrEpisodeCache")]
public class SonarrEpisodeCache : Entity
{
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
public int TvDbId { get; set; }
}
}

@ -0,0 +1,763 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using System;
namespace Ombi.Store.Migrations
{
[DbContext(typeof(OmbiContext))]
[Migration("20171004155149_SonarrEpisodes")]
partial class SonarrEpisodes
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ProviderId");
b.Property<string>("Title");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("UserType");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("Key");
b.Property<string>("ProviderId");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("Title");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int?>("IssueId");
b.Property<int>("MovieId");
b.Property<string>("Subect");
b.HasKey("Id");
b.HasIndex("IssueId");
b.HasIndex("MovieId");
b.ToTable("MovieIssues");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int?>("IssueId");
b.Property<string>("Subect");
b.Property<int>("TvId");
b.HasKey("Id");
b.HasIndex("IssueId");
b.HasIndex("TvId");
b.ToTable("TvIssues");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexContent")
.WithMany("Seasons")
.HasForeignKey("PlexContentId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
.WithMany("ChildRequests")
.HasForeignKey("ParentRequestId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", "Movie")
.WithMany()
.HasForeignKey("MovieId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
.WithMany()
.HasForeignKey("RequestedUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
.WithMany("Issues")
.HasForeignKey("IssueId");
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "Child")
.WithMany()
.HasForeignKey("TvId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
.WithMany("Episodes")
.HasForeignKey("SeasonId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
.WithMany("SeasonRequests")
.HasForeignKey("ChildRequestId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace Ombi.Store.Migrations
{
public partial class SonarrEpisodes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SonarrEpisodeCache",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
EpisodeNumber = table.Column<int>(type: "INTEGER", nullable: false),
SeasonNumber = table.Column<int>(type: "INTEGER", nullable: false),
TvDbId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SonarrEpisodeCache", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SonarrEpisodeCache");
}
}
}

@ -545,6 +545,22 @@ namespace Ombi.Store.Migrations
b.ToTable("SonarrCache"); b.ToTable("SonarrCache");
}); });
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")

@ -1,5 +1,5 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router"; import { NavigationStart, Router } from "@angular/router";
import { AuthService } from "./auth/auth.service"; import { AuthService } from "./auth/auth.service";
import { ILocalUser } from "./auth/IUserLogin"; import { ILocalUser } from "./auth/IUserLogin";
import { NotificationService } from "./services"; import { NotificationService } from "./services";
@ -30,14 +30,16 @@ export class AppComponent implements OnInit {
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.router.events.subscribe(() => { this.router.events.subscribe((event: NavigationStart) => {
this.user = this.authService.claims(); if (event instanceof NavigationStart) {
this.showNav = this.authService.loggedIn(); this.user = this.authService.claims();
this.showNav = this.authService.loggedIn();
if (this.user !== null && this.user.name) { if (this.user !== null && this.user.name) {
this.jobService.getCachedUpdate().subscribe(x => { this.jobService.getCachedUpdate().subscribe(x => {
this.updateAvailable = (x === true); this.updateAvailable = x;
}); });
}
} }
}); });
} }

@ -100,8 +100,7 @@ img.bg {
} }
.profile-img-card { .profile-img-card {
width: 96px; width: 100%;
height: 96px;
margin: 0 auto 10px; margin: 0 auto 10px;
display: block; display: block;
-moz-border-radius: 50%; -moz-border-radius: 50%;

@ -2,7 +2,7 @@
<div role="tabpanel" class="tab-pane active" id="MoviesTab"> <div role="tabpanel" class="tab-pane active" id="MoviesTab">
<div class="input-group"> <div class="input-group">
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)"> <input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
<div class="input-group-addon"> <div class="input-group-addon right-radius">
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions Suggestions

@ -58,7 +58,7 @@
<ng-template [ngIf]="ep.approved && !ep.available "><span class="label label-info">Processing Request</span></ng-template> <ng-template [ngIf]="ep.approved && !ep.available "><span class="label label-info">Processing Request</span></ng-template>
<ng-template [ngIf]="ep.selected"><span class="label label-info">Selected</span></ng-template> <ng-template [ngIf]="ep.selected"><span class="label label-info">Selected</span></ng-template>
<ng-template [ngIf]="ep.requested && !ep.approved && !ep.available && !ep.selected"><span class="label label-warning">Pending Approval</span></ng-template> <ng-template [ngIf]="ep.requested && !ep.approved && !ep.available && !ep.selected"><span class="label label-warning">Pending Approval</span></ng-template>
<ng-template [ngIf]="!ep.requested && !ep.available"><span class="label label-danger">Not Requested</span></ng-template> <ng-template [ngIf]="!ep.requested && !ep.available && !ep.approved"><span class="label label-danger">Not Requested</span></ng-template>
</td> </td>
<td> <td>

@ -2,7 +2,7 @@
<div role="tabpanel" class="tab-pane" id="TvShowTab"> <div role="tabpanel" class="tab-pane" id="TvShowTab">
<div class="input-group"> <div class="input-group">
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)"> <input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
<div class="input-group-addon"> <div class="input-group-addon right-radius">
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions Suggestions

@ -44,6 +44,7 @@ export class TvSearchComponent implements OnInit, OnDestroy {
.subscribe(x => { .subscribe(x => {
this.tvResults = x; this.tvResults = x;
this.searchApplied = true; this.searchApplied = true;
this.getExtraInfo();
}); });
}); });
} }

@ -21,7 +21,7 @@ export class ServiceHelpers {
protected extractData(res: Response) { protected extractData(res: Response) {
const body = res.json(); const body = res.json();
//console.log('extractData', body || {}); //console.log('extractData', body || {});
return body || {}; return body;
} }
protected handleError(error: any) { protected handleError(error: any) {
@ -49,7 +49,7 @@ export class ServiceAuthHelpers {
protected extractData(res: Response) { protected extractData(res: Response) {
const body = res.json(); const body = res.json();
//console.log('extractData', body || {}); //console.log('extractData', body || {});
return body || {}; return body;
} }
protected handleError(error: any) { protected handleError(error: any) {

@ -74,18 +74,12 @@
<div class="form-group"> <div class="form-group">
<div> <div>
<button [disabled]="emailForm.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
<button [disabled]="emailForm.invalid" type="submit" (click)="test(emailForm)" class="btn btn-primary-outline"> <button [disabled]="emailForm.invalid" type="submit" (click)="test(emailForm)" class="btn btn-primary-outline">
Test Connectivity Test
<div id="spinner"></div> <div id="spinner"></div>
</button> </button>
</div>
</div>
<div class="form-group">
<div>
<button [disabled]="emailForm.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div> </div>
</div> </div>
</form> </form>

@ -5,7 +5,7 @@
<div class="row"> <div class="row">
<div class="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
<div class="input-group-addon"> <div class="input-group-addon left-radius">
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</div> </div>

@ -52,6 +52,7 @@ legend {
.form-control { .form-control {
color: #fefefe; color: #fefefe;
background-color: #333; background-color: #333;
border-radius: 5px;
} }
.radio input[type="radio"], .radio input[type="radio"],
@ -83,6 +84,14 @@ legend {
.input-group-addon { .input-group-addon {
background-color: #333333; background-color: #333333;
}
.right-radius {
border-radius: 0px 5px 5px 0px;
}
.left-radius {
border-radius: 5px 0px 0px 5px;
} }
.nav > li > a:hover, .nav > li > a:hover,

@ -824,3 +824,8 @@ a > h4:hover {
.error-text { .error-text {
color: #d9534f; color: #d9534f;
} }
.btn-split .btn.dropdown-toggle {
border-radius: 0 0.25rem 0.25rem 0 !important;
padding: 3.5px 10px;
}

@ -62,7 +62,11 @@ namespace Ombi.Controllers.External
{ {
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
var settings = await RadarrSettings.GetSettingsAsync(); var settings = await RadarrSettings.GetSettingsAsync();
return await RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri); if (settings.Enabled)
{
return await RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri);
}
return null;
}); });
} }
@ -78,7 +82,11 @@ namespace Ombi.Controllers.External
{ {
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1); entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
var settings = await RadarrSettings.GetSettingsAsync(); var settings = await RadarrSettings.GetSettingsAsync();
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); if (settings.Enabled)
{
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
return null;
}); });
} }
} }

Loading…
Cancel
Save