Fixed logo on reset password pages

fixed the run importer button on the user management settings

Added root and qulaity profile selection for movies #1517
Added the Sonarr Cacher #1513

Refactored what we do to tv searches to use the rules engine
Cache a few more things to speed some searches up
pull/1526/head^2
Jamie.Rees 7 years ago
parent aad5c2a4bc
commit 9ae5ad0ecd

@ -10,9 +10,7 @@ using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Core.Rule.Interfaces;
using StackExchange.Profiling;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Api.Trakt;
using Ombi.Core.Authentication;
using Ombi.Helpers;
@ -55,22 +53,14 @@ namespace Ombi.Core.Engine
/// <returns></returns>
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
{
using (MiniProfiler.Current.Step("Starting Movie Search Engine"))
using (MiniProfiler.Current.Step("Searching Movie"))
var result = await MovieApi.SearchMovie(search);
if (result != null)
{
var result = await MovieApi.SearchMovie(search);
using (MiniProfiler.Current.Step("Fin API, Transforming"))
{
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
}
}
return null;
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
}
return null;
}
/// <summary>
@ -174,7 +164,7 @@ namespace Ombi.Core.Engine
// So set the ImdbId to viewMovie.Id and then set it back afterwards
var oldId = viewMovie.Id;
viewMovie.CustomId = viewMovie.ImdbId ?? string.Empty;
await RunSearchRules(viewMovie);
viewMovie.Id = oldId;

@ -15,11 +15,7 @@ using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
using Ombi.Store.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Core.Authentication;
using Ombi.Helpers;
@ -117,11 +113,7 @@ namespace Ombi.Core.Engine
});
}
}
var existingRequests = await GetTvRequests();
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
return await ProcessResult(mapped, existingRequests, plexSettings, embySettings);
return await ProcessResult(mapped);
}
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
@ -189,127 +181,21 @@ namespace Ombi.Core.Engine
};
}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
{
var existingRequests = await GetTvRequests();
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
{
var viewT = Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
retVal.Add(await ProcessResult(viewT, existingRequests, plexSettings, embySettings));
retVal.Add(await ProcessResult(viewT));
}
return retVal;
}
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item, Dictionary<int, TvRequests> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item)
{
if (embySettings.Enable)
{
var content = await EmbyContentRepo.Get(item.Id.ToString());
if (content != null)
{
item.Available = true;
}
// Let's go through the episodes now
if (item.SeasonRequests.Any())
{
var allEpisodes = EmbyContentRepo.GetAllEpisodes().Include(x => x.Series);
foreach (var season in item.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && item.Id.ToString() == x.Series.ProviderId);
if (epExists != null)
{
episode.Available = true;
}
}
}
}
}
if (plexSettings.Enable)
{
var content = await PlexContentRepo.Get(item.Id.ToString());
if (content != null)
{
item.Available = true;
item.PlexUrl = content.Url;
}
// Let's go through the episodes now
if (item.SeasonRequests.Any())
{
var allEpisodes = PlexContentRepo.GetAllEpisodes();
foreach (var season in item.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.ProviderId == item.Id.ToString());
if (epExists != null)
{
episode.Available = true;
}
}
}
}
}
if (item.SeasonRequests.Any() && item.SeasonRequests.All(x => x.Episodes.All(e => e.Approved)))
{
item.FullyAvailable = true;
}
if (item.Id > 0)
{
var tvdbid = item.Id;
if (existingRequests.ContainsKey(tvdbid))
{
var existingRequest = existingRequests[tvdbid];
item.Requested = true;
item.Approved = existingRequest.ChildRequests.Any(x => x.Approved);
// Let's modify the seasonsrequested to reflect what we have requested...
foreach (var season in item.SeasonRequests)
{
foreach (var existingRequestChildRequest in existingRequest.ChildRequests)
{
// Find the existing request season
var existingSeason =
existingRequestChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
if (existingSeason == null) continue;
foreach (var ep in existingSeason.Episodes)
{
// Find the episode from what we are searching
var episodeSearching = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber);
if (episodeSearching == null)
{
continue;
}
episodeSearching.Requested = true;
episodeSearching.Available = ep.Available;
episodeSearching.Approved = ep.Season.ChildRequest.Approved;
}
}
}
}
// TODO CHECK SONARR/RADARR
//if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid))
// // compare to the sonarr/sickrage db
//{
// item.Requested = true;
//}
}
item.CustomId = item.Id.ToString();
await RunSearchRules(item);
return item;
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search
{
@ -22,5 +23,8 @@ namespace Ombi.Core.Models.Search
public string Trailer { get; set; }
public string Homepage { get; set; }
public string ImdbId { get; set; }
public int RootPathOverride { get; set; }
public int QualityOverride { get; set; }
public override RequestType Type => RequestType.Movie;
}
}

