Sickrage done. Ish... So i've written all the code by looking at the API. the key there is i've looked at the api. I have not tested anything so expect this to fail.

pull/1741/head
Jamie 7 years ago
parent 62a3ed924b
commit a1ee52daef

@ -6,12 +6,14 @@ namespace Ombi.Api.SickRage
public interface ISickRageApi
{
Task<SickRageTvAdd> AddSeason(int tvdbId, int season, string apiKey, string baseUrl);
Task<SickRageTvAdd> AddSeries(int tvdbId, int seasonCount, int[] seasons, string quality, string apiKey, string baseUrl);
Task<SickRageTvAdd> AddSeries(int tvdbId, string quality, string status, string apiKey, string baseUrl);
Task<SickRageShows> GetShows(string apiKey, string baseUrl);
Task<SickRagePing> Ping(string apiKey, string baseUrl);
Task<SickRageSeasonList> VerifyShowHasLoaded(int tvdbId, string apiKey, string baseUrl);
Task<SickRageShowInformation> GetShow(int tvdbid, string apikey, string baseUrl);
Task<SickRageEpisodeStatus> SetEpisodeStatus(string apiKey, string baseUrl, int tvdbid, string status,
int season, int episode = -1);
Task<SickRageEpisodes> GetEpisodesForSeason(int tvdbid, int season, string apikey, string baseUrl);
Task<SeasonList> GetSeasonList(int tvdbId, string apikey, string baseurl);
}
}

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Ombi.Api.SickRage.Models
{
public class SeasonList : SickRageBase<List<int>>
{
}
}

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Ombi.Api.SickRage.Models
{
public class SickRageEpisodes : SickRageBase<Dictionary<int, SickRageEpisodesData>>
{
}
public class SickRageEpisodesData
{
public string airdate { get; set; }
public string name { get; set; }
public string quality { get; set; }
public string status { get; set; }
}
}

@ -0,0 +1,41 @@
namespace Ombi.Api.SickRage.Models
{
public class SickRageShowInformation : SickRageBase<SickRageShowInformationData>
{
}
public class SickRageShowInformationData
{
public int air_by_date { get; set; }
public string airs { get; set; }
public Cache cache { get; set; }
public int flatten_folders { get; set; }
public string[] genre { get; set; }
public string language { get; set; }
public string location { get; set; }
public string network { get; set; }
public string next_ep_airdate { get; set; }
public int paused { get; set; }
public string quality { get; set; }
public Quality_Details quality_details { get; set; }
public int[] season_list { get; set; }
public string show_name { get; set; }
public string status { get; set; }
public int tvrage_id { get; set; }
public string tvrage_name { get; set; }
}
public class Cache
{
public int banner { get; set; }
public int poster { get; set; }
}
public class Quality_Details
{
public object[] archive { get; set; }
public string[] initial { get; set; }
}
}

