#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 Ombi.Api; using Ombi.Api.Interfaces; using Ombi.Core; using Ombi.Core.SettingModels; using Ombi.Helpers; using Ombi.Helpers.Permissions; using Ombi.Services.Interfaces; using Ombi.Store; using Ombi.Store.Models; using Ombi.Store.Repository; using Quartz; namespace Ombi.Services.Jobs { public class FaultQueueHandler : IJob, IFaultQueueHandler { private static readonly Logger Log = LogManager.GetCurrentClassLogger(); public FaultQueueHandler(IJobRecord record, IRepository repo, ISonarrApi sonarrApi, ISickRageApi srApi, ISettingsService sonarrSettings, ISettingsService srSettings, ICouchPotatoApi cpApi, ISettingsService cpsettings, IRequestService requestService, ISettingsService hpSettings, IHeadphonesApi headphonesApi, ISettingsService prSettings, ISecurityExtensions security, IMovieSender movieSender, ICacheProvider cache) { Record = record; Repo = repo; SonarrApi = sonarrApi; SrApi = srApi; CpApi = cpApi; HpApi = headphonesApi; RequestService = requestService; SickrageSettings = srSettings; SonarrSettings = sonarrSettings; CpSettings = cpsettings; HeadphoneSettings = hpSettings; Security = security; PrSettings = prSettings.GetSettings(); MovieSender = movieSender; Cache = cache; } private IMovieSender MovieSender { get; } private IRepository Repo { get; } private IJobRecord Record { get; } private ISonarrApi SonarrApi { get; } private ISickRageApi SrApi { get; } private ICacheProvider Cache { get; } private ICouchPotatoApi CpApi { get; } private IHeadphonesApi HpApi { get; } private IRequestService RequestService { get; } private PlexRequestSettings PrSettings { get; } private ISettingsService SonarrSettings { get; } private ISettingsService SickrageSettings { get; } private ISettingsService CpSettings { get; } private ISettingsService HeadphoneSettings { get; } private ISecurityExtensions Security { get; } public void Start() { Record.SetRunning(true, JobNames.CpCacher); 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.FaultQueueHandler); Record.SetRunning(false, JobNames.CpCacher); } } public void Execute(IJobExecutionContext context) { Start(); } private void ProcessMissingInformation(List requests) { if (!requests.Any()) { return; } var sonarrSettings = SonarrSettings.GetSettings(); var sickrageSettings = SickrageSettings.GetSettings(); 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.ShowLookup(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, Cache); 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; } // Approve it tvModel.Approved = true; RequestService.UpdateRequest(tvModel); return true; } return false; } catch (Exception e) { Log.Error(e); return false; // It fails so it will get added back into the queue } } private bool ProcessMovies(RequestedModel model) { try { var result = MovieSender.Send(model).Result; if (result.Result) { // Approve it now model.Approved = true; RequestService.UpdateRequest(model); return true; }; return false; } catch (Exception e) { Log.Error(e); return false; // It fails so it will get added back into the queue } } private bool ProcessAlbums(RequestedModel model, HeadphonesSettings hp) { try { if (hp.Enabled) { var sender = new HeadphonesSender(HpApi, hp, RequestService); var result = sender.AddAlbum(model).Result; if (result) { if (ShouldAutoApprove(model.Type, PrSettings, model.RequestedUsers)) // Approve it now model.Approved = true; RequestService.UpdateRequest(model); }; return result; } 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(); var cpSettings = CpSettings.GetSettings(); var hpSettings = HeadphoneSettings.GetSettings(); if (!requests.Any()) { return; } foreach (var request in requests) { var model = ByteConverterHelper.ReturnObject(request.Content); bool result; switch (request.Type) { case RequestType.Movie: result = ProcessMovies(model); break; case RequestType.TvShow: result = ProcessTvShow(model, sonarrSettings, sickrageSettings); break; case RequestType.Album: result = ProcessAlbums(model, hpSettings); break; default: throw new ArgumentOutOfRangeException(); } if (!result) { // we now have the info but couldn't add it, so do nothing now. request.LastRetry = DateTime.UtcNow; Repo.Update(request); } else { // Successful, remove from the fault queue Repo.Delete(request); } } } public bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings, List username) { foreach (var user in username) { var admin = Security.HasPermissions(user, Permissions.Administrator); // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval if (admin) return true; // check by request type if the category requires approval or not switch (requestType) { case RequestType.Movie: return Security.HasPermissions(user, Permissions.AutoApproveMovie); case RequestType.TvShow: return Security.HasPermissions(user, Permissions.AutoApproveTv); case RequestType.Album: return Security.HasPermissions(user, Permissions.AutoApproveAlbum); default: return false; } } return false; } } }