@ -1,6 +1,7 @@
using Ombi.Core.Models.Requests;
using Ombi.Store.Repository.Requests;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search
{
@ -54,5 +55,7 @@ namespace Ombi.Core.Models.Search
/// This is where we have EVERY Episode for that series
/// </summary>
public bool FullyAvailable { get; set; }
public override RequestType Type => RequestType.TvShow;
}
}

@ -1,8 +1,9 @@
using System.ComponentModel.DataAnnotations.Schema;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search
{
public class SearchViewModel
public abstract class SearchViewModel
{
public int Id { get; set; }
public bool Approved { get; set; }
@ -10,6 +11,7 @@ namespace Ombi.Core.Models.Search
public bool Available { get; set; }
public string PlexUrl { get; set; }
public string Quality { get; set; }
public abstract RequestType Type { get; }
/// <summary>

@ -1,9 +1,8 @@
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
namespace Ombi.Core.Rule.Interfaces
{
public interface IRules<T> where T : new()
public interface IRules<T>
{
Task<RuleResult> Execute(T obj);
}

@ -73,7 +73,7 @@ namespace Ombi.Core.Rule
}
private void GetTypes<T>(IServiceProvider provider, Assembly ass, string baseSearchType, List<IRules<T>> ruleList) where T : new()
private void GetTypes<T>(IServiceProvider provider, Assembly ass, string baseSearchType, List<IRules<T>> ruleList)
{
foreach (var ti in ass.DefinedTypes)
{

@ -1,6 +1,9 @@
using System.Threading.Tasks;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Rule.Rules.Search
@ -20,6 +23,28 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null)
{
obj.Available = true;
if (obj.Type == RequestType.TvShow)
{
var searchResult = (SearchTvShowViewModel)obj;
// Let's go through the episodes now
if (searchResult.SeasonRequests.Any())
{
var allEpisodes = EmbyContentRepository.GetAllEpisodes().Include(x => x.Series);
foreach (var season in searchResult.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && item.ProviderId.ToString() == x.Series.ProviderId);
if (epExists != null)
{
episode.Available = true;
}
}
}
}
}
}
return Success();
}

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
@ -21,28 +22,75 @@ namespace Ombi.Core.Rule.Rules.Search
public Task<RuleResult> Execute(SearchViewModel obj)
{
var movieRequests = Movie.GetRequest(obj.Id);
if (movieRequests != null) // Do we already have a request for this?
if (obj.Type == RequestType.Movie)
{
var movieRequests = Movie.GetRequest(obj.Id);
if (movieRequests != null) // Do we already have a request for this?
{
obj.Requested = true;
obj.Approved = movieRequests.Approved;
obj.Available = movieRequests.Available;
obj.Requested = true;
obj.Approved = movieRequests.Approved;
obj.Available = movieRequests.Available;
return Task.FromResult(Success());
}
return Task.FromResult(Success());
}
var tvRequests = Tv.GetRequest(obj.Id);
if (tvRequests != null) // Do we already have a request for this?
else
{
//var tvRequests = Tv.GetRequest(obj.Id);
//if (tvRequests != null) // Do we already have a request for this?
//{
// obj.Requested = true;
// obj.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
// obj.Available = tvRequests.ChildRequests.Any(x => x.Available);
// return Task.FromResult(Success());
//}
var request = (SearchTvShowViewModel) obj;
var tvRequests = Tv.GetRequest(obj.Id);
if (tvRequests != null) // Do we already have a request for this?
{
request.Requested = true;
request.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
// Let's modify the seasonsrequested to reflect what we have requested...
foreach (var season in request.SeasonRequests)
{
foreach (var existingRequestChildRequest in tvRequests.ChildRequests)
{
// Find the existing request season
var existingSeason =
existingRequestChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
if (existingSeason == null) continue;
foreach (var ep in existingSeason.Episodes)
{
// Find the episode from what we are searching
var episodeSearching = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber);
if (episodeSearching == null)
{
continue;
}
episodeSearching.Requested = true;
episodeSearching.Available = ep.Available;
episodeSearching.Approved = ep.Season.ChildRequest.Approved;
}
}
}
}
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Approved)))
{
request.FullyAvailable = true;
}
obj.Requested = true;
obj.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
obj.Available = tvRequests.ChildRequests.Any(x => x.Available);
return Task.FromResult(Success());
}
return Task.FromResult(Success());
}
}
}