@ -1,6 +1,4 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -27,15 +25,12 @@ namespace Ombi.Api.SickRage
return await _api.Request<SickRageSeasonList>(request);
}
public async Task<SickRageTvAdd> AddSeries(int tvdbId, int seasonCount, int[] seasons, string quality, string apiKey, string baseUrl)
public async Task<SickRageTvAdd> AddSeries(int tvdbId, string quality, string status, string apiKey, string baseUrl)
{
var futureStatus = seasons.Length > 0 && seasons.All(x => x != seasonCount) ? SickRageStatus.Skipped : SickRageStatus.Wanted;
var status = seasons.Length > 0 ? SickRageStatus.Skipped : SickRageStatus.Wanted;
var request = new Request($"/api/{apiKey}/?cmd=show.addnew", baseUrl, HttpMethod.Get);
request.AddQueryString("tvdbid", tvdbId.ToString());
request.AddQueryString("status", status);
request.AddQueryString("future_status", futureStatus);
if (!quality.Equals("default", StringComparison.CurrentCultureIgnoreCase))
{
@ -44,55 +39,6 @@ namespace Ombi.Api.SickRage
var obj = await _api.Request<SickRageTvAdd>(request);
if (obj.result != "failure")
{
var sw = new Stopwatch();
sw.Start();
var seasonIncrement = 0;
try
{
while (seasonIncrement < seasonCount)
{
var seasonList = await VerifyShowHasLoaded(tvdbId, apiKey, baseUrl);
if (seasonList.result.Equals("failure"))
{
await Task.Delay(3000);
continue;
}
seasonIncrement = seasonList.Data?.Length ?? 0;
if (sw.ElapsedMilliseconds > 30000) // Break out after 30 seconds, it's not going to get added
{
_log.LogWarning("Couldn't find out if the show had been added after 10 seconds. I doubt we can change the status to wanted.");
break;
}
}
sw.Stop();
}
catch (Exception e)
{
_log.LogCritical(e, "Exception thrown when getting the seasonList");
}
}
try
{
if (seasons.Length > 0)
{
//handle the seasons requested
foreach (var s in seasons)
{
var result = await AddSeason(tvdbId, s, apiKey, baseUrl);
}
}
}
catch (Exception e)
{
_log.LogCritical(e, "Exception when adding seasons:");
throw;
}
return obj;
}
@ -113,6 +59,23 @@ namespace Ombi.Api.SickRage
return await _api.Request<SickRageShows>(request);
}
public async Task<SickRageShowInformation> GetShow(int tvdbid, string apikey, string baseUrl)
{
var request = new Request($"/api/{apikey}/?cmd=show", baseUrl, HttpMethod.Get);
request.AddQueryString("tvdbid", tvdbid.ToString());
return await _api.Request<SickRageShowInformation>(request);
}
public async Task<SickRageEpisodes> GetEpisodesForSeason(int tvdbid, int season, string apikey, string baseUrl)
{
var request = new Request($"/api/{apikey}/?cmd=show.seasons", baseUrl, HttpMethod.Get);
request.AddQueryString("tvdbid", tvdbid.ToString());
request.AddQueryString("season", season.ToString());
return await _api.Request<SickRageEpisodes>(request);
}
public async Task<SickRagePing> Ping(string apiKey, string baseUrl)
{
var request = new Request($"/api/{apiKey}/?cmd=sb.ping", baseUrl, HttpMethod.Get);
@ -139,5 +102,13 @@ namespace Ombi.Api.SickRage
return await _api.Request<SickRageEpisodeStatus>(request);
}
public async Task<SeasonList> GetSeasonList(int tvdbId, string apikey, string baseurl)
{
var request = new Request($"/api/{apikey}/?cmd=show.seasonlist", baseurl, HttpMethod.Get);
request.AddQueryString("tvdbid", tvdbId.ToString());
return await _api.Request<SeasonList>(request);
}
}
}

