diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index fdfec7760..a80c02d67 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -10,10 +10,11 @@ namespace Ombi.Api.Plex Task GetStatus(string authToken, string uri); Task SignIn(UserRequest user); Task GetServer(string authToken); - Task GetLibrarySections(string authToken, string plexFullHost); - Task GetLibrary(string authToken, string plexFullHost, string libraryId); + Task GetLibrarySections(string authToken, string plexFullHost); + Task GetLibrary(string authToken, string plexFullHost, string libraryId); Task GetEpisodeMetaData(string authToken, string host, string ratingKey); Task GetMetadata(string authToken, string plexFullHost, string itemId); Task GetSeasons(string authToken, string plexFullHost, string ratingKey); + Task GetAllEpisodes(string authToken, string host, string section, int start, int retCount); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/PlexLibraries.cs b/src/Ombi.Api.Plex/Models/PlexContainer.cs similarity index 97% rename from src/Ombi.Api.Plex/Models/PlexLibraries.cs rename to src/Ombi.Api.Plex/Models/PlexContainer.cs index 996a69742..7a0b10efc 100644 --- a/src/Ombi.Api.Plex/Models/PlexLibraries.cs +++ b/src/Ombi.Api.Plex/Models/PlexContainer.cs @@ -26,7 +26,7 @@ #endregion namespace Ombi.Api.Plex.Models { - public class PlexLibraries + public class PlexContainer { public Mediacontainer MediaContainer { get; set; } } diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 43ee1a31b..34748096d 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -60,18 +60,18 @@ namespace Ombi.Api.Plex return await Api.Request(request); } - public async Task GetLibrarySections(string authToken, string plexFullHost) + public async Task GetLibrarySections(string authToken, string plexFullHost) { var request = new Request("library/sections", plexFullHost, HttpMethod.Get); AddHeaders(request, authToken); - return await Api.Request(request); + return await Api.Request(request); } - public async Task GetLibrary(string authToken, string plexFullHost, string libraryId) + public async Task GetLibrary(string authToken, string plexFullHost, string libraryId) { var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get); AddHeaders(request, authToken); - return await Api.Request(request); + return await Api.Request(request); } /// @@ -106,6 +106,26 @@ namespace Ombi.Api.Plex return await Api.Request(request); } + /// + /// Gets all episodes. + /// + /// The authentication token. + /// The host. + /// The section. + /// The start count. + /// The return count, how many items you want returned. + /// + public async Task GetAllEpisodes(string authToken, string host, string section, int start, int retCount) + { + var request = new Request($"/library/sections/{section}/all", host, HttpMethod.Get); + + request.AddQueryString("type", "4"); + AddLimitHeaders(request, start, retCount); + AddHeaders(request, authToken); + + return await Api.Request(request); + } + /// /// Adds the required headers and also the authorization header /// @@ -129,5 +149,11 @@ namespace Ombi.Api.Plex request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); request.AddHeader("Accept", "application/json"); } + + private void AddLimitHeaders(Request request, int from, int to) + { + request.AddHeader("X-Plex-Container-Start", from.ToString()); + request.AddHeader("X-Plex-Container-Size", to.ToString()); + } } } diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index 716ba4bf5..9ec66eb70 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -98,7 +98,7 @@ namespace Ombi.Api.Radarr } catch (JsonSerializationException jse) { - Logger.LogError(LoggingEvents.RadarrApiException, jse, "Error When adding movie to Radarr"); + Logger.LogError(LoggingEvents.RadarrApi, jse, "Error When adding movie to Radarr"); } return null; } diff --git a/src/Ombi.Api.TvMaze/Ombi.Api.TvMaze.csproj b/src/Ombi.Api.TvMaze/Ombi.Api.TvMaze.csproj index 761bb6ee1..c173d1dd7 100644 --- a/src/Ombi.Api.TvMaze/Ombi.Api.TvMaze.csproj +++ b/src/Ombi.Api.TvMaze/Ombi.Api.TvMaze.csproj @@ -9,10 +9,4 @@ - - - ..\..\..\..\..\.nuget\packages\microsoft.extensions.logging.abstractions\1.1.1\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll - - - \ No newline at end of file diff --git a/src/Ombi.Api.TvMaze/TvMazeApi.cs b/src/Ombi.Api.TvMaze/TvMazeApi.cs index 3324a5b63..9aa547483 100644 --- a/src/Ombi.Api.TvMaze/TvMazeApi.cs +++ b/src/Ombi.Api.TvMaze/TvMazeApi.cs @@ -61,7 +61,7 @@ namespace Ombi.Api.TvMaze } catch (Exception e) { - Logger.LogError(LoggingEvents.ApiException, e, "Exception when calling ShowLookupByTheTvDbId with id:{0}",theTvDbId); + Logger.LogError(LoggingEvents.Api, e, "Exception when calling ShowLookupByTheTvDbId with id:{0}",theTvDbId); return null; } } diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index 6e168d63f..de1ec8caf 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -45,7 +45,7 @@ namespace Ombi.Api { if (!httpResponseMessage.IsSuccessStatusCode) { - Logger.LogError(LoggingEvents.ApiException, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); + Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); } // do something with the response var data = httpResponseMessage.Content; @@ -89,7 +89,7 @@ namespace Ombi.Api { if (!httpResponseMessage.IsSuccessStatusCode) { - Logger.LogError(LoggingEvents.ApiException, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); + Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); } // do something with the response var data = httpResponseMessage.Content; @@ -123,7 +123,7 @@ namespace Ombi.Api { if (!httpResponseMessage.IsSuccessStatusCode) { - Logger.LogError(LoggingEvents.ApiException, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); + Logger.LogError(LoggingEvents.Api, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}"); } } } diff --git a/src/Ombi.Core/Senders/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs index 49d5d982a..051081df2 100644 --- a/src/Ombi.Core/Senders/MovieSender.cs +++ b/src/Ombi.Core/Senders/MovieSender.cs @@ -68,7 +68,7 @@ namespace Ombi.Core if (!string.IsNullOrEmpty(result.Error?.message)) { - Log.LogError(LoggingEvents.RadarrCacherException,result.Error.message); + Log.LogError(LoggingEvents.RadarrCacher,result.Error.message); return new MovieSenderResult { Success = false, Message = result.Error.message, MovieSent = false }; } if (!string.IsNullOrEmpty(result.title)) diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index d12b4a26c..b15d37700 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -35,6 +35,7 @@ using Ombi.Api.Service; using Ombi.Api.Slack; using Ombi.Core.Rule.Interfaces; using Ombi.Core.Senders; +using Ombi.Schedule.Jobs.Plex; using Ombi.Schedule.Ombi; using Ombi.Store.Repository.Requests; using PlexContentCacher = Ombi.Schedule.Jobs.Plex.PlexContentCacher; @@ -121,6 +122,7 @@ namespace Ombi.DependencyInjection public static void RegisterJobs(this IServiceCollection services) { services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Ombi.Helpers/LoggingEvents.cs b/src/Ombi.Helpers/LoggingEvents.cs index 2921df654..9c45e0831 100644 --- a/src/Ombi.Helpers/LoggingEvents.cs +++ b/src/Ombi.Helpers/LoggingEvents.cs @@ -6,11 +6,12 @@ namespace Ombi.Helpers { public static EventId Authentication => new EventId(500); - public static EventId ApiException => new EventId(1000); - public static EventId RadarrApiException => new EventId(1001); + public static EventId Api => new EventId(1000); + public static EventId RadarrApi => new EventId(1001); - public static EventId CacherException => new EventId(2000); - public static EventId RadarrCacherException => new EventId(2001); + public static EventId Cacher => new EventId(2000); + public static EventId RadarrCacher => new EventId(2001); + public static EventId PlexEpisodeCacher => new EventId(2001); public static EventId MovieSender => new EventId(3000); diff --git a/src/Ombi.Schedule/Jobs/Plex/IPlexEpisodeCacher.cs b/src/Ombi.Schedule/Jobs/Plex/IPlexEpisodeCacher.cs new file mode 100644 index 000000000..b6fdc9b41 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Plex/IPlexEpisodeCacher.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Plex +{ + public interface IPlexEpisodeCacher + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentCacher.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentCacher.cs index d90d419b4..cda4b1d21 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentCacher.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentCacher.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Hangfire; using Microsoft.Extensions.Logging; using Ombi.Api.Plex; using Ombi.Api.Plex.Models; @@ -42,18 +43,21 @@ namespace Ombi.Schedule.Jobs.Plex { public class PlexContentCacher : IPlexContentCacher { - public PlexContentCacher(ISettingsService plex, IPlexApi plexApi, ILogger logger, IPlexContentRepository repo) + public PlexContentCacher(ISettingsService plex, IPlexApi plexApi, ILogger logger, IPlexContentRepository repo, + IPlexEpisodeCacher epsiodeCacher) { Plex = plex; PlexApi = plexApi; Logger = logger; Repo = repo; + EpisodeCacher = epsiodeCacher; } private ISettingsService Plex { get; } private IPlexApi PlexApi { get; } private ILogger Logger { get; } private IPlexContentRepository Repo { get; } + private IPlexEpisodeCacher EpisodeCacher { get; } public async Task CacheContent() { @@ -71,10 +75,12 @@ namespace Ombi.Schedule.Jobs.Plex try { await StartTheCache(plexSettings); + + BackgroundJob.Enqueue(() => EpisodeCacher.Start()); } catch (Exception e) { - Logger.LogWarning(LoggingEvents.CacherException, e, "Exception thrown when attempting to cache the Plex Content"); + Logger.LogWarning(LoggingEvents.Cacher, e, "Exception thrown when attempting to cache the Plex Content"); } } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeCacher.cs b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeCacher.cs new file mode 100644 index 000000000..d65e7166a --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Plex/PlexEpisodeCacher.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Hangfire.Common; +using Microsoft.Extensions.Logging; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models; +using Ombi.Api.Plex.Models.Server; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; +using Ombi.Store.Entities; +using Ombi.Store.Repository; +using Serilog; + +namespace Ombi.Schedule.Jobs.Plex +{ + public class PlexEpisodeCacher : IPlexEpisodeCacher + { + public PlexEpisodeCacher(ISettingsService s, ILogger log, IPlexApi plexApi, + IPlexContentRepository repo) + { + _settings = s; + _log = log; + _api = plexApi; + _repo = repo; + } + + private readonly ISettingsService _settings; + private readonly ILogger _log; + private readonly IPlexApi _api; + private readonly IPlexContentRepository _repo; + + public async Task Start() + { + try + { + var s = await _settings.GetSettingsAsync(); + if (!s.Enable) + { + return; + } + + foreach (var server in s.Servers) + { + + await Cache(server); + } + } + catch (Exception e) + { + _log.LogError(LoggingEvents.Cacher, e, "Caching Episodes Failed"); + } + } + + private async Task Cache(PlexServers settings) + { + if (!Validate(settings)) + { + return; + } + + // Get the librarys and then get the tv section + var sections = await _api.GetLibrarySections(settings.PlexAuthToken, settings.FullUri); + + // Filter the libSections + var tvSections = sections.MediaContainer.Directory.Where(x => x.type.Equals(Jobs.PlexContentCacher.PlexMediaType.Show.ToString(), StringComparison.CurrentCultureIgnoreCase)); + + foreach (var section in tvSections) + { + if (settings.PlexSelectedLibraries.Any()) + { + // Make sure we have enabled this + var keys = settings.PlexSelectedLibraries.Where(x => x.Enabled).Select(x => x.Key.ToString()).ToList(); + if (!keys.Contains(section.key)) + { + // We are not monitoring this lib + continue; + } + + // Get the episodes + await GetEpisodes(settings, section); + } + } + + } + + private async Task GetEpisodes(PlexServers settings, Directory section) + { + + // Get the first 50 + var currentPosition = 0; + var ResultCount = 50; + var episodes = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, ResultCount); + + _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Total Epsiodes found for {episodes.MediaContainer.librarySectionTitle} = {episodes.MediaContainer.size}"); + + await ProcessEpsiodes(episodes); + currentPosition += ResultCount; + + while (currentPosition < episodes.MediaContainer.size) + { + var ep = await _api.GetAllEpisodes(settings.PlexAuthToken, settings.FullUri, section.key, currentPosition, + ResultCount); + await ProcessEpsiodes(ep); + _log.LogInformation(LoggingEvents.PlexEpisodeCacher, $"Processed {ResultCount} more episodes. Total Remaining {currentPosition - episodes.MediaContainer.size}"); + currentPosition += ResultCount; + } + + } + + private async Task ProcessEpsiodes(PlexContainer episodes) + { + var ep = new HashSet(); + foreach (var episode in episodes.MediaContainer.Metadata) + { + // I don't think we need to get the metadata, we only need to get the metadata if we need the provider id (TheTvDbid). Why do we need it for episodes? + // We have the parent and grandparent rating keys to link up to the season and series + //var metadata = _api.GetEpisodeMetaData(server.PlexAuthToken, server.FullUri, episode.ratingKey); + + ep.Add(new PlexEpisode + { + EpisodeNumber = episode.index, + SeasonNumber = episode.parentIndex, + GrandparentKey = episode.grandparentKey, + ParentKey = episode.parentKey, + Key = episode.key, + Title = episode.title + }); + } + + await _repo.AddRange(ep); + } + + private bool Validate(PlexServers settings) + { + if (string.IsNullOrEmpty(settings.PlexAuthToken)) + { + return false ; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Radarr/RadarrCacher.cs b/src/Ombi.Schedule/Jobs/Radarr/RadarrCacher.cs index fdacd6307..f8437d502 100644 --- a/src/Ombi.Schedule/Jobs/Radarr/RadarrCacher.cs +++ b/src/Ombi.Schedule/Jobs/Radarr/RadarrCacher.cs @@ -59,7 +59,7 @@ namespace Ombi.Schedule.Jobs.Radarr } catch (System.Exception ex) { - Logger.LogError(LoggingEvents.CacherException, ex, "Failed caching queued items from Radarr"); + Logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Radarr"); } } } diff --git a/src/Ombi.Settings/Settings/Models/AuthenticationSettings.cs b/src/Ombi.Settings/Settings/Models/AuthenticationSettings.cs new file mode 100644 index 000000000..7c2725ec5 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/AuthenticationSettings.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Ombi.Settings.Settings.Models +{ + public class AuthenticationSettings + { + /// + /// This determins if Plex and/or Emby users can log into Ombi + /// + /// + /// true if [allow external users to authenticate]; otherwise, false. + /// + public bool AllowExternalUsersToAuthenticate { get; set; } + + // Password Options + public bool RequireDigit { get; set; } + public int RequiredLength { get; set; } + public bool RequireLowercase { get; set; } + public bool RequireNonAlphanumeric { get; set; } + public bool RequireUppercase { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index 7363e8fb1..94963a03d 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -17,7 +17,7 @@ namespace Ombi.Core.Settings.Models.External public string PlexAuthToken { get; set; } public string MachineIdentifier { get; set; } - public List PlexSelectedLibraries { get; set; } + public List PlexSelectedLibraries { get; set; } = new List(); } public class PlexSelectedLibraries { diff --git a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs index 3c989dc2e..da13109b0 100644 --- a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs +++ b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs @@ -7,6 +7,5 @@ public bool Wizard { get; set; } public string ApiKey { get; set; } - public bool AllowExternalUsersToAuthenticate { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index c7e613a7c..af77a73d3 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -15,6 +15,7 @@ namespace Ombi.Store.Context Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); DbSet Settings { get; set; } DbSet PlexContent { get; set; } + DbSet PlexEpisode { get; set; } DbSet RadarrCache { get; set; } DatabaseFacade Database { get; } EntityEntry Entry(T entry) where T : class; diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index e71880db7..057a6e91b 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -26,6 +26,7 @@ namespace Ombi.Store.Context public DbSet NotificationTemplates { get; set; } public DbSet Settings { get; set; } public DbSet PlexContent { get; set; } + public DbSet PlexEpisode { get; set; } public DbSet RadarrCache { get; set; } public DbSet MovieRequests { get; set; } diff --git a/src/Ombi.Store/Entities/PlexEpisode.cs b/src/Ombi.Store/Entities/PlexEpisode.cs new file mode 100644 index 000000000..161184f63 --- /dev/null +++ b/src/Ombi.Store/Entities/PlexEpisode.cs @@ -0,0 +1,27 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Ombi.Store.Entities +{ + [Table("PlexEpisode")] + public class PlexEpisode : Entity + { + public int EpisodeNumber { get; set; } + public int SeasonNumber { get; set; } + public string Key { get; set; } // RatingKey + public string Title { get; set; } + /// + /// The Show key + /// + /// + /// The parent key. + /// + public string ParentKey { get; set; } + /// + /// The Series key + /// + /// + /// The grandparent key. + /// + public string GrandparentKey { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20170811145836_Inital.Designer.cs b/src/Ombi.Store/Migrations/20170823144220_Inital.Designer.cs similarity index 96% rename from src/Ombi.Store/Migrations/20170811145836_Inital.Designer.cs rename to src/Ombi.Store/Migrations/20170823144220_Inital.Designer.cs index 80c66cbfd..5af0be140 100644 --- a/src/Ombi.Store/Migrations/20170811145836_Inital.Designer.cs +++ b/src/Ombi.Store/Migrations/20170823144220_Inital.Designer.cs @@ -10,7 +10,7 @@ using Ombi.Helpers; namespace Ombi.Store.Migrations { [DbContext(typeof(OmbiContext))] - [Migration("20170811145836_Inital")] + [Migration("20170823144220_Inital")] partial class Inital { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -271,6 +271,28 @@ namespace Ombi.Store.Migrations b.ToTable("PlexContent"); }); + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.ToTable("PlexEpisode"); + }); + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => { b.Property("Id") diff --git a/src/Ombi.Store/Migrations/20170811145836_Inital.cs b/src/Ombi.Store/Migrations/20170823144220_Inital.cs similarity index 96% rename from src/Ombi.Store/Migrations/20170811145836_Inital.cs rename to src/Ombi.Store/Migrations/20170823144220_Inital.cs index d366494c9..f65bbb76f 100644 --- a/src/Ombi.Store/Migrations/20170811145836_Inital.cs +++ b/src/Ombi.Store/Migrations/20170823144220_Inital.cs @@ -144,6 +144,24 @@ namespace Ombi.Store.Migrations table.PrimaryKey("PK_PlexContent", x => x.Id); }); + migrationBuilder.CreateTable( + name: "PlexEpisode", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + EpisodeNumber = table.Column(nullable: false), + GrandparentKey = table.Column(nullable: true), + Key = table.Column(nullable: true), + ParentKey = table.Column(nullable: true), + SeasonNumber = table.Column(nullable: false), + Title = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlexEpisode", x => x.Id); + }); + migrationBuilder.CreateTable( name: "RadarrCache", columns: table => new @@ -596,6 +614,9 @@ namespace Ombi.Store.Migrations migrationBuilder.DropTable( name: "NotificationTemplates"); + migrationBuilder.DropTable( + name: "PlexEpisode"); + migrationBuilder.DropTable( name: "PlexSeasonsContent"); diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index ed875997e..a25e0d1b2 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -270,6 +270,28 @@ namespace Ombi.Store.Migrations b.ToTable("PlexContent"); }); + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.ToTable("PlexEpisode"); + }); + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => { b.Property("Id") diff --git a/src/Ombi.Store/Repository/IPlexContentRepository.cs b/src/Ombi.Store/Repository/IPlexContentRepository.cs index 41e9456ac..ac7d60155 100644 --- a/src/Ombi.Store/Repository/IPlexContentRepository.cs +++ b/src/Ombi.Store/Repository/IPlexContentRepository.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Ombi.Store.Entities; @@ -13,5 +14,9 @@ namespace Ombi.Store.Repository Task Get(string providerId); Task GetByKey(string key); Task Update(PlexContent existingContent); + IQueryable GetAllEpisodes(); + Task Add(PlexEpisode content); + Task GetEpisodeByKey(string key); + Task AddRange(IEnumerable content); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index 48ee88e4e..232513863 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -26,6 +26,7 @@ #endregion using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Store.Context; @@ -81,5 +82,26 @@ namespace Ombi.Store.Repository Db.PlexContent.Update(existingContent); await Db.SaveChangesAsync(); } + + public IQueryable GetAllEpisodes() + { + return Db.PlexEpisode.AsQueryable(); + } + + public async Task Add(PlexEpisode content) + { + await Db.PlexEpisode.AddAsync(content); + await Db.SaveChangesAsync(); + return content; + } + public async Task GetEpisodeByKey(string key) + { + return await Db.PlexEpisode.FirstOrDefaultAsync(x => x.Key == key); + } + public async Task AddRange(IEnumerable content) + { + Db.PlexEpisode.AddRange(content); + await Db.SaveChangesAsync(); + } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/SettingsJsonRepository.cs b/src/Ombi.Store/Repository/SettingsJsonRepository.cs index 89958665f..77c06221c 100644 --- a/src/Ombi.Store/Repository/SettingsJsonRepository.cs +++ b/src/Ombi.Store/Repository/SettingsJsonRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -50,6 +51,10 @@ namespace Ombi.Store.Repository public GlobalSettings Get(string pageName) { var entity = Db.Settings.FirstOrDefault(x => x.SettingsName == pageName); + if (entity == null) + { + throw new ArgumentNullException($"The setting {pageName} does not exist"); + } Db.Entry(entity).Reload(); return entity; } diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index d140a8991..89399e1c8 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -86,4 +86,16 @@ export interface ILandingPageSettings extends ISettings { export interface ICustomizationSettings extends ISettings { applicationName: string, logo: string, +} + +export interface IAuthenticationSettings extends ISettings { + + allowExternalUsersToAuthenticate: boolean, + // Password + + requiredDigit: boolean, + requiredLength: number, + requiredLowercase: boolean, + requireNonAlphanumeric: boolean, + requireUppercase:boolean, } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index 3b5c9644b..ef75a43f7 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -11,7 +11,8 @@ import { ISonarrSettings, ILandingPageSettings, ICustomizationSettings, - IRadarrSettings + IRadarrSettings, + IAuthenticationSettings } from '../interfaces/ISettings'; import { IEmailNotificationSettings, @@ -80,6 +81,17 @@ export class SettingsService extends ServiceAuthHelpers { .map(this.extractData).catch(this.handleError); } + + getAuthentication(): Observable { + return this.httpAuth.get(`${this.url}/Authentication`).map(this.extractData) + .catch(this.handleError); + } + + saveAuthentication(settings: IAuthenticationSettings): Observable { + return this.httpAuth.post(`${this.url}/Authentication`, JSON.stringify(settings), { headers: this.headers }) + .map(this.extractData).catch(this.handleError); + } + // Using http since we need it not to be authenticated to get the landing page settings getLandingPage(): Observable { return this.nonAuthHttp.get(`${this.url}/LandingPage`).map(this.extractData).catch(this.handleError); diff --git a/src/Ombi/Controllers/BaseV1ApiController.cs b/src/Ombi/Controllers/ApiV1Attribute.cs similarity index 100% rename from src/Ombi/Controllers/BaseV1ApiController.cs rename to src/Ombi/Controllers/ApiV1Attribute.cs diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 170df0451..ec62d36f4 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -47,7 +47,7 @@ namespace Ombi.Controllers.External settings.Enable = true; settings.Servers = new List { new PlexServers{ -PlexAuthToken = result.user.authentication_token, + PlexAuthToken = result.user.authentication_token, Id = new Random().Next(), Ip = servers.LocalAddresses.Split(new []{','}, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(), MachineIdentifier = servers.MachineIdentifier, @@ -87,7 +87,7 @@ PlexAuthToken = result.user.authentication_token, /// The settings. /// [HttpPost("Libraries")] - public async Task GetPlexLibraries([FromBody] PlexServers settings) + public async Task GetPlexLibraries([FromBody] PlexServers settings) { var libs = await PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri); diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 49cf3fc77..a8946eab0 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -192,6 +192,27 @@ namespace Ombi.Controllers return await Get(); } + /// + /// Save the Authentication settings. + /// + /// The settings. + /// + [HttpPost("authentication")] + public async Task AuthenticationsSettings([FromBody]AuthenticationSettings settings) + { + return await Save(settings); + } + + /// + /// Gets the Authentication Settings. + /// + /// + [HttpGet("authentication")] + public async Task AuthenticationsSettings() + { + return await Get(); + } + /// /// Save the Radarr settings. ///