@ -1,6 +1,9 @@
using System.Threading.Tasks;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Rule.Rules.Search
@ -22,6 +25,29 @@ namespace Ombi.Core.Rule.Rules.Search
obj.Available = true;
obj.PlexUrl = item.Url;
obj.Quality = item.Quality;
if (obj.Type == RequestType.TvShow)
{
var search = (SearchTvShowViewModel)obj;
// Let's go through the episodes now
if (search.SeasonRequests.Any())
{
var allEpisodes = PlexContentRepository.GetAllEpisodes();
foreach (var season in search.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
x.Series.ProviderId == item.ProviderId.ToString());
if (epExists != null)
{
episode.Available = true;
}
}
}
}
}
}
return Success();
}

@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Context;
using Ombi.Store.Entities;
namespace Ombi.Core.Rule.Rules.Search
{
@ -18,13 +19,16 @@ namespace Ombi.Core.Rule.Rules.Search
public async Task<RuleResult> Execute(SearchViewModel obj)
{
// Check if it's in Radarr
var result = await _ctx.RadarrCache.FirstOrDefaultAsync(x => x.TheMovieDbId == obj.Id);
if (result != null)
if (obj.Type == RequestType.Movie)
{
obj.Approved = true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
// Check if it's in Radarr
var result = await _ctx.RadarrCache.FirstOrDefaultAsync(x => x.TheMovieDbId == obj.Id);
if (result != null)
{
obj.Approved =
true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
}
}
return Success();
}
}

@ -0,0 +1,34 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Context;
using Ombi.Store.Entities;
namespace Ombi.Core.Rule.Rules.Search
{
public class SonarrCacheRule : BaseSearchRule, IRules<SearchViewModel>
{
public SonarrCacheRule(IOmbiContext ctx)
{
_ctx = ctx;
}
private readonly IOmbiContext _ctx;
public async Task<RuleResult> Execute(SearchViewModel obj)
{
if (obj.Type == RequestType.TvShow)
{
// Check if it's in Radarr
var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == obj.Id);
if (result != null)
{
obj.Approved =
true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
}
}
return Success();
}
}
}

@ -5,6 +5,6 @@ namespace Ombi.Core
{
public interface IMovieSender
{
Task<MovieSenderResult> Send(MovieRequests model, string qualityId = "");
Task<MovieSenderResult> Send(MovieRequests model);
}
}

@ -1,4 +1,5 @@
using Ombi.Core.Settings;
using System.Linq;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models.External;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -21,7 +22,7 @@ namespace Ombi.Core
private IRadarrApi RadarrApi { get; }
private ILogger<MovieSender> Log { get; }
public async Task<MovieSenderResult> Send(MovieRequests model, string qualityId = "")
public async Task<MovieSenderResult> Send(MovieRequests model)
{
//var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
@ -39,7 +40,7 @@ namespace Ombi.Core
if (radarrSettings.Enabled)
{
return await SendToRadarr(model, radarrSettings, qualityId);
return await SendToRadarr(model, radarrSettings);
}
return new MovieSenderResult
@ -49,22 +50,16 @@ namespace Ombi.Core
};
}
private async Task<MovieSenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings, string qualityId)
private async Task<MovieSenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings)
{
var qualityProfile = 0;
if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality
var qualityToUse = int.Parse(settings.DefaultQualityProfile);
if (model.QualityOverride <= 0)
{
int.TryParse(qualityId, out qualityProfile);
qualityToUse = model.QualityOverride;
}
if (qualityProfile <= 0)
{
int.TryParse(settings.DefaultQualityProfile, out qualityProfile);
}
//var rootFolderPath = model.RootFolderSelected <= 0 ? settings.FullRootPath : GetRootPath(model.RootFolderSelected, settings);
var rootFolderPath = settings.DefaultRootPath; // TODO Allow changing in the UI
var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, qualityProfile, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, settings.MinimumAvailability);
var rootFolderPath = model.RootPathOverride <= 0 ? settings.DefaultRootPath : await RadarrRootPath(model.RootPathOverride, settings);
var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, settings.MinimumAvailability);
if (!string.IsNullOrEmpty(result.Error?.message))
{
@ -77,5 +72,12 @@ namespace Ombi.Core
}
return new MovieSenderResult { Success = true, MovieSent = false };
}
private async Task<string> RadarrRootPath(int overrideId, RadarrSettings settings)
{
var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
return selectedPath.path;
}
}
}