@ -1,308 +1,363 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.DogNzb;
using Ombi.Api.DogNzb.Models;
using Ombi.Api.SickRage;
using Ombi.Api.Sonarr;
using Ombi.Api.Sonarr.Models;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Senders
{
public class TvSender : ITvSender
{
public TvSender(ISonarrApi sonarrApi, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings,
ISickRageApi srApi)
{
SonarrApi = sonarrApi;
Logger = log;
SonarrSettings = sonarrSettings;
DogNzbSettings = dog;
DogNzbApi = dogApi;
SickRageSettings = srSettings;
SickRageApi = srApi;
}
private ISonarrApi SonarrApi { get; }
private IDogNzbApi DogNzbApi { get; }
private ISickRageApi SickRageApi { get; }
private ILogger<TvSender> Logger { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<DogNzbSettings> DogNzbSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
public async Task<SenderResult> Send(ChildRequests model)
{
var sonarr = await SonarrSettings.GetSettingsAsync();
if (sonarr.Enabled)
{
var result = await SendToSonarr(model);
if (result != null)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
}
var dog = await DogNzbSettings.GetSettingsAsync();
if (dog.Enabled)
{
var result = await SendToDogNzb(model, dog);
if (!result.Failure)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
return new SenderResult
{
Message = result.ErrorMessage
};
}
return new SenderResult
{
Success = true
};
}
private async Task<DogNzbAddResult> SendToDogNzb(ChildRequests model, DogNzbSettings settings)
{
var id = model.ParentRequest.TvDbId;
return await DogNzbApi.AddTvShow(settings.ApiKey, id.ToString());
}
/// <summary>
/// Send the request to Sonarr to process
/// </summary>
/// <param name="s"></param>
/// <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, string qualityId = null)
{
var s = await SonarrSettings.GetSettingsAsync();
if (!s.Enabled)
{
return null;
}
if(string.IsNullOrEmpty(s.ApiKey))
{
return null;
}
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(s.QualityProfile, out qualityProfile);
}
// Get the root path from the rootfolder selected.
// 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 ?? int.Parse(s.RootPath), 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);
if (existingSeries == null)
{
// 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 (var i = 1; i < model.ParentRequest.TotalSeasons + 1; i++)
{
var index = i;
var season = new Season
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index)
};
seasonsToAdd.Add(season);
}
newSeries.seasons = seasonsToAdd;
var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri);
existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri);
await SendToSonarr(model, existingSeries, s);
}
else
{
await SendToSonarr(model, existingSeries, s);
}
return new NewSeries
{
id = existingSeries.id,
seasons = existingSeries.seasons.ToList(),
cleanTitle = existingSeries.cleanTitle,
title = existingSeries.title,
tvdbId = existingSeries.tvdbId
};
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.SonarrSender, e, "Exception thrown when attempting to send series over to Sonarr");
throw;
}
}
private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s)
{
var episodesToUpdate = new List<Episode>();
// Ok, now let's sort out the episodes.
var sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri);
var sonarrEpList = sonarrEpisodes.ToList() ?? new List<Episode>();
while (!sonarrEpList.Any())
{
// It could be that the series metadata is not ready yet. So wait
sonarrEpList = (await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri)).ToList();
await Task.Delay(500);
}
foreach (var req in model.SeasonRequests)
{
foreach (var ep in req.Episodes)
{
var sonarrEp = sonarrEpList.FirstOrDefault(x =>
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber);
if (sonarrEp != null)
{
sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp);
}
}
}
var seriesChanges = false;
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.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.
var existingSeason =
result.seasons.First(x => x.seasonNumber == season.SeasonNumber);
existingSeason.monitored = true;
seriesChanges = true;
}
else
{
// Now update the episodes that need updating
foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber))
{
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
}
}
}
if (seriesChanges)
{
await SonarrApi.SeasonPass(s.ApiKey, s.FullUri, result);
}
if (!s.AddOnly)
{
await SearchForRequest(model, sonarrEpList, result, s, episodesToUpdate);
}
}
public async Task SendToSickRage(ChildRequests model, string qualityId = null)
{
var settings = await SickRageSettings.GetSettingsAsync();
if (qualityId.HasValue())
{
if (settings.Qualities.All(x => x.Key != qualityId))
{
qualityId = settings.QualityProfile;
}
}
//var apiResult = SickRageApi.AddSeries(model.ParentRequest.TvDbId, model.SeasonCount, model.SeasonList, qualityId,
// sickRageSettings.ApiKey, sickRageSettings.FullUri);
//var result = apiResult.Result;
//return result;
}
private async Task SearchForRequest(ChildRequests model, IEnumerable<Episode> sonarrEpList, SonarrSeries existingSeries, SonarrSettings s,
IReadOnlyCollection<Episode> episodesToUpdate)
{
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.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(existingSeries.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);
}
}
}
private async Task<string> GetSonarrRootPath(int pathId, SonarrSettings sonarrSettings)
{
var rootFoldersResult = await SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri);
if (pathId == 0)
{
return rootFoldersResult.FirstOrDefault().path;
}
foreach (var r in rootFoldersResult.Where(r => r.id == pathId))
{
return r.path;
}
return string.Empty;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.DogNzb;
using Ombi.Api.DogNzb.Models;
using Ombi.Api.SickRage;
using Ombi.Api.SickRage.Models;
using Ombi.Api.Sonarr;
using Ombi.Api.Sonarr.Models;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Senders
{
public class TvSender : ITvSender
{
public TvSender(ISonarrApi sonarrApi, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings,
ISickRageApi srApi)
{
SonarrApi = sonarrApi;
Logger = log;
SonarrSettings = sonarrSettings;
DogNzbSettings = dog;
DogNzbApi = dogApi;
SickRageSettings = srSettings;
SickRageApi = srApi;
}
private ISonarrApi SonarrApi { get; }
private IDogNzbApi DogNzbApi { get; }
private ISickRageApi SickRageApi { get; }
private ILogger<TvSender> Logger { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<DogNzbSettings> DogNzbSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
public async Task<SenderResult> Send(ChildRequests model)
{
var sonarr = await SonarrSettings.GetSettingsAsync();
if (sonarr.Enabled)
{
var result = await SendToSonarr(model);
if (result != null)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
}
var dog = await DogNzbSettings.GetSettingsAsync();
if (dog.Enabled)
{
var result = await SendToDogNzb(model, dog);
if (!result.Failure)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
return new SenderResult
{
Message = result.ErrorMessage
};
}
var sr = await SickRageSettings.GetSettingsAsync();
if (sr.Enabled)
{
var result = await SendToSickRage(model, sr);
if (result)
{
return new SenderResult
{
Sent = true,
Success = true
};
}
return new SenderResult();
}
return new SenderResult
{
Success = true
};
}
private async Task<DogNzbAddResult> SendToDogNzb(ChildRequests model, DogNzbSettings settings)
{
var id = model.ParentRequest.TvDbId;
return await DogNzbApi.AddTvShow(settings.ApiKey, id.ToString());
}
/// <summary>
/// Send the request to Sonarr to process
/// </summary>
/// <param name="s"></param>
/// <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, string qualityId = null)
{
var s = await SonarrSettings.GetSettingsAsync();
if (!s.Enabled)
{
return null;
}
if(string.IsNullOrEmpty(s.ApiKey))
{
return null;
}
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(s.QualityProfile, out qualityProfile);
}
// Get the root path from the rootfolder selected.
// 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 ?? int.Parse(s.RootPath), 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);
if (existingSeries == null)
{
// 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 (var i = 1; i < model.ParentRequest.TotalSeasons + 1; i++)
{
var index = i;
var season = new Season
{
seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index)
};
seasonsToAdd.Add(season);
}
newSeries.seasons = seasonsToAdd;
var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri);
existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri);
await SendToSonarr(model, existingSeries, s);
}
else
{
await SendToSonarr(model, existingSeries, s);
}
return new NewSeries
{
id = existingSeries.id,
seasons = existingSeries.seasons.ToList(),
cleanTitle = existingSeries.cleanTitle,
title = existingSeries.title,
tvdbId = existingSeries.tvdbId
};
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.SonarrSender, e, "Exception thrown when attempting to send series over to Sonarr");
throw;
}
}
private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s)
{
var episodesToUpdate = new List<Episode>();
// Ok, now let's sort out the episodes.
var sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri);
var sonarrEpList = sonarrEpisodes.ToList() ?? new List<Episode>();
while (!sonarrEpList.Any())
{
// It could be that the series metadata is not ready yet. So wait
sonarrEpList = (await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri)).ToList();
await Task.Delay(500);
}
foreach (var req in model.SeasonRequests)
{
foreach (var ep in req.Episodes)
{
var sonarrEp = sonarrEpList.FirstOrDefault(x =>
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber);
if (sonarrEp != null)
{
sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp);
}
}
}
var seriesChanges = false;
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.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.
var existingSeason =
result.seasons.First(x => x.seasonNumber == season.SeasonNumber);
existingSeason.monitored = true;
seriesChanges = true;
}
else
{
// Now update the episodes that need updating
foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber))
{
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
}
}
}
if (seriesChanges)
{
await SonarrApi.SeasonPass(s.ApiKey, s.FullUri, result);
}
if (!s.AddOnly)
{
await SearchForRequest(model, sonarrEpList, result, s, episodesToUpdate);
}
}
private async Task<bool> SendToSickRage(ChildRequests model, SickRageSettings settings, string qualityId = null)
{
var tvdbid = model.ParentRequest.TvDbId;
if (qualityId.HasValue()) { var id = qualityId; if (settings.Qualities.All(x => x.Value != id)) { qualityId = settings.QualityProfile; } }
// Check if the show exists
var existingShow = await SickRageApi.GetShow(tvdbid, settings.ApiKey, settings.FullUri);
if (existingShow == null)
{
var addResult = await SickRageApi.AddSeries(model.ParentRequest.TvDbId, SickRageStatus.Wanted,
qualityId,
settings.ApiKey, settings.FullUri);
Logger.LogDebug("Added the show (tvdbid) {0}. The result is '{2}' : '{3}'", tvdbid, addResult.result, addResult.message);
if (addResult.result.Equals("failure"))
{
// Do something
return false;
}
}
foreach (var seasonRequests in model.SeasonRequests)
{
var srEpisodes = await SickRageApi.GetEpisodesForSeason(tvdbid, seasonRequests.SeasonNumber, settings.ApiKey, settings.FullUri);
var totalSrEpisodes = srEpisodes.data.Count;
if (totalSrEpisodes == seasonRequests.Episodes.Count)
{
// This is a request for the whole season
var wholeSeasonResult = await SickRageApi.SetEpisodeStatus(settings.ApiKey, settings.FullUri, tvdbid, SickRageStatus.Wanted,
seasonRequests.SeasonNumber);
Logger.LogDebug("Set the status to Wanted for season {0}. The result is '{1}' : '{2}'", seasonRequests.SeasonNumber, wholeSeasonResult.result, wholeSeasonResult.message);
continue;
}
foreach (var srEp in srEpisodes.data)
{
var epNumber = srEp.Key;
var epData = srEp.Value;
var epRequest = seasonRequests.Episodes.FirstOrDefault(x => x.EpisodeNumber == epNumber);
if (epRequest != null)
{
// We want to monior this episode since we have a request for it
// Let's check to see if it's wanted first, save an api call
if (epData.status.Equals(SickRageStatus.Wanted, StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
var epResult = await SickRageApi.SetEpisodeStatus(settings.ApiKey, settings.FullUri, tvdbid,
SickRageStatus.Wanted, seasonRequests.SeasonNumber, epNumber);
Logger.LogDebug("Set the status to Wanted for Episode {0} in season {1}. The result is '{2}' : '{3}'", seasonRequests.SeasonNumber, epNumber, epResult.result, epResult.message);
}
}
}
return true;
}
private async Task SearchForRequest(ChildRequests model, IEnumerable<Episode> sonarrEpList, SonarrSeries existingSeries, SonarrSettings s,
IReadOnlyCollection<Episode> episodesToUpdate)
{
foreach (var season in model.SeasonRequests)
{
var sonarrSeason = sonarrEpList.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(existingSeries.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);
}
}
}
private async Task<string> GetSonarrRootPath(int pathId, SonarrSettings sonarrSettings)
{
var rootFoldersResult = await SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri);
if (pathId == 0)
{
return rootFoldersResult.FirstOrDefault().path;
}
foreach (var r in rootFoldersResult.Where(r => r.id == pathId))
{
return r.path;
}
return string.Empty;
}
}
}

