diff --git a/.azuredevops/pipelines/templates/variables.yml b/.azuredevops/pipelines/templates/variables.yml index 205e68eaa..e90ecc049 100644 --- a/.azuredevops/pipelines/templates/variables.yml +++ b/.azuredevops/pipelines/templates/variables.yml @@ -27,4 +27,4 @@ variables: value: "4.0.$(Build.BuildId)" - name: isMain - value: $[or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))] \ No newline at end of file + value: $[or(eq(variables['Build.SourceBranch'], 'refs/heads/develop'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))] diff --git a/.gitignore b/.gitignore index 1a1a229b8..fc31d399b 100644 --- a/.gitignore +++ b/.gitignore @@ -247,5 +247,6 @@ _Pvt_Extensions # Ignore local vscode config *.vscode /src/Ombi/database.json +/src/Ombi/databases.json /src/Ombi/healthchecksdb /src/Ombi/ClientApp/package-lock.json diff --git a/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs b/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs index 74d5c75ea..6eb2f1c5a 100644 --- a/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs +++ b/src/Ombi.Api.Radarr/Models/V2/MovieResponse.cs @@ -1,39 +1,122 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Net.Mime; namespace Ombi.Api.Radarr.Models -{ +{ public class MovieResponse { public string title { get; set; } + public string originalTitle { get; set; } + public Alternatetitle[] alternateTitles { get; set; } + public int secondaryYearSourceId { get; set; } public string sortTitle { get; set; } - public double sizeOnDisk { get; set; } + public long sizeOnDisk { get; set; } public string status { get; set; } public string overview { get; set; } - public string inCinemas { get; set; } - public string physicalRelease { get; set; } - public List images { get; set; } + public DateTime inCinemas { get; set; } + public DateTime physicalRelease { get; set; } + public DateTime digitalRelease { get; set; } + public Image[] images { get; set; } public string website { get; set; } - public bool downloaded { get; set; } public int year { get; set; } public bool hasFile { get; set; } public string youTubeTrailerId { get; set; } public string studio { get; set; } public string path { get; set; } - public int profileId { get; set; } - public string minimumAvailability { get; set; } + public int qualityProfileId { get; set; } public bool monitored { get; set; } + public string minimumAvailability { get; set; } + public bool isAvailable { get; set; } + public string folderName { get; set; } public int runtime { get; set; } - public string lastInfoSync { get; set; } public string cleanTitle { get; set; } public string imdbId { get; set; } public int tmdbId { get; set; } public string titleSlug { get; set; } - public List genres { get; set; } - public List tags { get; set; } - public string added { get; set; } + public string certification { get; set; } + public string[] genres { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } public Ratings ratings { get; set; } - //public List alternativeTitles { get; set; } - public int qualityProfileId { get; set; } + public Moviefile movieFile { get; set; } + public Collection collection { get; set; } + public int id { get; set; } + } + + + public class Moviefile + { + public int movieId { get; set; } + public string relativePath { get; set; } + public string path { get; set; } + public long size { get; set; } + public DateTime dateAdded { get; set; } + public string sceneName { get; set; } + public int indexerFlags { get; set; } + public V3.Quality quality { get; set; } + public Mediainfo mediaInfo { get; set; } + public string originalFilePath { get; set; } + public bool qualityCutoffNotMet { get; set; } + public Language[] languages { get; set; } + public string releaseGroup { get; set; } + public string edition { get; set; } + public int id { get; set; } + } + + public class Revision + { + public int version { get; set; } + public int real { get; set; } + public bool isRepack { get; set; } + } + + public class Mediainfo + { + public string audioAdditionalFeatures { get; set; } + public int audioBitrate { get; set; } + public float audioChannels { get; set; } + public string audioCodec { get; set; } + public string audioLanguages { get; set; } + public int audioStreamCount { get; set; } + public int videoBitDepth { get; set; } + public int videoBitrate { get; set; } + public string videoCodec { get; set; } + public float videoFps { get; set; } + public string resolution { get; set; } + public string runTime { get; set; } + public string scanType { get; set; } + public string subtitles { get; set; } + } + + public class Language + { + public int id { get; set; } + public string name { get; set; } + } + + public class Collection + { + public string name { get; set; } + public int tmdbId { get; set; } + public object[] images { get; set; } + } + + public class Alternatetitle + { + public string sourceType { get; set; } + public int movieId { get; set; } + public string title { get; set; } + public int sourceId { get; set; } + public int votes { get; set; } + public int voteCount { get; set; } + public Language1 language { get; set; } + public int id { get; set; } + } + + public class Language1 + { public int id { get; set; } + public string name { get; set; } } -} +} \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs b/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs index b56049c9a..09e985f43 100644 --- a/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs +++ b/src/Ombi.Api.Radarr/Models/V2/RadarrAddMovie.cs @@ -28,5 +28,6 @@ namespace Ombi.Api.Radarr.Models public string titleSlug { get; set; } public int year { get; set; } public string minimumAvailability { get; set; } + public long sizeOnDisk { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index b461ccda8..e1879bba3 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -82,7 +82,8 @@ namespace Ombi.Api.Radarr titleSlug = title + year, monitored = true, year = year, - minimumAvailability = minimumAvailability + minimumAvailability = minimumAvailability, + sizeOnDisk = 0 }; if (searchNow) diff --git a/src/Ombi.Api.Radarr/RadarrV3Api.cs b/src/Ombi.Api.Radarr/RadarrV3Api.cs index dd1d0b279..c50ccb8df 100644 --- a/src/Ombi.Api.Radarr/RadarrV3Api.cs +++ b/src/Ombi.Api.Radarr/RadarrV3Api.cs @@ -65,7 +65,7 @@ namespace Ombi.Api.Radarr public async Task UpdateMovie(MovieResponse movie, string apiKey, string baseUrl) { - var request = new Request($"/api/v3/movie/", baseUrl, HttpMethod.Put); + var request = new Request($"/api/v3/movie/{movie.id}", baseUrl, HttpMethod.Put); AddHeaders(request, apiKey); request.AddJsonBody(movie); @@ -85,7 +85,8 @@ namespace Ombi.Api.Radarr titleSlug = title + year, monitored = true, year = year, - minimumAvailability = minimumAvailability + minimumAvailability = minimumAvailability, + sizeOnDisk = 0 }; if (searchNow) diff --git a/src/Ombi.Api.Webhook/WebhookApi.cs b/src/Ombi.Api.Webhook/WebhookApi.cs index 8b6b35ca0..f2faaa18b 100644 --- a/src/Ombi.Api.Webhook/WebhookApi.cs +++ b/src/Ombi.Api.Webhook/WebhookApi.cs @@ -19,7 +19,7 @@ namespace Ombi.Api.Webhook public async Task PushAsync(string baseUrl, string accessToken, IDictionary parameters) { - var request = new Request("/", baseUrl, HttpMethod.Post); + var request = new Request("", baseUrl, HttpMethod.Post); if (!string.IsNullOrWhiteSpace(accessToken)) { diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index 764da5a13..7a8e7678b 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -16,14 +16,14 @@ namespace Ombi.Api { public class Api : IApi { - public Api(ILogger log, IOmbiHttpClient client) + public Api(ILogger log, HttpClient client) { Logger = log; _client = client; } private ILogger Logger { get; } - private readonly IOmbiHttpClient _client; + private readonly HttpClient _client; public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { diff --git a/src/Ombi.Api/IOmbiHttpClient.cs b/src/Ombi.Api/IOmbiHttpClient.cs deleted file mode 100644 index 1f6ff9514..000000000 --- a/src/Ombi.Api/IOmbiHttpClient.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Ombi.Api -{ - public interface IOmbiHttpClient - { - Task SendAsync(HttpRequestMessage request); - Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); - Task GetStringAsync(Uri requestUri); - } -} \ No newline at end of file diff --git a/src/Ombi.Api/Ombi.Api.csproj b/src/Ombi.Api/Ombi.Api.csproj index 98da29c2a..91821c83b 100644 --- a/src/Ombi.Api/Ombi.Api.csproj +++ b/src/Ombi.Api/Ombi.Api.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Ombi.Api/OmbiHttpClient.cs b/src/Ombi.Api/OmbiHttpClient.cs deleted file mode 100644 index 978476cf7..000000000 --- a/src/Ombi.Api/OmbiHttpClient.cs +++ /dev/null @@ -1,110 +0,0 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2017 Jamie Rees -// File: OmbiHttpClient.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Ombi.Core.Settings; -using Ombi.Helpers; -using Ombi.Settings.Settings.Models; - -namespace Ombi.Api -{ - /// - /// The purpose of this class is simple, keep one instance of the HttpClient in play. - /// There are many articles related to when using multiple HttpClient's keeping the socket in a WAIT state - /// https://blogs.msdn.microsoft.com/alazarev/2017/12/29/disposable-finalizers-and-httpclient/ - /// https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ - /// - public class OmbiHttpClient : IOmbiHttpClient - { - public OmbiHttpClient(ICacheService cache, ISettingsService s) - { - _cache = cache; - _settings = s; - _runtimeVersion = AssemblyHelper.GetRuntimeVersion(); - } - - private static HttpClient _client; - private static HttpMessageHandler _handler; - - private readonly ICacheService _cache; - private readonly ISettingsService _settings; - private readonly string _runtimeVersion; - - - public async Task SendAsync(HttpRequestMessage request) - { - await Setup(); - return await _client.SendAsync(request); - } - - public async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - await Setup(); - return await _client.SendAsync(request, cancellationToken); - } - - public async Task GetStringAsync(Uri requestUri) - { - await Setup(); - return await _client.GetStringAsync(requestUri); - } - - private async Task Setup() - { - if (_client == null) - { - if (_handler == null) - { - // Get the handler - _handler = await GetHandler(); - } - _client = new HttpClient(_handler); - _client.DefaultRequestHeaders.Add("User-Agent", $"Ombi/{_runtimeVersion} (https://ombi.io/)"); - } - } - - private async Task GetHandler() - { - if (_cache == null) - { - return new HttpClientHandler(); - } - var settings = await _cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await _settings.GetSettingsAsync(), DateTime.Now.AddHours(1)); - if (settings.IgnoreCertificateErrors) - { - return new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true, - }; - } - return new HttpClientHandler(); - } - } -} \ No newline at end of file diff --git a/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs index 27dbee614..7bf84d05f 100644 --- a/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/LidarrAlbumCacheRuleTests.cs @@ -30,7 +30,7 @@ namespace Ombi.Core.Tests.Rule.Search public async Task Should_Not_Be_Monitored_Or_Available() { var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -49,7 +49,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -71,7 +71,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -93,7 +93,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); @@ -114,7 +114,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Approved); diff --git a/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs index c17400acb..e13d8d1cd 100644 --- a/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/LidarrArtistCacheRuleTests.cs @@ -29,7 +29,7 @@ namespace Ombi.Core.Tests.Rule.Search public async Task Should_Not_Be_Monitored() { var request = new SearchArtistViewModel { ForignArtistId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.False(request.Monitored); @@ -46,7 +46,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchArtistViewModel { ForignArtistId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.True(request.Monitored); @@ -64,7 +64,7 @@ namespace Ombi.Core.Tests.Rule.Search } }.AsQueryable()); var request = new SearchArtistViewModel { ForignArtistId = "abc" }; - var result = await Rule.Execute(request); + var result = await Rule.Execute(request, string.Empty); Assert.True(result.Success); Assert.True(request.Monitored); diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index 8ddf27656..68a4335cb 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -9,6 +9,7 @@ using Ombi.Store.Entities.Requests; using Ombi.Store.Entities; using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; +using Ombi.Helpers; namespace Ombi.Core.Engine.Interfaces { @@ -29,6 +30,10 @@ namespace Ombi.Core.Engine.Interfaces private OmbiUser _user; protected async Task GetUser() { + if(!Username.HasValue()) + { + return null; + } var username = Username.ToUpper(); return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username)); } @@ -54,9 +59,9 @@ namespace Ombi.Core.Engine.Interfaces var ruleResults = await Rules.StartSearchRules(model); return ruleResults; } - public async Task RunSpecificRule(object model, SpecificRules rule) + public async Task RunSpecificRule(object model, SpecificRules rule, string requestOnBehalf) { - var ruleResults = await Rules.StartSpecificRules(model, rule); + var ruleResults = await Rules.StartSpecificRules(model, rule, requestOnBehalf); return ruleResults; } } diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs b/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs index c4bd0aa0e..442359f0f 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieEngineV2.cs @@ -19,7 +19,7 @@ namespace Ombi.Core.Engine.Interfaces Task> NowPlayingMovies(int currentPosition, int amountToLoad); Task GetCollection(int collectionId, CancellationToken cancellationToken, string langCode = null); Task GetTvDbId(int theMovieDbId); - Task> PopularMovies(int currentlyLoaded, int toLoad, CancellationToken cancellationToken); + Task> PopularMovies(int currentlyLoaded, int toLoad, CancellationToken cancellationToken, string langCustomCode = null); Task> TopRatedMovies(int currentlyLoaded, int toLoad); Task> UpcomingMovies(int currentlyLoaded, int toLoad); Task GetMoviesByActor(int actorId, string langCode); diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index c8b7746f0..f4eeb6fc3 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Ombi.Core.Models; using Ombi.Core.Models.Requests; @@ -24,5 +25,6 @@ namespace Ombi.Core.Engine.Interfaces Task UnSubscribeRequest(int requestId, RequestType type); Task SubscribeToRequest(int requestId, RequestType type); Task GetRemainingRequests(OmbiUser user = null); + Task ReProcessRequest(int requestId, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs index 2fc78b6bb..3e2d859c8 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngineV2.cs @@ -11,7 +11,7 @@ namespace Ombi.Core Task GetShowInformation(string tvdbid, CancellationToken token); Task GetShowByRequest(int requestId, CancellationToken token); Task> GetStreamInformation(int movieDbId, CancellationToken cancellationToken); - Task> Popular(int currentlyLoaded, int amountToLoad); + Task> Popular(int currentlyLoaded, int amountToLoad, string langCustomCode = null); Task> Anticipated(int currentlyLoaded, int amountToLoad); Task> Trending(int currentlyLoaded, int amountToLoad); } diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 00a97f766..d37983d21 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -21,6 +21,7 @@ using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Core.Models; +using System.Threading; namespace Ombi.Core.Engine { @@ -70,7 +71,7 @@ namespace Ombi.Core.Engine var canRequestOnBehalf = model.RequestOnBehalf.HasValue(); var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); - if (model.RequestOnBehalf.HasValue() && !isAdmin) + if (canRequestOnBehalf && !isAdmin) { return new RequestEngineResult { @@ -549,12 +550,17 @@ namespace Ombi.Core.Engine request.Denied = false; await MovieRepository.Update(request); - var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification); + var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty); if (canNotify.Success) { await NotificationHelper.Notify(request, NotificationType.RequestApproved); } + return await ProcessSendingMovie(request); + } + + private async Task ProcessSendingMovie(MovieRequests request) + { if (request.Approved) { var result = await Sender.Send(request); @@ -634,6 +640,21 @@ namespace Ombi.Core.Engine return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId); } + public async Task ReProcessRequest(int requestId, CancellationToken cancellationToken) + { + var request = await MovieRepository.Find(requestId); + if (request == null) + { + return new RequestEngineResult + { + Result = false, + ErrorMessage = "Request does not exist" + }; + } + + return await ProcessSendingMovie(request); + } + public async Task MarkUnavailable(int modelId) { var request = await MovieRepository.Find(modelId); @@ -682,7 +703,7 @@ namespace Ombi.Core.Engine { await MovieRepository.Add(model); - var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); + var result = await RunSpecificRule(model, SpecificRules.CanSendNotification, requestOnBehalf); if (result.Success) { await NotificationHelper.NewRequest(model); diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs index e473c1fb1..032b2ae7d 100644 --- a/src/Ombi.Core/Engine/MusicRequestEngine.cs +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -362,7 +362,7 @@ namespace Ombi.Core.Engine await MusicRepository.Update(request); - var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification); + var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty); if (canNotify.Success) { await NotificationHelper.Notify(request, NotificationType.RequestApproved); @@ -506,7 +506,7 @@ namespace Ombi.Core.Engine { await MusicRepository.Add(model); - var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); + var result = await RunSpecificRule(model, SpecificRules.CanSendNotification, string.Empty); if (result.Success) { await NotificationHelper.NewRequest(model); diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs index a9af03ecf..89bae7069 100644 --- a/src/Ombi.Core/Engine/MusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -151,7 +151,7 @@ namespace Ombi.Core.Engine } - await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist, string.Empty); return vm; } @@ -190,7 +190,7 @@ namespace Ombi.Core.Engine vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url?.ToHttpsUrl(); - await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum, string.Empty); await RunSearchRules(vm); @@ -230,7 +230,7 @@ namespace Ombi.Core.Engine vm.Cover = a.remoteCover; } - await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum, string.Empty); await RunSearchRules(vm); @@ -258,7 +258,7 @@ namespace Ombi.Core.Engine vm.Cover = fullAlbum.remoteCover; } - await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum, string.Empty); await RunSearchRules(vm); diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 9a2ccbf1e..00655e921 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -25,6 +25,7 @@ using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Core.Models; +using System.Threading; namespace Ombi.Core.Engine { @@ -164,7 +165,7 @@ namespace Ombi.Core.Engine }; } - if ((tv.RootFolderOverride.HasValue || tv.QualityPathOverride.HasValue) && !isAdmin) + if ((tv.RootFolderOverride.HasValue || tv.QualityPathOverride.HasValue || tv.LanguageProfile.HasValue) && !isAdmin) { return new RequestEngineResult { @@ -250,7 +251,7 @@ namespace Ombi.Core.Engine } // This is a new request - var newRequest = tvBuilder.CreateNewRequest(tv, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault()); + var newRequest = tvBuilder.CreateNewRequest(tv, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault(), tv.LanguageProfile.GetValueOrDefault()); return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf); } @@ -896,9 +897,25 @@ namespace Ombi.Core.Engine } + public async Task ReProcessRequest(int requestId, CancellationToken cancellationToken) + { + var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId, cancellationToken); + if (request == null) + { + return new RequestEngineResult + { + Result = false, + ErrorMessage = "Request does not exist" + }; + } + + return await ProcessSendingShow(request); + } + + private async Task AfterRequest(ChildRequests model, string requestOnBehalf) { - var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification); + var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification, requestOnBehalf); if (sendRuleResult.Success) { await NotificationHelper.NewRequest(model); @@ -913,6 +930,11 @@ namespace Ombi.Core.Engine EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(), }); + return await ProcessSendingShow(model); + } + + private async Task ProcessSendingShow(ChildRequests model) + { if (model.Approved) { // Autosend @@ -997,6 +1019,10 @@ namespace Ombi.Core.Engine request.QualityOverride = options.QualityOverride; request.RootFolder = options.RootPathOverride; + if (options.LanguageProfile > 0) + { + request.LanguageProfile = options.LanguageProfile; + } await TvRepository.Update(request); diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs index 3714e58a8..f131662be 100644 --- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs @@ -124,9 +124,9 @@ namespace Ombi.Core.Engine.V2 /// Gets popular movies by paging /// /// - public async Task> PopularMovies(int currentlyLoaded, int toLoad, CancellationToken cancellationToken) + public async Task> PopularMovies(int currentlyLoaded, int toLoad, CancellationToken cancellationToken, string langCustomCode = null) { - var langCode = await DefaultLanguageCode(null); + var langCode = await DefaultLanguageCode(langCustomCode); var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems); @@ -371,6 +371,7 @@ namespace Ombi.Core.Engine.V2 mapped.Requested = movie.Requested; mapped.PlexUrl = movie.PlexUrl; mapped.EmbyUrl = movie.EmbyUrl; + mapped.JellyfinUrl = movie.JellyfinUrl; mapped.Subscribed = movie.Subscribed; mapped.ShowSubscribe = movie.ShowSubscribe; mapped.ReleaseDate = movie.ReleaseDate; diff --git a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs index 9b9b40906..e02ae1c51 100644 --- a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs @@ -22,6 +22,7 @@ using Microsoft.EntityFrameworkCore; using System.Threading; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; +using System.Diagnostics; namespace Ombi.Core.Engine.V2 { @@ -49,13 +50,14 @@ namespace Ombi.Core.Engine.V2 public async Task GetShowByRequest(int requestId, CancellationToken token) { var request = await RequestService.TvRequestService.Get().FirstOrDefaultAsync(x => x.Id == requestId); - return await GetShowInformation(request.ExternalProviderId.ToString(), token); // TODO + return await GetShowInformation(request.ExternalProviderId.ToString(), token); } public async Task GetShowInformation(string tvdbid, CancellationToken token) { - var show = await Cache.GetOrAdd(nameof(GetShowInformation) + tvdbid, - async () => await _movieApi.GetTVInfo(tvdbid), DateTime.Now.AddHours(12)); + var langCode = await DefaultLanguageCode(null); + var show = await Cache.GetOrAdd(nameof(GetShowInformation) + langCode + tvdbid, + async () => await _movieApi.GetTVInfo(tvdbid, langCode), DateTime.Now.AddHours(12)); if (show == null || show.name == null) { // We don't have enough information @@ -69,47 +71,15 @@ namespace Ombi.Core.Engine.V2 { var seasonEpisodes = (await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token)); - foreach (var episode in seasonEpisodes.episodes) - { - var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == episode.season_number); - if (season == null) - { - var newSeason = new SeasonRequests - { - SeasonNumber = episode.season_number, - Overview = tvSeason.overview, - Episodes = new List() - }; - newSeason.Episodes.Add(new EpisodeRequests - { - //Url = episode...ToHttpsUrl(), - Title = episode.name, - AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue, - EpisodeNumber = episode.episode_number, - - }); - mapped.SeasonRequests.Add(newSeason); - } - else - { - // We already have the season, so just add the episode - season.Episodes.Add(new EpisodeRequests - { - //Url = e.url.ToHttpsUrl(), - Title = episode.name, - AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue, - EpisodeNumber = episode.episode_number, - }); - } - } + MapSeasons(mapped.SeasonRequests, tvSeason, seasonEpisodes); } return await ProcessResult(mapped); } - public async Task> Popular(int currentlyLoaded, int amountToLoad) + public async Task> Popular(int currentlyLoaded, int amountToLoad, string langCustomCode = null) { - var langCode = await DefaultLanguageCode(null); + var langCode = await DefaultLanguageCode(langCustomCode); var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit); var results = new List(); @@ -152,6 +122,7 @@ namespace Ombi.Core.Engine.V2 async () => await _movieApi.TopRatedTv(langCode, pagesToLoad.Page), DateTime.Now.AddHours(12)); results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } + var processed = ProcessResults(results); return await processed; } @@ -177,22 +148,77 @@ namespace Ombi.Core.Engine.V2 return data; } - private async Task> ProcessResults(IEnumerable items) + private async Task> ProcessResults(List items) { var retVal = new List(); var settings = await _customization.GetSettingsAsync(); + foreach (var tvMazeSearch in items) { + if (settings.HideAvailableFromDiscover) + { + // To hide, we need to know if it's fully available, the only way to do this is to lookup it's episodes to check if we have every episode + var show = await Cache.GetOrAdd(nameof(GetShowInformation) + tvMazeSearch.Id.ToString(), + async () => await _movieApi.GetTVInfo(tvMazeSearch.Id.ToString()), DateTime.Now.AddHours(12)); + foreach (var tvSeason in show.seasons.Where(x => x.season_number != 0)) // skip the first season + { + var seasonEpisodes = await Cache.GetOrAdd("SeasonEpisodes" + show.id + tvSeason.season_number, async () => + { + return await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, CancellationToken.None); + }, DateTime.Now.AddHours(12)); + + MapSeasons(tvMazeSearch.SeasonRequests, tvSeason, seasonEpisodes); + } + } + var result = await ProcessResult(tvMazeSearch); - if (result == null || settings.HideAvailableFromDiscover && result.Available) + if (result == null || settings.HideAvailableFromDiscover && result.FullyAvailable) { continue; } retVal.Add(result); } + return retVal; } + private static void MapSeasons(List seasonRequests, Season tvSeason, SeasonDetails seasonEpisodes) + { + foreach (var episode in seasonEpisodes.episodes) + { + var season = seasonRequests.FirstOrDefault(x => x.SeasonNumber == episode.season_number); + if (season == null) + { + var newSeason = new SeasonRequests + { + SeasonNumber = episode.season_number, + Overview = tvSeason.overview, + Episodes = new List() + }; + newSeason.Episodes.Add(new EpisodeRequests + { + //Url = episode...ToHttpsUrl(), + Title = episode.name, + AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue, + EpisodeNumber = episode.episode_number, + + }); + seasonRequests.Add(newSeason); + } + else + { + // We already have the season, so just add the episode + season.Episodes.Add(new EpisodeRequests + { + //Url = e.url.ToHttpsUrl(), + Title = episode.name, + AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue, + EpisodeNumber = episode.episode_number, + }); + } + } + } + private async Task ProcessResult(T tvMazeSearch) { var item = _mapper.Map(tvMazeSearch); @@ -216,6 +242,9 @@ namespace Ombi.Core.Engine.V2 item.Approved = oldModel.Approved; item.SeasonRequests = oldModel.SeasonRequests; item.RequestId = oldModel.RequestId; + item.PlexUrl = oldModel.PlexUrl; + item.EmbyUrl = oldModel.EmbyUrl; + item.JellyfinUrl = oldModel.JellyfinUrl; if (!string.IsNullOrEmpty(item.Images?.Medium)) { diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs index 02150cc4d..1a77be97a 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs @@ -217,7 +217,7 @@ namespace Ombi.Core.Helpers } - public TvShowRequestBuilderV2 CreateNewRequest(TvRequestViewModelV2 tv, int rootPathOverride, int qualityOverride) + public TvShowRequestBuilderV2 CreateNewRequest(TvRequestViewModelV2 tv, int rootPathOverride, int qualityOverride, int langProfile) { int.TryParse(TheMovieDbRecord.ExternalIds?.TvDbId, out var tvdbId); NewRequest = new TvRequests @@ -234,7 +234,8 @@ namespace Ombi.Core.Helpers TotalSeasons = tv.Seasons.Count(), Background = BackdropPath, RootFolder = rootPathOverride, - QualityOverride = qualityOverride + QualityOverride = qualityOverride, + LanguageProfile = langProfile }; NewRequest.ChildRequests.Add(ChildRequest); diff --git a/src/Ombi.Core/Models/Requests/MovieAdvancedOptions.cs b/src/Ombi.Core/Models/Requests/MovieAdvancedOptions.cs index 02ce0a6bd..570c3f979 100644 --- a/src/Ombi.Core/Models/Requests/MovieAdvancedOptions.cs +++ b/src/Ombi.Core/Models/Requests/MovieAdvancedOptions.cs @@ -5,5 +5,6 @@ public int RequestId { get; set; } public int RootPathOverride { get; set; } public int QualityOverride { get; set; } + public int LanguageProfile { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs index 960b30ab9..1f56fb9c0 100644 --- a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs @@ -24,6 +24,7 @@ namespace Ombi.Core.Models.Requests { public bool RequestAll { get; set; } public bool LatestSeason { get; set; } + public int? LanguageProfile { get; set; } public bool FirstSeason { get; set; } public List Seasons { get; set; } = new List(); [JsonIgnore] diff --git a/src/Ombi.Core/Models/Search/SearchTvShowViewModel.cs b/src/Ombi.Core/Models/Search/SearchTvShowViewModel.cs index dfd038a91..1a3f47175 100644 --- a/src/Ombi.Core/Models/Search/SearchTvShowViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchTvShowViewModel.cs @@ -58,9 +58,6 @@ namespace Ombi.Core.Models.Search public bool PartlyAvailable { get; set; } public override RequestType Type => RequestType.TvShow; - /// - /// Only set on the images call - /// public string BackdropPath { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs b/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs index c32342146..5b438124e 100644 --- a/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs +++ b/src/Ombi.Core/Rule/Interfaces/IRuleEvaluator.cs @@ -9,6 +9,6 @@ namespace Ombi.Core.Rule.Interfaces { Task> StartRequestRules(BaseRequest obj); Task> StartSearchRules(SearchViewModel obj); - Task StartSpecificRules(object obj, SpecificRules selectedRule); + Task StartSpecificRules(object obj, SpecificRules selectedRule, string requestOnBehalf); } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs b/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs index 065b2f990..f76464888 100644 --- a/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs +++ b/src/Ombi.Core/Rule/Interfaces/ISpecificRule.cs @@ -5,7 +5,7 @@ namespace Ombi.Core.Rule.Interfaces { public interface ISpecificRule where T : new() { - Task Execute(T obj); + Task Execute(T obj, string requestOnBehalf); SpecificRules Rule { get; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/RuleEvaluator.cs b/src/Ombi.Core/Rule/RuleEvaluator.cs index a0c272c00..712d6031e 100644 --- a/src/Ombi.Core/Rule/RuleEvaluator.cs +++ b/src/Ombi.Core/Rule/RuleEvaluator.cs @@ -58,13 +58,13 @@ namespace Ombi.Core.Rule return results; } - public async Task StartSpecificRules(object obj, SpecificRules selectedRule) + public async Task StartSpecificRules(object obj, SpecificRules selectedRule, string requestOnBehalf) { foreach (var rule in SpecificRules) { if (selectedRule == rule.Rule) { - var result = await rule.Execute(obj); + var result = await rule.Execute(obj, requestOnBehalf); return result; } } diff --git a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs index 71331c51d..fe062e851 100644 --- a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs +++ b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs @@ -13,12 +13,12 @@ namespace Ombi.Core.Rule.Rules.Search { public static void CheckForUnairedEpisodes(SearchTvShowViewModel search) { - foreach (var season in search.SeasonRequests) + foreach (var season in search.SeasonRequests.ToList()) { // If we have all the episodes for this season, then this season is available if (season.Episodes.All(x => x.Available)) { - season.SeasonAvailable = true; + season.SeasonAvailable = true; } } if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available))) diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 3fe11cbc4..7f6718a6b 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -67,7 +67,7 @@ namespace Ombi.Core.Rule.Rules.Search var s = await EmbySettings.GetSettingsAsync(); if (s.Enable) { - var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null); + var server = s.Servers.FirstOrDefault(); if ((server?.ServerHostname ?? string.Empty).HasValue()) { obj.EmbyUrl = EmbyHelper.GetEmbyMediaUrl(item.EmbyId, server?.ServerId, server?.ServerHostname); diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs index 3ca57d635..ca0c0cd97 100644 --- a/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs @@ -9,7 +9,7 @@ using Ombi.Store.Repository; namespace Ombi.Core.Rule.Rules.Search { - public class LidarrAlbumCacheRule : BaseSearchRule, IRules + public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule { public LidarrAlbumCacheRule(IExternalRepository db) { @@ -18,7 +18,9 @@ namespace Ombi.Core.Rule.Rules.Search private readonly IExternalRepository _db; - public Task Execute(SearchViewModel objec) + public override SpecificRules Rule => SpecificRules.LidarrAlbum; + + public Task Execute(object objec, string requestOnBehalf) { if (objec is SearchAlbumViewModel obj) { diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs index a254ea2d3..06ca3f06c 100644 --- a/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs @@ -17,7 +17,7 @@ namespace Ombi.Core.Rule.Rules.Search private readonly IExternalRepository _db; - public Task Execute(object objec) + public Task Execute(object objec, string requestOnBehalf) { var obj = (SearchArtistViewModel) objec; // Check if it's in Lidarr @@ -30,6 +30,7 @@ namespace Ombi.Core.Rule.Rules.Search return Task.FromResult(Success()); } + public override SpecificRules Rule => SpecificRules.LidarrArtist; } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs index 2a239d1d3..68551aac4 100644 --- a/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/PlexAvailabilityRule.cs @@ -25,10 +25,11 @@ namespace Ombi.Core.Rule.Rules.Search PlexServerContent item = null; var useImdb = false; var useTheMovieDb = false; + var useId = false; var useTvDb = false; if (obj.ImdbId.HasValue()) { - item = await PlexContentRepository.Get(obj.ImdbId); + item = await PlexContentRepository.Get(obj.ImdbId, ProviderType.ImdbId); if (item != null) { useImdb = true; @@ -36,9 +37,17 @@ namespace Ombi.Core.Rule.Rules.Search } if (item == null) { + if (obj.Id > 0) + { + item = await PlexContentRepository.Get(obj.Id.ToString(), ProviderType.TheMovieDbId); + if (item != null) + { + useId = true; + } + } if (obj.TheMovieDbId.HasValue()) { - item = await PlexContentRepository.Get(obj.TheMovieDbId); + item = await PlexContentRepository.Get(obj.TheMovieDbId, ProviderType.TheMovieDbId); if (item != null) { useTheMovieDb = true; @@ -49,7 +58,7 @@ namespace Ombi.Core.Rule.Rules.Search { if (obj.TheTvDbId.HasValue()) { - item = await PlexContentRepository.Get(obj.TheTvDbId); + item = await PlexContentRepository.Get(obj.TheTvDbId, ProviderType.TvDbId); if (item != null) { useTvDb = true; @@ -60,6 +69,11 @@ namespace Ombi.Core.Rule.Rules.Search if (item != null) { + if (useId) + { + obj.TheMovieDbId = obj.Id.ToString(); + useTheMovieDb = true; + } obj.Available = true; obj.PlexUrl = item.Url; obj.Quality = item.Quality; @@ -71,9 +85,9 @@ namespace Ombi.Core.Rule.Rules.Search if (search.SeasonRequests.Any()) { var allEpisodes = PlexContentRepository.GetAllEpisodes(); - foreach (var season in search.SeasonRequests) + foreach (var season in search.SeasonRequests.ToList()) { - foreach (var episode in season.Episodes) + foreach (var episode in season.Episodes.ToList()) { await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb, Log); } diff --git a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs index 30ec9b14a..af71df9ac 100644 --- a/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs +++ b/src/Ombi.Core/Rule/Rules/Specific/SendNotificationRule.cs @@ -22,11 +22,20 @@ namespace Ombi.Core.Rule.Rules.Specific private OmbiUserManager UserManager { get; } private ISettingsService Settings { get; } - public async Task Execute(object obj) + public async Task Execute(object obj, string requestOnBehalf) { var req = (BaseRequest)obj; + var canRequestonBehalf = requestOnBehalf.HasValue(); var settings = await Settings.GetSettingsAsync(); var sendNotification = true; + + if (settings.DoNotSendNotificationsForAutoApprove && canRequestonBehalf) + { + return new RuleResult + { + Success = false + }; + } var requestedUser = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == req.RequestedUserId); if (req.RequestType == RequestType.Movie) { diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 8187cad59..2f42a12c0 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -158,6 +158,8 @@ namespace Ombi.Core.Senders } int qualityToUse; + var sonarrV3 = s.V3; + var languageProfileId = s.LanguageProfile; string rootFolderPath; string seriesType; @@ -167,8 +169,17 @@ namespace Ombi.Core.Senders { // Get the root path from the rootfolder selected. // For some reason, if we haven't got one use the first root folder in Sonarr - rootFolderPath = await GetSonarrRootPath(int.Parse(s.RootPathAnime), s); - int.TryParse(s.QualityProfileAnime, out qualityToUse); + if (!int.TryParse(s.RootPathAnime, out int animePath)) + { + animePath = int.Parse(s.RootPath); // Set it to the main root folder if we have no anime folder. + } + rootFolderPath = await GetSonarrRootPath(animePath, s); + languageProfileId = s.LanguageProfileAnime > 0 ? s.LanguageProfileAnime : s.LanguageProfile; + + if (!int.TryParse(s.QualityProfileAnime, out qualityToUse)) + { + qualityToUse = int.Parse(s.QualityProfile); + } if (profiles != null) { if (profiles.SonarrRootPathAnime > 0) @@ -181,7 +192,6 @@ namespace Ombi.Core.Senders } } seriesType = "anime"; - } else { @@ -220,11 +230,16 @@ namespace Ombi.Core.Senders rootFolderPath = await GetSonarrRootPath(rootfolderOverride, s); } } - - // Are we using v3 sonarr? - var sonarrV3 = s.V3; - var languageProfileId = s.LanguageProfile; + if (model.ParentRequest.LanguageProfile.HasValue) + { + var languageProfile = model.ParentRequest.LanguageProfile.Value; + if (languageProfile > 0) + { + languageProfileId = languageProfile; + } + } + try { // Does the series actually exist? @@ -264,6 +279,10 @@ namespace Ombi.Core.Senders var seasonsToAdd = GetSeasonsToCreate(model); newSeries.seasons = seasonsToAdd; var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri); + if (result?.ErrorMessages?.Any() ?? false) + { + throw new Exception(string.Join(',', result.ErrorMessages)); + } existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri); await SendToSonarr(model, existingSeries, s); } @@ -407,7 +426,6 @@ namespace Ombi.Core.Senders await SonarrApi.SeasonPass(s.ApiKey, s.FullUri, result); } - if (!s.AddOnly) { await SearchForRequest(model, sonarrEpList, result, s, episodesToUpdate); diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index ef9c9230c..73a69203a 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -23,7 +23,6 @@ using Ombi.Notifications; using Ombi.Schedule; using Ombi.Schedule.Jobs; using Ombi.Settings.Settings; -using Ombi.Store.Context; using Ombi.Store.Repository; using Ombi.Notifications.Agents; using Ombi.Schedule.Jobs.Radarr; @@ -68,6 +67,8 @@ using Ombi.Api.MusicBrainz; using Ombi.Api.Twilio; using Ombi.Api.CloudService; using Ombi.Api.RottenTomatoes; +using System.Net.Http; +using Microsoft.Extensions.Logging; namespace Ombi.DependencyInjection { @@ -119,14 +120,24 @@ namespace Ombi.DependencyInjection public static void RegisterHttp(this IServiceCollection services) { + var runtimeVersion = AssemblyHelper.GetRuntimeVersion(); services.AddSingleton(); services.AddScoped(sp => sp.GetService().HttpContext.User); + services.AddHttpClient("OmbiClient", client => + { + client.DefaultRequestHeaders.Add("User-Agent", $"Ombi/{runtimeVersion} (https://ombi.io/)"); + }).ConfigurePrimaryHttpMessageHandler(() => + { + var httpClientHandler = new HttpClientHandler(); + httpClientHandler.ServerCertificateCustomValidationCallback = (message, certificate2, arg3, arg4) => true; + + return httpClientHandler; + }); } public static void RegisterApi(this IServiceCollection services) { - services.AddScoped(); - services.AddScoped(); // https://blogs.msdn.microsoft.com/alazarev/2017/12/29/disposable-finalizers-and-httpclient/ + services.AddScoped(s => new Api.Api(s.GetRequiredService>(), s.GetRequiredService().CreateClient("OmbiClient"))); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index ed1e9e4a2..49d1dd194 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Ombi.Helpers.Tests/PlexHelperTests.cs b/src/Ombi.Helpers.Tests/PlexHelperTests.cs index 82b59fdf7..e7d35a583 100644 --- a/src/Ombi.Helpers.Tests/PlexHelperTests.cs +++ b/src/Ombi.Helpers.Tests/PlexHelperTests.cs @@ -61,6 +61,7 @@ namespace Ombi.Helpers.Tests get { yield return new TestCaseData("plex://movie/5e1632df2d4d84003e48e54e|imdb://tt9178402|tmdb://610201", new ProviderId { ImdbId = "tt9178402", TheMovieDb = "610201" }).SetName("V2 Regular Plex Id"); + yield return new TestCaseData("plex://movie/5e1632df2d4d84003e48e54e|imdb://tt9178402|tmdb://610201|thetvdb://12345", new ProviderId { ImdbId = "tt9178402", TheMovieDb = "610201", TheTvDb = "12345" }).SetName("V2 Regular Plex Id w/ tvdb"); yield return new TestCaseData("plex://movie/5d7768253c3c2a001fbcab72|imdb://tt0119567|tmdb://330", new ProviderId { ImdbId = "tt0119567", TheMovieDb = "330" }).SetName("V2 Regular Plex Id Another"); yield return new TestCaseData("plex://movie/5d7768253c3c2a001fbcab72|imdb://tt0119567", new ProviderId { ImdbId = "tt0119567" }).SetName("V2 Regular Plex Id Single Imdb"); yield return new TestCaseData("plex://movie/5d7768253c3c2a001fbcab72|tmdb://330", new ProviderId { TheMovieDb = "330" }).SetName("V2 Regular Plex Id Single Tmdb"); diff --git a/src/Ombi.Helpers/AssemblyHelper.cs b/src/Ombi.Helpers/AssemblyHelper.cs index eb4dc55fd..9abc3bf92 100644 --- a/src/Ombi.Helpers/AssemblyHelper.cs +++ b/src/Ombi.Helpers/AssemblyHelper.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.PlatformAbstractions; +using System.Linq; using System.Reflection; namespace Ombi.Helpers @@ -8,7 +9,8 @@ namespace Ombi.Helpers public static string GetRuntimeVersion() { ApplicationEnvironment app = PlatformServices.Default.Application; - return app.ApplicationVersion; + var split = app.ApplicationVersion.Split('.'); + return string.Join('.', split.Take(3)); } } } \ No newline at end of file diff --git a/src/Ombi.Mapping/Profiles/TvProfile.cs b/src/Ombi.Mapping/Profiles/TvProfile.cs index 55f5ccf9e..0aceffc4f 100644 --- a/src/Ombi.Mapping/Profiles/TvProfile.cs +++ b/src/Ombi.Mapping/Profiles/TvProfile.cs @@ -81,7 +81,9 @@ namespace Ombi.Mapping.Profiles .ForMember(dest => dest.Rating, opts => opts.MapFrom(src => src.VoteAverage.ToString())) .ForMember(dest => dest.BackdropPath, opts => opts.MapFrom(src => src.PosterPath)) //.ForMember(dest => dest.Runtime, opts => opts.MapFrom(src => src.Runtime.ToString())) - .ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.Title)); + .ForMember(dest => dest.Title, opts => opts.MapFrom(src => src.Title)) + .ForMember(dest => dest.SeasonRequests, opts => opts.MapFrom(src => src.SeasonRequests)) + ; //.ForMember(dest => dest.Status, opts => opts.MapFrom(src => TraktEnumHelper.GetDescription(src.Status))) //.ForMember(dest => dest.Trailer, // opts => opts.MapFrom(src => src.Trailer != null ? src.Trailer.ToString().ToHttpsUrl() : string.Empty)) diff --git a/src/Ombi.Notifications.Tests/NotificationMessageCurlysTests.cs b/src/Ombi.Notifications.Tests/NotificationMessageCurlysTests.cs new file mode 100644 index 000000000..655ffeb23 --- /dev/null +++ b/src/Ombi.Notifications.Tests/NotificationMessageCurlysTests.cs @@ -0,0 +1,342 @@ +using AutoFixture; +using NUnit.Framework; +using Ombi.Notifications.Models; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; +using System.Collections.Generic; +using System.Linq; + +namespace Ombi.Notifications.Tests +{ + public class NotificationMessageCurlysTests + { + private NotificationMessageCurlys sut { get; set; } + private Fixture F { get; set; } + + [SetUp] + public void Setup() + { + F = new Fixture(); + F.Behaviors.OfType().ToList() + .ForEach(b => F.Behaviors.Remove(b)); + F.Behaviors.Add(new OmitOnRecursionBehavior()); + sut = new NotificationMessageCurlys(); + } + + [Test] + public void MovieNotificationTests() + { + var notificationOptions = new NotificationOptions(); + var req = F.Build() + .With(x => x.RequestType, RequestType.Movie) + .With(x => x.Available, true) + .Create(); + var customization = new CustomizationSettings + { + ApplicationUrl = "url", + ApplicationName = "name" + }; + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + + Assert.That(req.Id.ToString(), Is.EqualTo(sut.RequestId)); + Assert.That(req.TheMovieDbId.ToString(), Is.EqualTo(sut.ProviderId)); + Assert.That(req.Title.ToString(), Is.EqualTo(sut.Title)); + Assert.That(req.RequestedUser.UserName, Is.EqualTo(sut.RequestedUser)); + Assert.That(req.RequestedUser.Alias, Is.EqualTo(sut.Alias)); + Assert.That(req.RequestedDate.ToString("D"), Is.EqualTo(sut.RequestedDate)); + Assert.That("Movie", Is.EqualTo(sut.Type)); + Assert.That(req.Overview, Is.EqualTo(sut.Overview)); + Assert.That(req.ReleaseDate.Year.ToString(), Is.EqualTo(sut.Year)); + Assert.That(req.DeniedReason, Is.EqualTo(sut.DenyReason)); + Assert.That(req.MarkedAsAvailable?.ToString("D"), Is.EqualTo(sut.AvailableDate)); + Assert.That("https://image.tmdb.org/t/p/w300/" + req.PosterPath, Is.EqualTo(sut.PosterImage)); + Assert.That(req.DeniedReason, Is.EqualTo(sut.DenyReason)); + Assert.That(req.RequestedUser.Alias, Is.EqualTo(sut.UserPreference)); + Assert.That(string.Empty, Is.EqualTo(sut.AdditionalInformation)); + Assert.That("Available", Is.EqualTo(sut.RequestStatus)); + Assert.That("url", Is.EqualTo(sut.ApplicationUrl)); + Assert.That("name", Is.EqualTo(sut.ApplicationName)); + } + + [Test] + public void MovieIssueNotificationTests() + { + var notificationOptions = new NotificationOptions + { + Substitutes = new Dictionary + { + { "IssueDescription", "Desc" }, + { "IssueCategory", "Cat" }, + { "IssueStatus", "state" }, + { "IssueSubject", "sub" }, + { "NewIssueComment", "a" }, + { "IssueUser", "User" }, + { "IssueUserAlias", "alias" }, + { "RequestType", "Movie" }, + } + }; + var req = F.Build() + .With(x => x.RequestType, RequestType.Movie) + .Create(); + var customization = new CustomizationSettings(); + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + + Assert.That("Desc", Is.EqualTo(sut.IssueDescription)); + Assert.That("Cat", Is.EqualTo(sut.IssueCategory)); + Assert.That("state", Is.EqualTo(sut.IssueStatus)); + Assert.That("a", Is.EqualTo(sut.NewIssueComment)); + Assert.That("User", Is.EqualTo(sut.UserName)); + Assert.That("alias", Is.EqualTo(sut.Alias)); + Assert.That("Movie", Is.EqualTo(sut.Type)); + } + + [Test] + public void MovieNotificationUserPreferences() + { + var notificationOptions = new NotificationOptions + { + AdditionalInformation = "add" + }; + var req = F.Build() + .With(x => x.RequestType, RequestType.Movie) + .Without(x => x.MarkedAsAvailable) + .Create(); + var customization = new CustomizationSettings(); + var userPrefs = new UserNotificationPreferences + { + Value = "PrefValue" + }; + sut.Setup(notificationOptions, req, customization, userPrefs); + + Assert.That("PrefValue", Is.EqualTo(sut.UserPreference)); + Assert.That(string.Empty, Is.EqualTo(sut.AvailableDate)); + Assert.That("add", Is.EqualTo(sut.AdditionalInformation)); + } + + [TestCaseSource(nameof(RequestStatusData))] + public string MovieNotificationTests_RequestStatus(bool available, bool denied, bool approved) + { + var notificationOptions = new NotificationOptions(); + var req = F.Build() + .With(x => x.RequestType, RequestType.Movie) + .With(x => x.Available, available) + .With(x => x.Denied, denied) + .With(x => x.Approved, approved) + .Create(); + var customization = new CustomizationSettings(); + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + return sut.RequestStatus; + } + + private static IEnumerable RequestStatusData + { + get + { + yield return new TestCaseData(true, false, false).Returns("Available"); + yield return new TestCaseData(false, true, false).Returns("Denied"); + yield return new TestCaseData(false, false, true).Returns("Processing Request"); + yield return new TestCaseData(false, false, false).Returns("Pending Approval"); + } + } + + + [Test] + public void NewsletterTests() + { + var customization = new CustomizationSettings + { + ApplicationUrl = "url", + ApplicationName = "name" + }; + sut.SetupNewsletter(customization); + + Assert.That("url", Is.EqualTo(sut.ApplicationUrl)); + Assert.That("name", Is.EqualTo(sut.ApplicationName)); + } + + [Test] + public void MusicNotificationTests() + { + var notificationOptions = new NotificationOptions(); + var req = F.Build() + .With(x => x.RequestType, RequestType.Album) + .With(x => x.Available, true) + .Create(); + var customization = new CustomizationSettings + { + ApplicationUrl = "url", + ApplicationName = "name" + }; + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + + Assert.That(req.Id.ToString(), Is.EqualTo(sut.RequestId)); + Assert.That(req.ForeignArtistId.ToString(), Is.EqualTo(sut.ProviderId)); + Assert.That(req.Title.ToString(), Is.EqualTo(sut.Title)); + Assert.That(req.RequestedUser.UserName, Is.EqualTo(sut.RequestedUser)); + Assert.That(req.RequestedUser.Alias, Is.EqualTo(sut.Alias)); + Assert.That(req.RequestedDate.ToString("D"), Is.EqualTo(sut.RequestedDate)); + Assert.That("Album", Is.EqualTo(sut.Type)); + Assert.That(req.ReleaseDate.Year.ToString(), Is.EqualTo(sut.Year)); + Assert.That(req.DeniedReason, Is.EqualTo(sut.DenyReason)); + Assert.That(req.MarkedAsAvailable?.ToString("D"), Is.EqualTo(sut.AvailableDate)); + Assert.That(req.Cover, Is.EqualTo(sut.PosterImage)); + Assert.That(req.DeniedReason, Is.EqualTo(sut.DenyReason)); + Assert.That(req.RequestedUser.Alias, Is.EqualTo(sut.UserPreference)); + Assert.That(string.Empty, Is.EqualTo(sut.AdditionalInformation)); + Assert.That("Available", Is.EqualTo(sut.RequestStatus)); + Assert.That("url", Is.EqualTo(sut.ApplicationUrl)); + Assert.That("name", Is.EqualTo(sut.ApplicationName)); + } + + [TestCaseSource(nameof(RequestStatusData))] + public string MusicNotificationTests_RequestStatus(bool available, bool denied, bool approved) + { + var notificationOptions = new NotificationOptions(); + var req = F.Build() + .With(x => x.RequestType, RequestType.Album) + .With(x => x.Available, available) + .With(x => x.Denied, denied) + .With(x => x.Approved, approved) + .Create(); + var customization = new CustomizationSettings(); + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + return sut.RequestStatus; + } + + [Test] + public void TvNotificationTests() + { + var notificationOptions = new NotificationOptions(); + var req = F.Build() + .With(x => x.RequestType, RequestType.TvShow) + .With(x => x.Available, true) + .Create(); + var customization = new CustomizationSettings + { + ApplicationUrl = "url", + ApplicationName = "name" + }; + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + + Assert.That(req.Id.ToString(), Is.EqualTo(sut.RequestId)); + Assert.That(req.ParentRequest.ExternalProviderId.ToString(), Is.EqualTo(sut.ProviderId)); + Assert.That(req.ParentRequest.Title.ToString(), Is.EqualTo(sut.Title)); + Assert.That(req.RequestedUser.UserName, Is.EqualTo(sut.RequestedUser)); + Assert.That(req.RequestedUser.Alias, Is.EqualTo(sut.Alias)); + Assert.That(req.RequestedDate.ToString("D"), Is.EqualTo(sut.RequestedDate)); + Assert.That("TV Show", Is.EqualTo(sut.Type)); + Assert.That(req.ParentRequest.Overview, Is.EqualTo(sut.Overview)); + Assert.That(req.ParentRequest.ReleaseDate.Year.ToString(), Is.EqualTo(sut.Year)); + Assert.That(req.DeniedReason, Is.EqualTo(sut.DenyReason)); + Assert.That(req.MarkedAsAvailable?.ToString("D"), Is.EqualTo(sut.AvailableDate)); + Assert.That("https://image.tmdb.org/t/p/w300/" + req.ParentRequest.PosterPath, Is.EqualTo(sut.PosterImage)); + Assert.That(req.DeniedReason, Is.EqualTo(sut.DenyReason)); + Assert.That(req.RequestedUser.Alias, Is.EqualTo(sut.UserPreference)); + Assert.That(null, Is.EqualTo(sut.AdditionalInformation)); + Assert.That("Available", Is.EqualTo(sut.RequestStatus)); + Assert.That("url", Is.EqualTo(sut.ApplicationUrl)); + Assert.That("name", Is.EqualTo(sut.ApplicationName)); + } + + [Test] + public void TvNotification_EpisodeList() + { + var episodeRequests = new List + { + new EpisodeRequests + { + EpisodeNumber = 1, + }, + new EpisodeRequests + { + EpisodeNumber = 2, + }, + new EpisodeRequests + { + EpisodeNumber = 3, + } + }; + var seasonRequests = new List + { + new SeasonRequests + { + Episodes = episodeRequests, + SeasonNumber = 1 + }, + new SeasonRequests + { + Episodes = episodeRequests, + SeasonNumber = 2 + }, + new SeasonRequests + { + Episodes = episodeRequests, + SeasonNumber = 3 + } + }; + + + var notificationOptions = new NotificationOptions(); + var req = F.Build() + .With(x => x.RequestType, RequestType.TvShow) + .With(x => x.Available, true) + .With(x => x.SeasonRequests, seasonRequests) + .Create(); + var customization = new CustomizationSettings + { + ApplicationUrl = "url", + ApplicationName = "name" + }; + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + + Assert.That(sut.EpisodesList, Is.EqualTo("1,1,1,2,2,2,3,3,3")); + Assert.That(sut.SeasonsList, Is.EqualTo("1,2,3")); + + } + + [TestCaseSource(nameof(RequestStatusData))] + public string TvShowNotificationTests_RequestStatus(bool available, bool denied, bool approved) + { + var notificationOptions = new NotificationOptions(); + var req = F.Build() + .With(x => x.RequestType, RequestType.TvShow) + .With(x => x.Available, available) + .With(x => x.Denied, denied) + .With(x => x.Approved, approved) + .Create(); + var customization = new CustomizationSettings(); + var userPrefs = new UserNotificationPreferences(); + sut.Setup(notificationOptions, req, customization, userPrefs); + return sut.RequestStatus; + } + + + [Test] + public void EmailSetupTests() + { + var user = F.Create(); + var customization = new CustomizationSettings + { + ApplicationUrl = "url", + ApplicationName = "name" + }; + sut.Setup(user, customization); + + Assert.That(user.UserName, Is.EqualTo(sut.RequestedUser)); + Assert.That(user.UserName, Is.EqualTo(sut.UserName)); + Assert.That(user.UserAlias, Is.EqualTo(sut.Alias)); + Assert.That(sut.ApplicationUrl, Is.EqualTo("url")); + Assert.That(sut.ApplicationName, Is.EqualTo("name")); + } + + } +} diff --git a/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj b/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj index 469611ac8..4964e8078 100644 --- a/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj +++ b/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj @@ -5,6 +5,7 @@ + diff --git a/src/Ombi.Notifications/Agents/DiscordNotification.cs b/src/Ombi.Notifications/Agents/DiscordNotification.cs index dda10f210..4a0c369fc 100644 --- a/src/Ombi.Notifications/Agents/DiscordNotification.cs +++ b/src/Ombi.Notifications/Agents/DiscordNotification.cs @@ -106,21 +106,23 @@ namespace Ombi.Notifications.Agents }; var fields = new List(); - - if (model.Data.TryGetValue("Alias", out var alias)) + if (!settings.HideUser) { - if (alias.HasValue()) + if (model.Data.TryGetValue("Alias", out var alias)) { - fields.Add(new DiscordField { name = "Requested By", value = alias, inline = true }); + if (alias.HasValue()) + { + fields.Add(new DiscordField { name = "Requested By", value = alias, inline = true }); + } } - } - else - { - if (model.Data.TryGetValue("RequestedUser", out var requestedUser)) + else { - if (requestedUser.HasValue()) + if (model.Data.TryGetValue("RequestedUser", out var requestedUser)) { - fields.Add(new DiscordField { name = "Requested By", value = requestedUser, inline = true }); + if (requestedUser.HasValue()) + { + fields.Add(new DiscordField { name = "Requested By", value = requestedUser, inline = true }); + } } } } diff --git a/src/Ombi.Notifications/Agents/EmailNotification.cs b/src/Ombi.Notifications/Agents/EmailNotification.cs index a7f9334fb..b6d03305b 100644 --- a/src/Ombi.Notifications/Agents/EmailNotification.cs +++ b/src/Ombi.Notifications/Agents/EmailNotification.cs @@ -240,9 +240,9 @@ namespace Ombi.Notifications.Agents private async Task SendToSubscribers(EmailNotificationSettings settings, NotificationMessage message) { - if (await SubsribedUsers.AnyAsync()) + if (await Subscribed.AnyAsync()) { - foreach (var user in SubsribedUsers) + foreach (var user in Subscribed) { if (user.Email.IsNullOrEmpty()) { diff --git a/src/Ombi.Notifications/Agents/LegacyMobileNotification.cs b/src/Ombi.Notifications/Agents/LegacyMobileNotification.cs index 5ac92d5bf..33cec783c 100644 --- a/src/Ombi.Notifications/Agents/LegacyMobileNotification.cs +++ b/src/Ombi.Notifications/Agents/LegacyMobileNotification.cs @@ -304,9 +304,9 @@ namespace Ombi.Notifications.Agents private async Task AddSubscribedUsers(List playerIds) { - if (await SubsribedUsers.AnyAsync()) + if (await Subscribed.AnyAsync()) { - foreach (var user in SubsribedUsers) + foreach (var user in Subscribed) { var notificationId = user.NotificationUserIds; if (notificationId.Any()) diff --git a/src/Ombi.Notifications/Agents/MobileNotification.cs b/src/Ombi.Notifications/Agents/MobileNotification.cs index 6080acdf0..bb6454bfc 100644 --- a/src/Ombi.Notifications/Agents/MobileNotification.cs +++ b/src/Ombi.Notifications/Agents/MobileNotification.cs @@ -57,6 +57,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, + Subject = "New Request", Data = GetNotificationData(parsed, NotificationType.NewRequest) }; @@ -76,6 +77,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, + Subject = "New Issue", Data = GetNotificationData(parsed, NotificationType.Issue) }; @@ -127,6 +129,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, + Subject = "Issue Resolved", Data = GetNotificationData(parsed, NotificationType.IssueResolved) }; @@ -149,6 +152,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, + Subject = "Request Error", Data = GetNotificationData(parsed, NotificationType.ItemAddedToFaultQueue) }; @@ -168,6 +172,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, + Subject = "Request Declined", Data = GetNotificationData(parsed, NotificationType.RequestDeclined) }; @@ -188,6 +193,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, + Subject = "Request Approved", Data = GetNotificationData(parsed, NotificationType.RequestApproved) }; @@ -212,6 +218,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, + Subject = "Request Available", Data = data }; // Send to user @@ -259,6 +266,7 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = message, + Subject = "Test Notification" }; // Send to user var user = await _userManager.Users.Include(x => x.NotificationUserIds).FirstOrDefaultAsync(x => x.Id.Equals(model.UserId)); @@ -338,9 +346,9 @@ namespace Ombi.Notifications.Agents private async Task AddSubscribedUsers(List playerIds) { - if (await SubsribedUsers.AnyAsync()) + if (await Subscribed.AnyAsync()) { - foreach (var user in SubsribedUsers) + foreach (var user in Subscribed) { var notificationIds = await _notifications.GetAll().Where(x => x.UserId == user.Id).ToListAsync(); diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/BaseNotification.cs index c9404eb2c..46db5d467 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/BaseNotification.cs @@ -48,7 +48,7 @@ namespace Ombi.Notifications protected ChildRequests TvRequest { get; set; } protected AlbumRequest AlbumRequest { get; set; } protected MovieRequests MovieRequest { get; set; } - protected IQueryable SubsribedUsers { get; private set; } + protected IQueryable Subscribed { get; private set; } public abstract string NotificationName { get; } @@ -75,7 +75,7 @@ namespace Ombi.Notifications if (model.RequestId > 0) { await LoadRequest(model.RequestId, model.RequestType); - SubsribedUsers = GetSubscriptions(model.RequestId, model.RequestType); + Subscribed = GetSubscriptions(model.RequestId, model.RequestType); } Customization = await CustomizationSettings.GetSettingsAsync(); @@ -209,7 +209,6 @@ namespace Ombi.Notifications if (model.RequestType == RequestType.Movie) { _log.LogDebug("Notification options: {@model}, Req: {@MovieRequest}, Settings: {@Customization}", model, MovieRequest, Customization); - curlys.Setup(model, MovieRequest, Customization, preference); } else if (model.RequestType == RequestType.TvShow) diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 3b6263fb6..0dffe0b90 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -14,218 +14,156 @@ namespace Ombi.Notifications { public class NotificationMessageCurlys { - public void Setup(NotificationOptions opts, MovieRequests req, CustomizationSettings s, UserNotificationPreferences pref) + public void SetupNewsletter(CustomizationSettings s) { - LoadIssues(opts); - - RequestId = req?.Id.ToString(); - ProviderId = req?.TheMovieDbId.ToString() ?? string.Empty; - string title; - if (req == null) - { - opts.Substitutes.TryGetValue("Title", out title); - } - else - { - title = req?.Title; - } - ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; - ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; - RequestedUser = req?.RequestedUser?.UserName; - if (UserName.IsNullOrEmpty()) - { - // Can be set if it's an issue - UserName = req?.RequestedUser?.UserName; - } + ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s.ApplicationName; + ApplicationUrl = s?.ApplicationUrl.HasValue() ?? false ? s.ApplicationUrl : string.Empty; + } - if (Alias.IsNullOrEmpty()) - { - // Can be set if it's an issue - Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; - } + public void Setup(OmbiUser user, CustomizationSettings s) + { + ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s.ApplicationName; + ApplicationUrl = s?.ApplicationUrl.HasValue() ?? false ? s.ApplicationUrl : string.Empty; + RequestedUser = user.UserName; + Alias = user.UserAlias; + UserName = user.UserName; + } - if (pref != null) - { - UserPreference = pref.Value.HasValue() ? pref.Value : Alias; - } - Title = title; - RequestedDate = req?.RequestedDate.ToString("D"); - if (Type.IsNullOrEmpty()) - { - Type = req?.RequestType.Humanize(); - } - Overview = req?.Overview; + public void Setup(NotificationOptions opts, MovieRequests req, CustomizationSettings s, + UserNotificationPreferences pref) + { + LoadIssues(opts); + LoadCommon(req, s, pref); + LoadTitle(opts, req); + ProviderId = req?.TheMovieDbId.ToString() ?? string.Empty; Year = req?.ReleaseDate.Year.ToString(); - DenyReason = req?.DeniedReason; - AvailableDate = req?.MarkedAsAvailable?.ToString("D") ?? string.Empty; - - PosterImage = string.Format((req?.PosterPath ?? string.Empty).StartsWith("/", StringComparison.InvariantCultureIgnoreCase) - ? "https://image.tmdb.org/t/p/w300{0}" : "https://image.tmdb.org/t/p/w300/{0}", req?.PosterPath); - - + Overview = req?.Overview; AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; - + PosterImage = $"https://image.tmdb.org/t/p/w300/{req?.PosterPath?.TrimStart('/') ?? string.Empty}"; CalculateRequestStatus(req); } - public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s, UserNotificationPreferences pref) + public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, + UserNotificationPreferences pref) { LoadIssues(opts); + LoadCommon(req, s, pref); + LoadTitle(opts, req); + ProviderId = req?.ParentRequest?.ExternalProviderId.ToString() ?? string.Empty; + Year = req?.ParentRequest?.ReleaseDate.Year.ToString(); + Overview = req?.ParentRequest?.Overview; + AdditionalInformation = opts.AdditionalInformation; + PosterImage = + $"https://image.tmdb.org/t/p/w300/{req?.ParentRequest?.PosterPath?.TrimStart('/') ?? string.Empty}"; - RequestId = req?.Id.ToString(); - ProviderId = req?.ForeignArtistId ?? string.Empty; + // Generate episode list. + StringBuilder epSb = new StringBuilder(); + IEnumerable episodes = req?.SeasonRequests? + .SelectMany(x => x.Episodes) ?? new List(); + episodes + .OrderBy(x => x.EpisodeNumber) + .ToList() + .ForEach(ep => epSb.Append($"{ep.EpisodeNumber},")); + if (epSb.Length > 0) epSb.Remove(epSb.Length - 1, 1); + EpisodesList = epSb.ToString(); - string title; - if (req == null) - { - opts.Substitutes.TryGetValue("Title", out title); - } - else - { - title = req?.Title; - } - ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; - ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; - RequestedUser = req?.RequestedUser?.UserName; - if (UserName.IsNullOrEmpty()) - { - // Can be set if it's an issue - UserName = req?.RequestedUser?.UserName; - } + // Generate season list. + StringBuilder seasonSb = new StringBuilder(); + List seasons = req?.SeasonRequests ?? new List(); + seasons + .OrderBy(x => x.SeasonNumber) + .ToList() + .ForEach(ep => seasonSb.Append($"{ep.SeasonNumber},")); + if (seasonSb.Length > 0) seasonSb.Remove(seasonSb.Length - 1, 1); + SeasonsList = seasonSb.ToString(); + CalculateRequestStatus(req); + } - AvailableDate = req?.MarkedAsAvailable?.ToString("D") ?? string.Empty; - DenyReason = req?.DeniedReason; - if (Alias.IsNullOrEmpty()) - { - Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; - } - if (pref != null) - { - UserPreference = pref.Value.HasValue() ? pref.Value : Alias; - } - Title = title; - RequestedDate = req?.RequestedDate.ToString("D"); - if (Type.IsNullOrEmpty()) - { - Type = req?.RequestType.Humanize(); - } + public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s, + UserNotificationPreferences pref) + { + LoadIssues(opts); + LoadCommon(req, s, pref); + LoadTitle(opts, req); + ProviderId = req?.ForeignArtistId ?? string.Empty; Year = req?.ReleaseDate.Year.ToString(); - PosterImage = (req?.Cover.HasValue() ?? false) ? req.Cover : req?.Disk ?? string.Empty; - AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; + PosterImage = req?.Cover.HasValue() ?? false ? req.Cover : req?.Disk ?? string.Empty; CalculateRequestStatus(req); } - public void SetupNewsletter(CustomizationSettings s) + private void LoadIssues(NotificationOptions opts) { - ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; - ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; + IssueDescription = opts.Substitutes.TryGetValue("IssueDescription", out string val) ? val : string.Empty; + IssueCategory = opts.Substitutes.TryGetValue("IssueCategory", out val) ? val : string.Empty; + IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty; + IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty; + NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty; + UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; + Alias = opts.Substitutes.TryGetValue("IssueUserAlias", out val) ? val : string.Empty; + Type = opts.Substitutes.TryGetValue("RequestType", out val) && Enum.TryParse(val, out RequestType type) + ? HumanizeReturnType(type) + : string.Empty; } - public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, UserNotificationPreferences pref) + private void LoadCommon(BaseRequest req, CustomizationSettings s, UserNotificationPreferences pref) { - LoadIssues(opts); + ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s.ApplicationName; + ApplicationUrl = s?.ApplicationUrl.HasValue() ?? false ? s.ApplicationUrl : string.Empty; + AvailableDate = req?.MarkedAsAvailable?.ToString("D") ?? string.Empty; + DenyReason = req?.DeniedReason; RequestId = req?.Id.ToString(); - ProviderId = req?.ParentRequest?.ExternalProviderId.ToString() ?? string.Empty; - string title; - if (req == null) - { - opts.Substitutes.TryGetValue("Title", out title); - } - else + RequestedUser = req?.RequestedUser?.UserName; + RequestedDate = req?.RequestedDate.ToString("D"); + + if (Type.IsNullOrEmpty()) { - title = req?.ParentRequest.Title; + Type = HumanizeReturnType(req?.RequestType); } - DenyReason = req?.DeniedReason; - ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; - ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; - RequestedUser = req?.RequestedUser?.UserName; + if (UserName.IsNullOrEmpty()) { - // Can be set if it's an issue UserName = req?.RequestedUser?.UserName; } - AvailableDate = req?.MarkedAsAvailable?.ToString("D") ?? string.Empty; + if (Alias.IsNullOrEmpty()) { - Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; + Alias = req?.RequestedUser?.Alias.HasValue() ?? false + ? req.RequestedUser?.Alias + : req?.RequestedUser?.UserName; } + if (pref != null) { UserPreference = pref.Value.HasValue() ? pref.Value : Alias; } - Title = title; - RequestedDate = req?.RequestedDate.ToString("D"); - if (Type.IsNullOrEmpty()) - { - Type = req?.RequestType.Humanize(); - } - - Overview = req?.ParentRequest.Overview; - Year = req?.ParentRequest.ReleaseDate.Year.ToString(); - - PosterImage = string.Format((req?.ParentRequest.PosterPath ?? string.Empty).StartsWith("/", StringComparison.InvariantCultureIgnoreCase) - ? "https://image.tmdb.org/t/p/w300{0}" : "https://image.tmdb.org/t/p/w300/{0}", req?.ParentRequest.PosterPath); - - AdditionalInformation = opts.AdditionalInformation; - // DO Episode and Season Lists - - var episodes = req?.SeasonRequests?.SelectMany(x => x.Episodes) ?? new List(); - var seasons = req?.SeasonRequests?.OrderBy(x => x.SeasonNumber).ToList() ?? new List(); - var orderedEpisodes = episodes.OrderBy(x => x.EpisodeNumber).ToList(); - var epSb = new StringBuilder(); - var seasonSb = new StringBuilder(); - for (var i = 0; i < orderedEpisodes.Count; i++) - { - var ep = orderedEpisodes[i]; - if (i < orderedEpisodes.Count - 1) - { - epSb.Append($"{ep.EpisodeNumber},"); - } - else - { - epSb.Append($"{ep.EpisodeNumber}"); - } - } - - for (var i = 0; i < seasons.Count; i++) - { - var ep = seasons[i]; - if (i < seasons.Count - 1) - { - seasonSb.Append($"{ep.SeasonNumber},"); - } - else - { - seasonSb.Append($"{ep.SeasonNumber}"); - } - } - - EpisodesList = epSb.ToString(); - SeasonsList = seasonSb.ToString(); - CalculateRequestStatus(req); } - public void Setup(OmbiUser user, CustomizationSettings s) + private static string HumanizeReturnType(RequestType? requestType) { - ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; - ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; - RequestedUser = user.UserName; - Alias = user.UserAlias; - UserName = user.UserName; + return requestType switch + { + null => string.Empty, + RequestType.TvShow => "TV Show", + _ => requestType.Humanize() + }; } - private void LoadIssues(NotificationOptions opts) + private void LoadTitle(NotificationOptions opts, BaseRequest req) { - var val = string.Empty; - IssueDescription = opts.Substitutes.TryGetValue("IssueDescription", out val) ? val : string.Empty; - IssueCategory = opts.Substitutes.TryGetValue("IssueCategory", out val) ? val : string.Empty; - IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty; - IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty; - NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty; - UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty; - Alias = opts.Substitutes.TryGetValue("IssueUserAlias", out val) ? val : string.Empty; - Type = opts.Substitutes.TryGetValue("RequestType", out val) ? val.Humanize() : string.Empty; + switch (req) + { + case null: + opts.Substitutes.TryGetValue("Title", out string title); + Title = title; + break; + case ChildRequests tvShowRequest: + Title = tvShowRequest.ParentRequest?.Title; + break; + default: + Title = req.Title; + break; + } } private void CalculateRequestStatus(BaseRequest req) @@ -238,16 +176,19 @@ namespace Ombi.Notifications RequestStatus = "Available"; return; } + if (req.Denied ?? false) { RequestStatus = "Denied"; return; } + if (!req.Available && req.Approved) { RequestStatus = "Processing Request"; return; } + RequestStatus = "Pending Approval"; } } @@ -288,36 +229,36 @@ namespace Ombi.Notifications public Dictionary Curlys => new Dictionary { - {nameof(RequestId), RequestId }, - {nameof(RequestedUser), RequestedUser }, - {nameof(Title), Title }, - {nameof(RequestedDate), RequestedDate }, - {nameof(Type), Type }, - {nameof(AdditionalInformation), AdditionalInformation }, - {nameof(LongDate),LongDate}, - {nameof(ShortDate),ShortDate}, - {nameof(LongTime),LongTime}, - {nameof(ShortTime),ShortTime}, - {nameof(Overview),Overview}, - {nameof(Year),Year}, - {nameof(EpisodesList),EpisodesList}, - {nameof(SeasonsList),SeasonsList}, - {nameof(PosterImage),PosterImage}, - {nameof(ApplicationName),ApplicationName}, - {nameof(ApplicationUrl),ApplicationUrl}, - {nameof(IssueDescription),IssueDescription}, - {nameof(IssueCategory),IssueCategory}, - {nameof(IssueStatus),IssueStatus}, - {nameof(IssueSubject),IssueSubject}, - {nameof(NewIssueComment),NewIssueComment}, - {nameof(IssueUser),IssueUser}, - {nameof(UserName),UserName}, - {nameof(Alias),Alias}, - {nameof(UserPreference),UserPreference}, - {nameof(DenyReason),DenyReason}, - {nameof(AvailableDate),AvailableDate}, - {nameof(RequestStatus),RequestStatus}, - {nameof(ProviderId),ProviderId}, + { nameof(RequestId), RequestId }, + { nameof(RequestedUser), RequestedUser }, + { nameof(Title), Title }, + { nameof(RequestedDate), RequestedDate }, + { nameof(Type), Type }, + { nameof(AdditionalInformation), AdditionalInformation }, + { nameof(LongDate), LongDate }, + { nameof(ShortDate), ShortDate }, + { nameof(LongTime), LongTime }, + { nameof(ShortTime), ShortTime }, + { nameof(Overview), Overview }, + { nameof(Year), Year }, + { nameof(EpisodesList), EpisodesList }, + { nameof(SeasonsList), SeasonsList }, + { nameof(PosterImage), PosterImage }, + { nameof(ApplicationName), ApplicationName }, + { nameof(ApplicationUrl), ApplicationUrl }, + { nameof(IssueDescription), IssueDescription }, + { nameof(IssueCategory), IssueCategory }, + { nameof(IssueStatus), IssueStatus }, + { nameof(IssueSubject), IssueSubject }, + { nameof(NewIssueComment), NewIssueComment }, + { nameof(IssueUser), IssueUser }, + { nameof(UserName), UserName }, + { nameof(Alias), Alias }, + { nameof(UserPreference), UserPreference }, + { nameof(DenyReason), DenyReason }, + { nameof(AvailableDate), AvailableDate }, + { nameof(RequestStatus), RequestStatus }, + { nameof(ProviderId), ProviderId }, }; } } \ No newline at end of file diff --git a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs index 7704b78ac..a86afcad0 100644 --- a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs +++ b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs @@ -17,6 +17,7 @@ using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; +using Ombi.Helpers; namespace Ombi.Schedule.Tests { @@ -53,7 +54,7 @@ namespace Ombi.Schedule.Tests ImdbId = "test" }; _movie.Setup(x => x.GetAll()).Returns(new List { request }.AsQueryable()); - _repo.Setup(x => x.Get("test")).ReturnsAsync(new PlexServerContent()); + _repo.Setup(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent()); await Checker.Execute(null); diff --git a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs index 85765b6c6..4c856c9cd 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/Interfaces/IOmbiAutomaticUpdater.cs @@ -5,6 +5,6 @@ namespace Ombi.Schedule.Jobs.Ombi public interface IOmbiAutomaticUpdater : IBaseJob { string[] GetVersion(); - Task UpdateAvailable(string branch, string currentVersion); + Task UpdateAvailable(string currentVersion); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs index e44728698..c2cf42441 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/OmbiAutomaticUpdater.cs @@ -49,10 +49,10 @@ namespace Ombi.Schedule.Jobs.Ombi var productArray = productVersion.Split('-'); return productArray; } - public async Task UpdateAvailable(string branch, string currentVersion) + public async Task UpdateAvailable(string currentVersion) { - var updates = await Processor.Process(branch); + var updates = await Processor.Process(); var serverVersion = updates.UpdateVersionString; return !serverVersion.Equals(currentVersion, StringComparison.CurrentCultureIgnoreCase); @@ -88,7 +88,7 @@ namespace Ombi.Schedule.Jobs.Ombi Logger.LogDebug(LoggingEvents.Updater, "Looking for updates now"); //TODO this fails because the branch = featureupdater when it should be feature/updater - var updates = await Processor.Process(branch); + var updates = await Processor.Process(); Logger.LogDebug(LoggingEvents.Updater, "Updates: {0}", updates); diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs index d1d26d6e1..f203f297a 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -183,13 +183,13 @@ namespace Ombi.Schedule.Jobs.Plex PlexServerContent item = null; if (movie.ImdbId.HasValue()) { - item = await _repo.Get(movie.ImdbId); + item = await _repo.Get(movie.ImdbId, ProviderType.ImdbId); } if (item == null) { if (movie.TheMovieDbId.ToString().HasValue()) { - item = await _repo.Get(movie.TheMovieDbId.ToString()); + item = await _repo.Get(movie.TheMovieDbId.ToString(), ProviderType.TheMovieDbId); } } if (item == null) diff --git a/src/Ombi.Schedule/Processor/AppVeyor.cs b/src/Ombi.Schedule/Processor/AppVeyor.cs index 6bb4e001c..5a760c50c 100644 --- a/src/Ombi.Schedule/Processor/AppVeyor.cs +++ b/src/Ombi.Schedule/Processor/AppVeyor.cs @@ -109,8 +109,8 @@ namespace Ombi.Core.Processor public string UpdateVersionString { get; set; } public int UpdateVersion { get; set; } public DateTime UpdateDate { get; set; } - - public List ChangeLogs { get; set; } + public bool UpdateAvailable { get; set; } + public string ChangeLogs { get; set; } public List Downloads { get; set; } } diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index a34bd89e1..cffb5e508 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -16,94 +16,41 @@ namespace Ombi.Schedule.Processor { public class ChangeLogProcessor : IChangeLogProcessor { - public ChangeLogProcessor(IApi api, IOmbiHttpClient client) + public ChangeLogProcessor(IApi api, IHttpClientFactory client) { _api = api; - _client = client; + _client = client.CreateClient("OmbiClient"); } private readonly IApi _api; - private readonly IOmbiHttpClient _client; + private readonly HttpClient _client; private const string _changeLogUrl = "https://raw.githubusercontent.com/tidusjar/Ombi/{0}/CHANGELOG.md"; private const string AppveyorApiUrl = "https://ci.appveyor.com/api"; private string ChangeLogUrl(string branch) => string.Format(_changeLogUrl, branch); - public async Task Process(string branch) + public async Task Process() { - var masterBranch = branch.Equals("master", StringComparison.CurrentCultureIgnoreCase); - string githubChangeLog; - - githubChangeLog = await _client.GetStringAsync(new Uri(ChangeLogUrl(branch))); - - - var html = Markdown.ToHtml(githubChangeLog); - - - var doc = new HtmlDocument(); - doc.LoadHtml(html); - - HtmlNode latestRelease; - if (masterBranch) - { - latestRelease = doc.DocumentNode.Descendants("h2") - .FirstOrDefault(x => x.InnerText != "(unreleased)"); - } - else - { - latestRelease = doc.DocumentNode.Descendants("h2") - .FirstOrDefault(x => x.InnerText == "(unreleased)"); - - if (latestRelease == null) - { - latestRelease = doc.DocumentNode.Descendants("h2") - .FirstOrDefault(x => x.InnerText != "(unreleased)"); - } - } - - var newFeatureList = latestRelease.NextSibling.NextSibling.NextSibling.NextSibling; - var featuresString = newFeatureList.ChildNodes.Where(x => x.Name != "#text").Select(x => x.InnerText.Replace("\\n", "")).ToList(); - var fixes = newFeatureList.NextSibling.NextSibling.NextSibling.NextSibling; - var fixesString = fixes.ChildNodes.Where(x => x.Name != "#text").Select(x => x.InnerText.Replace("\\n", "")).ToList(); - - // Cleanup - var featuresList = featuresString.Distinct().ToList(); - var fixesList = fixesString.Distinct().ToList(); - - // Get release var release = new Release { - Version = latestRelease.InnerText, - Features = featuresList, - Fixes = fixesList, Downloads = new List() }; - if (masterBranch) - { - var releaseTag = latestRelease.InnerText.Substring(0, 9); - await GetGitubRelease(release, releaseTag); - } - else - { - // Get AppVeyor - await GetAppVeyorRelease(release, branch); - } - - - return TransformUpdate(release,!masterBranch); - + await GetGitubRelease(release); + + return TransformUpdate(release); } - private UpdateModel TransformUpdate(Release release, bool develop) + private UpdateModel TransformUpdate(Release release) { var newUpdate = new UpdateModel { - UpdateVersionString = develop ? release.Version : release.Version.Substring(1,8), - UpdateVersion = release.Version == "(unreleased)" ? 0 : int.Parse(release.Version.Substring(1, 5).Replace(".", "")), + UpdateVersionString = release.Version, + UpdateVersion = int.Parse(release.Version.Substring(1, 5).Replace(".", "")), UpdateDate = DateTime.Now, - ChangeLogs = new List(), - Downloads = new List() - }; + ChangeLogs = release.Description, + Downloads = new List(), + UpdateAvailable = release.Version != "v" + AssemblyHelper.GetRuntimeVersion() + }; foreach (var dl in release.Downloads) { @@ -114,75 +61,16 @@ namespace Ombi.Schedule.Processor }); } - foreach (var f in release.Features) - { - var change = new ChangeLog - { - Descripion = f, - Type = "New", - }; - - newUpdate.ChangeLogs.Add(change); - } - - foreach (var f in release.Fixes) - { - var change = new ChangeLog - { - Descripion = f, - Type = "Fixed", - }; - - newUpdate.ChangeLogs.Add(change); - } - return newUpdate; } - private async Task GetAppVeyorRelease(Release release, string branch) + private async Task GetGitubRelease(Release release) { - var request = new Request($"/projects/tidusjar/requestplex/branch/{branch}", AppVeyorApi.AppveyorApiUrl, HttpMethod.Get); - request.ApplicationJsonContentType(); + var client = new GitHubClient(Octokit.ProductHeaderValue.Parse("OmbiV4")); - var builds = await _api.Request(request); - var jobId = builds.build.jobs.FirstOrDefault()?.jobId ?? string.Empty; + var releases = await client.Repository.Release.GetAll("ombi-app", "ombi"); + var latest = releases.OrderByDescending(x => x.CreatedAt).FirstOrDefault(); - if (builds.build.finished == DateTime.MinValue || builds.build.status.Equals("failed")) - { - return; - } - release.Version = builds.build.version; - // get the artifacts - request = new Request($"/buildjobs/{jobId}/artifacts", AppVeyorApi.AppveyorApiUrl, HttpMethod.Get); - request.ApplicationJsonContentType(); - - var artifacts = await _api.Request>(request); - - foreach (var item in artifacts) - { - var d = new Downloads - { - Name = item.fileName, - Url = $"{AppveyorApiUrl}/buildjobs/{jobId}/artifacts/{item.fileName}" - }; - release.Downloads.Add(d); - } - } - - private async Task GetGitubRelease(Release release, string releaseTag) - { - var client = new GitHubClient(Octokit.ProductHeaderValue.Parse("OmbiV3")); - - var releases = await client.Repository.Release.GetAll("tidusjar", "ombi"); - var latest = releases.FirstOrDefault(x => x.TagName.Equals(releaseTag, StringComparison.InvariantCultureIgnoreCase)); - if (latest.Name.Contains("V2", CompareOptions.IgnoreCase)) - { - latest = null; - } - if (latest == null) - { - latest = releases.OrderByDescending(x => x.CreatedAt).FirstOrDefault(); - } foreach (var item in latest.Assets) { var d = new Downloads @@ -192,6 +80,8 @@ namespace Ombi.Schedule.Processor }; release.Downloads.Add(d); } + release.Description = Markdown.ToHtml(latest.Body); + release.Version = latest.TagName; } } public class Release @@ -199,8 +89,7 @@ namespace Ombi.Schedule.Processor public string Version { get; set; } public string CheckinVersion { get; set; } public List Downloads { get; set; } - public List Features { get; set; } - public List Fixes { get; set; } + public string Description { get; set; } } public class Downloads diff --git a/src/Ombi.Schedule/Processor/IChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/IChangeLogProcessor.cs index 97780245f..434faedd2 100644 --- a/src/Ombi.Schedule/Processor/IChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/IChangeLogProcessor.cs @@ -4,6 +4,6 @@ namespace Ombi.Core.Processor { public interface IChangeLogProcessor { - Task Process(string branch); + Task Process(); } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs index 44c910519..2dc612f7e 100644 --- a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs @@ -20,6 +20,7 @@ public bool AddOnly { get; set; } public bool V3 { get; set; } public int LanguageProfile { get; set; } + public int LanguageProfileAnime { get; set; } public bool ScanForAvailability { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/Notifications/DiscordNotificationSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/DiscordNotificationSettings.cs index 45dc17ee1..0ec81a6d8 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/DiscordNotificationSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/DiscordNotificationSettings.cs @@ -9,6 +9,7 @@ namespace Ombi.Settings.Settings.Models.Notifications public string WebhookUrl { get; set; } public string Username { get; set; } public string Icon { get; set; } + public bool HideUser { get; set; } [JsonIgnore] public string WebHookId => SplitWebUrl(4); diff --git a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs index 36b540026..6c622f2ce 100644 --- a/src/Ombi.Settings/Settings/Models/OmbiSettings.cs +++ b/src/Ombi.Settings/Settings/Models/OmbiSettings.cs @@ -6,7 +6,6 @@ public bool CollectAnalyticData { get; set; } public bool Wizard { get; set; } public string ApiKey { get; set; } - public bool IgnoreCertificateErrors { get; set; } public bool DoNotSendNotificationsForAutoApprove { get; set; } public bool HideRequestsUsers { get; set; } public bool DisableHealthChecks { get; set; } diff --git a/src/Ombi.Store/Entities/Requests/TvRequests.cs b/src/Ombi.Store/Entities/Requests/TvRequests.cs index 9ba54c58f..302700348 100644 --- a/src/Ombi.Store/Entities/Requests/TvRequests.cs +++ b/src/Ombi.Store/Entities/Requests/TvRequests.cs @@ -16,6 +16,7 @@ namespace Ombi.Store.Entities.Requests public string ImdbId { get; set; } public int? QualityOverride { get; set; } public int? RootFolder { get; set; } + public int? LanguageProfile { get; set; } public string Overview { get; set; } public string Title { get; set; } public string PosterPath { get; set; } diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20210408073336_SonarrProfileOnRequest.Designer.cs b/src/Ombi.Store/Migrations/OmbiMySql/20210408073336_SonarrProfileOnRequest.Designer.cs new file mode 100644 index 000000000..bd829a98d --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20210408073336_SonarrProfileOnRequest.Designer.cs @@ -0,0 +1,1235 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.MySql; + +namespace Ombi.Store.Migrations.OmbiMySql +{ + [DbContext(typeof(OmbiMySqlContext))] + [Migration("20210408073336_SonarrProfileOnRequest")] + partial class SonarrProfileOnRequest + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuditArea") + .HasColumnType("int"); + + b.Property("AuditType") + .HasColumnType("int"); + + b.Property("DateTime") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("User") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("PlayerId") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Alias") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("int"); + + b.Property("Language") + .HasColumnType("longtext"); + + b.Property("LastLoggedIn") + .HasColumnType("datetime(6)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("MovieRequestLimit") + .HasColumnType("int"); + + b.Property("MusicRequestLimit") + .HasColumnType("int"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("ProviderUserId") + .HasColumnType("longtext"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserAccessToken") + .HasColumnType("longtext"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("UserType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AddedAt") + .HasColumnType("datetime(6)"); + + b.Property("AlbumId") + .HasColumnType("longtext"); + + b.Property("ContentId") + .HasColumnType("int"); + + b.Property("ContentType") + .HasColumnType("int"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Completed") + .HasColumnType("datetime(6)"); + + b.Property("Dts") + .HasColumnType("datetime(6)"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RetryCount") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("ArtistName") + .HasColumnType("longtext"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Cover") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("Disk") + .HasColumnType("longtext"); + + b.Property("ForeignAlbumId") + .HasColumnType("longtext"); + + b.Property("ForeignArtistId") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("Rating") + .HasColumnType("decimal(65,30)"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("ParentRequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("SeriesType") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Comment") + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("IssuesId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedDate") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IssueCategoryId") + .HasColumnType("int"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("ProviderId") + .HasColumnType("longtext"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("ResovledDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("UserReportedId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("Denied") + .HasColumnType("tinyint(1)"); + + b.Property("DeniedReason") + .HasColumnType("longtext"); + + b.Property("DigitalReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("IssueId") + .HasColumnType("int"); + + b.Property("LangCode") + .HasColumnType("longtext"); + + b.Property("MarkedAsApproved") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsAvailable") + .HasColumnType("datetime(6)"); + + b.Property("MarkedAsDenied") + .HasColumnType("datetime(6)"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("RequestedByAlias") + .HasColumnType("longtext"); + + b.Property("RequestedDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestedUserId") + .HasColumnType("varchar(255)"); + + b.Property("RootPathOverride") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("TheMovieDbId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("EpisodeCount") + .HasColumnType("int"); + + b.Property("RequestDate") + .HasColumnType("datetime(6)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Background") + .HasColumnType("longtext"); + + b.Property("ExternalProviderId") + .HasColumnType("int"); + + b.Property("ImdbId") + .HasColumnType("longtext"); + + b.Property("LanguageProfile") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("PosterPath") + .HasColumnType("longtext"); + + b.Property("QualityOverride") + .HasColumnType("int"); + + b.Property("ReleaseDate") + .HasColumnType("datetime(6)"); + + b.Property("RootFolder") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("longtext"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("TotalSeasons") + .HasColumnType("int"); + + b.Property("TvDbId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Token") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Agent") + .HasColumnType("int"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("RadarrQualityProfile") + .HasColumnType("int"); + + b.Property("RadarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrQualityProfile") + .HasColumnType("int"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("int"); + + b.Property("SonarrRootPath") + .HasColumnType("int"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Deleted") + .HasColumnType("tinyint(1)"); + + b.Property("RequestId") + .HasColumnType("int"); + + b.Property("RequestType") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("VoteType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AirDate") + .HasColumnType("datetime(6)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("Available") + .HasColumnType("tinyint(1)"); + + b.Property("EpisodeNumber") + .HasColumnType("int"); + + b.Property("Requested") + .HasColumnType("tinyint(1)"); + + b.Property("SeasonId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("longtext"); + + b.Property("Url") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ChildRequestId") + .HasColumnType("int"); + + b.Property("Overview") + .HasColumnType("longtext"); + + b.Property("SeasonNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/20210408073336_SonarrProfileOnRequest.cs b/src/Ombi.Store/Migrations/OmbiMySql/20210408073336_SonarrProfileOnRequest.cs new file mode 100644 index 000000000..79faf16fd --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiMySql/20210408073336_SonarrProfileOnRequest.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.OmbiMySql +{ + public partial class SonarrProfileOnRequest : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LanguageProfile", + table: "TvRequests", + type: "int", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LanguageProfile", + table: "TvRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs index f127b6c23..06e8e76f7 100644 --- a/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiMySql/OmbiMySqlContextModelSnapshot.cs @@ -771,6 +771,9 @@ namespace Ombi.Store.Migrations.OmbiMySql b.Property("ImdbId") .HasColumnType("longtext"); + b.Property("LanguageProfile") + .HasColumnType("int"); + b.Property("Overview") .HasColumnType("longtext"); diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20210408073232_SonarrProfileOnRequest.Designer.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20210408073232_SonarrProfileOnRequest.Designer.cs new file mode 100644 index 000000000..916573687 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20210408073232_SonarrProfileOnRequest.Designer.cs @@ -0,0 +1,1234 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.Sqlite; + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + [DbContext(typeof(OmbiSqliteContext))] + [Migration("20210408073232_SonarrProfileOnRequest")] + partial class SonarrProfileOnRequest + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.1"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuditArea") + .HasColumnType("INTEGER"); + + b.Property("AuditType") + .HasColumnType("INTEGER"); + + b.Property("DateTime") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("User") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("NotificationType") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastLoggedIn") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MovieRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserAccessToken") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("AlbumId") + .HasColumnType("TEXT"); + + b.Property("ContentId") + .HasColumnType("INTEGER"); + + b.Property("ContentType") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completed") + .HasColumnType("TEXT"); + + b.Property("Dts") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RetryCount") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("ArtistName") + .HasColumnType("TEXT"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("Disk") + .HasColumnType("TEXT"); + + b.Property("ForeignAlbumId") + .HasColumnType("TEXT"); + + b.Property("ForeignArtistId") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("ParentRequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("SeriesType") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("IssuesId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IssueCategoryId") + .HasColumnType("INTEGER"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("ResovledDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("UserReportedId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("DigitalReleaseDate") + .HasColumnType("TEXT"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("LangCode") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("RootPathOverride") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeCount") + .HasColumnType("INTEGER"); + + b.Property("RequestDate") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("ExternalProviderId") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("LanguageProfile") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RootFolder") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSeasons") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RadarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("RadarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VoteType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AirDate") + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("Requested") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChildRequestId") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20210408073232_SonarrProfileOnRequest.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20210408073232_SonarrProfileOnRequest.cs new file mode 100644 index 000000000..c35e74d2a --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20210408073232_SonarrProfileOnRequest.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + public partial class SonarrProfileOnRequest : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LanguageProfile", + table: "TvRequests", + type: "INTEGER", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LanguageProfile", + table: "TvRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs index 144ca4613..4a595fa92 100644 --- a/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs @@ -770,6 +770,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Property("ImdbId") .HasColumnType("TEXT"); + b.Property("LanguageProfile") + .HasColumnType("INTEGER"); + b.Property("Overview") .HasColumnType("TEXT"); diff --git a/src/Ombi.Store/Repository/BaseRepository.cs b/src/Ombi.Store/Repository/BaseRepository.cs index 2908309ce..9d7d91447 100644 --- a/src/Ombi.Store/Repository/BaseRepository.cs +++ b/src/Ombi.Store/Repository/BaseRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -28,6 +29,11 @@ namespace Ombi.Store.Repository return await _db.FindAsync(key); } + public async Task Find(object key, CancellationToken cancellationToken) + { + return await _db.FindAsync(new[] { key }, cancellationToken: cancellationToken); + } + public IQueryable GetAll() { return _db.AsQueryable(); diff --git a/src/Ombi.Store/Repository/IPlexContentRepository.cs b/src/Ombi.Store/Repository/IPlexContentRepository.cs index 7bce2e75a..d1d30a630 100644 --- a/src/Ombi.Store/Repository/IPlexContentRepository.cs +++ b/src/Ombi.Store/Repository/IPlexContentRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; +using Ombi.Helpers; using Ombi.Store.Entities; namespace Ombi.Store.Repository @@ -10,7 +11,7 @@ namespace Ombi.Store.Repository public interface IPlexContentRepository : IExternalRepository { Task ContentExists(string providerId); - Task Get(string providerId); + Task Get(string providerId, ProviderType type); Task GetByKey(int key); Task Update(PlexServerContent existingContent); IQueryable GetAllEpisodes(); diff --git a/src/Ombi.Store/Repository/IRepository.cs b/src/Ombi.Store/Repository/IRepository.cs index fd7dcc86d..b93b07d45 100644 --- a/src/Ombi.Store/Repository/IRepository.cs +++ b/src/Ombi.Store/Repository/IRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Query; @@ -12,6 +13,7 @@ namespace Ombi.Store.Repository public interface IRepository where T : Entity { Task Find(object key); + Task Find(object key, CancellationToken cancellationToken); IQueryable GetAll(); Task FirstOrDefaultAsync(Expression> predicate); Task AddRange(IEnumerable content, bool save = true); diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index 1d53874a1..e5c31172e 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -31,6 +31,7 @@ using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Ombi.Helpers; using Ombi.Store.Context; using Ombi.Store.Entities; @@ -61,18 +62,21 @@ namespace Ombi.Store.Repository return any; } - public async Task Get(string providerId) + public async Task Get(string providerId, ProviderType type) { - var item = await Db.PlexServerContent.FirstOrDefaultAsync(x => x.ImdbId == providerId); - if (item == null) + switch (type) { - item = await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TheMovieDbId == providerId); - if (item == null) - { - item = await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TvDbId == providerId); - } + case ProviderType.ImdbId: + return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.ImdbId == providerId); + case ProviderType.TheMovieDbId: + return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TheMovieDbId == providerId); + case ProviderType.TvDbId: + return await Db.PlexServerContent.FirstOrDefaultAsync(x => x.TvDbId == providerId); + default: + break; } - return item; + + return null; } public async Task GetByKey(int key) diff --git a/src/Ombi.TheMovieDbApi/Models/FullMovieInfo.cs b/src/Ombi.TheMovieDbApi/Models/FullMovieInfo.cs index edc0c7e23..1ab33990e 100644 --- a/src/Ombi.TheMovieDbApi/Models/FullMovieInfo.cs +++ b/src/Ombi.TheMovieDbApi/Models/FullMovieInfo.cs @@ -97,6 +97,8 @@ namespace Ombi.Api.TheMovieDb.Models { [JsonProperty("results")] public List KeywordsValue { get; set; } + [JsonProperty("keywords")] + private List _movieKeywordValue { set { KeywordsValue = value; }} } public class KeywordsValue diff --git a/src/Ombi.TheMovieDbApi/Models/MovieDbSearchResult.cs b/src/Ombi.TheMovieDbApi/Models/MovieDbSearchResult.cs index 0e4a8852f..e936578e9 100644 --- a/src/Ombi.TheMovieDbApi/Models/MovieDbSearchResult.cs +++ b/src/Ombi.TheMovieDbApi/Models/MovieDbSearchResult.cs @@ -1,4 +1,7 @@ -namespace Ombi.Api.TheMovieDb.Models +using Ombi.Store.Repository.Requests; +using System.Collections.Generic; + +namespace Ombi.Api.TheMovieDb.Models { public class MovieDbSearchResult { @@ -16,5 +19,10 @@ public int VoteCount { get; set; } public bool Video { get; set; } public float VoteAverage { get; set; } + + /// + /// Mapped Property and not set from the API + /// + public List SeasonRequests { get; set; } = new List(); } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/src/app/interfaces/INotificationSettings.ts index d08778e2a..e1c52103c 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/INotificationSettings.ts @@ -59,6 +59,7 @@ export interface IDiscordNotifcationSettings extends INotificationSettings { webhookUrl: string; username: string; icon: string; + hideUser: boolean; notificationTemplates: INotificationTemplates[]; } diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts b/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts index e01d74cd5..889fac22a 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts @@ -1,5 +1,6 @@ import { IChildRequests, IMovieRequests } from "."; import { ITvRequests } from "./IRequestModel"; +import { ILanguageProfiles } from "./ISonarr"; export interface IRadarrRootFolder { id: number; @@ -27,6 +28,9 @@ export interface IAdvancedData { rootFolder: IRadarrRootFolder; rootFolders: IRadarrRootFolder[]; rootFolderId: number; + language: ILanguageProfiles; + languages: ILanguageProfiles[]; + languageId: number; movieRequest: IMovieRequests; tvRequest: ITvRequests; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts index 4f72b0631..3c2626cf5 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts @@ -26,6 +26,7 @@ export interface IMovieAdvancedOptions { requestId: number; qualityOverride: number; rootPathOverride: number; + languageProfile: number; } export interface IAlbumRequest extends IBaseRequest { @@ -110,6 +111,7 @@ export interface ITvRequests { status: string; childRequests: IChildRequests[]; qualityOverride: number; + languageProfile: number; background: any; totalSeasons: number; tvDbId: number; // NO LONGER USED @@ -119,6 +121,7 @@ export interface ITvRequests { // For UI display qualityOverrideTitle: string; rootPathOverrideTitle: string; + languageOverrideTitle: string; } export interface IChildRequests extends IBaseRequest { diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts b/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts index b59ad3780..8928c188b 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts @@ -51,6 +51,7 @@ export interface ITvRequestViewModelBase extends BaseRequestOptions { requestAll: boolean; firstSeason: boolean; latestSeason: boolean; + languageProfile: number | undefined; seasons: ISeasonsViewModel[]; } diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index c5b29adff..d73fdddc8 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -12,7 +12,6 @@ export interface IOmbiSettings extends ISettings { collectAnalyticData: boolean; wizard: boolean; apiKey: string; - ignoreCertificateErrors: boolean; doNotSendNotificationsForAutoApprove: boolean; hideRequestsUsers: boolean; defaultLanguageCode: string; @@ -104,6 +103,7 @@ export interface ISonarrSettings extends IExternalSettings { addOnly: boolean; v3: boolean; languageProfile: number; + languageProfileAnime: number; scanForAvailability: boolean; } @@ -285,3 +285,19 @@ export interface ITheMovieDbSettings extends ISettings { showAdultMovies: boolean; excludedKeywordIds: number[]; } + +export interface IUpdateModel +{ + updateVersionString: string; + updateVersion: number; + updateDate: Date, + updateAvailable: boolean; + changeLogs: string; + downloads: IUpdateDonloads[]; +} + +export interface IUpdateDonloads +{ + name: string; + url: string +} diff --git a/src/Ombi/ClientApp/src/app/login/login.component.ts b/src/Ombi/ClientApp/src/app/login/login.component.ts index 5245b8239..e9260d685 100644 --- a/src/Ombi/ClientApp/src/app/login/login.component.ts +++ b/src/Ombi/ClientApp/src/app/login/login.component.ts @@ -146,6 +146,9 @@ export class LoginComponent implements OnDestroy, OnInit { } public oauth() { + if (this.oAuthWindow) { + this.oAuthWindow.close(); + } this.oAuthWindow = window.open(window.location.toString(), "_blank", `toolbar=0, location=0, status=0, @@ -159,16 +162,22 @@ export class LoginComponent implements OnDestroy, OnInit { this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => { this.oAuthWindow!.location.replace(x.url); - this.pinTimer = setInterval(() => { + if (this.pinTimer) { + clearInterval(this.pinTimer); + } - this.oauthLoading = true; - this.getPinResult(x.pinId); - }, 4000); + this.pinTimer = setInterval(() => { + if(this.oAuthWindow.closed) { + this.oauthLoading = true; + this.getPinResult(x.pinId); + } + }, 1000); }); }); } public getPinResult(pinId: number) { + clearInterval(this.pinTimer); this.authService.oAuth(pinId).subscribe(x => { if(x.access_token) { this.store.save("id_token", x.access_token); @@ -176,7 +185,7 @@ export class LoginComponent implements OnDestroy, OnInit { if (this.authService.loggedIn()) { this.ngOnDestroy(); - if(this.oAuthWindow) { + if (this.oAuthWindow) { this.oAuthWindow.close(); } this.oauthLoading = false; @@ -184,6 +193,10 @@ export class LoginComponent implements OnDestroy, OnInit { return; } } + this.notify.open("Could not log you in!", "OK", { + duration: 3000 + }); + this.oauthLoading = false; }, err => { console.log(err); diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html index 928645f47..c0434980d 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html @@ -24,6 +24,7 @@ [type]="requestType" (openTrailer)="openDialog()" (onAdvancedOptions)="openAdvancedOptions()" + (onReProcessRequest)="reProcessRequest()" > diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts index 6d1ba4614..01c8a5783 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts @@ -184,12 +184,22 @@ export class MovieDetailsComponent { if (result) { result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; - await this.requestService2.updateMovieAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, requestId: this.movieRequest.id }).toPromise(); + await this.requestService2.updateMovieAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, languageProfile: 0, requestId: this.movieRequest.id }).toPromise(); this.setAdvancedOptions(result); } }); } + public reProcessRequest() { + this.requestService2.reprocessRequest(this.movieRequest.id, RequestType.movie).subscribe(result => { + if (result.result) { + this.messageService.send(result.message ? result.message : "Successfully Re-processed the request", "Ok"); + } else { + this.messageService.send(result.errorMessage, "Ok"); + } + }); + } + private loadBanner() { this.imageService.getMovieBanner(this.theMovidDbId.toString()).subscribe(x => { if (!this.movie.backdropPath) { diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html index 5ba86e631..6b0da8604 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html @@ -2,7 +2,7 @@
- {{movie.voteAverage | number:'1.0-1'}}/10 + {{movie.voteAverage | number:'1.0-1'}}/10 -
\ No newline at end of file + diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html index 60cad7bb0..f05792aa9 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html @@ -35,5 +35,9 @@ {{ 'MediaDetails.RadarrConfiguration' | translate}} {{ 'MediaDetails.SonarrConfiguration' | translate}} + diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts index 3a27fa723..bc504347b 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.ts @@ -26,6 +26,7 @@ export class SocialIconsComponent { @Output() openTrailer: EventEmitter = new EventEmitter(); @Output() onAdvancedOptions: EventEmitter = new EventEmitter(); + @Output() onReProcessRequest: EventEmitter = new EventEmitter(); public RequestType = RequestType; @@ -37,4 +38,8 @@ export class SocialIconsComponent { public openAdvancedOptions() { this.onAdvancedOptions.emit(); } + + public reProcessRequest() { + this.onReProcessRequest.emit(); + } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html index 13c39a111..682cb3db5 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html @@ -5,7 +5,7 @@ {{'MediaDetails.AutoApproveOptionsTvShort' | translate }} - + {{'MediaDetails.QualityProfilesSelect' | translate }} {{profile.name}} @@ -13,13 +13,21 @@
- + {{'MediaDetails.RootFolderSelect' | translate }} {{profile.path}}
+
+ + {{'MediaDetails.LanguageProfileSelect' | translate }} + + {{profile.name}} + + +
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts index 7846b69df..97e15f774 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts @@ -1,55 +1,92 @@ import { Component, Inject, OnInit } from "@angular/core"; import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { IAdvancedData, ISonarrProfile, ISonarrRootFolder } from "../../../../../interfaces"; -import { SonarrService } from "../../../../../services"; +import { + IAdvancedData, + ILanguageProfiles, + ISonarrProfile, + ISonarrRootFolder, + ISonarrSettings, +} from "../../../../../interfaces"; +import { SettingsService, SonarrService } from "../../../../../services"; @Component({ - templateUrl: "./tv-advanced-options.component.html", - selector: "tv-advanced-options", + templateUrl: "./tv-advanced-options.component.html", + selector: "tv-advanced-options", }) export class TvAdvancedOptionsComponent implements OnInit { + public sonarrProfiles: ISonarrProfile[]; + public sonarrRootFolders: ISonarrRootFolder[]; + public sonarrLanguageProfiles: ILanguageProfiles[]; + public sonarrEnabled: boolean; - public sonarrProfiles: ISonarrProfile[]; - public sonarrRootFolders: ISonarrRootFolder[]; + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: IAdvancedData, + private sonarrService: SonarrService, + private settingsService: SettingsService + ) {} - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: IAdvancedData, - private sonarrService: SonarrService - ) { - } + public async ngOnInit() { + this.settingsService.getSonarr().subscribe((settings: ISonarrSettings) => { + if (!settings.enabled) { + this.sonarrEnabled = false; + return; + } + + this.sonarrEnabled = true; + this.sonarrService.getQualityProfilesWithoutSettings().subscribe((c) => { + this.sonarrProfiles = c; + this.data.profiles = c; + this.setQualityOverrides(); + }); + this.sonarrService.getRootFoldersWithoutSettings().subscribe((c) => { + this.sonarrRootFolders = c; + this.data.rootFolders = c; + this.setRootFolderOverrides(); + }); + if (settings.v3) { + this.sonarrService + .getV3LanguageProfiles(settings) + .subscribe((profiles: ILanguageProfiles[]) => { + this.sonarrLanguageProfiles = profiles; + this.data.languages = profiles; + this.setLanguageOverride(); + }); + } + }); + } - public async ngOnInit() { - this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => { - this.sonarrProfiles = c; - this.data.profiles = c; - this.setQualityOverrides(); - }); - this.sonarrService.getRootFoldersWithoutSettings().subscribe(c => { - this.sonarrRootFolders = c; - this.data.rootFolders = c; - this.setRootFolderOverrides(); - }); + private setQualityOverrides(): void { + if (this.sonarrProfiles) { + const profile = this.sonarrProfiles.filter((p) => { + return p.id === this.data.tvRequest.qualityOverride; + }); + if (profile.length > 0) { + this.data.tvRequest.qualityOverrideTitle = profile[0].name; + } } + } - private setQualityOverrides(): void { - if (this.sonarrProfiles) { - const profile = this.sonarrProfiles.filter((p) => { - return p.id === this.data.tvRequest.qualityOverride; - }); - if (profile.length > 0) { - this.data.movieRequest.qualityOverrideTitle = profile[0].name; - } - } + private setRootFolderOverrides(): void { + if (this.sonarrRootFolders) { + const path = this.sonarrRootFolders.filter((folder) => { + return folder.id === this.data.tvRequest.rootFolder; + }); + if (path.length > 0) { + this.data.tvRequest.rootPathOverrideTitle = path[0].path; + } } + } - private setRootFolderOverrides(): void { - if (this.sonarrRootFolders) { - const path = this.sonarrRootFolders.filter((folder) => { - return folder.id === this.data.tvRequest.rootFolder; - }); - if (path.length > 0) { - this.data.movieRequest.rootPathOverrideTitle = path[0].path; - } - } + private setLanguageOverride(): void { + if (this.sonarrLanguageProfiles) { + const profile = this.sonarrLanguageProfiles.filter((p) => { + return p.id === this.data.tvRequest.languageProfile; + }); + if (profile.length > 0) { + this.data.tvRequest.languageOverrideTitle = profile[0].name; + } } + } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts index 8c3633cf6..0a103c75a 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts @@ -67,6 +67,7 @@ export class TvRequestGridComponent { viewModel.requestOnBehalf = result.username?.id; viewModel.qualityPathOverride = result?.sonarrPathId; viewModel.rootFolderOverride = result?.sonarrFolderId; + viewModel.languageProfile = result?.sonarrLanguageId; const requestResult = await this.requestServiceV2.requestTv(viewModel).toPromise(); this.postRequest(requestResult); diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.html index 5db522024..8bca28fb5 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.html @@ -70,6 +70,7 @@ +
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts index c7b0711b6..e51414902 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-requests/tv-requests-panel.component.ts @@ -4,6 +4,7 @@ import { RequestService } from "../../../../../services/request.service"; import { MessageService } from "../../../../../services"; import { MatDialog } from "@angular/material/dialog"; import { DenyDialogComponent } from "../../../shared/deny-dialog/deny-dialog.component"; +import { RequestServiceV2 } from "../../../../../services/requestV2.service"; @Component({ templateUrl: "./tv-requests-panel.component.html", @@ -16,7 +17,9 @@ export class TvRequestsPanelComponent { public displayedColumns: string[] = ['number', 'title', 'airDate', 'status']; - constructor(private requestService: RequestService, private messageService: MessageService, + constructor(private requestService: RequestService, + private requestService2: RequestServiceV2, + private messageService: MessageService, public dialog: MatDialog) { } @@ -83,9 +86,9 @@ export class TvRequestsPanelComponent { width: '250px', data: {requestId: request.id, requestType: RequestType.tvShow} }); - + dialogRef.afterClosed().subscribe(result => { - request.denied = true; + request.denied = true; request.seasonRequests.forEach((season) => { season.episodes.forEach((ep) => { ep.approved = false; @@ -93,4 +96,14 @@ export class TvRequestsPanelComponent { }); }); } + + public reProcessRequest(request: IChildRequests) { + this.requestService2.reprocessRequest(request.id, RequestType.tvShow).subscribe(result => { + if (result.result) { + this.messageService.send(result.message ? result.message : "Successfully Re-processed the request", "Ok"); + } else { + this.messageService.send(result.errorMessage, "Ok"); + } + }); + } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts index 13989fcdf..f9b686a48 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts @@ -102,7 +102,8 @@ export class TvDetailsComponent implements OnInit { // get the name and ids result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; - await this.requestService2.updateTvAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, requestId: this.showRequest.id }).toPromise(); + result.language = result.languages.filter(x => x.id === +result.langaugeId)[0]; + await this.requestService2.updateTvAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, languageProfile: result.languageId, requestId: this.showRequest.id }).toPromise(); this.setAdvancedOptions(result); } }); @@ -117,15 +118,20 @@ export class TvDetailsComponent implements OnInit { if (data.profileId) { this.showRequest.rootPathOverrideTitle = data.rootFolders.filter(x => x.id == data.rootFolderId)[0].path; } + if (data.languageId) { + this.showRequest.languageOverrideTitle = data.languages.filter(x => x.id == data.languageId)[0].name; + } } private loadAdvancedInfo() { const profile = this.sonarrService.getQualityProfilesWithoutSettings(); const folders = this.sonarrService.getRootFoldersWithoutSettings(); + const languages = this.sonarrService.getV3LanguageProfilesWithoutSettings(); - forkJoin([profile, folders]).subscribe(x => { + forkJoin([profile, folders, languages]).subscribe(x => { const sonarrProfiles = x[0]; const sonarrRootFolders = x[1]; + const languageProfiles = x[2]; const profile = sonarrProfiles.filter((p) => { return p.id === this.showRequest.qualityOverride; @@ -141,6 +147,13 @@ export class TvDetailsComponent implements OnInit { this.showRequest.rootPathOverrideTitle = path[0].path; } + const lang = languageProfiles.filter((folder) => { + return folder.id === this.showRequest.languageProfile; + }); + if (lang.length > 0) { + this.showRequest.languageOverrideTitle = lang[0].name; + } + }); } } diff --git a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html index c1b69b303..f73c9bbc8 100644 --- a/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html +++ b/src/Ombi/ClientApp/src/app/my-nav/my-nav.component.html @@ -6,12 +6,12 @@ {{applicationName}} - + - diff --git a/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts index 8e16dd87a..acf6560b9 100644 --- a/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts +++ b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts @@ -3,8 +3,8 @@ import { FormBuilder, FormGroup } from "@angular/forms"; import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { Observable } from "rxjs"; import { startWith, map } from "rxjs/operators"; -import { IRadarrProfile, IRadarrRootFolder, ISonarrProfile, ISonarrRootFolder, IUserDropdown, RequestType } from "../../interfaces"; -import { IdentityService, MessageService, RadarrService, RequestService, SonarrService } from "../../services"; +import { ILanguageProfiles, IRadarrProfile, IRadarrRootFolder, ISonarrProfile, ISonarrRootFolder, ISonarrSettings, IUserDropdown, RequestType } from "../../interfaces"; +import { IdentityService, MessageService, RadarrService, RequestService, SettingsService, SonarrService } from "../../services"; import { RequestServiceV2 } from "../../services/requestV2.service"; export interface IAdminRequestDialogData { @@ -23,6 +23,7 @@ export class AdminRequestDialogComponent implements OnInit { @Inject(MAT_DIALOG_DATA) public data: IAdminRequestDialogData, private identityService: IdentityService, private sonarrService: SonarrService, + private settingsService: SettingsService, private radarrService: RadarrService, private fb: FormBuilder ) {} @@ -39,6 +40,7 @@ export class AdminRequestDialogComponent implements OnInit { public sonarrProfiles: ISonarrProfile[]; public sonarrRootFolders: ISonarrRootFolder[]; + public sonarrLanguageProfiles: ILanguageProfiles[]; public radarrProfiles: IRadarrProfile[]; public radarrRootFolders: IRadarrRootFolder[]; @@ -48,6 +50,7 @@ export class AdminRequestDialogComponent implements OnInit { username: [null], sonarrPathId: [null], sonarrFolderId: [null], + sonarrLanguageId: [null], radarrPathId: [null], radarrFolderId: [null] }) @@ -62,6 +65,13 @@ export class AdminRequestDialogComponent implements OnInit { if (this.data.type === RequestType.tvShow) { this.sonarrEnabled = await this.sonarrService.isEnabled(); if (this.sonarrEnabled) { + this.settingsService.getSonarr().subscribe((settings: ISonarrSettings) => { + if (settings.v3) { + this.sonarrService.getV3LanguageProfiles(settings).subscribe((profiles: ILanguageProfiles[]) => { + this.sonarrLanguageProfiles = profiles; + }) + } + }); this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => { this.sonarrProfiles = c; }); @@ -106,6 +116,7 @@ export class AdminRequestDialogComponent implements OnInit { model.radarrRootFolderTitle = this.radarrRootFolders?.filter(x => x.id == model.radarrFolderId)[0]?.path; model.sonarrRootFolderTitle = this.sonarrRootFolders?.filter(x => x.id == model.sonarrFolderId)[0]?.path; model.sonarrQualityOverrideTitle = this.sonarrProfiles?.filter(x => x.id == model.sonarrPathId)[0]?.name; + model.sonarrLanguageProfileTitle = this.sonarrLanguageProfiles?.filter(x => x.id == model.sonarrLanguageId)[0]?.name; this.dialogRef.close(model); } } diff --git a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts index f6d7a6e92..146f31269 100644 --- a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts +++ b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts @@ -66,6 +66,7 @@ export class EpisodeRequestComponent { viewModel.requestOnBehalf = result.username?.id; viewModel.qualityPathOverride = result?.sonarrPathId; viewModel.rootFolderOverride = result?.sonarrFolderId; + viewModel.languageProfile = result?.sonarrLanguageId; const requestResult = await this.requestService.requestTv(viewModel).toPromise(); this.postRequest(requestResult); diff --git a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html index e4c7758f6..0f3dbb082 100644 --- a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html +++ b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html @@ -1,30 +1,26 @@
-
-
- -
-
-

{{username}} ({{user.emailAddress}})

- -
+
+ +

{{username}} + ({{user.emailAddress}}) +

-
-
-
+
+
User Type:
-
+
{{UserType[user.userType]}}
-
+
{{'UserPreferences.LanguageDescription' | translate}}
@@ -38,14 +34,7 @@
-
- -
-
-
- -
{{'UserPreferences.StreamingCountryDescription' | translate}}
@@ -58,53 +47,71 @@
-
+
+ +
+ +
+
+ +
+
+ Get it on Google Play +
+
+ Get it from the App Store +
+
+ +
+
- - -
- +

Change Details

-
+
You need your current password to make any changes here - - Current Password - - -
+ + Current Password + + +
- - Email Address - - -
+ + Email Address + + +
- - New Password - - -
+ + New Password + + +
- - New Password Confirm - - -
+ + New Password Confirm + + +
@@ -117,19 +124,8 @@ - -
-
- -
- - -
-
-
-
\ No newline at end of file +
diff --git a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.scss b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.scss index faaef4fd5..b7085d2dc 100644 --- a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.scss +++ b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.scss @@ -5,9 +5,22 @@ } .profile-img { - border-radius: 100%; width: 75px; + height: 75px; + margin-right: 20px; + border-radius: 100%; +} + +#usernameTitle { + margin: 0; + align-self: center; + overflow-wrap: anywhere; } + +.user-type-row { + padding-bottom: 1.25em; +} + .my-auto { margin-top: auto; margin-bottom: auto; @@ -19,5 +32,5 @@ } .tab-content { - margin-top: 1%; + margin-top: 1.5em; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts index 50753dfc8..b4cfa6709 100644 --- a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts +++ b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, Inject, OnInit } from "@angular/core"; import { AuthService } from "../../../auth/auth.service"; import { TranslateService } from "@ngx-translate/core"; import { AvailableLanguages, ILanguage } from "./user-preference.constants"; @@ -6,6 +6,7 @@ import { IdentityService, NotificationService, SettingsService, ValidationServic import { ICustomizationSettings, IUser, UserType } from "../../../interfaces"; import { Md5 } from "ts-md5"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { APP_BASE_HREF } from "@angular/common"; @Component({ templateUrl: "./user-preference.component.html", @@ -22,6 +23,7 @@ export class UserPreferenceComponent implements OnInit { public selectedCountry: string; public customizationSettings: ICustomizationSettings; public UserType = UserType; + public baseUrl: string; public passwordForm: FormGroup; @@ -33,9 +35,13 @@ export class UserPreferenceComponent implements OnInit { private readonly identityService: IdentityService, private readonly settingsService: SettingsService, private readonly fb: FormBuilder, - private readonly validationService: ValidationService) { } + private readonly validationService: ValidationService, + @Inject(APP_BASE_HREF) public internalBaseUrl: string) { } public async ngOnInit() { + if (this.internalBaseUrl.length > 1) { + this.baseUrl = this.internalBaseUrl; + } const user = this.authService.claims(); if (user.name) { this.username = user.name; @@ -86,15 +92,6 @@ export class UserPreferenceComponent implements OnInit { this.identityService.updateStreamingCountry(this.selectedCountry).subscribe(x => this.notification.success(this.translate.instant("UserPreferences.Updated"))); } - public openMobileApp(event: any) { - event.preventDefault(); - - this.identityService.getAccessToken().subscribe(x => { - const url = `ombi://${this.customizationSettings.applicationUrl}_${x}`; - window.location.assign(url); - }); - } - public getProfileImage(): string { let emailHash: string|Int32Array; if (this.user.emailAddress) { @@ -129,9 +126,16 @@ export class UserPreferenceComponent implements OnInit { }) } + public openMobileApp(event: any) { + event.preventDefault(); + + const url = `ombi://${this.qrCode}`; + window.location.assign(url); + } + private welcomeText: string; - private setWelcomeText() { + private setWelcomeText() { var d = new Date(); var hour = d.getHours(); diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html index 1dcbf29c0..492fe4f32 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.html @@ -105,14 +105,8 @@ - - - - - - - - + + diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.scss b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.scss index e0404f197..df34508cf 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.scss +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.scss @@ -37,6 +37,8 @@ .content { margin-top: 2em; + overflow-x: auto; + } .buttons { diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts index 66268082b..d119c8961 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, Component, OnInit, ViewChild } from "@angular/core"; - import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; import { IdentityService, NotificationService, SettingsService } from "../services"; + import { MatSort } from "@angular/material/sort"; import { MatTableDataSource } from "@angular/material/table"; import { SelectionModel } from "@angular/cdk/collections"; @@ -13,7 +13,7 @@ import { SelectionModel } from "@angular/cdk/collections"; export class UserManagementComponent implements OnInit { public displayedColumns: string[] = ['select', 'username', 'alias', 'email', 'roles', 'remainingRequests', - 'nextRequestDue', 'lastLoggedIn', 'userType', 'actions', 'welcome']; + 'nextRequestDue', 'lastLoggedIn', 'userType', 'actions']; public dataSource: MatTableDataSource; public selection = new SelectionModel(true, []); @@ -90,6 +90,9 @@ export class UserManagementComponent implements OnInit { if (this.bulkMusicLimit) { x.musicRequestLimit = this.bulkMusicLimit; } + if (this.bulkStreaming) { + x.streamingCountry = this.bulkStreaming; + } this.identityService.updateUser(x).subscribe(y => { if (!y.successful) { this.notificationService.error(`Could not update user ${x.userName}. Reason ${y.errors[0]}`); @@ -102,6 +105,7 @@ export class UserManagementComponent implements OnInit { this.bulkMovieLimit = undefined; this.bulkEpisodeLimit = undefined; this.bulkMusicLimit = undefined; + this.bulkStreaming = undefined; } public isAllSelected() { diff --git a/src/Ombi/Controllers/External/TheMovieDbController.cs b/src/Ombi/Controllers/V1/External/TheMovieDbController.cs similarity index 100% rename from src/Ombi/Controllers/External/TheMovieDbController.cs rename to src/Ombi/Controllers/V1/External/TheMovieDbController.cs diff --git a/src/Ombi/Controllers/V1/ImagesController.cs b/src/Ombi/Controllers/V1/ImagesController.cs index c119efe70..847cd8777 100644 --- a/src/Ombi/Controllers/V1/ImagesController.cs +++ b/src/Ombi/Controllers/V1/ImagesController.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using Ombi.Api.FanartTv; using Ombi.Config; using Ombi.Core; +using Ombi.Core.Engine.Interfaces; using Ombi.Helpers; using Ombi.Store.Repository; @@ -17,13 +18,16 @@ namespace Ombi.Controllers.V1 public class ImagesController : ControllerBase { public ImagesController(IFanartTvApi fanartTvApi, IApplicationConfigRepository config, - IOptions options, ICacheService c, IImageService imageService) + IOptions options, ICacheService c, IImageService imageService, + IMovieEngineV2 movieEngineV2, ITVSearchEngineV2 tVSearchEngineV2) { FanartTvApi = fanartTvApi; Config = config; Options = options.Value; _cache = c; _imageService = imageService; + _movieEngineV2 = movieEngineV2; + _tvSearchEngineV2 = tVSearchEngineV2; } private IFanartTvApi FanartTvApi { get; } @@ -31,6 +35,8 @@ namespace Ombi.Controllers.V1 private LandingPageBackground Options { get; } private readonly ICacheService _cache; private readonly IImageService _imageService; + private readonly IMovieEngineV2 _movieEngineV2; + private readonly ITVSearchEngineV2 _tvSearchEngineV2; [HttpGet("tv/{tvdbid}")] public async Task GetTvBanner(int tvdbid) @@ -61,6 +67,50 @@ namespace Ombi.Controllers.V1 return string.Empty; } + [HttpGet("poster")] + public async Task GetRandomPoster() + { + var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.GetAsync(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1)); + var rand = new Random(); + var val = rand.Next(1, 3); + if (val == 1) + { + var movies = (await _movieEngineV2.PopularMovies(0, 10, HttpContext.RequestAborted ,"en")).ToArray(); + var selectedMovieIndex = rand.Next(movies.Count()); + var movie = movies[selectedMovieIndex]; + + var images = await _cache.GetOrAdd($"{CacheKeys.FanartTv}movie{movie.Id}", async () => await FanartTvApi.GetMovieImages(movie.Id.ToString(), key.Value), DateTime.Now.AddDays(1)); + if (images == null) + { + return string.Empty; + } + + if (images.movieposter?.Any() ?? false) + { + var enImage = images.movieposter.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + if (enImage == null) + { + return images.movieposter.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault(); + } + return enImage; + } + + if (images.moviethumb?.Any() ?? false) + { + return images.moviethumb.OrderBy(x => x.likes).Select(x => x.url).FirstOrDefault(); + } + } + else + { + var tv = (await _tvSearchEngineV2.Popular(0, 10, "en")).ToArray(); + var selectedMovieIndex = rand.Next(tv.Count()); + var selected = tv[selectedMovieIndex]; + + return $"https://image.tmdb.org/t/p/original{selected.BackdropPath}"; + } + return ""; + } + [HttpGet("poster/movie/{movieDbId}")] public async Task GetMoviePoster(string movieDbId) { diff --git a/src/Ombi/Controllers/V1/JobController.cs b/src/Ombi/Controllers/V1/JobController.cs index e182e1239..668a795a1 100644 --- a/src/Ombi/Controllers/V1/JobController.cs +++ b/src/Ombi/Controllers/V1/JobController.cs @@ -52,8 +52,7 @@ namespace Ombi.Controllers.V1 { var productArray = _updater.GetVersion(); var version = productArray[0]; - var branch = productArray[1]; - var updateAvailable = await _updater.UpdateAvailable(branch, version); + var updateAvailable = await _updater.UpdateAvailable(version); return updateAvailable; } @@ -70,12 +69,11 @@ namespace Ombi.Controllers.V1 var val = await _memCache.GetOrAdd(CacheKeys.Update, async () => { var productArray = _updater.GetVersion(); - if (productArray.Length > 1) - { + var version = productArray[0]; - var branch = productArray[1]; - var updateAvailable = await _updater.UpdateAvailable(branch, version); - } + + var updateAvailable = await _updater.UpdateAvailable( version); + return true; }); diff --git a/src/Ombi/Controllers/V1/MobileController.cs b/src/Ombi/Controllers/V1/MobileController.cs index 2ca640769..485535816 100644 --- a/src/Ombi/Controllers/V1/MobileController.cs +++ b/src/Ombi/Controllers/V1/MobileController.cs @@ -92,7 +92,7 @@ namespace Ombi.Controllers.V1 [Admin] public async Task RemoveUser([FromBody] RemoveUserModel userId) { - var user = await _userManager.Users.Include(x => x.NotificationUserIds).FirstOrDefaultAsync(x => x.Id.Equals(userId.UserId, StringComparison.InvariantCultureIgnoreCase)); + var user = await _userManager.Users.Include(x => x.NotificationUserIds).FirstOrDefaultAsync(x => x.Id == userId.UserId); try { await _notification.DeleteRange(user.NotificationUserIds); diff --git a/src/Ombi/Controllers/V1/UpdateController.cs b/src/Ombi/Controllers/V1/UpdateController.cs index 4a943ae73..419442205 100644 --- a/src/Ombi/Controllers/V1/UpdateController.cs +++ b/src/Ombi/Controllers/V1/UpdateController.cs @@ -21,10 +21,10 @@ namespace Ombi.Controllers.V1 private readonly ICacheService _cache; private readonly IChangeLogProcessor _processor; - [HttpGet("{branch}")] - public async Task UpdateAvailable(string branch) + [HttpGet()] + public async Task UpdateAvailable() { - return await _cache.GetOrAdd(branch, async () => await _processor.Process(branch)); + return await _cache.GetOrAdd("Update", async () => await _processor.Process()); } } } \ No newline at end of file diff --git a/src/Ombi/Controllers/V2/RequestsController.cs b/src/Ombi/Controllers/V2/RequestsController.cs index 0bbbd4128..67d206e0b 100644 --- a/src/Ombi/Controllers/V2/RequestsController.cs +++ b/src/Ombi/Controllers/V2/RequestsController.cs @@ -11,6 +11,7 @@ using System; using Ombi.Store.Entities; using System.Linq; using Microsoft.Extensions.Logging; +using Ombi.Attributes; namespace Ombi.Controllers.V2 { @@ -134,12 +135,14 @@ namespace Ombi.Controllers.V2 return await _tvRequestEngine.GetUnavailableRequests(count, position, sort, sortOrder); } + [PowerUser] [HttpPost("movie/advancedoptions")] public async Task UpdateAdvancedOptions([FromBody] MediaAdvancedOptions options) { return await _movieRequestEngine.UpdateAdvancedOptions(options); } + [PowerUser] [HttpPost("tv/advancedoptions")] public async Task UpdateTvAdvancedOptions([FromBody] MediaAdvancedOptions options) { @@ -198,6 +201,21 @@ namespace Ombi.Controllers.V2 return result; } + [PowerUser] + [HttpPost("reprocess/{type}/{requestId}")] + public async Task ReProcessRequest(RequestType type, int requestId) + { + switch (type) + { + case RequestType.TvShow: + return Ok(await _tvRequestEngine.ReProcessRequest(requestId, HttpContext.RequestAborted)); + case RequestType.Movie: + return Ok(await _movieRequestEngine.ReProcessRequest(requestId, HttpContext.RequestAborted)); + } + + return BadRequest(); + } + private string GetApiAlias() { // Make sure this only applies when using the API KEY diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 4ae2c07ee..63c4f5a87 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -63,6 +63,8 @@ + + @@ -95,6 +97,10 @@ + + + + diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index 1b57aaffb..22da8e6b0 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -15,12 +15,11 @@ using System.Threading.Tasks; using System.Collections.Generic; using Newtonsoft.Json; using Ombi.Settings.Settings.Models; -using System.Diagnostics; using System.IO; -using Ombi.Api.TheMovieDb; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging; using Ombi.Api.TheMovieDb.Models; +using System.Net.Http; namespace Ombi { @@ -131,8 +130,8 @@ namespace Ombi } await SortOutBaseUrl(baseUrl, settingsDb, ombiSettingsContent); - var httpClient = new Ombi.Api.OmbiHttpClient(null, null); - var api = new Ombi.Api.Api(new Logger(NullLoggerFactory.Instance), httpClient); + var httpClient = new HttpClient(); + var api = new Ombi.Api.Api(new Logger(NullLoggerFactory.Instance), httpClient); await MigrateOldTvDbIds(ombiDb, ombiSettingsContent, settingsDb, new Ombi.Api.TheMovieDb.TheMovieDbApi(null, (Api.IApi)api, null)); Console.WriteLine($"We are running on {urlValue}"); diff --git a/src/Ombi/wwwroot/images/appstore.svg b/src/Ombi/wwwroot/images/appstore.svg new file mode 100644 index 000000000..072b425a1 --- /dev/null +++ b/src/Ombi/wwwroot/images/appstore.svg @@ -0,0 +1,46 @@ + + Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index fab6213a7..5e24656fe 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -254,11 +254,12 @@ "ViewCollection":"View Collection", "NotEnoughInfo": "Unfortunately there is not enough information about this show yet!", "AdvancedOptions":"Advanced Options", - "AutoApproveOptions":"You can configure the request here, once requested it will be send to your DVR application and will be auto approved!", - "AutoApproveOptionsTv":"You can configure the request here, once requested it will be send to your DVR application and will be auto approved! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it!", - "AutoApproveOptionsTvShort":"You can configure the request here, once requested it will be send to your DVR application! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it!", + "AutoApproveOptions":"You can configure the request here, once requested it will be send to your DVR application and will be auto approved! Please note, this is optional, just press Request to skip!", + "AutoApproveOptionsTv":"You can configure the request here, once requested it will be send to your DVR application and will be auto approved! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!", + "AutoApproveOptionsTvShort":"You can configure the request here, once requested it will be send to your DVR application! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!", "QualityProfilesSelect":"Select A Quality Profile", "RootFolderSelect":"Select A Root Folder", + "LanguageProfileSelect":"Select A Language Profile", "Status":"Status", "Availability":"Availability", "RequestStatus":"Request Status", @@ -287,7 +288,8 @@ "RadarrConfiguration": "Radarr Configuration", "RequestOnBehalf": "Request on behalf of", "PleaseSelectUser": "Please select a user", - "StreamingOn": "Streaming On" + "StreamingOn": "Streaming On", + "ReProcessRequest": "Re-Process Request" }, "Discovery": { "PopularTab": "Popular",