@ -42,6 +42,7 @@ using Ombi.Core.Senders;
using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Sonarr;
using Ombi.Schedule.Ombi;
using Ombi.Store.Repository.Requests;
using PlexContentCacher = Ombi.Schedule.Jobs.Plex.PlexContentCacher;
@ -142,6 +143,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IPlexAvailabilityChecker, PlexAvailabilityChecker>();
services.AddTransient<IJobSetup, JobSetup>();
services.AddTransient<IRadarrCacher, RadarrCacher>();
services.AddTransient<ISonarrCacher, SonarrCacher>();
services.AddTransient<IOmbiAutomaticUpdater, OmbiAutomaticUpdater>();
services.AddTransient<IPlexUserImporter, PlexUserImporter>();
services.AddTransient<IEmbyUserImporter, EmbyUserImporter>();

@ -16,5 +16,7 @@ namespace Ombi.Helpers
public const string TopRatedMovies = nameof(TopRatedMovies);
public const string UpcomingMovies = nameof(UpcomingMovies);
public const string NowPlayingMovies = nameof(NowPlayingMovies);
public const string RadarrRootProfiles = nameof(RadarrRootProfiles);
public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles);
}
}

@ -11,10 +11,11 @@ namespace Ombi.Helpers
public static EventId Cacher => new EventId(2000);
public static EventId RadarrCacher => new EventId(2001);
public static EventId PlexEpisodeCacher => new EventId(2001);
public static EventId EmbyContentCacher => new EventId(2002);
public static EventId PlexUserImporter => new EventId(2003);
public static EventId EmbyUserImporter => new EventId(2004);
public static EventId PlexEpisodeCacher => new EventId(2002);
public static EventId EmbyContentCacher => new EventId(2003);
public static EventId PlexUserImporter => new EventId(2004);
public static EventId EmbyUserImporter => new EventId(2005);
public static EventId SonarrCacher => new EventId(2006);
public static EventId MovieSender => new EventId(3000);