@ -19,6 +19,7 @@ namespace Ombi.Helpers
public static EventId SonarrCacher => new EventId(2006);
public static EventId CouchPotatoCacher => new EventId(2007);
public static EventId PlexContentCacher => new EventId(2008);
public static EventId SickRageCacher => new EventId(2009);
public static EventId MovieSender => new EventId(3000);

@ -6,6 +6,7 @@ using Ombi.Schedule.Jobs.Emby;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Schedule.Jobs.Plex;
using Ombi.Schedule.Jobs.Radarr;
using Ombi.Schedule.Jobs.SickRage;
using Ombi.Schedule.Jobs.Sonarr;
using Ombi.Settings.Settings.Models;
@ -16,7 +17,7 @@ namespace Ombi.Schedule
public JobSetup(IPlexContentSync plexContentSync, IRadarrSync radarrSync,
IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter,
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
ISettingsService<JobSettings> jobsettings)
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync)
{
PlexContentSync = plexContentSync;
RadarrSync = radarrSync;
@ -37,6 +38,7 @@ namespace Ombi.Schedule
private IEmbyUserImporter EmbyUserImporter { get; }
private ISonarrSync SonarrSync { get; }
private ICouchPotatoSync CpCache { get; }
private ISickRageSync SrSync { get; }
private ISettingsService<JobSettings> JobSettings { get; set; }
public void Setup()
@ -48,6 +50,7 @@ namespace Ombi.Schedule
RecurringJob.AddOrUpdate(() => RadarrSync.CacheContent(), JobSettingsHelper.Radarr(s));
RecurringJob.AddOrUpdate(() => PlexContentSync.CacheContent(), JobSettingsHelper.PlexContent(s));
RecurringJob.AddOrUpdate(() => CpCache.Start(), JobSettingsHelper.CouchPotato(s));
RecurringJob.AddOrUpdate(() => SrSync.Start(), JobSettingsHelper.SickRageSync(s));
RecurringJob.AddOrUpdate(() => Updater.Update(null), JobSettingsHelper.Updater(s));

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Schedule.Jobs.SickRage
{
public interface ISickRageSync
{
Task Start();
}
}

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.SickRage;
using Ombi.Api.SickRage.Models;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Context;
using Ombi.Store.Entities;
namespace Ombi.Schedule.Jobs.SickRage
{
public class SickRageSync : ISickRageSync
{
public SickRageSync(ISettingsService<SickRageSettings> s, ISickRageApi api, ILogger<SickRageSync> l, IOmbiContext ctx)
{
_settings = s;
_api = api;
_log = l;
_ctx = ctx;
_settings.ClearCache();
}
private readonly ISettingsService<SickRageSettings> _settings;
private readonly ISickRageApi _api;
private readonly ILogger<SickRageSync> _log;
private readonly IOmbiContext _ctx;
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
public async Task Start()
{
await SemaphoreSlim.WaitAsync();
try
{
var settings = await _settings.GetSettingsAsync();
if (!settings.Enabled)
{
return;
}
var shows = await _api.GetShows(settings.ApiKey, settings.FullUri);
if (shows != null)
{
var srShows = shows.data.Values;
var ids = srShows.Select(x => x.tvdbid);
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageCache");
var entites = ids.Select(id => new SickRageCache { TvDbId = id }).ToList();
await _ctx.SickRageCache.AddRangeAsync(entites);
var episodesToAdd = new List<SickRageEpisodeCache>();
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SickRageEpisodeCache");
foreach (var s in srShows)
{
var seasons = await _api.GetSeasonList(s.tvdbid, settings.ApiKey, settings.FullUri);
foreach (var season in seasons.data)
{
var episodes =
await _api.GetEpisodesForSeason(s.tvdbid, season, settings.ApiKey, settings.FullUri);
var monitoredEpisodes = episodes.data.Where(x => x.Value.status.Equals(SickRageStatus.Wanted));
episodesToAdd.AddRange(monitoredEpisodes.Select(episode => new SickRageEpisodeCache
{
EpisodeNumber = episode.Key,
SeasonNumber = season,
TvDbId = s.tvdbid
}));
}
}
await _ctx.SickRageEpisodeCache.AddRangeAsync(episodesToAdd);
await _ctx.SaveChangesAsync();
}
}
catch (Exception e)
{
_log.LogError(LoggingEvents.SickRageCacher, e, "Exception when trying to cache SickRage");
}
finally
{
SemaphoreSlim.Release();
}
}
}
}

@ -27,6 +27,7 @@
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
<ProjectReference Include="..\Ombi.Api.Service\Ombi.Api.Service.csproj" />
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />

@ -1,5 +1,4 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Ombi.Settings.Settings.Models.External
{
@ -8,19 +7,34 @@ namespace Ombi.Settings.Settings.Models.External
public bool Enabled { get; set; }
public string ApiKey { get; set; }
public string QualityProfile { get; set; }
[JsonIgnore]
public Dictionary<string, string> Qualities => new Dictionary<string, string>
public List<DropDownModel> Qualities => new List<DropDownModel>
{
{ "default", "Use Default" },
{ "sdtv", "SD TV" },
{ "sddvd", "SD DVD" },
{ "hdtv", "HD TV" },
{ "rawhdtv", "Raw HD TV" },
{ "hdwebdl", "HD Web DL" },
{ "fullhdwebdl", "Full HD Web DL" },
{ "hdbluray", "HD Bluray" },
{ "fullhdbluray", "Full HD Bluray" }
new DropDownModel("default", "Use Default"),
new DropDownModel("sdtv", "SD TV"),
new DropDownModel("sddvd", "SD DVD"),
new DropDownModel("hdtv", "HD TV"),
new DropDownModel("rawhdtv", "Raw HD TV"),
new DropDownModel("hdwebdl", "HD Web DL"),
new DropDownModel("fullhdwebdl", "Full HD Web DL"),
new DropDownModel("hdbluray", "HD Bluray"),
new DropDownModel("fullhdbluray", "Full HD Bluray"),
};
}
public class DropDownModel
{
public DropDownModel(string val, string display)
{
Value = val;
Display = display;
}
public DropDownModel()
{
}
public string Value { get; set; }
public string Display { get; set; }
}
}

