diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 8faf4e83a..17bb7ceca 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -137,6 +137,8 @@ + + diff --git a/PlexRequests.Core/Queue/ITransientFaultQueue.cs b/PlexRequests.Core/Queue/ITransientFaultQueue.cs index ac62add0f..649209307 100644 --- a/PlexRequests.Core/Queue/ITransientFaultQueue.cs +++ b/PlexRequests.Core/Queue/ITransientFaultQueue.cs @@ -11,7 +11,7 @@ namespace PlexRequests.Core.Queue Task DequeueAsync(); IEnumerable GetQueue(); Task> GetQueueAsync(); - void QueueItem(RequestedModel request, RequestType type, FaultType faultType); - Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType); + void QueueItem(RequestedModel request, string id, RequestType type, FaultType faultType); + Task QueueItemAsync(RequestedModel request, string id, RequestType type, FaultType faultType); } } \ No newline at end of file diff --git a/PlexRequests.Core/Queue/TransientFaultQueue.cs b/PlexRequests.Core/Queue/TransientFaultQueue.cs index 108387f03..57fd2eb34 100644 --- a/PlexRequests.Core/Queue/TransientFaultQueue.cs +++ b/PlexRequests.Core/Queue/TransientFaultQueue.cs @@ -46,14 +46,14 @@ namespace PlexRequests.Core.Queue private IRepository RequestQueue { get; } - public void QueueItem(RequestedModel request, RequestType type, FaultType faultType) + public void QueueItem(RequestedModel request, string id, RequestType type, FaultType faultType) { //Ensure there is not a duplicate queued item var existingItem = RequestQueue.Custom( connection => { connection.Open(); - var result = connection.Query("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + var result = connection.Query("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = id }); return result; }).FirstOrDefault(); @@ -68,19 +68,19 @@ namespace PlexRequests.Core.Queue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId, + PrimaryIdentifier = id, FaultType = faultType }; RequestQueue.Insert(queue); } - public async Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType) + public async Task QueueItemAsync(RequestedModel request, string id, RequestType type, FaultType faultType) { //Ensure there is not a duplicate queued item var existingItem = await RequestQueue.CustomAsync(async connection => { connection.Open(); - var result = await connection.QueryAsync("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + var result = await connection.QueryAsync("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = id }); return result; }); @@ -95,7 +95,7 @@ namespace PlexRequests.Core.Queue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId, + PrimaryIdentifier = id, FaultType = faultType }; await RequestQueue.InsertAsync(queue); diff --git a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs index 234dc5774..2f1f2ccdc 100644 --- a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs +++ b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs @@ -42,5 +42,6 @@ namespace PlexRequests.Core.SettingModels [Obsolete("We use the CRON job now")] public int RecentlyAdded { get; set; } public string RecentlyAddedCron { get; set; } + public int FaultQueueHandler { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.Core/TvSender.cs similarity index 99% rename from PlexRequests.UI/Helpers/TvSender.cs rename to PlexRequests.Core/TvSender.cs index b67311d64..7ec462582 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.Core/TvSender.cs @@ -24,18 +24,19 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.SickRage; using PlexRequests.Api.Models.Sonarr; using PlexRequests.Core.SettingModels; using PlexRequests.Store; -using System.Linq; -using System.Threading.Tasks; -namespace PlexRequests.UI.Helpers +namespace PlexRequests.Core { public class TvSender { diff --git a/PlexRequests.UI/Helpers/TvSenderOld.cs b/PlexRequests.Core/TvSenderOld.cs similarity index 99% rename from PlexRequests.UI/Helpers/TvSenderOld.cs rename to PlexRequests.Core/TvSenderOld.cs index ced1c81dd..b603272b1 100644 --- a/PlexRequests.UI/Helpers/TvSenderOld.cs +++ b/PlexRequests.Core/TvSenderOld.cs @@ -36,7 +36,7 @@ using PlexRequests.Api.Models.Sonarr; using PlexRequests.Core.SettingModels; using PlexRequests.Store; -namespace PlexRequests.UI.Helpers +namespace PlexRequests.Core { public class TvSenderOld { diff --git a/PlexRequests.Services/Jobs/FaultQueueHandler.cs b/PlexRequests.Services/Jobs/FaultQueueHandler.cs new file mode 100644 index 000000000..0a540d5ad --- /dev/null +++ b/PlexRequests.Services/Jobs/FaultQueueHandler.cs @@ -0,0 +1,214 @@ +#region Copyright + +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserRequestLimitResetter.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.Collections.Generic; +using System.Linq; + +using NLog; +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; + +using Quartz; + +namespace PlexRequests.Services.Jobs +{ + public class FaultQueueHandler : IJob + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public FaultQueueHandler(IJobRecord record, IRepository repo, ISonarrApi sonarrApi, + ISickRageApi srApi, + ISettingsService sonarrSettings, ISettingsService srSettings) + { + Record = record; + Repo = repo; + SonarrApi = sonarrApi; + SrApi = srApi; + SickrageSettings = srSettings; + SonarrSettings = sonarrSettings; + } + + private IRepository Repo { get; } + private IJobRecord Record { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SrApi { get; } + private ISettingsService SonarrSettings { get; set; } + private ISettingsService SickrageSettings { get; set; } + + + public void Execute(IJobExecutionContext context) + { + try + { + var faultedRequests = Repo.GetAll().ToList(); + + + var missingInfo = faultedRequests.Where(x => x.FaultType == FaultType.MissingInformation).ToList(); + ProcessMissingInformation(missingInfo); + + var transientErrors = faultedRequests.Where(x => x.FaultType == FaultType.RequestFault).ToList(); + ProcessTransientErrors(transientErrors); + + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Record.Record(JobNames.RequestLimitReset); + } + } + + + private void ProcessMissingInformation(List requests) + { + var sonarrSettings = SonarrSettings.GetSettings(); + var sickrageSettings = SickrageSettings.GetSettings(); + + if (!requests.Any()) + { + return; + } + var tv = requests.Where(x => x.Type == RequestType.TvShow); + + // TV + var tvApi = new TvMazeApi(); + foreach (var t in tv) + { + var providerId = int.Parse(t.PrimaryIdentifier); + var showInfo = tvApi.ShowLookupByTheTvDbId(providerId); + + if (showInfo.externals?.thetvdb != null) + { + // We now have the info + var tvModel = ByteConverterHelper.ReturnObject(t.Content); + tvModel.ProviderId = showInfo.externals.thetvdb.Value; + var result = ProcessTvShow(tvModel, sonarrSettings, sickrageSettings); + + if (!result) + { + // we now have the info but couldn't add it, so add it back into the queue but with a different fault + t.Content = ByteConverterHelper.ReturnBytes(tvModel); + t.FaultType = FaultType.RequestFault; + t.LastRetry = DateTime.UtcNow; + Repo.Update(t); + } + else + { + // Successful, remove from the fault queue + Repo.Delete(t); + } + } + } + } + + private bool ProcessTvShow(RequestedModel tvModel, SonarrSettings sonarr, SickRageSettings sickrage) + { + try + { + + var sender = new TvSenderOld(SonarrApi, SrApi); + if (sonarr.Enabled) + { + var task = sender.SendToSonarr(sonarr, tvModel, sonarr.QualityProfile); + var a = task.Result; + if (string.IsNullOrEmpty(a?.title)) + { + // Couldn't send it + return false; + } + return true; + } + + if (sickrage.Enabled) + { + var result = sender.SendToSickRage(sickrage, tvModel); + if (result?.result != "success") + { + // Couldn't send it + return false; + } + return true; + } + + return false; + } + catch (Exception e) + { + Log.Error(e); + return false; // It fails so it will get added back into the queue + } + } + + private void ProcessTransientErrors(List requests) + { + var sonarrSettings = SonarrSettings.GetSettings(); + var sickrageSettings = SickrageSettings.GetSettings(); + + if (!requests.Any()) + { + return; + } + var tv = requests.Where(x => x.Type == RequestType.TvShow); + var movie = requests.Where(x => x.Type == RequestType.Movie); + var album = requests.Where(x => x.Type == RequestType.Album); + + + + foreach (var t in tv) + { + var tvModel = ByteConverterHelper.ReturnObject(t.Content); + var result = ProcessTvShow(tvModel, sonarrSettings, sickrageSettings); + + if (!result) + { + // we now have the info but couldn't add it, so do nothing now. + t.LastRetry = DateTime.UtcNow; + Repo.Update(t); + } + else + { + // Successful, remove from the fault queue + Repo.Delete(t); + } + } + + + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj index d53128879..1f542944b 100644 --- a/PlexRequests.Services/PlexRequests.Services.csproj +++ b/PlexRequests.Services/PlexRequests.Services.csproj @@ -93,6 +93,7 @@ + diff --git a/PlexRequests.Store/Models/RequestQueue.cs b/PlexRequests.Store/Models/RequestQueue.cs index 15067fbc7..f98517ab3 100644 --- a/PlexRequests.Store/Models/RequestQueue.cs +++ b/PlexRequests.Store/Models/RequestQueue.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System; using Dapper.Contrib.Extensions; namespace PlexRequests.Store.Models @@ -32,13 +33,14 @@ namespace PlexRequests.Store.Models [Table("RequestQueue")] public class RequestQueue : Entity { - public int PrimaryIdentifier { get; set; } + public string PrimaryIdentifier { get; set; } public RequestType Type { get; set; } public byte[] Content { get; set; } public FaultType FaultType { get; set; } + public DateTime? LastRetry { get; set; } } public enum FaultType diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 220664ba1..f0e852717 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -136,10 +136,11 @@ CREATE INDEX IF NOT EXISTS PlexEpisodes_ProviderId ON PlexEpisodes (ProviderId); CREATE TABLE IF NOT EXISTS RequestQueue ( Id INTEGER PRIMARY KEY AUTOINCREMENT, - PrimaryIdentifier INTEGER NOT NULL, + PrimaryIdentifier VARCHAR(100) NOT NULL, Type INTEGER NOT NULL, FaultType INTEGER NOT NULL, - Content BLOB NOT NULL + Content BLOB NOT NULL, + LastRetry VARCHAR(100) ); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); diff --git a/PlexRequests.UI.Tests/TvSenderTests.cs b/PlexRequests.UI.Tests/TvSenderTests.cs index 678a196d9..a63ef719b 100644 --- a/PlexRequests.UI.Tests/TvSenderTests.cs +++ b/PlexRequests.UI.Tests/TvSenderTests.cs @@ -35,6 +35,7 @@ using NUnit.Framework; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.Sonarr; +using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Store; using PlexRequests.UI.Helpers; @@ -146,32 +147,6 @@ namespace PlexRequests.UI.Tests true, It.IsAny()), Times.Once); } - [Test] - public async Task RequestEpisodesWithExistingSeriesTest() - { - var episodesReturned = new List - { - new SonarrEpisodes {episodeNumber = 1, seasonNumber = 2, monitored = false, id=22} - }; - SonarrMock.Setup(x => x.GetEpisodes(It.IsAny(), It.IsAny(), - It.IsAny())).Returns(episodesReturned); - SonarrMock.Setup(x => x.GetEpisode("22", It.IsAny(), It.IsAny())).Returns(new SonarrEpisode {id=22}); - - - Sender = new TvSender(SonarrMock.Object, SickrageMock.Object); - - var model = new RequestedModel - { - Episodes = new List { new EpisodesModel { EpisodeNumber = 1, SeasonNumber = 2 } } - }; - - var series = new Series(); - await Sender.RequestEpisodesWithExistingSeries(model, series, GetSonarrSettings()); - - SonarrMock.Verify(x => x.UpdateEpisode(It.Is(e => e.monitored), It.IsAny(), It.IsAny())); - SonarrMock.Verify(x => x.GetEpisode("22", It.IsAny(), It.IsAny()),Times.Once); - SonarrMock.Verify(x => x.SearchForEpisodes(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } private SonarrSettings GetSonarrSettings() { diff --git a/PlexRequests.UI/Jobs/Scheduler.cs b/PlexRequests.UI/Jobs/Scheduler.cs index 669500168..5ea4ba540 100644 --- a/PlexRequests.UI/Jobs/Scheduler.cs +++ b/PlexRequests.UI/Jobs/Scheduler.cs @@ -69,11 +69,10 @@ namespace PlexRequests.UI.Jobs JobBuilder.Create().WithIdentity("StoreBackup", "Database").Build(), JobBuilder.Create().WithIdentity("StoreCleanup", "Database").Build(), JobBuilder.Create().WithIdentity("UserRequestLimiter", "Request").Build(), - JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build() + JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build(), + JobBuilder.Create().WithIdentity("FaultQueueHandler", "Fault").Build(), }; - - - + jobs.AddRange(jobList); return jobs; @@ -151,7 +150,10 @@ namespace PlexRequests.UI.Jobs { s.UserRequestLimitResetter = 12; } - + if (s.FaultQueueHandler == 0) + { + s.FaultQueueHandler = 6; + } var triggers = new List(); @@ -221,6 +223,13 @@ namespace PlexRequests.UI.Jobs .WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever()) .Build(); + var fault = + TriggerBuilder.Create() + .WithIdentity("FaultQueueHandler", "Fault") + .StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) + .WithSimpleSchedule(x => x.WithIntervalInHours(s.FaultQueueHandler).RepeatForever()) + .Build(); + triggers.Add(rencentlyAdded); triggers.Add(plexAvailabilityChecker); triggers.Add(srCacher); @@ -230,6 +239,7 @@ namespace PlexRequests.UI.Jobs triggers.Add(storeCleanup); triggers.Add(userRequestLimiter); triggers.Add(plexEpCacher); + triggers.Add(fault); return triggers; } diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index ef35e08b6..fcd82d964 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -589,7 +589,7 @@ namespace PlexRequests.UI.Modules catch (Exception e) { Log.Fatal(e); - await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); + await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault); await NotificationService.Publish(new NotificationModel { @@ -690,26 +690,6 @@ namespace PlexRequests.UI.Modules TvDbId = showId.ToString() }; - if (showInfo.externals?.thetvdb == null) - { - await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.MissingInformation); - await NotificationService.Publish(new NotificationModel - { - DateTime = DateTime.Now, - User = Username, - RequestType = RequestType.TvShow, - Title = model.Title, - NotificationType = NotificationType.ItemAddedToFaultQueue - }); - return Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" - }); - } - - model.ProviderId = showInfo.externals?.thetvdb ?? 0; - var seasonsList = new List(); switch (seasons) { @@ -876,6 +856,26 @@ namespace PlexRequests.UI.Modules }); } + if (showInfo.externals?.thetvdb == null) + { + await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.MissingInformation); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + model.ProviderId = showInfo.externals?.thetvdb ?? 0; + try { if (ShouldAutoApprove(RequestType.TvShow, settings)) @@ -936,7 +936,7 @@ namespace PlexRequests.UI.Modules } catch (Exception e) { - await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.RequestFault); + await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.RequestFault); await NotificationService.Publish(new NotificationModel { DateTime = DateTime.Now, @@ -1102,7 +1102,7 @@ namespace PlexRequests.UI.Modules catch (Exception e) { Log.Error(e); - await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); + await FaultQueue.QueueItemAsync(model, albumInfo.id, RequestType.Movie, FaultType.RequestFault); await NotificationService.Publish(new NotificationModel { diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index a18aec630..949f29117 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -216,8 +216,6 @@ - -