@ -3,6 +3,7 @@ using Ombi.Schedule.Jobs;
using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Radarr;
using Ombi.Schedule.Jobs.Sonarr;
using Ombi.Schedule.Ombi;
namespace Ombi.Schedule
@ -11,7 +12,7 @@ namespace Ombi.Schedule
{
public JobSetup(IPlexContentCacher plexContentCacher, IRadarrCacher radarrCacher,
IOmbiAutomaticUpdater updater, IEmbyContentCacher embyCacher, IPlexUserImporter userImporter,
IEmbyUserImporter embyUserImporter)
IEmbyUserImporter embyUserImporter, ISonarrCacher cache)
{
PlexContentCacher = plexContentCacher;
RadarrCacher = radarrCacher;
@ -19,6 +20,7 @@ namespace Ombi.Schedule
EmbyContentCacher = embyCacher;
PlexUserImporter = userImporter;
EmbyUserImporter = embyUserImporter;
SonarrCacher = cache;
}
private IPlexContentCacher PlexContentCacher { get; }
@ -27,17 +29,17 @@ namespace Ombi.Schedule
private IPlexUserImporter PlexUserImporter { get; }
private IEmbyContentCacher EmbyContentCacher { get; }
private IEmbyUserImporter EmbyUserImporter { get; }
private ISonarrCacher SonarrCacher { get; }
public void Setup()
{
RecurringJob.AddOrUpdate(() => PlexContentCacher.CacheContent(), Cron.Hourly(20));
RecurringJob.AddOrUpdate(() => EmbyContentCacher.Start(), Cron.Hourly(5));
RecurringJob.AddOrUpdate(() => RadarrCacher.CacheContent(), Cron.Hourly(10));
RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), Cron.Daily(1));
RecurringJob.AddOrUpdate(() => RadarrCacher.CacheContent(), Cron.Hourly(15));
RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), Cron.Daily(5));
RecurringJob.AddOrUpdate(() => EmbyUserImporter.Start(), Cron.Daily);
RecurringJob.AddOrUpdate(() => Updater.Update(null), Cron.Daily(3));
//BackgroundJob.Enqueue(() => PlexUserImporter.Start());
RecurringJob.AddOrUpdate(() => Updater.Update(null), Cron.HourInterval(6));
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -28,8 +29,11 @@ namespace Ombi.Schedule.Jobs.Radarr
private ILogger<RadarrCacher> Logger { get; }
private readonly IOmbiContext _ctx;
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
public async Task CacheContent()
{
await SemaphoreSlim.WaitAsync();
try
{
var settings = RadarrSettings.GetSettings();
@ -48,7 +52,7 @@ namespace Ombi.Schedule.Jobs.Radarr
{
if (m.tmdbId > 0)
{
movieIds.Add(new RadarrCache { TheMovieDbId = m.tmdbId });
movieIds.Add(new RadarrCache {TheMovieDbId = m.tmdbId});
}
else
{
@ -70,6 +74,10 @@ namespace Ombi.Schedule.Jobs.Radarr
{
Logger.LogInformation(LoggingEvents.RadarrCacher, "Radarr is not setup, cannot cache episodes");
}
finally
{
SemaphoreSlim.Release();
}
}
public async Task<IEnumerable<RadarrCache>> GetCachedContent()

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Schedule.Jobs.Sonarr
{
public interface ISonarrCacher
{
Task Start();
}
}

@ -0,0 +1,116 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.Sonarr;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities;
namespace Ombi.Schedule.Jobs.Sonarr
{
public class SonarrCacher : ISonarrCacher
{
public SonarrCacher(ISettingsService<SonarrSettings> s, ISonarrApi api, ILogger<SonarrCacher> l, IOmbiContext ctx)
{
_settings = s;
_api = api;
_log = l;
}
private readonly ISettingsService<SonarrSettings> _settings;
private readonly ISonarrApi _api;
private readonly ILogger<SonarrCacher> _log;
private readonly IOmbiContext _ctx;
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
public async Task Start()
{
await SemaphoreSlim.WaitAsync();
try
{
var settings = await _settings.GetSettingsAsync();
if (!settings.Enabled)
{
return;
}
var series = await _api.GetSeries(settings.ApiKey, settings.FullUri);
if (series != null)
{
var ids = series.Select(x => x.tvdbId);
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache");
var entites = ids.Select(id => new SonarrCache {TvDbId = id}).ToList();
await _ctx.SonarrCache.AddRangeAsync(entites);
await _ctx.SaveChangesAsync();
}
}
catch (Exception e)
{
_log.LogError(LoggingEvents.SonarrCacher, e, "Exception when trying to cache Sonarr");
}
finally
{
SemaphoreSlim.Release();
}
}
//public void Queued()
//{
// var settings = SonarrSettings.GetSettings();
// if (settings.Enabled)
// {
// Job.SetRunning(true, JobNames.SonarrCacher);
// try
// {
// var series = SonarrApi.GetSeries(settings.ApiKey, settings.FullUri);
// if (series != null)
// {
// Cache.Set(CacheKeys.SonarrQueued, series, CacheKeys.TimeFrameMinutes.SchedulerCaching);
// }
// }
// catch (System.Exception ex)
// {
// Log.Error(ex, "Failed caching queued items from Sonarr");
// }
// finally
// {
// Job.Record(JobNames.SonarrCacher);
// Job.SetRunning(false, JobNames.SonarrCacher);
// }
// }
//}
//// we do not want to set here...
//public IEnumerable<SonarrCachedResult> QueuedIds()
//{
// var result = new List<SonarrCachedResult>();
// var series = Cache.Get<List<Series>>(CacheKeys.SonarrQueued);
// if (series != null)
// {
// foreach (var s in series)
// {
// var cached = new SonarrCachedResult { TvdbId = s.tvdbId };
// foreach (var season in s.seasons)
// {
// cached.Seasons.Add(new SonarrSeasons
// {
// SeasonNumber = season.seasonNumber,
// Monitored = season.monitored
// });
// }
// result.Add(cached);
// }
// }
// return result;
//}
}
}

@ -23,6 +23,7 @@
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Service\Ombi.Api.Service.csproj" />
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
</ItemGroup>

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

@ -38,6 +38,7 @@ namespace Ombi.Store.Context
public DbSet<Audit> Audit { get; set; }
public DbSet<Tokens> Tokens { get; set; }
public DbSet<SonarrCache> SonarrCache { get; set; }
public DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; }

@ -11,5 +11,8 @@ namespace Ombi.Store.Entities.Requests
public int? IssueId { get; set; }
[ForeignKey(nameof(IssueId))]
public List<MovieIssues> Issues { get; set; }
public int RootPathOverride { get; set; }
public int QualityOverride { get; set; }
}
}

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

@ -0,0 +1,747 @@
// <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("20171002113357_SonarrCacher")]
partial class SonarrCacher
{
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.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,53 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace Ombi.Store.Migrations
{
public partial class SonarrCacher : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "QualityOverride",
table: "MovieRequests",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "RootPathOverride",
table: "MovieRequests",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "SonarrCache",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
TvDbId = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SonarrCache", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SonarrCache");
migrationBuilder.DropColumn(
name: "QualityOverride",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "RootPathOverride",
table: "MovieRequests");
}
}
}

