More work on the Sonarr Api Integration #865

pull/1488/head
tidusjar 7 years ago
parent 2c945ebb9b
commit c0d019f7d4

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models;
using System.Net.Http;
namespace Ombi.Api.Sonarr
{
@ -12,5 +13,12 @@ namespace Ombi.Api.Sonarr
Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl);
Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl);
Task<NewSeries> AddSeries(NewSeries seriesToAdd, string apiKey, string baseUrl);
Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl);
Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl);
Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl);
Task<bool> EpisodeSearch(int[] episodeIds, string apiKey, string baseUrl);
Task<bool> SeasonSearch(int seriesId, int seasonNumber, string apiKey, string baseUrl);
Task<bool> SeriesSearch(int seriesId, string apiKey, string baseUrl);
}
}

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Sonarr.Models
{
public class CommandResult
{
public string name { get; set; }
public DateTime startedOn { get; set; }
public DateTime stateChangeTime { get; set; }
public bool sendUpdatesToClient { get; set; }
public string state { get; set; }
public int id { get; set; }
}
}

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Sonarr.Models
{
public class Episode
{
public int seriesId { get; set; }
public int episodeFileId { get; set; }
public int seasonNumber { get; set; }
public int episodeNumber { get; set; }
public string title { get; set; }
public string airDate { get; set; }
public DateTime airDateUtc { get; set; }
public string overview { get; set; }
public bool hasFile { get; set; }
public bool monitored { get; set; }
public bool unverifiedSceneNumbering { get; set; }
public int id { get; set; }
public int absoluteEpisodeNumber { get; set; }
public int sceneAbsoluteEpisodeNumber { get; set; }
public int sceneEpisodeNumber { get; set; }
public int sceneSeasonNumber { get; set; }
public Episodefile episodeFile { get; set; }
}
public class Episodefile
{
public int seriesId { get; set; }
public int seasonNumber { get; set; }
public string relativePath { get; set; }
public string path { get; set; }
public int size { get; set; }
public DateTime dateAdded { get; set; }
public string sceneName { get; set; }
public EpisodeQuality quality { get; set; }
public bool qualityCutoffNotMet { get; set; }
public int id { get; set; }
}
public class EpisodeQuality
{
public Quality quality { get; set; }
public Revision revision { get; set; }
}
public class Revision
{
public int version { get; set; }
public int real { get; set; }
}
public class EpisodeUpdateResult
{
public int seriesId { get; set; }
public int episodeFileId { get; set; }
public int seasonNumber { get; set; }
public int episodeNumber { get; set; }
public string title { get; set; }
public string airDate { get; set; }
public DateTime airDateUtc { get; set; }
public string overview { get; set; }
public bool hasFile { get; set; }
public bool monitored { get; set; }
public int sceneEpisodeNumber { get; set; }
public int sceneSeasonNumber { get; set; }
public int tvDbEpisodeId { get; set; }
public int absoluteEpisodeNumber { get; set; }
public bool downloading { get; set; }
public int id { get; set; }
}
}

@ -28,7 +28,7 @@ namespace Ombi.Api.Sonarr.Models
public bool seasonFolder { get; set; }
public bool monitored { get; set; }
public bool useSceneNumbering { get; set; }
public int runtime { get; set; }
public long runtime { get; set; }
public int tvdbId { get; set; }
public int tvRageId { get; set; }
public int tvMazeId { get; set; }
@ -80,7 +80,7 @@ namespace Ombi.Api.Sonarr.Models
public int episodeCount { get; set; }
public int totalEpisodeCount { get; set; }
public long sizeOnDisk { get; set; }
public int percentOfEpisodes { get; set; }
public long percentOfEpisodes { get; set; }
public DateTime previousAiring { get; set; }
public DateTime nextAiring { get; set; }
}