@ -9,5 +9,6 @@
public string CouchPotatoSync { get; set; }
public string AutomaticUpdater { get; set; }
public string UserImporter { get; set; }
public string SickRageSync { get; set; }
}
}

@ -35,6 +35,10 @@ namespace Ombi.Settings.Settings.Models
{
return Get(s.UserImporter, Cron.Daily());
}
public static string SickRageSync(JobSettings s)
{
return Get(s.SickRageSync, Cron.Hourly(35));
}
private static string Get(string settings, string defaultCron)

@ -38,5 +38,7 @@ namespace Ombi.Store.Context
EntityEntry Update(object entity);
EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class;
DbSet<CouchPotatoCache> CouchPotatoCache { get; set; }
DbSet<SickRageCache> SickRageCache { get; set; }
DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
}
}

@ -40,6 +40,8 @@ namespace Ombi.Store.Context
public DbSet<Tokens> Tokens { get; set; }
public DbSet<SonarrCache> SonarrCache { get; set; }
public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
public DbSet<SickRageCache> SickRageCache { get; set; }
public DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
public DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; }

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("SickRageCache")]
public class SickRageCache : Entity
{
public int TvDbId { get; set; }
}
}

@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("SickRageEpisodeCache")]
public class SickRageEpisodeCache : Entity
{
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
public int TvDbId { get; set; }
}
}