@ -460,6 +460,8 @@ namespace Ombi.Store.Migrations
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
@ -468,6 +470,8 @@ namespace Ombi.Store.Migrations
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
@ -529,6 +533,18 @@ namespace Ombi.Store.Migrations
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.Tokens", b =>
{
b.Property<int>("Id")

@ -45,8 +45,8 @@ namespace Ombi.TheMovieDbApi.Models
public ProductionCompanies[] production_companies { get; set; }
public ProductionCountries[] production_countries { get; set; }
public string release_date { get; set; }
public int revenue { get; set; }
public int runtime { get; set; }
public float revenue { get; set; }
public float runtime { get; set; }
public SpokenLanguages[] spoken_languages { get; set; }
public string status { get; set; }
public string tagline { get; set; }

@ -15,8 +15,8 @@
public float Popularity { get; set; }
public string PosterPath { get; set; }
public string ReleaseDate { get; set; }
public int Revenue { get; set; }
public int Runtime { get; set; }
public float Revenue { get; set; }
public float Runtime { get; set; }
public string Status { get; set; }
public string Tagline { get; set; }
public string Title { get; set; }

@ -79,6 +79,8 @@ export interface IRequestGrid<T> {
export interface IMovieRequests extends IFullBaseRequest {
theMovieDbId: number;
rootPathOverride: number;
qualityOverride: number;
}
export interface IFullBaseRequest extends IBaseRequest {

@ -41,7 +41,7 @@
</div>
</div>
<div class="col-md-3 col-md-push-4 vcenter">
<button [routerLink]="['/login', 'true']" class="btn btn-lg btn-success-outline">Contine</button>
<button [routerLink]="['/login', 'true']" class="btn btn-lg btn-success-outline">Continue</button>
</div>
</div>
</div>

@ -7,7 +7,7 @@ include the remember me checkbox
<div class="card card-container">
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="/images/ms-icon-150x150.png" /></div>
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-card" [src]="customizationSettings.logo" /></div>
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="center" [src]="customizationSettings.logo" /></div>
<p id="profile-name" class="profile-name-card"></p>
<form class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">

@ -3,7 +3,7 @@
<div class="card card-container">
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="/images/ms-icon-150x150.png" /></div>
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-card" [src]="customizationSettings.logo" /></div>
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="center" [src]="customizationSettings.logo" /></div>
<p id="profile-name" class="profile-name-card"></p>

@ -78,45 +78,32 @@
<div *ngIf="isAdmin">
<div *ngIf="!request.approved">
<form>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
<div *ngIf="request.hasQualities" class="btn-group btn-split">
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<!--<ul class="dropdown-menu">
{{#each qualities}}
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
{{/each}}
</ul>-->
</div>
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
</form>
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
{{#if_eq hasRootFolders true}}
<div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
{{#each rootFolders}}
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
{{/each}}
</ul>
</div>
{{/if_eq}}
</form>-->
<!--Radarr Root Folder-->
<div *ngIf="radarrRootFolders" class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline"><i class="fa fa-plus"></i> Change Root Folder</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let folder of radarrRootFolders"><a href="#" (click)="selectRootFolder(request, folder)">{{folder.path}}</a></li>
</ul>
</div>
<!--Radarr Quality Profiles -->
<div *ngIf="radarrProfiles" class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline"><i class="fa fa-plus"></i> Change Quality Profile</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let profile of radarrProfiles"><a href="#" (click)="selectQualityProfile(request, profile)">{{profile.name}}</a></li>
</ul>
</div>
<div *ngIf="!request.denied">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>

@ -5,9 +5,9 @@ import "rxjs/add/operator/map";
import { Subject } from "rxjs/Subject";
import { AuthService } from "../auth/auth.service";
import { NotificationService, RequestService } from "../services";
import { NotificationService, RadarrService, RequestService } from "../services";
import { IMovieRequests } from "../interfaces";
import { IMovieRequests, IRadarrProfile, IRadarrRootFolder } from "../interfaces";
@Component({
selector: "movie-requests",
@ -21,12 +21,16 @@ export class MovieRequestsComponent implements OnInit {
public isAdmin: boolean;
public radarrProfiles: IRadarrProfile[];
public radarrRootFolders: IRadarrRootFolder[];
private currentlyLoaded: number;
private amountToLoad: number;
constructor(private requestService: RequestService,
private auth: AuthService,
private notificationService: NotificationService) {
private notificationService: NotificationService,
private radarrService: RadarrService) {
this.searchChanged
.debounceTime(600) // Wait Xms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
@ -42,10 +46,13 @@ export class MovieRequestsComponent implements OnInit {
}
public ngOnInit() {
this.radarrService.getQualityProfilesFromSettings().subscribe(x => this.radarrProfiles = x);
this.radarrService.getRootFoldersFromSettings().subscribe(x => this.radarrRootFolders = x);
this.amountToLoad = 5;
this.currentlyLoaded = 5;
this.loadInit();
this.isAdmin = this.auth.hasRole("admin");
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
}
public loadMore() {
@ -80,6 +87,14 @@ export class MovieRequestsComponent implements OnInit {
this.updateRequest(request);
}
public selectRootFolder(searchResult: IMovieRequests, rootFolderSelected: IRadarrRootFolder) {
searchResult.rootPathOverride = rootFolderSelected.id;
}
public selectQualityProfile(searchResult: IMovieRequests, profileSelected: IRadarrProfile) {
searchResult.qualityOverride = profileSelected.id;
}
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
.subscribe(x => {

@ -1,38 +1,32 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import "rxjs/add/operator/takeUntil";
import { Subject } from "rxjs/Subject";
import { AuthService } from "../auth/auth.service";
import { NotificationService } from "../services";
import { RequestService } from "../services";
import { SearchService } from "../services";
import { NotificationService, RequestService, SearchService } from "../services";
import { IRequestEngineResult } from "../interfaces";
import { ISearchMovieResult } from "../interfaces";
import { IRequestEngineResult, ISearchMovieResult } from "../interfaces";
@Component({
selector: "movie-search",
templateUrl: "./moviesearch.component.html",
})
export class MovieSearchComponent implements OnInit, OnDestroy {
export class MovieSearchComponent implements OnInit {
public searchText: string;
public searchChanged: Subject<string> = new Subject<string>();
public movieResults: ISearchMovieResult[];
public result: IRequestEngineResult;
public searchApplied = false;
private subscriptions = new Subject<void>();
constructor(private searchService: SearchService, private requestService: RequestService,
private notificationService: NotificationService, private authService: AuthService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.takeUntil(this.subscriptions)
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
@ -40,7 +34,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
return;
}
this.searchService.searchMovie(this.searchText)
.takeUntil(this.subscriptions)
.subscribe(x => {
this.movieResults = x;
this.searchApplied = true;
@ -72,7 +65,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
}
this.requestService.requestMovie(searchResult)
.takeUntil(this.subscriptions)
.subscribe(x => {
this.result = x;
@ -90,7 +82,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
public popularMovies() {
this.clearResults();
this.searchService.popularMovies()
.takeUntil(this.subscriptions)
.subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
@ -99,7 +90,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
public nowPlayingMovies() {
this.clearResults();
this.searchService.nowPlayingMovies()
.takeUntil(this.subscriptions)
.subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
@ -108,7 +98,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
public topRatedMovies() {
this.clearResults();
this.searchService.topRatedMovies()
.takeUntil(this.subscriptions)
.subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
@ -117,23 +106,16 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
public upcomingMovies() {
this.clearResults();
this.searchService.upcomignMovies()
.takeUntil(this.subscriptions)
.subscribe(x => {
this.movieResults = x;
this.getExtaInfo();
});
}
public ngOnDestroy() {
this.subscriptions.next();
this.subscriptions.complete();
}
private getExtaInfo() {
private getExtaInfo() {
this.movieResults.forEach((val, index) => {
this.searchService.getMovieInformation(val.id)
.takeUntil(this.subscriptions)
.subscribe(m => this.updateItem(val, m));
});
}

@ -19,4 +19,11 @@ export class RadarrService extends ServiceAuthHelpers {
public getQualityProfiles(settings: IRadarrSettings): Observable<IRadarrProfile[]> {
return this.http.post(`${this.url}/Profiles/`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
}
public getRootFoldersFromSettings(): Observable<IRadarrRootFolder[]> {
return this.http.get(`${this.url}/RootFolders/`, { headers: this.headers }).map(this.extractData);
}
public getQualityProfilesFromSettings(): Observable<IRadarrProfile[]> {
return this.http.get(`${this.url}/Profiles/`, { headers: this.headers }).map(this.extractData);
}
}

@ -102,7 +102,7 @@ export class UserManagementComponent implements OnInit {
public runImporter(): void {
this.jobService.runPlexImporter().subscribe();
this.jobService.runPlexImporter().subscribe();
this.jobService.runEmbyImporter().subscribe();
}
private filter(query: string, users: IUsersModel[]): IUsersModel[] {

@ -1,28 +1,33 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Api.Radarr;
using Ombi.Api.Radarr.Models;
using Ombi.Attributes;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
namespace Ombi.Controllers.External
{
[Admin]
[PowerUser]
[ApiV1]
[Produces("application/json")]
public class RadarrController : Controller
{
public RadarrController(IRadarrApi radarr, ISettingsService<RadarrSettings> settings)
public RadarrController(IRadarrApi radarr, ISettingsService<RadarrSettings> settings,
IMemoryCache mem)
{
RadarrApi = radarr;
RadarrSettings = settings;
Cache = mem;
}
private IRadarrApi RadarrApi { get; }
private ISettingsService<RadarrSettings> RadarrSettings { get; }
private IMemoryCache Cache { get; }
/// <summary>
/// Gets the Radarr profiles.
/// </summary>
@ -44,5 +49,37 @@ namespace Ombi.Controllers.External
{
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
/// <summary>
/// Gets the Radarr profiles using the saved settings
/// <remarks>The data is cached for an hour</remarks>
/// </summary>
/// <returns></returns>
[HttpGet("Profiles")]
public async Task<IEnumerable<RadarrProfile>> GetProfiles()
{
return await Cache.GetOrCreate(CacheKeys.RadarrQualityProfiles, async entry =>
{
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
var settings = await RadarrSettings.GetSettingsAsync();
return await RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri);
});
}
/// <summary>
/// Gets the Radar root folders using the saved settings.
/// <remarks>The data is cached for an hour</remarks>
/// </summary>
/// <returns></returns>
[HttpGet("RootFolders")]
public async Task<IEnumerable<RadarrRootFolder>> GetRootFolders()
{
return await Cache.GetOrCreate(CacheKeys.RadarrRootProfiles, async entry =>
{
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
var settings = await RadarrSettings.GetSettingsAsync();
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
});
}
}
}

@ -7,6 +7,7 @@ using AutoMapper;
using Hangfire;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.PlatformAbstractions;
using Ombi.Api.Emby;
using Ombi.Attributes;
@ -52,7 +53,8 @@ namespace Ombi.Controllers
IEmbyApi embyApi,
IPlexContentCacher cacher,
IEmbyContentCacher embyCacher,
IRadarrCacher radarrCacher)
IRadarrCacher radarrCacher,
IMemoryCache memCache)
{
SettingsResolver = resolver;
Mapper = mapper;
@ -61,6 +63,7 @@ namespace Ombi.Controllers
_plexContentCacher = cacher;
_embyContentCacher = embyCacher;
_radarrCacher = radarrCacher;
_cache = memCache;
}
private ISettingsResolver SettingsResolver { get; }
@ -70,6 +73,7 @@ namespace Ombi.Controllers
private readonly IPlexContentCacher _plexContentCacher;
private readonly IEmbyContentCacher _embyContentCacher;
private readonly IRadarrCacher _radarrCacher;
private readonly IMemoryCache _cache;
/// <summary>
/// Gets the Ombi settings.
@ -290,6 +294,8 @@ namespace Ombi.Controllers
var result = await Save(settings);
if (result)
{
_cache.Remove(CacheKeys.RadarrRootProfiles);
_cache.Remove(CacheKeys.RadarrQualityProfiles);
BackgroundJob.Enqueue(() => _radarrCacher.CacheContent());
}
return result;

@ -138,11 +138,20 @@ namespace Ombi
});
}
app.UseHangfireServer();
app.UseHangfireDashboard("/hangfire", new DashboardOptions
var ombiService =
app.ApplicationServices.GetService<ISettingsService<OmbiSettings>>();
var settings = ombiService.GetSettings();
if (settings.BaseUrl.HasValue())
{
Authorization = new[] { new HangfireAuthorizationFilter() }
});
app.UsePathBase(settings.BaseUrl);
}
app.UseHangfireServer();
app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire",
new DashboardOptions
{
Authorization = new[] {new HangfireAuthorizationFilter()}
});
// Setup the scheduler
var jobSetup = app.ApplicationServices.GetService<IJobSetup>();
@ -151,14 +160,6 @@ namespace Ombi
var provider = new FileExtensionContentTypeProvider { Mappings = { [".map"] = "application/octet-stream" } };
var ombiService =
app.ApplicationServices.GetService<ISettingsService<OmbiSettings>>();
var settings = ombiService.GetSettings();
if (settings.BaseUrl.HasValue())
{
app.UsePathBase(settings.BaseUrl);
}
app.UseStaticFiles(new StaticFileOptions()
{
ContentTypeProvider = provider,

Loading…
Cancel
Save