From ddefbf31db289d8f2a856faebdbb9191a4d67598 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 30 Mar 2017 22:46:51 +0100 Subject: [PATCH] Made a start on the new Sonarr integration. the Latest series option works. --- Ombi.Api.Interfaces/ISonarrApi.cs | 3 + Ombi.Api/SonarrApi.cs | 59 +++++- Ombi.Core/Ombi.Core.csproj | 5 +- Ombi.Core/{ => Tv}/TvSender.cs | 2 + Ombi.Core/{ => Tv}/TvSenderOld.cs | 0 Ombi.Core/Tv/TvSenderV2.cs | 244 ++++++++++++++++++++++++ Ombi.Services/Jobs/PlexEpisodeCacher.cs | 10 +- Ombi.UI/Modules/SearchModule.cs | 7 +- 8 files changed, 315 insertions(+), 15 deletions(-) rename Ombi.Core/{ => Tv}/TvSender.cs (99%) rename Ombi.Core/{ => Tv}/TvSenderOld.cs (100%) create mode 100644 Ombi.Core/Tv/TvSenderV2.cs diff --git a/Ombi.Api.Interfaces/ISonarrApi.cs b/Ombi.Api.Interfaces/ISonarrApi.cs index bce750901..06dd4ee75 100644 --- a/Ombi.Api.Interfaces/ISonarrApi.cs +++ b/Ombi.Api.Interfaces/ISonarrApi.cs @@ -56,5 +56,8 @@ namespace Ombi.Api.Interfaces Series UpdateSeries(Series series, string apiKey, Uri baseUrl); SonarrSeasonSearchResult SearchForSeason(int seriesId, int seasonNumber, string apiKey, Uri baseUrl); SonarrSeriesSearchResult SearchForSeries(int seriesId, string apiKey, Uri baseUrl); + + + SonarrAddSeries AddSeries(SonarrAddSeries series, string apiKey, Uri baseUrl); } } \ No newline at end of file diff --git a/Ombi.Api/SonarrApi.cs b/Ombi.Api/SonarrApi.cs index 3cf8565e8..afbe74e72 100644 --- a/Ombi.Api/SonarrApi.cs +++ b/Ombi.Api/SonarrApi.cs @@ -148,6 +148,42 @@ namespace Ombi.Api return result; } + public SonarrAddSeries AddSeries(SonarrAddSeries series,string apiKey, Uri baseUrl) + { + + var request = new RestRequest + { + Resource = "/api/Series?", + Method = Method.POST + }; + + Log.Debug("Sonarr API Options:"); + Log.Debug(series.DumpJson()); + + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(series); + + SonarrAddSeries result; + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2) + }); + + result = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (JsonSerializationException jse) + { + Log.Error(jse); + var error = Api.ExecuteJson>(request, baseUrl); + var messages = error?.Select(x => x.errorMessage).ToList(); + messages?.ForEach(x => Log.Error(x)); + result = new SonarrAddSeries { ErrorMessages = messages }; + } + + return result; + } + public SonarrAddSeries AddSeriesNew(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int[] seasons, string apiKey, Uri baseUrl, bool monitor = true, bool searchForMissingEpisodes = false) { var request = new RestRequest @@ -244,7 +280,18 @@ namespace Ombi.Api TimeSpan.FromSeconds(5) }); - return policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + var series = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + + // Remove the 'specials from the object' + foreach (var s in series) + { + var seasonToRemove = s.seasons.FirstOrDefault(x => x.seasonNumber == 0); + if (seasonToRemove != null) + { + s.seasons.Remove(seasonToRemove); + } + } + return series; } catch (Exception e) { @@ -266,7 +313,15 @@ namespace Ombi.Api Log.Error(exception, "Exception when calling GetSeries by ID for Sonarr, Retrying {0}", timespan)); - return policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + var series = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + + // Remove the specials season + var toRemove = series.seasons.FirstOrDefault(x => x.seasonNumber == 0); + if (toRemove != null) + { + series.seasons.Remove(toRemove); + } + return series; } catch (Exception e) { diff --git a/Ombi.Core/Ombi.Core.csproj b/Ombi.Core/Ombi.Core.csproj index 49c37b597..3ad547420 100644 --- a/Ombi.Core/Ombi.Core.csproj +++ b/Ombi.Core/Ombi.Core.csproj @@ -155,8 +155,9 @@ - - + + + diff --git a/Ombi.Core/TvSender.cs b/Ombi.Core/Tv/TvSender.cs similarity index 99% rename from Ombi.Core/TvSender.cs rename to Ombi.Core/Tv/TvSender.cs index 3f6d5f86e..f43910c94 100644 --- a/Ombi.Core/TvSender.cs +++ b/Ombi.Core/Tv/TvSender.cs @@ -87,6 +87,8 @@ namespace Ombi.Core var rootFolderPath = model.RootFolderSelected <= 0 ? sonarrSettings.FullRootPath : await GetRootPath(model.RootFolderSelected, sonarrSettings); + + if (episodeRequest) { // Does series exist? diff --git a/Ombi.Core/TvSenderOld.cs b/Ombi.Core/Tv/TvSenderOld.cs similarity index 100% rename from Ombi.Core/TvSenderOld.cs rename to Ombi.Core/Tv/TvSenderOld.cs diff --git a/Ombi.Core/Tv/TvSenderV2.cs b/Ombi.Core/Tv/TvSenderV2.cs new file mode 100644 index 000000000..08833ff4e --- /dev/null +++ b/Ombi.Core/Tv/TvSenderV2.cs @@ -0,0 +1,244 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: TvSenderV2.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 System.Threading.Tasks; +using NLog; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.SickRage; +using Ombi.Api.Models.Sonarr; +using Ombi.Core.SettingModels; +using Ombi.Helpers; +using Ombi.Store; + +namespace Ombi.Core.Tv +{ + public class TvSenderV2 + { + public TvSenderV2(ISonarrApi sonarrApi, ISickRageApi srApi, ICacheProvider cache) + { + SonarrApi = sonarrApi; + SickrageApi = srApi; + Cache = cache; + } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickrageApi { get; } + private ICacheProvider Cache { get; } + private static Logger _log = LogManager.GetCurrentClassLogger(); + + + public async Task SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model) + { + return await SendToSonarr(sonarrSettings, model, string.Empty); + } + + + public async Task SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, + string qualityId) + { + var qualityProfile = 0; + if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality + { + int.TryParse(qualityId, out qualityProfile); + } + + if (qualityProfile <= 0) + { + int.TryParse(sonarrSettings.QualityProfile, out qualityProfile); + } + var rootFolderPath = model.RootFolderSelected <= 0 ? sonarrSettings.FullRootPath : await GetSonarrRootPath(model.RootFolderSelected, sonarrSettings); + + var episodeRequest = model.Episodes.Any(); + var requestAll = model.SeasonsRequested?.Equals("All", StringComparison.CurrentCultureIgnoreCase); + var first = model.SeasonsRequested?.Equals("First", StringComparison.CurrentCultureIgnoreCase); + var latest = model.SeasonsRequested?.Equals("Latest", StringComparison.CurrentCultureIgnoreCase); + var specificSeasonRequest = model.SeasonList?.Any(); + + if (episodeRequest) + { + return await ProcessSonarrEpisodeRequest(sonarrSettings, model, qualityProfile, rootFolderPath); + } + + if (requestAll ?? false) + { + return await ProcessSonarrRequestAll(sonarrSettings, model, qualityProfile, rootFolderPath); + } + + if (first ?? false) + { + return await ProcessSonarrRequestFirstSeason(sonarrSettings, model, qualityProfile, rootFolderPath); + } + + if (latest ?? false) + { + return await ProcessSonarrRequestLatestSeason(sonarrSettings, model, qualityProfile, rootFolderPath); + } + + if (specificSeasonRequest ?? false) + { + return await ProcessSonarrRequestSpecificSeason(sonarrSettings, model, qualityProfile, rootFolderPath); + } + + return null; + } + + private async Task ProcessSonarrRequestSpecificSeason(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + { + throw new NotImplementedException(); + } + + private async Task ProcessSonarrRequestLatestSeason(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + { + // Does the series exist? + + var series = await GetSonarrSeries(sonarrSettings, model.ProviderId); + if (series == null) + { + //WORKS + // Add the series + var seriesToAdd = new SonarrAddSeries + { + seasonFolder = sonarrSettings.SeasonFolders, + title = model.Title, + qualityProfileId = qualityId, + tvdbId = model.ProviderId, + titleSlug = model.Title, + seasons = new List(), + rootFolderPath = rootFolderPath, + monitored = true, // Montior the series + images = new List(), + addOptions = new AddOptions + { + ignoreEpisodesWithFiles = true, // We don't really care about these + ignoreEpisodesWithoutFiles = false, // We want to get the whole season + searchForMissingEpisodes = true // we want to search for missing + } + }; + + for (var i = 1; i <= model.SeasonCount; i++) + { + var season = new Season + { + seasonNumber = i, + // ReSharper disable once SimplifyConditionalTernaryExpression + monitored = true ? model.SeasonList.Length == 0 || model.SeasonList.Any(x => x == i) : false + }; + seriesToAdd.seasons.Add(season); + } + + return SonarrApi.AddSeries(seriesToAdd, sonarrSettings.ApiKey, sonarrSettings.FullUri); + } + else + { + // Mark the latest as monitored and search + // Also make sure the series is now monitored otherwise we won't search for it + series.monitored = true; + foreach (var seasons in series.seasons) + { + if (model.SeasonList.Any(x => x == seasons.seasonNumber)) + { + seasons.monitored = true; + } + } + + // Send the update command + series = SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); + SonarrApi.SearchForSeries(series.id, sonarrSettings.ApiKey, sonarrSettings.FullUri); + return new SonarrAddSeries{title = series.title}; + } + } + + private async Task ProcessSonarrRequestFirstSeason(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + { + throw new NotImplementedException(); + } + + private async Task ProcessSonarrRequestAll(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + { + throw new NotImplementedException(); + } + + private async Task ProcessSonarrEpisodeRequest(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + { + // Does the series exist? + + var series = await GetSonarrSeries(sonarrSettings, model.ProviderId); + if (series == null) + { + // Add the series + } + + return null; + } + + public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model) + { + return SendToSickRage(sickRageSettings, model, sickRageSettings.QualityProfile); + } + + public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model, string qualityId) + { + _log.Info("Sending to SickRage {0}", model.Title); + if (sickRageSettings.Qualities.All(x => x.Key != qualityId)) + { + qualityId = sickRageSettings.QualityProfile; + } + + var apiResult = SickrageApi.AddSeries(model.ProviderId, model.SeasonCount, model.SeasonList, qualityId, + sickRageSettings.ApiKey, sickRageSettings.FullUri); + + var result = apiResult.Result; + + + return result; + } + + private async Task GetSonarrSeries(SonarrSettings sonarrSettings, int showId) + { + var task = await Task.Run(() => SonarrApi.GetSeries(sonarrSettings.ApiKey, sonarrSettings.FullUri)).ConfigureAwait(false); + var selectedSeries = task.FirstOrDefault(series => series.tvdbId == showId); + + return selectedSeries; + } + + private async Task GetSonarrRootPath(int pathId, SonarrSettings sonarrSettings) + { + var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () => + { + return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + }); + + foreach (var r in rootFoldersResult.Where(r => r.id == pathId)) + { + return r.path; + } + return string.Empty; + } + } +} \ No newline at end of file diff --git a/Ombi.Services/Jobs/PlexEpisodeCacher.cs b/Ombi.Services/Jobs/PlexEpisodeCacher.cs index 58ebe7fd3..26820de94 100644 --- a/Ombi.Services/Jobs/PlexEpisodeCacher.cs +++ b/Ombi.Services/Jobs/PlexEpisodeCacher.cs @@ -152,15 +152,7 @@ namespace Ombi.Services.Jobs return; } - var jobs = Job.GetJobs(); - var job = jobs.FirstOrDefault(x => x.Name.Equals(JobNames.EpisodeCacher, StringComparison.CurrentCultureIgnoreCase)); - if (job != null) - { - if (job.LastRun > DateTime.Now.AddHours(-11)) // If it's been run in the last 11 hours - { - return; - } - } + Job.SetRunning(true, JobNames.EpisodeCacher); CacheEpisodes(s); } diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 185392a14..0d52ae6c6 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -46,6 +46,7 @@ using Ombi.Core; using Ombi.Core.Models; using Ombi.Core.Queue; using Ombi.Core.SettingModels; +using Ombi.Core.Tv; using Ombi.Helpers; using Ombi.Helpers.Analytics; using Ombi.Helpers.Permissions; @@ -1119,10 +1120,11 @@ namespace Ombi.UI.Modules RequestedUsers = new List { Username }, Issues = IssueState.None, ImdbId = showInfo.externals?.imdb ?? string.Empty, - SeasonCount = showInfo.Season.Count, TvDbId = showId.ToString() }; + var totalSeasons = showInfo.Season.GroupBy(x => x.SeasonNumber); + model.SeasonCount = totalSeasons.Count(); var seasonsList = new List(); switch (seasons) { @@ -1884,7 +1886,8 @@ namespace Ombi.UI.Modules { model.Approved = true; var s = await sonarrSettings; - var sender = new TvSenderOld(SonarrApi, SickrageApi, Cache); // TODO put back + //var sender = new TvSenderV2(SonarrApi, SickrageApi, Cache); + var sender = new TvSenderOld(SonarrApi, SickrageApi, Cache); if (s.Enabled) { var result = await sender.SendToSonarr(s, model);