@ -26,7 +26,3 @@ export interface IUsersModel {
id: string;
username: string;
}
export interface IDictionary<T> {
[Key: string]: T;
}

@ -1,4 +1,4 @@
import { IDictionary, ISettings } from "./ICommon";
import { ISettings } from "./ICommon";
export interface IExternalSettings extends ISettings {
ssl: boolean;
@ -115,6 +115,7 @@ export interface IJobSettings {
couchPotatoSync: string;
automaticUpdater: string;
userImporter: string;
sickRageSync: string;
}
export interface IAuthenticationSettings extends ISettings {
@ -158,7 +159,12 @@ export interface ISickRageSettings extends IExternalSettings {
enabled: boolean;
apiKey: string;
qualityProfile: string;
qualities: IDictionary<string>;
qualities: IDropDownModel[];
}
export interface IDropDownModel {
value: string;
display: string;
}
export interface IDogNzbSettings extends ISettings {

@ -13,6 +13,11 @@
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('sonarrSync').hasError('required')}" id="sonarrSync" name="sonarrSync" formControlName="sonarrSync">
<small *ngIf="form.get('sonarrSync').hasError('required')" class="error-text">The Sonarr Sync is required</small>
</div>
<div class="form-group">
<label for="sickRageSync" class="control-label">SickRage Sync</label>
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('sonarrSync').hasError('required')}" id="sickRageSync" name="sickRageSync" formControlName="sickRageSync">
<small *ngIf="form.get('sickRageSync').hasError('required')" class="error-text">The SickRage Sync is required</small>
</div>
<div class="form-group">
<label for="radarrSync" class="control-label">Radarr Sync</label>

@ -23,8 +23,9 @@ export class JobsComponent implements OnInit {
embyContentSync: [x.embyContentSync, Validators.required],
plexContentSync: [x.plexContentSync, Validators.required],
userImporter: [x.userImporter, Validators.required],
sonarrSync: [x.radarrSync, Validators.required],
sonarrSync: [x.radarrSync, Validators.required],
radarrSync: [x.sonarrSync, Validators.required],
sickRageSync: [x.sickRageSync, Validators.required],
});
});
}