@ -10,7 +10,6 @@ namespace Ombi.Api.Sonarr
{
public class SonarrApi : ISonarrApi
{
public SonarrApi(IApi api)
{
Api = api;
@ -21,18 +20,14 @@ namespace Ombi.Api.Sonarr
public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl)
{
var request = new Request("/api/profile", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrProfile>>(request);
}
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{
var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrRootFolder>>(request);
}
@ -45,10 +40,14 @@ namespace Ombi.Api.Sonarr
public async Task<IEnumerable<SonarrSeries>> GetSeries(string apiKey, string baseUrl)
{
var request = new Request("/api/series", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
var results = await Api.Request<List<SonarrSeries>>(request);
return await Api.Request<List<SonarrSeries>>(request);
foreach (var s in results)
{
s.seasons.ToList().RemoveAt(0);
}
return results;
}
/// <summary>
@ -61,10 +60,11 @@ namespace Ombi.Api.Sonarr
public async Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl)
{
var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
var result = await Api.Request<SonarrSeries>(request);
result.seasons.ToList().RemoveAt(0);
return await Api.Request<SonarrSeries>(request);
return result;
}
/// <summary>
@ -77,22 +77,21 @@ namespace Ombi.Api.Sonarr
public async Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl)
{
var request = new Request("/api/series/", baseUrl, HttpMethod.Put);
request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(updated);
return await Api.Request<SonarrSeries>(request);
}
public async Task<NewSeries> AddSeries(NewSeries seriesToAdd, string apiKey, string baseUrl)
{
if(!string.IsNullOrEmpty(seriesToAdd.Validate()))
if (!string.IsNullOrEmpty(seriesToAdd.Validate()))
{
return new NewSeries { ErrorMessages = new List<string> { seriesToAdd.Validate() } };
}
var request = new Request("/api/series/", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(seriesToAdd);
try
{
@ -102,10 +101,92 @@ namespace Ombi.Api.Sonarr
{
var error = await Api.Request<List<SonarrError>>(request);
var messages = error?.Select(x => x.errorMessage).ToList();
messages?.ForEach(x => Log.Error(x));
return new NewSeries { ErrorMessages = messages };
}
}
/// <summary>
/// Returns the episodes for the series
/// </summary>
/// <param name="seriesId">The Sonarr SeriesId value</param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public async Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl)
{
var request = new Request($"/api/Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<Episode>>(request);
}
/// <summary>
/// Returns the episode for the series
/// </summary>
/// <param name="episodeId">The Sonarr Episode ID</param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public async Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl)
{
var request = new Request($"/api/Episode/{episodeId}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<Episode>(request);
}
public async Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl)
{
var request = new Request($"/api/Episode/", baseUrl, HttpMethod.Put);
request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(episodeToUpdate);
return await Api.Request<EpisodeUpdateResult>(request);
}
/// <summary>
/// Search for a list of episodes
/// </summary>
/// <param name="episodeIds">The episodes to search for</param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public async Task<bool> EpisodeSearch(int[] episodeIds, string apiKey, string baseUrl)
{
var result = await Command("EpisodeSearch", apiKey, baseUrl, episodeIds);
return result != null;
}
/// <summary>
/// Search for all episodes of a particular season
/// </summary>
/// <param name="seriesId">Series to search for</param>
/// <param name="seasonNumber">Season to get all episodes</param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public async Task<bool> SeasonSearch(int seriesId, int seasonNumber, string apiKey, string baseUrl)
{
var result = await Command("SeasonSearch", apiKey, baseUrl, new { seriesId, seasonNumber });
return result != null;
}
/// <summary>
/// Search for all episodes in a series
/// </summary>
/// <param name="seriesId">Series to search for</param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public async Task<bool> SeriesSearch(int seriesId, string apiKey, string baseUrl)
{
var result = await Command("SeasonSearch", apiKey, baseUrl, seriesId);
return result != null;
}
private async Task<CommandResult> Command(string commandName, string apiKey, string baseUrl, object body = null)
{
var request = new Request($"/api/Command/{commandName}", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey);
if(body != null) request.AddJsonBody(body);
return await Api.Request<CommandResult>(request);
}
}
}

@ -301,7 +301,7 @@ namespace Ombi.Core.Engine
if(model.Approved)
{
// Autosend
TvSender.SendToSonarr(model,model.ParentRequest.TotalSeasons);
TvSender.SendToSonarr(model);
}
//var limit = await RequestLimitRepo.GetAllAsync();

@ -7,6 +7,6 @@ namespace Ombi.Core
{
public interface ITvSender
{
Task<NewSeries> SendToSonarr(ChildRequests model, int totalSeasons, string qualityId = null);
Task<NewSeries> SendToSonarr(ChildRequests model, string qualityId = null);
}
}

@ -30,7 +30,7 @@ namespace Ombi.Core
/// <param name="model"></param>
/// <param name="qualityId">This is for any qualities overriden from the UI</param>
/// <returns></returns>
public async Task<NewSeries> SendToSonarr(ChildRequests model, int totalSeasons, string qualityId = null)
public async Task<NewSeries> SendToSonarr(ChildRequests model, string qualityId = null)
{
var s = await Settings.GetSettingsAsync();
var qualityProfile = 0;
@ -48,58 +48,119 @@ namespace Ombi.Core
// For some reason, if we haven't got one use the first root folder in Sonarr
// TODO make this overrideable via the UI
var rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? 0, s);
try
{
// Does the series actually exist?
var allSeries = await SonarrApi.GetSeries(s.ApiKey, s.FullUri);
var existingSeries = allSeries.FirstOrDefault(x => x.tvdbId == model.ParentRequest.TvDbId);
// Does the series actually exist?
var allSeries = await SonarrApi.GetSeries(s.ApiKey, s.FullUri);
var existingSeries = allSeries.FirstOrDefault(x => x.tvdbId == model.ParentRequest.TvDbId);
if(existingSeries == null)
{
// Time to add a new one
var newSeries = new NewSeries
if (existingSeries == null)
{
title = model.ParentRequest.Title,
imdbId = model.ParentRequest.ImdbId,
tvdbId = model.ParentRequest.TvDbId,
cleanTitle = model.ParentRequest.Title,
monitored = true,
seasonFolder = s.SeasonFolders,
rootFolderPath = rootFolderPath,
qualityProfileId = qualityProfile,
titleSlug = model.ParentRequest.Title,
addOptions = new AddOptions
// Time to add a new one
var newSeries = new NewSeries
{
title = model.ParentRequest.Title,
imdbId = model.ParentRequest.ImdbId,
tvdbId = model.ParentRequest.TvDbId,
cleanTitle = model.ParentRequest.Title,
monitored = true,
seasonFolder = s.SeasonFolders,
rootFolderPath = rootFolderPath,
qualityProfileId = qualityProfile,
titleSlug = model.ParentRequest.Title,
addOptions = new AddOptions
{
ignoreEpisodesWithFiles = true, // There shouldn't be any episodes with files, this is a new season
ignoreEpisodesWithoutFiles = true, // We want all missing
searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly.
}
};
// Montitor the correct seasons,
// If we have that season in the model then it's monitored!
var seasonsToAdd = new List<Season>();
for (int i = 1; i < model.ParentRequest.TotalSeasons +1 ; i++)
{
ignoreEpisodesWithFiles = true, // There shouldn't be any episodes with files, this is a new season
ignoreEpisodesWithoutFiles = false, // We want all missing
searchForMissingEpisodes = true // we want to search for missing TODO pass this in
var season = new Season
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == i)
};
seasonsToAdd.Add(season);
}
};
newSeries.seasons = seasonsToAdd;
var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri);
// Montitor the correct seasons,
// If we have that season in the model then it's monitored!
var seasonsToAdd = new List<Season>();
for (int i = 1; i < totalSeasons; i++)
{
var season = new Season
// Ok, now let's sort out the episodes.
var sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri);
while(sonarrEpisodes.Count() == 0)
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == i)
};
seasonsToAdd.Add(season);
sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri);
await Task.Delay(300);
}
var episodesToUpdate = new List<Episode>();
foreach (var req in model.SeasonRequests)
{
foreach (var ep in req.Episodes)
{
var sonarrEp = sonarrEpisodes.FirstOrDefault(x => x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == ep.Season.SeasonNumber);
if(sonarrEp != null)
{
sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp);
}
}
}
// Now update the episodes that need updating
foreach (var epToUpdate in episodesToUpdate)
{
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
}
if(!s.AddOnly)
{
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpisodes.Where(x => x.seasonNumber == season.SeasonNumber);
var sonarrEpCount = sonarrSeason.Count();
var ourRequestCount = season.Episodes.Count();
if(sonarrEpCount == ourRequestCount)
{
// We have the same amount of requests as all of the episodes in the season.
// Do a season search
await SonarrApi.SeasonSearch(result.id, season.SeasonNumber, s.ApiKey, s.FullUri);
}
else
{
// There is a miss-match, let's search the episodes indiviaully
await SonarrApi.EpisodeSearch(episodesToUpdate.Select(x => x.id).ToArray(), s.ApiKey, s.FullUri);
}
}
}
return result;
}
else
{
// Let's update the existing
}
var result = SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri);
return result;
}
else
catch (System.Exception e)
{
// Let's update the existing
throw;
}
return null;
}
@ -107,7 +168,7 @@ namespace Ombi.Core
{
var rootFoldersResult = await SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri);
if(pathId == 0)
if (pathId == 0)
{
return rootFoldersResult.FirstOrDefault().path;
}

@ -13,5 +13,6 @@
/// The root path.
/// </value>
public string RootPath { get; set; }
public bool AddOnly { get; set; }
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities.Requests
{
@ -16,6 +17,7 @@ namespace Ombi.Store.Entities.Requests
/// <summary>
/// This is so we can correctly send the right amount of seasons to Sonarr
/// </summary>
[NotMapped]
public int TotalSeasons { get; set; }
public List<ChildRequests> ChildRequests { get; set; }

Loading…
Cancel
Save