From b941d32b60e142e3a8a4260a163c4fd53555130c Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 30 Mar 2017 21:03:47 +0100 Subject: [PATCH 01/45] Fixed an issue where the emby newsletter was always showing series. --- .../EmbyRecentlyAddedNewsletter.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs index 6c6be9ec5..c2a8dc9d6 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs @@ -255,6 +255,14 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter AddedAt = DateTime.UtcNow }); } + foreach (var s in filteredSeries) + { + RecentlyAddedLog.Insert(new RecentlyAddedLog + { + ProviderId = s.ProviderId, + AddedAt = DateTime.UtcNow + }); + } } From 8a55de2b71b5472fccabd1bcb842ea140095f871 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 30 Mar 2017 21:09:55 +0100 Subject: [PATCH 02/45] For test emails, if there is no new content then just grab some old data. --- .../PlexRecentlyAddedNewsletter.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs index 6ac7d76db..1f9601b07 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/PlexRecentlyAddedNewsletter.cs @@ -121,6 +121,12 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter var filteredSeries = series.Where(x => recentlyAdded.All(c => c.ProviderId != x.ProviderId)).ToList(); var info = new List(); + + if (test && !filteredMovies.Any()) + { + // if this is a test make sure we show something + filteredMovies = movie.Take(5).ToList(); + } foreach (var m in filteredMovies) { var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, m.ItemId); @@ -178,6 +184,11 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter } else { + if (test && !filteredSeries.Any()) + { + // if this is a test make sure we show something + filteredSeries = series.Take(5).ToList(); + } foreach (var t in filteredSeries) { var i = Api.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, t.ItemId); From ddefbf31db289d8f2a856faebdbb9191a4d67598 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 30 Mar 2017 22:46:51 +0100 Subject: [PATCH 03/45] 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); From d0c994a9253b570b60568d333ae826786c2a21b9 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 3 Apr 2017 11:14:22 +0100 Subject: [PATCH 04/45] Added a retry policy around the emby newsletter --- .../EmbyRecentlyAddedNewsletter.cs | 110 ++++++++++-------- Ombi.Services/Ombi.Services.csproj | 1 + 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs index b3830fc58..5dda578ca 100644 --- a/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs +++ b/Ombi.Services/Jobs/RecentlyAddedNewsletter/EmbyRecentlyAddedNewsletter.cs @@ -45,6 +45,7 @@ using Ombi.Store.Models.Emby; using Ombi.Store.Repository; using TMDbLib.Objects.Exceptions; using EmbyMediaType = Ombi.Store.Models.Plex.EmbyMediaType; +using Polly; namespace Ombi.Services.Jobs.RecentlyAddedNewsletter { @@ -121,14 +122,20 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter var info = new List(); foreach (var m in filteredMovies) { - - var i = Api.GetInformation(m.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Movie, - embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); - info.Add(new EmbyRecentlyAddedModel + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception thrown when processing an emby movie for the newsletter, Retrying {0}", timespan)); + var result = policy.Execute(() => { - EmbyInformation = i, - EmbyContent = m + var i = Api.GetInformation(m.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Movie, + embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); + + return new EmbyRecentlyAddedModel + { + EmbyInformation = i, + EmbyContent = m + }; }); + info.Add(result); } GenerateMovieHtml(info, sb); newsletter.MovieCount = info.Count; @@ -142,46 +149,49 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter var recentlyAddedModel = new List(); foreach (var embyEpisodes in filteredEp) { - // Let's sleep, Emby can't keep up with us. - Thread.Sleep(1000); try { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception thrown when processing an emby episode for the newsletter, Retrying {0}", timespan)); - // Find related series item - var relatedSeries = series.FirstOrDefault(x => x.EmbyId == embyEpisodes.ParentId); - - if (relatedSeries == null) + policy.Execute(() => { - continue; - } + // Find related series item + var relatedSeries = series.FirstOrDefault(x => x.EmbyId == embyEpisodes.ParentId); - // Get series information - var i = Api.GetInformation(relatedSeries.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series, - embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); - - Thread.Sleep(200); - var episodeInfo = Api.GetInformation(embyEpisodes.EmbyId, - Ombi.Api.Models.Emby.EmbyMediaType.Episode, - embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); - - // Check if we already have this series - var existingSeries = recentlyAddedModel.FirstOrDefault(x => - x.EmbyInformation.SeriesInformation.Id.Equals(i.SeriesInformation.Id, - StringComparison.CurrentCultureIgnoreCase)); - - if (existingSeries != null) - { - existingSeries.EpisodeInformation.Add(episodeInfo.EpisodeInformation); - } - else - { - recentlyAddedModel.Add(new EmbyRecentlyAddedModel + if (relatedSeries == null) { - EmbyInformation = i, - EpisodeInformation = new List() { episodeInfo.EpisodeInformation }, - EmbyContent = relatedSeries - }); - } + return; + } + + // Get series information + var i = Api.GetInformation(relatedSeries.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series, + embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); + + Thread.Sleep(200); + var episodeInfo = Api.GetInformation(embyEpisodes.EmbyId, + Ombi.Api.Models.Emby.EmbyMediaType.Episode, + embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); + + // Check if we already have this series + var existingSeries = recentlyAddedModel.FirstOrDefault(x => + x.EmbyInformation.SeriesInformation.Id.Equals(i.SeriesInformation.Id, + StringComparison.CurrentCultureIgnoreCase)); + + if (existingSeries != null) + { + existingSeries.EpisodeInformation.Add(episodeInfo.EpisodeInformation); + } + else + { + recentlyAddedModel.Add(new EmbyRecentlyAddedModel + { + EmbyInformation = i, + EpisodeInformation = new List() { episodeInfo.EpisodeInformation }, + EmbyContent = relatedSeries + }); + } + }); } catch (JsonReaderException) @@ -197,13 +207,21 @@ namespace Ombi.Services.Jobs.RecentlyAddedNewsletter { foreach (var t in filteredSeries) { - var i = Api.GetInformation(t.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series, - embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); - var item = new EmbyRecentlyAddedModel + + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception thrown when processing an emby series for the newsletter, Retrying {0}", timespan)); + var item = policy.Execute(() => { - EmbyContent = t, - EmbyInformation = i, - }; + var i = Api.GetInformation(t.EmbyId, Ombi.Api.Models.Emby.EmbyMediaType.Series, + embySettings.ApiKey, embySettings.AdministratorId, embySettings.FullUri); + var model = new EmbyRecentlyAddedModel + { + EmbyContent = t, + EmbyInformation = i, + }; + return model; + }); info.Add(item); } } diff --git a/Ombi.Services/Ombi.Services.csproj b/Ombi.Services/Ombi.Services.csproj index 725f1a4ab..d3ca25f5d 100644 --- a/Ombi.Services/Ombi.Services.csproj +++ b/Ombi.Services/Ombi.Services.csproj @@ -48,6 +48,7 @@ ..\packages\NLog.4.3.6\lib\net45\NLog.dll True + From 858171e92a427197404b7a34b49166a50e00ef8a Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 3 Apr 2017 20:54:34 +0100 Subject: [PATCH 05/45] small bit of work --- Ombi.Core/Tv/TvSenderV2.cs | 152 ++++++++++++++++++++++++++----------- Ombi.UI/Bootstrapper.cs | 4 +- 2 files changed, 108 insertions(+), 48 deletions(-) diff --git a/Ombi.Core/Tv/TvSenderV2.cs b/Ombi.Core/Tv/TvSenderV2.cs index 08833ff4e..d3ad64fa4 100644 --- a/Ombi.Core/Tv/TvSenderV2.cs +++ b/Ombi.Core/Tv/TvSenderV2.cs @@ -87,41 +87,65 @@ namespace Ombi.Core.Tv if (requestAll ?? false) { - return await ProcessSonarrRequestAll(sonarrSettings, model, qualityProfile, rootFolderPath); + return await ProcessSonarrRequestSeason(sonarrSettings, model, qualityProfile, rootFolderPath); } if (first ?? false) { - return await ProcessSonarrRequestFirstSeason(sonarrSettings, model, qualityProfile, rootFolderPath); + return await ProcessSonarrRequestSeason(sonarrSettings, model, qualityProfile, rootFolderPath); } if (latest ?? false) { - return await ProcessSonarrRequestLatestSeason(sonarrSettings, model, qualityProfile, rootFolderPath); + return await ProcessSonarrRequestSeason(sonarrSettings, model, qualityProfile, rootFolderPath); } if (specificSeasonRequest ?? false) { - return await ProcessSonarrRequestSpecificSeason(sonarrSettings, model, qualityProfile, rootFolderPath); + return await ProcessSonarrRequestSeason(sonarrSettings, model, qualityProfile, rootFolderPath); } return null; } - private async Task ProcessSonarrRequestSpecificSeason(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + + + private async Task ProcessSonarrRequestSeason(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) { - throw new NotImplementedException(); + // Does the series exist? + var series = await GetSonarrSeries(sonarrSettings, model.ProviderId); + if (series == null) + { + //WORKS + // Add the series + return AddSeries(sonarrSettings, model, rootFolderPath, qualityId); + } + + // 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 ProcessSonarrRequestLatestSeason(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + + + 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) { - //WORKS - // Add the series var seriesToAdd = new SonarrAddSeries { seasonFolder = sonarrSettings.SeasonFolders, @@ -136,8 +160,8 @@ namespace Ombi.Core.Tv 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 + ignoreEpisodesWithoutFiles = true, // We do not want to grab random episodes missing + searchForMissingEpisodes = false // we want don't want to search for the missing episodes either } }; @@ -146,55 +170,91 @@ namespace Ombi.Core.Tv var season = new Season { seasonNumber = i, - // ReSharper disable once SimplifyConditionalTernaryExpression - monitored = true ? model.SeasonList.Length == 0 || model.SeasonList.Any(x => x == i) : false + monitored = false // Do not monitor any seasons }; seriesToAdd.seasons.Add(season); } - return SonarrApi.AddSeries(seriesToAdd, sonarrSettings.ApiKey, sonarrSettings.FullUri); + // Add the series now + var result = SonarrApi.AddSeries(seriesToAdd, sonarrSettings.ApiKey, sonarrSettings.FullUri); + + await RequestEpisodesForSonarr(model, result.id, sonarrSettings); + } 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}; + await RequestEpisodesForSonarr(model, series.id, sonarrSettings); } + + return new SonarrAddSeries(){title = model.Title}; } - private async Task ProcessSonarrRequestFirstSeason(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) + public SonarrAddSeries AddSeries(SonarrSettings sonarrSettings, RequestedModel model, string rootFolderPath, int qualityId) { - 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) + //WORKS + // Add the series + var seriesToAdd = new SonarrAddSeries { - // Add the series + 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, + monitored = model.SeasonList.Length == 0 || model.SeasonList.Any(x => x == i) + }; + seriesToAdd.seasons.Add(season); } - return null; + return SonarrApi.AddSeries(seriesToAdd, sonarrSettings.ApiKey, sonarrSettings.FullUri); + } + + private async Task RequestEpisodesForSonarr(RequestedModel model, int showId, SonarrSettings sonarrSettings) + { + // Now lookup all episodes + var ep = SonarrApi.GetEpisodes(showId.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri); + var episodes = ep?.ToList() ?? new List(); + + var internalEpisodeIds = new List(); + var tasks = new List(); + foreach (var r in model.Episodes) + { + // Match the episode and season number. + // If the episode is monitored we might not be searching for it. + var episode = + episodes.FirstOrDefault( + x => x.episodeNumber == r.EpisodeNumber && x.seasonNumber == r.SeasonNumber); + if (episode == null) + { + continue; + } + var episodeInfo = SonarrApi.GetEpisode(episode.id.ToString(), sonarrSettings.ApiKey, + sonarrSettings.FullUri); + episodeInfo.monitored = true; // Set the episode to monitored + tasks.Add(Task.Run(() => SonarrApi.UpdateEpisode(episodeInfo, sonarrSettings.ApiKey, + sonarrSettings.FullUri))); + internalEpisodeIds.Add(episode.id); + } + + await Task.WhenAll(tasks.ToArray()); + + SonarrApi.SearchForEpisodes(internalEpisodeIds.ToArray(), sonarrSettings.ApiKey, sonarrSettings.FullUri); } public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model) diff --git a/Ombi.UI/Bootstrapper.cs b/Ombi.UI/Bootstrapper.cs index acb9d871e..ee480294d 100644 --- a/Ombi.UI/Bootstrapper.cs +++ b/Ombi.UI/Bootstrapper.cs @@ -142,8 +142,8 @@ namespace Ombi.UI pipelines.AfterRequest.AddItemToEndOfPipeline((ctx) => { ctx.Response.WithHeader("Access-Control-Allow-Origin", "*") - .WithHeader("Access-Control-Allow-Methods", "POST,GET") - .WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type"); + .WithHeader("Access-Control-Allow-Methods", "POST,GET") + .WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type"); }); base.RequestStartup(container, pipelines, context); From 4836ab7e6c2429dc9f56a2714f1e016258d82617 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Mon, 3 Apr 2017 21:11:59 +0100 Subject: [PATCH 06/45] Finished reworking the Sonarr Integration. Seems to be working as expected, faster and most stable. It's Not A Toomah! --- Ombi.Core/Tv/TvSenderV2.cs | 14 ++++++-------- Ombi.Services/Jobs/PlexEpisodeCacher.cs | 8 ++++---- Ombi.UI/Modules/SearchModule.cs | 7 ++++--- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Ombi.Core/Tv/TvSenderV2.cs b/Ombi.Core/Tv/TvSenderV2.cs index d3ad64fa4..11a97f498 100644 --- a/Ombi.Core/Tv/TvSenderV2.cs +++ b/Ombi.Core/Tv/TvSenderV2.cs @@ -120,24 +120,21 @@ namespace Ombi.Core.Tv // Add the series return AddSeries(sonarrSettings, model, rootFolderPath, qualityId); } - + // 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; - } + 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}; + return new SonarrAddSeries { title = series.title }; } - + private async Task ProcessSonarrEpisodeRequest(SonarrSettings sonarrSettings, RequestedModel model, int qualityId, string rootFolderPath) { @@ -186,7 +183,7 @@ namespace Ombi.Core.Tv await RequestEpisodesForSonarr(model, series.id, sonarrSettings); } - return new SonarrAddSeries(){title = model.Title}; + return new SonarrAddSeries() { title = model.Title }; } public SonarrAddSeries AddSeries(SonarrSettings sonarrSettings, RequestedModel model, string rootFolderPath, int qualityId) @@ -217,6 +214,7 @@ namespace Ombi.Core.Tv var season = new Season { seasonNumber = i, + // The model.SeasonList.Lenth is 0 when this is a "request all" monitored = model.SeasonList.Length == 0 || model.SeasonList.Any(x => x == i) }; seriesToAdd.seasons.Add(season); diff --git a/Ombi.Services/Jobs/PlexEpisodeCacher.cs b/Ombi.Services/Jobs/PlexEpisodeCacher.cs index 26820de94..6939ae924 100644 --- a/Ombi.Services/Jobs/PlexEpisodeCacher.cs +++ b/Ombi.Services/Jobs/PlexEpisodeCacher.cs @@ -109,7 +109,7 @@ namespace Ombi.Services.Jobs var metadata = PlexApi.GetEpisodeMetaData(settings.PlexAuthToken, settings.FullUri, video.RatingKey); // Loop through the metadata and create the model to insert into the DB - foreach (var metadataVideo in metadata.Video) + foreach (var metadataVideo in metadata?.Video ?? new List