@ -30,8 +30,7 @@
<ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Sonarr']">Sonarr</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/DogNzb']">DogNzb</a></li>
<li [routerLinkActive]="['active']"><a>More Coming Soon...</a></li>
<!--<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/SickRage']">SickRage</a></li>-->
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/SickRage']">SickRage</a></li>
</ul>
</li>
@ -44,7 +43,6 @@
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/DogNzb']">DogNzb</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Radarr']">Radarr</a></li>
<!--<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Watcher']">Watcher</a></li>-->
<li [routerLinkActive]="['active']"><a>More Coming Soon...</a></li>
</ul>
</li>

@ -53,7 +53,7 @@
<label for="select" class="control-label">Quality Profiles</label>
<div id="profiles">
<select class="form-control form-control-custom" [ngClass]="{'form-error': form.get('qualityProfile').hasError('required')}" id="select" formControlName="qualityProfile">
<option *ngFor="let quality of qualities" value="{{quality.id}}" >{{quality.name}}</option>
<option *ngFor="let quality of qualities" value="{{quality.value}}" >{{quality.display}}</option>
</select>
</div>
<small *ngIf="form.get('qualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>

@ -1,9 +1,7 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ISonarrProfile, ISonarrRootFolder } from "../../interfaces";
import { ISickRageSettings } from "../../interfaces";
import { IDropDownModel, ISickRageSettings } from "../../interfaces";
import { TesterService } from "../../services";
import { NotificationService } from "../../services";
import { SettingsService } from "../../services";
@ -13,14 +11,8 @@ import { SettingsService } from "../../services";
})
export class SickRageComponent implements OnInit {
public qualities: ISonarrProfile[];
public rootFolders: ISonarrRootFolder[];
public selectedRootFolder: ISonarrRootFolder;
public selectedQuality: ISonarrProfile;
public profilesRunning: boolean;
public rootFoldersRunning: boolean;
public qualities: IDropDownModel[];
public form: FormGroup;
public advanced = false;
constructor(private settingsService: SettingsService,
private notificationService: NotificationService,
@ -34,13 +26,12 @@ export class SickRageComponent implements OnInit {
enabled: [x.enabled],
apiKey: [x.apiKey, [Validators.required]],
qualityProfile: [x.qualityProfile, [Validators.required]],
qualities: [x.qualities],
ssl: [x.ssl],
subDir: [x.subDir],
ip: [x.ip, [Validators.required]],
port: [x.port, [Validators.required]],
});
this.qualities = x.qualities;
});
}

@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Hangfire;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Ombi.Api.CouchPotato;
@ -10,7 +9,6 @@ using Ombi.Api.Plex;
using Ombi.Api.Radarr;
using Ombi.Api.SickRage;
using Ombi.Api.Sonarr;
using Ombi.Api.Telegram;
using Ombi.Attributes;
using Ombi.Core.Notifications;
using Ombi.Core.Settings.Models.External;
@ -18,7 +16,6 @@ using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Agents;
using Ombi.Notifications.Models;
using Ombi.Notifications.Templates;
using Ombi.Settings.Settings.Models.External;
using Ombi.Settings.Settings.Models.Notifications;
@ -84,11 +81,19 @@ namespace Ombi.Controllers.External
[HttpPost("discord")]
public bool Discord([FromBody] DiscordNotificationSettings settings)
{
settings.Enabled = true;
DiscordNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
try
{
settings.Enabled = true;
DiscordNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test Discord");
return false;
}
}
/// <summary>
@ -99,11 +104,20 @@ namespace Ombi.Controllers.External
[HttpPost("pushbullet")]
public bool Pushbullet([FromBody] PushbulletSettings settings)
{
settings.Enabled = true;
PushbulletNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
try
{
return true;
settings.Enabled = true;
PushbulletNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test Pushbullet");
return false;
}
}
/// <summary>
@ -114,11 +128,20 @@ namespace Ombi.Controllers.External
[HttpPost("pushover")]
public bool Pushover([FromBody] PushoverSettings settings)
{
settings.Enabled = true;
PushoverNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
try
{
settings.Enabled = true;
PushoverNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test Pushover");
return false;
}
return true;
}
/// <summary>
@ -129,11 +152,21 @@ namespace Ombi.Controllers.External
[HttpPost("mattermost")]
public bool Mattermost([FromBody] MattermostNotificationSettings settings)
{
settings.Enabled = true;
MattermostNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
try
{
settings.Enabled = true;
MattermostNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test Mattermost");
return false;
}
return true;
}
@ -145,11 +178,19 @@ namespace Ombi.Controllers.External
[HttpPost("slack")]
public bool Slack([FromBody] SlackNotificationSettings settings)
{
settings.Enabled = true;
SlackNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
try
{
settings.Enabled = true;
SlackNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test Slack");
return false;
}
}
/// <summary>
@ -293,10 +334,18 @@ namespace Ombi.Controllers.External
[HttpPost("telegram")]
public async Task<bool> Telegram([FromBody] TelegramSettings settings)
{
settings.Enabled = true;
await TelegramNotification.NotifyAsync(new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
try
{
settings.Enabled = true;
await TelegramNotification.NotifyAsync(new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test Telegram");
return false;
}
}
/// <summary>
@ -307,9 +356,17 @@ namespace Ombi.Controllers.External
[HttpPost("sickrage")]
public async Task<bool> SickRage([FromBody] SickRageSettings settings)
{
settings.Enabled = true;
var result = await SickRageApi.Ping(settings.ApiKey, settings.FullUri);
return result?.data?.pid != null;
try
{
settings.Enabled = true;
var result = await SickRageApi.Ping(settings.ApiKey, settings.FullUri);
return result?.data?.pid != null;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test SickRage");
return false;
}
}
}
}
Loading…
Cancel
Save