mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 13:23:20 -07:00
Merge branch 'DotNetCore' of https://github.com/tidusjar/Ombi into DotNetCore
This commit is contained in:
commit
b69a5a2e14
88 changed files with 2053 additions and 417 deletions
|
@ -9,7 +9,6 @@ ____
|
|||
|
||||
[](https://patreon.com/tidusjar/Ombi)
|
||||
[](https://paypal.me/PlexRequestsNet)
|
||||
[](https://gratipay.com/Ombi/)
|
||||
|
||||
[](https://github.com/tidusjar/Ombi/issues/new) [](http://feathub.com/tidusjar/Ombi)
|
||||
|
||||
|
@ -100,10 +99,6 @@ You may need to install libwind8.
|
|||
# FAQ
|
||||
Do you have an issue or a question? if so check out our [FAQ](https://github.com/tidusjar/Ombi/wiki/FAQ)!
|
||||
|
||||
# Docker
|
||||
|
||||
Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile:
|
||||
|
||||
# Contributors
|
||||
|
||||
We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it!
|
||||
|
|
84
src/Ombi.Api.CouchPotato/CouchPotatoApi.cs
Normal file
84
src/Ombi.Api.CouchPotato/CouchPotatoApi.cs
Normal file
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Ombi.Api.CouchPotato.Models;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Api.CouchPotato
|
||||
{
|
||||
public class CouchPotatoApi : ICouchPotatoApi
|
||||
{
|
||||
public CouchPotatoApi(IApi api, ILogger<CouchPotatoApi> log)
|
||||
{
|
||||
_api = api;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
private readonly IApi _api;
|
||||
private readonly ILogger<CouchPotatoApi> _log;
|
||||
|
||||
public async Task<bool> AddMovie(string imdbid, string apiKey, string title, string baseUrl, string profileId = default(string))
|
||||
{
|
||||
var request = new Request($"/api/{apiKey}/movie.add", baseUrl, HttpMethod.Get);
|
||||
|
||||
request.AddQueryString("title", title);
|
||||
request.AddQueryString("identifier", imdbid);
|
||||
if (!string.IsNullOrEmpty(profileId))
|
||||
{
|
||||
request.AddQueryString("profile_id", profileId);
|
||||
}
|
||||
|
||||
var obj = await _api.Request<JObject>(request);
|
||||
|
||||
if (obj.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = (bool)obj["success"];
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(LoggingEvents.CouchPotatoApi, e, "Error calling AddMovie");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<CouchPotatoStatus> Status(string url, string apiKey)
|
||||
{
|
||||
var request = new Request($"api/{apiKey}/app.available/", url, HttpMethod.Get);
|
||||
return await _api.Request<CouchPotatoStatus>(request);
|
||||
}
|
||||
|
||||
public async Task<CouchPotatoProfiles> GetProfiles(string url, string apiKey)
|
||||
{
|
||||
var request = new Request($"api/{apiKey}/profile.list/", url, HttpMethod.Get);
|
||||
return await _api.Request<CouchPotatoProfiles>(request);
|
||||
}
|
||||
|
||||
public async Task<CouchPotatoMovies> GetMovies(string baseUrl, string apiKey, string[] status)
|
||||
{
|
||||
var request = new Request($"/api/{apiKey}/movie.list", baseUrl, HttpMethod.Get);
|
||||
|
||||
request.AddQueryString("status",string.Join(",", status));
|
||||
request.OnBeforeDeserialization = json =>
|
||||
{
|
||||
json.Replace("[]", "{}");
|
||||
};
|
||||
return await _api.Request<CouchPotatoMovies>(request);
|
||||
}
|
||||
|
||||
public async Task<CouchPotatoApiKey> GetApiKey(string baseUrl, string username, string password)
|
||||
{
|
||||
var request = new Request("getkey/",baseUrl, HttpMethod.Get);
|
||||
request.AddQueryString("u",username.CalcuateMd5Hash());
|
||||
request.AddQueryString("p",password.CalcuateMd5Hash());
|
||||
|
||||
return await _api.Request<CouchPotatoApiKey>(request);
|
||||
}
|
||||
}
|
||||
}
|
14
src/Ombi.Api.CouchPotato/ICouchPotatoApi.cs
Normal file
14
src/Ombi.Api.CouchPotato/ICouchPotatoApi.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Api.CouchPotato.Models;
|
||||
|
||||
namespace Ombi.Api.CouchPotato
|
||||
{
|
||||
public interface ICouchPotatoApi
|
||||
{
|
||||
Task<bool> AddMovie(string imdbid, string apiKey, string title, string baseUrl, string profileId = null);
|
||||
Task<CouchPotatoApiKey> GetApiKey(string baseUrl, string username, string password);
|
||||
Task<CouchPotatoMovies> GetMovies(string baseUrl, string apiKey, string[] status);
|
||||
Task<CouchPotatoProfiles> GetProfiles(string url, string apiKey);
|
||||
Task<CouchPotatoStatus> Status(string url, string apiKey);
|
||||
}
|
||||
}
|
12
src/Ombi.Api.CouchPotato/Models/CouchPotatoApiKey.cs
Normal file
12
src/Ombi.Api.CouchPotato/Models/CouchPotatoApiKey.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ombi.Api.CouchPotato.Models
|
||||
{
|
||||
public class CouchPotatoApiKey
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool success { get; set; }
|
||||
[JsonProperty("api_key")]
|
||||
public string ApiKey { get; set; }
|
||||
}
|
||||
}
|
99
src/Ombi.Api.CouchPotato/Models/CouchPotatoMovies.cs
Normal file
99
src/Ombi.Api.CouchPotato/Models/CouchPotatoMovies.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Api.CouchPotato.Models
|
||||
{
|
||||
public class CouchPotatoMovies
|
||||
{
|
||||
public List<Movie> movies { get; set; }
|
||||
public int total { get; set; }
|
||||
public bool success { get; set; }
|
||||
public bool empty { get; set; }
|
||||
}
|
||||
|
||||
public class Movie
|
||||
{
|
||||
public string _id { get; set; }
|
||||
public string _rev { get; set; }
|
||||
public string _t { get; set; }
|
||||
public object category_id { get; set; }
|
||||
public Files files { get; set; }
|
||||
public Identifiers identifiers { get; set; }
|
||||
public Info info { get; set; }
|
||||
public int last_edit { get; set; }
|
||||
public string profile_id { get; set; }
|
||||
public List<object> releases { get; set; }
|
||||
public string status { get; set; }
|
||||
public List<object> tags { get; set; }
|
||||
public string title { get; set; }
|
||||
public string type { get; set; }
|
||||
}
|
||||
public class CouchPotatoAdd
|
||||
{
|
||||
public Movie movie { get; set; }
|
||||
public bool success { get; set; }
|
||||
}
|
||||
|
||||
public class Rating
|
||||
{
|
||||
public List<string> imdb { get; set; }
|
||||
}
|
||||
|
||||
public class Images
|
||||
{
|
||||
public List<string> actors { get; set; }
|
||||
public List<string> backdrop { get; set; }
|
||||
public List<string> backdrop_original { get; set; }
|
||||
public List<object> banner { get; set; }
|
||||
public List<object> clear_art { get; set; }
|
||||
public List<object> disc_art { get; set; }
|
||||
public List<object> extra_fanart { get; set; }
|
||||
public List<object> extra_thumbs { get; set; }
|
||||
public List<object> landscape { get; set; }
|
||||
public List<object> logo { get; set; }
|
||||
public List<string> poster { get; set; }
|
||||
public List<string> poster_original { get; set; }
|
||||
}
|
||||
|
||||
public class Info
|
||||
{
|
||||
public List<string> actor_roles { get; set; }
|
||||
public List<string> actors { get; set; }
|
||||
public List<string> directors { get; set; }
|
||||
public List<string> genres { get; set; }
|
||||
public Images images { get; set; }
|
||||
public string imdb { get; set; }
|
||||
public string mpaa { get; set; }
|
||||
public string original_title { get; set; }
|
||||
public string plot { get; set; }
|
||||
public Rating rating { get; set; }
|
||||
public Release_Date release_date { get; set; }
|
||||
public string released { get; set; }
|
||||
public int runtime { get; set; }
|
||||
public string tagline { get; set; }
|
||||
public List<string> titles { get; set; }
|
||||
public int tmdb_id { get; set; }
|
||||
public string type { get; set; }
|
||||
public bool via_imdb { get; set; }
|
||||
public bool via_tmdb { get; set; }
|
||||
public List<string> writers { get; set; }
|
||||
public int year { get; set; }
|
||||
}
|
||||
|
||||
public class Release_Date
|
||||
{
|
||||
public bool bluray { get; set; }
|
||||
public int dvd { get; set; }
|
||||
public int expires { get; set; }
|
||||
public int theater { get; set; }
|
||||
}
|
||||
|
||||
public class Files
|
||||
{
|
||||
public List<string> image_poster { get; set; }
|
||||
}
|
||||
|
||||
public class Identifiers
|
||||
{
|
||||
public string imdb { get; set; }
|
||||
}
|
||||
}
|
29
src/Ombi.Api.CouchPotato/Models/CouchPotatoProfiles.cs
Normal file
29
src/Ombi.Api.CouchPotato/Models/CouchPotatoProfiles.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ombi.Api.CouchPotato.Models
|
||||
{
|
||||
public class ProfileList
|
||||
{
|
||||
public bool core { get; set; }
|
||||
public bool hide { get; set; }
|
||||
public string _rev { get; set; }
|
||||
public List<bool> finish { get; set; }
|
||||
public List<string> qualities { get; set; }
|
||||
public string _id { get; set; }
|
||||
public string _t { get; set; }
|
||||
public string label { get; set; }
|
||||
public int minimum_score { get; set; }
|
||||
public List<int> stop_after { get; set; }
|
||||
public List<object> wait_for { get; set; }
|
||||
public int order { get; set; }
|
||||
[JsonProperty(PropertyName = "3d")]
|
||||
public List<object> threeD { get; set; }
|
||||
}
|
||||
|
||||
public class CouchPotatoProfiles
|
||||
{
|
||||
public List<ProfileList> list { get; set; }
|
||||
public bool success { get; set; }
|
||||
}
|
||||
}
|
7
src/Ombi.Api.CouchPotato/Models/CouchPotatoStatus.cs
Normal file
7
src/Ombi.Api.CouchPotato/Models/CouchPotatoStatus.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ombi.Api.CouchPotato.Models
|
||||
{
|
||||
public class CouchPotatoStatus
|
||||
{
|
||||
public bool success { get; set; }
|
||||
}
|
||||
}
|
11
src/Ombi.Api.CouchPotato/Ombi.Api.CouchPotato.csproj
Normal file
11
src/Ombi.Api.CouchPotato/Ombi.Api.CouchPotato.csproj
Normal file
|
@ -0,0 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -52,6 +52,7 @@ namespace Ombi.Api
|
|||
var receivedString = await data.ReadAsStringAsync();
|
||||
if (request.ContentType == ContentType.Json)
|
||||
{
|
||||
request.OnBeforeDeserialization?.Invoke(receivedString);
|
||||
return JsonConvert.DeserializeObject<T>(receivedString, Settings);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -25,6 +25,8 @@ namespace Ombi.Api
|
|||
public string BaseUrl { get; }
|
||||
public HttpMethod HttpMethod { get; }
|
||||
|
||||
public Action<string> OnBeforeDeserialization { get; set; }
|
||||
|
||||
private string FullUrl
|
||||
{
|
||||
get
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace Ombi.Core.Engine
|
|||
RequestType = RequestType.Movie,
|
||||
Overview = movieInfo.Overview,
|
||||
ImdbId = movieInfo.ImdbId,
|
||||
PosterPath = movieInfo.PosterPath,
|
||||
PosterPath = movieInfo.PosterPath.TrimStart('/'),
|
||||
Title = movieInfo.Title,
|
||||
ReleaseDate = !string.IsNullOrEmpty(movieInfo.ReleaseDate)
|
||||
? DateTime.Parse(movieInfo.ReleaseDate)
|
||||
|
|
|
@ -10,9 +10,7 @@ using System.Linq;
|
|||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using StackExchange.Profiling;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Ombi.Api.Trakt;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Helpers;
|
||||
|
||||
|
@ -54,24 +52,16 @@ namespace Ombi.Core.Engine
|
|||
/// <param name="search">The search.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
|
||||
{
|
||||
using (MiniProfiler.Current.Step("Starting Movie Search Engine"))
|
||||
using (MiniProfiler.Current.Step("Searching Movie"))
|
||||
{
|
||||
var result = await MovieApi.SearchMovie(search);
|
||||
|
||||
using (MiniProfiler.Current.Step("Fin API, Transforming"))
|
||||
{
|
||||
if (result != null)
|
||||
{
|
||||
Logger.LogDebug("Search Result: {result}", result);
|
||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets popular movies.
|
||||
|
|
|
@ -15,11 +15,7 @@ using System.Linq;
|
|||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
using Ombi.Store.Entities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Helpers;
|
||||
|
@ -117,11 +113,7 @@ namespace Ombi.Core.Engine
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
var existingRequests = await GetTvRequests();
|
||||
var plexSettings = await PlexSettings.GetSettingsAsync();
|
||||
var embySettings = await EmbySettings.GetSettingsAsync();
|
||||
return await ProcessResult(mapped, existingRequests, plexSettings, embySettings);
|
||||
return await ProcessResult(mapped);
|
||||
}
|
||||
|
||||
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
|
||||
|
@ -189,127 +181,21 @@ namespace Ombi.Core.Engine
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
|
||||
{
|
||||
var existingRequests = await GetTvRequests();
|
||||
|
||||
var plexSettings = await PlexSettings.GetSettingsAsync();
|
||||
var embySettings = await EmbySettings.GetSettingsAsync();
|
||||
|
||||
var retVal = new List<SearchTvShowViewModel>();
|
||||
foreach (var tvMazeSearch in items)
|
||||
{
|
||||
var viewT = Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
|
||||
retVal.Add(await ProcessResult(viewT, existingRequests, plexSettings, embySettings));
|
||||
retVal.Add(await ProcessResult(viewT));
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item, Dictionary<int, TvRequests> existingRequests, PlexSettings plexSettings, EmbySettings embySettings)
|
||||
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item)
|
||||
{
|
||||
if (embySettings.Enable)
|
||||
{
|
||||
var content = await EmbyContentRepo.Get(item.Id.ToString());
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
item.Available = true;
|
||||
}
|
||||
|
||||
// Let's go through the episodes now
|
||||
if (item.SeasonRequests.Any())
|
||||
{
|
||||
var allEpisodes = EmbyContentRepo.GetAllEpisodes().Include(x => x.Series);
|
||||
foreach (var season in item.SeasonRequests)
|
||||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && item.Id.ToString() == x.Series.ProviderId);
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (plexSettings.Enable)
|
||||
{
|
||||
var content = await PlexContentRepo.Get(item.Id.ToString());
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
item.Available = true;
|
||||
item.PlexUrl = content.Url;
|
||||
}
|
||||
// Let's go through the episodes now
|
||||
if (item.SeasonRequests.Any())
|
||||
{
|
||||
var allEpisodes = PlexContentRepo.GetAllEpisodes();
|
||||
foreach (var season in item.SeasonRequests)
|
||||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && x.Series.ProviderId == item.Id.ToString());
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.SeasonRequests.Any() && item.SeasonRequests.All(x => x.Episodes.All(e => e.Approved)))
|
||||
{
|
||||
item.FullyAvailable = true;
|
||||
}
|
||||
|
||||
if (item.Id > 0)
|
||||
{
|
||||
var tvdbid = item.Id;
|
||||
if (existingRequests.ContainsKey(tvdbid))
|
||||
{
|
||||
var existingRequest = existingRequests[tvdbid];
|
||||
|
||||
item.Requested = true;
|
||||
item.Approved = existingRequest.ChildRequests.Any(x => x.Approved);
|
||||
|
||||
// Let's modify the seasonsrequested to reflect what we have requested...
|
||||
foreach (var season in item.SeasonRequests)
|
||||
{
|
||||
foreach (var existingRequestChildRequest in existingRequest.ChildRequests)
|
||||
{
|
||||
// Find the existing request season
|
||||
var existingSeason =
|
||||
existingRequestChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
|
||||
if (existingSeason == null) continue;
|
||||
|
||||
foreach (var ep in existingSeason.Episodes)
|
||||
{
|
||||
// Find the episode from what we are searching
|
||||
var episodeSearching = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber);
|
||||
if (episodeSearching == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
episodeSearching.Requested = true;
|
||||
episodeSearching.Available = ep.Available;
|
||||
episodeSearching.Approved = ep.Season.ChildRequest.Approved;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO CHECK SONARR/RADARR
|
||||
//if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid))
|
||||
// // compare to the sonarr/sickrage db
|
||||
//{
|
||||
// item.Requested = true;
|
||||
//}
|
||||
}
|
||||
item.CustomId = item.Id.ToString();
|
||||
await RunSearchRules(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Models.Search
|
||||
{
|
||||
|
@ -22,5 +23,8 @@ namespace Ombi.Core.Models.Search
|
|||
public string Trailer { get; set; }
|
||||
public string Homepage { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public int RootPathOverride { get; set; }
|
||||
public int QualityOverride { get; set; }
|
||||
public override RequestType Type => RequestType.Movie;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using Ombi.Core.Models.Requests;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
using System.Collections.Generic;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Models.Search
|
||||
{
|
||||
|
@ -54,5 +55,7 @@ namespace Ombi.Core.Models.Search
|
|||
/// This is where we have EVERY Episode for that series
|
||||
/// </summary>
|
||||
public bool FullyAvailable { get; set; }
|
||||
|
||||
public override RequestType Type => RequestType.TvShow;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Models.Search
|
||||
{
|
||||
public class SearchViewModel
|
||||
public abstract class SearchViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public bool Approved { get; set; }
|
||||
|
@ -10,6 +11,7 @@ namespace Ombi.Core.Models.Search
|
|||
public bool Available { get; set; }
|
||||
public string PlexUrl { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public abstract RequestType Type { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models.Requests;
|
||||
|
||||
namespace Ombi.Core.Rule.Interfaces
|
||||
{
|
||||
public interface IRules<T> where T : new()
|
||||
public interface IRules<T>
|
||||
{
|
||||
Task<RuleResult> Execute(T obj);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ namespace Ombi.Core.Rule
|
|||
}
|
||||
|
||||
|
||||
private void GetTypes<T>(IServiceProvider provider, Assembly ass, string baseSearchType, List<IRules<T>> ruleList) where T : new()
|
||||
private void GetTypes<T>(IServiceProvider provider, Assembly ass, string baseSearchType, List<IRules<T>> ruleList)
|
||||
{
|
||||
foreach (var ti in ass.DefinedTypes)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Search
|
||||
|
@ -20,6 +23,28 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
if (item != null)
|
||||
{
|
||||
obj.Available = true;
|
||||
|
||||
if (obj.Type == RequestType.TvShow)
|
||||
{
|
||||
var searchResult = (SearchTvShowViewModel)obj;
|
||||
// Let's go through the episodes now
|
||||
if (searchResult.SeasonRequests.Any())
|
||||
{
|
||||
var allEpisodes = EmbyContentRepository.GetAllEpisodes().Include(x => x.Series);
|
||||
foreach (var season in searchResult.SeasonRequests)
|
||||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && item.ProviderId.ToString() == x.Series.ProviderId);
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
|
@ -20,6 +21,8 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
private ITvRequestRepository Tv { get; }
|
||||
|
||||
public Task<RuleResult> Execute(SearchViewModel obj)
|
||||
{
|
||||
if (obj.Type == RequestType.Movie)
|
||||
{
|
||||
var movieRequests = Movie.GetRequest(obj.Id);
|
||||
if (movieRequests != null) // Do we already have a request for this?
|
||||
|
@ -31,18 +34,63 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
|
||||
return Task.FromResult(Success());
|
||||
}
|
||||
return Task.FromResult(Success());
|
||||
}
|
||||
else
|
||||
{
|
||||
//var tvRequests = Tv.GetRequest(obj.Id);
|
||||
//if (tvRequests != null) // Do we already have a request for this?
|
||||
//{
|
||||
|
||||
// obj.Requested = true;
|
||||
// obj.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
|
||||
// obj.Available = tvRequests.ChildRequests.Any(x => x.Available);
|
||||
|
||||
// return Task.FromResult(Success());
|
||||
//}
|
||||
|
||||
var request = (SearchTvShowViewModel) obj;
|
||||
var tvRequests = Tv.GetRequest(obj.Id);
|
||||
if (tvRequests != null) // Do we already have a request for this?
|
||||
{
|
||||
|
||||
obj.Requested = true;
|
||||
obj.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
|
||||
obj.Available = tvRequests.ChildRequests.Any(x => x.Available);
|
||||
request.Requested = true;
|
||||
request.Approved = tvRequests.ChildRequests.Any(x => x.Approved);
|
||||
|
||||
// Let's modify the seasonsrequested to reflect what we have requested...
|
||||
foreach (var season in request.SeasonRequests)
|
||||
{
|
||||
foreach (var existingRequestChildRequest in tvRequests.ChildRequests)
|
||||
{
|
||||
// Find the existing request season
|
||||
var existingSeason =
|
||||
existingRequestChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == season.SeasonNumber);
|
||||
if (existingSeason == null) continue;
|
||||
|
||||
foreach (var ep in existingSeason.Episodes)
|
||||
{
|
||||
// Find the episode from what we are searching
|
||||
var episodeSearching = season.Episodes.FirstOrDefault(x => x.EpisodeNumber == ep.EpisodeNumber);
|
||||
if (episodeSearching == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
episodeSearching.Requested = true;
|
||||
episodeSearching.Available = ep.Available;
|
||||
episodeSearching.Approved = ep.Season.ChildRequest.Approved;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.All(e => e.Approved)))
|
||||
{
|
||||
request.FullyAvailable = true;
|
||||
}
|
||||
|
||||
|
||||
return Task.FromResult(Success());
|
||||
}
|
||||
return Task.FromResult(Success());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Search
|
||||
|
@ -22,6 +25,29 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
obj.Available = true;
|
||||
obj.PlexUrl = item.Url;
|
||||
obj.Quality = item.Quality;
|
||||
|
||||
if (obj.Type == RequestType.TvShow)
|
||||
{
|
||||
var search = (SearchTvShowViewModel)obj;
|
||||
// Let's go through the episodes now
|
||||
if (search.SeasonRequests.Any())
|
||||
{
|
||||
var allEpisodes = PlexContentRepository.GetAllEpisodes();
|
||||
foreach (var season in search.SeasonRequests)
|
||||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.ProviderId == item.ProviderId.ToString());
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Search
|
||||
{
|
||||
|
@ -17,14 +18,17 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
private readonly IOmbiContext _ctx;
|
||||
|
||||
public async Task<RuleResult> Execute(SearchViewModel obj)
|
||||
{
|
||||
if (obj.Type == RequestType.Movie)
|
||||
{
|
||||
// Check if it's in Radarr
|
||||
var result = await _ctx.RadarrCache.FirstOrDefaultAsync(x => x.TheMovieDbId == obj.Id);
|
||||
if (result != null)
|
||||
{
|
||||
obj.Approved = true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
|
||||
obj.Approved =
|
||||
true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
|
||||
}
|
||||
}
|
||||
|
||||
return Success();
|
||||
}
|
||||
}
|
||||
|
|
34
src/Ombi.Core/Rule/Rules/Search/SonarrCacheRule.cs
Normal file
34
src/Ombi.Core/Rule/Rules/Search/SonarrCacheRule.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Search
|
||||
{
|
||||
public class SonarrCacheRule : BaseSearchRule, IRules<SearchViewModel>
|
||||
{
|
||||
public SonarrCacheRule(IOmbiContext ctx)
|
||||
{
|
||||
_ctx = ctx;
|
||||
}
|
||||
|
||||
private readonly IOmbiContext _ctx;
|
||||
|
||||
public async Task<RuleResult> Execute(SearchViewModel obj)
|
||||
{
|
||||
if (obj.Type == RequestType.TvShow)
|
||||
{
|
||||
// Check if it's in Radarr
|
||||
var result = await _ctx.SonarrCache.FirstOrDefaultAsync(x => x.TvDbId == obj.Id);
|
||||
if (result != null)
|
||||
{
|
||||
obj.Approved =
|
||||
true; // It's in radarr so it's approved... Maybe have a new property called "Processing" or something?
|
||||
}
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,6 @@ namespace Ombi.Core
|
|||
{
|
||||
public interface IMovieSender
|
||||
{
|
||||
Task<MovieSenderResult> Send(MovieRequests model, string qualityId = "");
|
||||
Task<MovieSenderResult> Send(MovieRequests model);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Ombi.Core.Settings;
|
||||
using System.Linq;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -21,7 +22,7 @@ namespace Ombi.Core
|
|||
private IRadarrApi RadarrApi { get; }
|
||||
private ILogger<MovieSender> Log { get; }
|
||||
|
||||
public async Task<MovieSenderResult> Send(MovieRequests model, string qualityId = "")
|
||||
public async Task<MovieSenderResult> Send(MovieRequests model)
|
||||
{
|
||||
//var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
|
||||
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
|
||||
|
@ -39,7 +40,7 @@ namespace Ombi.Core
|
|||
|
||||
if (radarrSettings.Enabled)
|
||||
{
|
||||
return await SendToRadarr(model, radarrSettings, qualityId);
|
||||
return await SendToRadarr(model, radarrSettings);
|
||||
}
|
||||
|
||||
return new MovieSenderResult
|
||||
|
@ -49,22 +50,16 @@ namespace Ombi.Core
|
|||
};
|
||||
}
|
||||
|
||||
private async Task<MovieSenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings, string qualityId)
|
||||
private async Task<MovieSenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings)
|
||||
{
|
||||
var qualityProfile = 0;
|
||||
if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality
|
||||
var qualityToUse = int.Parse(settings.DefaultQualityProfile);
|
||||
if (model.QualityOverride > 0)
|
||||
{
|
||||
int.TryParse(qualityId, out qualityProfile);
|
||||
qualityToUse = model.QualityOverride;
|
||||
}
|
||||
|
||||
if (qualityProfile <= 0)
|
||||
{
|
||||
int.TryParse(settings.DefaultQualityProfile, out qualityProfile);
|
||||
}
|
||||
|
||||
//var rootFolderPath = model.RootFolderSelected <= 0 ? settings.FullRootPath : GetRootPath(model.RootFolderSelected, settings);
|
||||
var rootFolderPath = settings.DefaultRootPath; // TODO Allow changing in the UI
|
||||
var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, qualityProfile, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, settings.MinimumAvailability);
|
||||
var rootFolderPath = model.RootPathOverride <= 0 ? settings.DefaultRootPath : await RadarrRootPath(model.RootPathOverride, settings);
|
||||
var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year, qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly, settings.MinimumAvailability);
|
||||
|
||||
if (!string.IsNullOrEmpty(result.Error?.message))
|
||||
{
|
||||
|
@ -77,5 +72,12 @@ namespace Ombi.Core
|
|||
}
|
||||
return new MovieSenderResult { Success = true, MovieSent = false };
|
||||
}
|
||||
|
||||
private async Task<string> RadarrRootPath(int overrideId, RadarrSettings settings)
|
||||
{
|
||||
var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
|
||||
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
|
||||
return selectedPath.path;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ using Ombi.Api.Sonarr.Models;
|
|||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
namespace Ombi.Core.Senders
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Security.Principal;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
@ -31,6 +33,7 @@ using Ombi.Store.Repository;
|
|||
using Ombi.Notifications.Agents;
|
||||
using Ombi.Schedule.Jobs.Radarr;
|
||||
using Ombi.Api;
|
||||
using Ombi.Api.CouchPotato;
|
||||
using Ombi.Api.FanartTv;
|
||||
using Ombi.Api.Mattermost;
|
||||
using Ombi.Api.Pushbullet;
|
||||
|
@ -42,6 +45,7 @@ using Ombi.Core.Senders;
|
|||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Schedule.Jobs.Sonarr;
|
||||
using Ombi.Schedule.Ombi;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
using PlexContentCacher = Ombi.Schedule.Jobs.Plex.PlexContentCacher;
|
||||
|
@ -94,10 +98,10 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IFanartTvApi, FanartTvApi>();
|
||||
services.AddTransient<IPushoverApi, PushoverApi>();
|
||||
services.AddTransient<IMattermostApi, MattermostApi>();
|
||||
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
|
||||
}
|
||||
|
||||
public static void RegisterStore(this IServiceCollection services)
|
||||
{
|
||||
public static void RegisterStore(this IServiceCollection services) {
|
||||
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
|
||||
|
||||
services.AddScoped<IOmbiContext, OmbiContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
|
||||
|
@ -142,6 +146,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IPlexAvailabilityChecker, PlexAvailabilityChecker>();
|
||||
services.AddTransient<IJobSetup, JobSetup>();
|
||||
services.AddTransient<IRadarrCacher, RadarrCacher>();
|
||||
services.AddTransient<ISonarrCacher, SonarrCacher>();
|
||||
services.AddTransient<IOmbiAutomaticUpdater, OmbiAutomaticUpdater>();
|
||||
services.AddTransient<IPlexUserImporter, PlexUserImporter>();
|
||||
services.AddTransient<IEmbyUserImporter, EmbyUserImporter>();
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Api.FanartTv\Ombi.Api.FanartTv.csproj" />
|
||||
|
|
|
@ -16,5 +16,7 @@ namespace Ombi.Helpers
|
|||
public const string TopRatedMovies = nameof(TopRatedMovies);
|
||||
public const string UpcomingMovies = nameof(UpcomingMovies);
|
||||
public const string NowPlayingMovies = nameof(NowPlayingMovies);
|
||||
public const string RadarrRootProfiles = nameof(RadarrRootProfiles);
|
||||
public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,15 @@ namespace Ombi.Helpers
|
|||
|
||||
public static EventId Api => new EventId(1000);
|
||||
public static EventId RadarrApi => new EventId(1001);
|
||||
public static EventId CouchPotatoApi => new EventId(1002);
|
||||
|
||||
public static EventId Cacher => new EventId(2000);
|
||||
public static EventId RadarrCacher => new EventId(2001);
|
||||
public static EventId PlexEpisodeCacher => new EventId(2001);
|
||||
public static EventId EmbyContentCacher => new EventId(2002);
|
||||
public static EventId PlexUserImporter => new EventId(2003);
|
||||
public static EventId EmbyUserImporter => new EventId(2004);
|
||||
public static EventId PlexEpisodeCacher => new EventId(2002);
|
||||
public static EventId EmbyContentCacher => new EventId(2003);
|
||||
public static EventId PlexUserImporter => new EventId(2004);
|
||||
public static EventId EmbyUserImporter => new EventId(2005);
|
||||
public static EventId SonarrCacher => new EventId(2006);
|
||||
|
||||
public static EventId MovieSender => new EventId(3000);
|
||||
|
||||
|
|
13
src/Ombi.Helpers/StoragePathSingleton.cs
Normal file
13
src/Ombi.Helpers/StoragePathSingleton.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Ombi.Helpers
|
||||
{
|
||||
public class StoragePathSingleton
|
||||
{
|
||||
private static StoragePathSingleton instance;
|
||||
|
||||
private StoragePathSingleton() { }
|
||||
|
||||
public static StoragePathSingleton Instance => instance ?? (instance = new StoragePathSingleton());
|
||||
|
||||
public string StoragePath { get; set; }
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ namespace Ombi.Notifications.Agents
|
|||
try
|
||||
{
|
||||
var a = settings.Token;
|
||||
var b = settings.WebookId;
|
||||
var b = settings.WebHookId;
|
||||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
|
@ -164,7 +164,7 @@ namespace Ombi.Notifications.Agents
|
|||
};
|
||||
}
|
||||
|
||||
await Api.SendMessage(discordBody, settings.WebookId, settings.Token);
|
||||
await Api.SendMessage(discordBody, settings.WebHookId, settings.Token);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@ using Ombi.Schedule.Jobs;
|
|||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Schedule.Jobs.Radarr;
|
||||
using Ombi.Schedule.Jobs.Sonarr;
|
||||
using Ombi.Schedule.Ombi;
|
||||
|
||||
namespace Ombi.Schedule
|
||||
|
@ -11,7 +12,7 @@ namespace Ombi.Schedule
|
|||
{
|
||||
public JobSetup(IPlexContentCacher plexContentCacher, IRadarrCacher radarrCacher,
|
||||
IOmbiAutomaticUpdater updater, IEmbyContentCacher embyCacher, IPlexUserImporter userImporter,
|
||||
IEmbyUserImporter embyUserImporter)
|
||||
IEmbyUserImporter embyUserImporter, ISonarrCacher cache)
|
||||
{
|
||||
PlexContentCacher = plexContentCacher;
|
||||
RadarrCacher = radarrCacher;
|
||||
|
@ -19,6 +20,7 @@ namespace Ombi.Schedule
|
|||
EmbyContentCacher = embyCacher;
|
||||
PlexUserImporter = userImporter;
|
||||
EmbyUserImporter = embyUserImporter;
|
||||
SonarrCacher = cache;
|
||||
}
|
||||
|
||||
private IPlexContentCacher PlexContentCacher { get; }
|
||||
|
@ -27,17 +29,17 @@ namespace Ombi.Schedule
|
|||
private IPlexUserImporter PlexUserImporter { get; }
|
||||
private IEmbyContentCacher EmbyContentCacher { get; }
|
||||
private IEmbyUserImporter EmbyUserImporter { get; }
|
||||
private ISonarrCacher SonarrCacher { get; }
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
RecurringJob.AddOrUpdate(() => PlexContentCacher.CacheContent(), Cron.Hourly(20));
|
||||
RecurringJob.AddOrUpdate(() => EmbyContentCacher.Start(), Cron.Hourly(5));
|
||||
RecurringJob.AddOrUpdate(() => RadarrCacher.CacheContent(), Cron.Hourly(10));
|
||||
RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), Cron.Daily(1));
|
||||
RecurringJob.AddOrUpdate(() => RadarrCacher.CacheContent(), Cron.Hourly(15));
|
||||
RecurringJob.AddOrUpdate(() => PlexUserImporter.Start(), Cron.Daily(5));
|
||||
RecurringJob.AddOrUpdate(() => EmbyUserImporter.Start(), Cron.Daily);
|
||||
RecurringJob.AddOrUpdate(() => Updater.Update(null), Cron.Daily(3));
|
||||
|
||||
//BackgroundJob.Enqueue(() => PlexUserImporter.Start());
|
||||
RecurringJob.AddOrUpdate(() => Updater.Update(null), Cron.HourInterval(6));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
@ -28,8 +29,11 @@ namespace Ombi.Schedule.Jobs.Radarr
|
|||
private ILogger<RadarrCacher> Logger { get; }
|
||||
private readonly IOmbiContext _ctx;
|
||||
|
||||
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
|
||||
|
||||
public async Task CacheContent()
|
||||
{
|
||||
await SemaphoreSlim.WaitAsync();
|
||||
try
|
||||
{
|
||||
var settings = RadarrSettings.GetSettings();
|
||||
|
@ -70,6 +74,10 @@ namespace Ombi.Schedule.Jobs.Radarr
|
|||
{
|
||||
Logger.LogInformation(LoggingEvents.RadarrCacher, "Radarr is not setup, cannot cache episodes");
|
||||
}
|
||||
finally
|
||||
{
|
||||
SemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RadarrCache>> GetCachedContent()
|
||||
|
|
9
src/Ombi.Schedule/Jobs/Sonarr/ISonarrCacher.cs
Normal file
9
src/Ombi.Schedule/Jobs/Sonarr/ISonarrCacher.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Sonarr
|
||||
{
|
||||
public interface ISonarrCacher
|
||||
{
|
||||
Task Start();
|
||||
}
|
||||
}
|
117
src/Ombi.Schedule/Jobs/Sonarr/SonarrCacher.cs
Normal file
117
src/Ombi.Schedule/Jobs/Sonarr/SonarrCacher.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Sonarr;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Sonarr
|
||||
{
|
||||
public class SonarrCacher : ISonarrCacher
|
||||
{
|
||||
public SonarrCacher(ISettingsService<SonarrSettings> s, ISonarrApi api, ILogger<SonarrCacher> l, IOmbiContext ctx)
|
||||
{
|
||||
_settings = s;
|
||||
_api = api;
|
||||
_log = l;
|
||||
}
|
||||
|
||||
private readonly ISettingsService<SonarrSettings> _settings;
|
||||
private readonly ISonarrApi _api;
|
||||
private readonly ILogger<SonarrCacher> _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 series = await _api.GetSeries(settings.ApiKey, settings.FullUri);
|
||||
if (series != null)
|
||||
{
|
||||
var ids = series.Select(x => x.tvdbId);
|
||||
|
||||
await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache");
|
||||
var entites = ids.Select(id => new SonarrCache {TvDbId = id}).ToList();
|
||||
|
||||
await _ctx.SonarrCache.AddRangeAsync(entites);
|
||||
await _ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(LoggingEvents.SonarrCacher, e, "Exception when trying to cache Sonarr");
|
||||
}
|
||||
finally
|
||||
{
|
||||
SemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//public void Queued()
|
||||
//{
|
||||
// var settings = SonarrSettings.GetSettings();
|
||||
// if (settings.Enabled)
|
||||
// {
|
||||
// Job.SetRunning(true, JobNames.SonarrCacher);
|
||||
// try
|
||||
// {
|
||||
// var series = SonarrApi.GetSeries(settings.ApiKey, settings.FullUri);
|
||||
// if (series != null)
|
||||
// {
|
||||
// Cache.Set(CacheKeys.SonarrQueued, series, CacheKeys.TimeFrameMinutes.SchedulerCaching);
|
||||
// }
|
||||
// }
|
||||
// catch (System.Exception ex)
|
||||
// {
|
||||
// Log.Error(ex, "Failed caching queued items from Sonarr");
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// Job.Record(JobNames.SonarrCacher);
|
||||
// Job.SetRunning(false, JobNames.SonarrCacher);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//// we do not want to set here...
|
||||
//public IEnumerable<SonarrCachedResult> QueuedIds()
|
||||
//{
|
||||
// var result = new List<SonarrCachedResult>();
|
||||
|
||||
// var series = Cache.Get<List<Series>>(CacheKeys.SonarrQueued);
|
||||
// if (series != null)
|
||||
// {
|
||||
// foreach (var s in series)
|
||||
// {
|
||||
// var cached = new SonarrCachedResult { TvdbId = s.tvdbId };
|
||||
// foreach (var season in s.seasons)
|
||||
// {
|
||||
// cached.Seasons.Add(new SonarrSeasons
|
||||
// {
|
||||
// SeasonNumber = season.seasonNumber,
|
||||
// Monitored = season.monitored
|
||||
// });
|
||||
// }
|
||||
|
||||
// result.Add(cached);
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,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.Sonarr\Ombi.Api.Sonarr.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
||||
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
13
src/Ombi.Settings/Settings/Models/External/CouchPotatoSettings.cs
vendored
Normal file
13
src/Ombi.Settings/Settings/Models/External/CouchPotatoSettings.cs
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Ombi.Core.Settings.Models.External;
|
||||
|
||||
namespace Ombi.Settings.Settings.Models.External
|
||||
{
|
||||
public class CouchPotatoSettings : ExternalSettings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public string DefaultProfileId { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
|
||||
namespace Ombi.Core.Settings.Models.External
|
||||
{
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Core.Settings.Models.External
|
||||
namespace Ombi.Settings.Settings.Models.External
|
||||
{
|
||||
public abstract class ExternalSettings : Ombi.Settings.Settings.Models.Settings
|
||||
public abstract class ExternalSettings : Models.Settings
|
||||
{
|
||||
public bool Ssl { get; set; }
|
||||
public string SubDir { get; set; }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
|
||||
namespace Ombi.Core.Settings.Models.External
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Ombi.Core.Settings.Models.External
|
||||
namespace Ombi.Settings.Settings.Models.External
|
||||
{
|
||||
public class SonarrSettings : ExternalSettings
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Ombi.Settings.Settings.Models.Notifications
|
|||
public string Username { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string WebookId => SplitWebUrl(4);
|
||||
public string WebHookId => SplitWebUrl(4);
|
||||
|
||||
[JsonIgnore]
|
||||
public string Token => SplitWebUrl(5);
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace Ombi.Store.Context
|
|||
DbSet<MovieIssues> MovieIssues { get; set; }
|
||||
DbSet<TvIssues> TvIssues { get; set; }
|
||||
DbSet<Tokens> Tokens { get; set; }
|
||||
DbSet<SonarrCache> SonarrCache { get; set; }
|
||||
EntityEntry Update(object entity);
|
||||
EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -18,8 +19,6 @@ namespace Ombi.Store.Context
|
|||
_created = true;
|
||||
Database.Migrate();
|
||||
|
||||
// Add the notifcation templates
|
||||
|
||||
}
|
||||
|
||||
public DbSet<NotificationTemplates> NotificationTemplates { get; set; }
|
||||
|
@ -38,12 +37,14 @@ namespace Ombi.Store.Context
|
|||
|
||||
public DbSet<Audit> Audit { get; set; }
|
||||
public DbSet<Tokens> Tokens { get; set; }
|
||||
public DbSet<SonarrCache> SonarrCache { get; set; }
|
||||
|
||||
public DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseSqlite("Data Source=Ombi.db");
|
||||
var i = StoragePathSingleton.Instance;
|
||||
optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath,"Ombi.db")}");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
|
|
|
@ -11,5 +11,8 @@ namespace Ombi.Store.Entities.Requests
|
|||
public int? IssueId { get; set; }
|
||||
[ForeignKey(nameof(IssueId))]
|
||||
public List<MovieIssues> Issues { get; set; }
|
||||
|
||||
public int RootPathOverride { get; set; }
|
||||
public int QualityOverride { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
10
src/Ombi.Store/Entities/SonarrCache.cs
Normal file
10
src/Ombi.Store/Entities/SonarrCache.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities
|
||||
{
|
||||
[Table("SonarrCache")]
|
||||
public class SonarrCache : Entity
|
||||
{
|
||||
public int TvDbId { get; set; }
|
||||
}
|
||||
}
|
747
src/Ombi.Store/Migrations/20171002113357_SonarrCacher.Designer.cs
generated
Normal file
747
src/Ombi.Store/Migrations/20171002113357_SonarrCacher.Designer.cs
generated
Normal file
|
@ -0,0 +1,747 @@
|
|||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
using System;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
[DbContext(typeof(OmbiContext))]
|
||||
[Migration("20171002113357_SonarrCacher")]
|
||||
partial class SonarrCacher
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApplicationConfiguration");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AuditArea");
|
||||
|
||||
b.Property<int>("AuditType");
|
||||
|
||||
b.Property<DateTime>("DateTime");
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<string>("User");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Audit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("EmbyId")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("EmbyContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("EmbyId");
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<string>("ParentId");
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("EmbyEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Content");
|
||||
|
||||
b.Property<string>("SettingsName");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("GlobalSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("Agent");
|
||||
|
||||
b.Property<bool>("Enabled");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<int>("NotificationType");
|
||||
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("NotificationTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("Alias");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<DateTime?>("LastLoggedIn");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<string>("ProviderUserId");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<int>("UserType");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<int>("Key");
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<string>("Quality");
|
||||
|
||||
b.Property<string>("ReleaseYear");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("Url");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlexContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<int>("GrandparentKey");
|
||||
|
||||
b.Property<int>("Key");
|
||||
|
||||
b.Property<int>("ParentKey");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GrandparentKey");
|
||||
|
||||
b.ToTable("PlexEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("ParentKey");
|
||||
|
||||
b.Property<int>("PlexContentId");
|
||||
|
||||
b.Property<int>("SeasonKey");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PlexContentId");
|
||||
|
||||
b.ToTable("PlexSeasonsContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RadarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<bool?>("Denied");
|
||||
|
||||
b.Property<string>("DeniedReason");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<int>("ParentRequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentRequestId");
|
||||
|
||||
b.HasIndex("RequestedUserId");
|
||||
|
||||
b.ToTable("ChildRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<int>("MovieId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueId");
|
||||
|
||||
b.HasIndex("MovieId");
|
||||
|
||||
b.ToTable("MovieIssues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<bool?>("Denied");
|
||||
|
||||
b.Property<string>("DeniedReason");
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Overview");
|
||||
|
||||
b.Property<string>("PosterPath");
|
||||
|
||||
b.Property<int>("QualityOverride");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
||||
b.Property<int>("RootPathOverride");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RequestedUserId");
|
||||
|
||||
b.ToTable("MovieRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
|
||||
b.Property<int>("TvId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueId");
|
||||
|
||||
b.HasIndex("TvId");
|
||||
|
||||
b.ToTable("TvIssues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<string>("Overview");
|
||||
|
||||
b.Property<string>("PosterPath");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate");
|
||||
|
||||
b.Property<int?>("RootFolder");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TvRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Token");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Tokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AirDate");
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<bool>("Requested");
|
||||
|
||||
b.Property<int>("SeasonId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("Url");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SeasonId");
|
||||
|
||||
b.ToTable("EpisodeRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("ChildRequestId");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChildRequestId");
|
||||
|
||||
b.ToTable("SeasonRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ParentId")
|
||||
.HasPrincipalKey("EmbyId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("GrandparentKey")
|
||||
.HasPrincipalKey("Key")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexContent")
|
||||
.WithMany("Seasons")
|
||||
.HasForeignKey("PlexContentId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
|
||||
.WithMany("ChildRequests")
|
||||
.HasForeignKey("ParentRequestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
|
||||
.WithMany("Issues")
|
||||
.HasForeignKey("IssueId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", "Movie")
|
||||
.WithMany()
|
||||
.HasForeignKey("MovieId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
|
||||
.WithMany("Issues")
|
||||
.HasForeignKey("IssueId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "Child")
|
||||
.WithMany()
|
||||
.HasForeignKey("TvId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("SeasonId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
|
||||
.WithMany("SeasonRequests")
|
||||
.HasForeignKey("ChildRequestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
53
src/Ombi.Store/Migrations/20171002113357_SonarrCacher.cs
Normal file
53
src/Ombi.Store/Migrations/20171002113357_SonarrCacher.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
public partial class SonarrCacher : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "QualityOverride",
|
||||
table: "MovieRequests",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "RootPathOverride",
|
||||
table: "MovieRequests",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SonarrCache",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
TvDbId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SonarrCache", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "SonarrCache");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "QualityOverride",
|
||||
table: "MovieRequests");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RootPathOverride",
|
||||
table: "MovieRequests");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -460,6 +460,8 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<string>("PosterPath");
|
||||
|
||||
b.Property<int>("QualityOverride");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
@ -468,6 +470,8 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
||||
b.Property<int>("RootPathOverride");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
@ -529,6 +533,18 @@ namespace Ombi.Store.Migrations
|
|||
b.ToTable("TvRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
|
@ -45,8 +45,8 @@ namespace Ombi.TheMovieDbApi.Models
|
|||
public ProductionCompanies[] production_companies { get; set; }
|
||||
public ProductionCountries[] production_countries { get; set; }
|
||||
public string release_date { get; set; }
|
||||
public int revenue { get; set; }
|
||||
public int runtime { get; set; }
|
||||
public float revenue { get; set; }
|
||||
public float runtime { get; set; }
|
||||
public SpokenLanguages[] spoken_languages { get; set; }
|
||||
public string status { get; set; }
|
||||
public string tagline { get; set; }
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
public float Popularity { get; set; }
|
||||
public string PosterPath { get; set; }
|
||||
public string ReleaseDate { get; set; }
|
||||
public int Revenue { get; set; }
|
||||
public int Runtime { get; set; }
|
||||
public float Revenue { get; set; }
|
||||
public float Runtime { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string Tagline { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
|
|
@ -80,6 +80,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Pushover", "Ombi.A
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Schedule.Tests", "Ombi.Schedule.Tests\Ombi.Schedule.Tests.csproj", "{BDD8B924-016E-4CDA-9FFA-50B0A34BCD3C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.CouchPotato", "Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj", "{87D7897D-7C73-4856-A0AA-FF5948F4EA86}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -202,6 +204,10 @@ Global
|
|||
{BDD8B924-016E-4CDA-9FFA-50B0A34BCD3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BDD8B924-016E-4CDA-9FFA-50B0A34BCD3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BDD8B924-016E-4CDA-9FFA-50B0A34BCD3C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{87D7897D-7C73-4856-A0AA-FF5948F4EA86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{87D7897D-7C73-4856-A0AA-FF5948F4EA86}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{87D7897D-7C73-4856-A0AA-FF5948F4EA86}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{87D7897D-7C73-4856-A0AA-FF5948F4EA86}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -230,6 +236,7 @@ Global
|
|||
{737B2620-FE5A-4135-A017-79C269A7D36C} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{CA55DD4F-4EFF-4906-A848-35FCC7BD5654} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{BDD8B924-016E-4CDA-9FFA-50B0A34BCD3C} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
||||
{87D7897D-7C73-4856-A0AA-FF5948F4EA86} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869}
|
||||
|
|
25
src/Ombi/ClientApp/app/interfaces/ICouchPotato.ts
Normal file
25
src/Ombi/ClientApp/app/interfaces/ICouchPotato.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
export interface ICouchPotatoProfiles {
|
||||
success: boolean;
|
||||
list: IProfileList[];
|
||||
}
|
||||
|
||||
export interface IProfileList {
|
||||
core: boolean;
|
||||
hide: boolean;
|
||||
_rev: string;
|
||||
finish: boolean[];
|
||||
qualities: string[];
|
||||
_id: string;
|
||||
_t: string;
|
||||
label: string;
|
||||
minimum_score: number;
|
||||
stop_after: number[];
|
||||
wait_for: object[];
|
||||
order: number;
|
||||
threeD: object[];
|
||||
}
|
||||
|
||||
export interface ICouchPotatoApiKey {
|
||||
success: boolean;
|
||||
apiKey: string;
|
||||
}
|
|
@ -79,6 +79,8 @@ export interface IRequestGrid<T> {
|
|||
|
||||
export interface IMovieRequests extends IFullBaseRequest {
|
||||
theMovieDbId: number;
|
||||
rootPathOverride: number;
|
||||
qualityOverride: number;
|
||||
}
|
||||
|
||||
export interface IFullBaseRequest extends IBaseRequest {
|
||||
|
|
|
@ -119,3 +119,11 @@ export interface IAbout {
|
|||
processArchitecture: string;
|
||||
applicationBasePath: string;
|
||||
}
|
||||
|
||||
export interface ICouchPotatoSettings extends IExternalSettings {
|
||||
enabled: boolean;
|
||||
apiKey: string;
|
||||
defaultProfileId: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export * from "./ICommon";
|
||||
export * from "./ICouchPotato";
|
||||
export * from "./IImages";
|
||||
export * from "./IMediaServerStatus";
|
||||
export * from "./INotificationSettings";
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-md-push-4 vcenter">
|
||||
<button [routerLink]="['/login', 'true']" class="btn btn-lg btn-success-outline">Contine</button>
|
||||
<button [routerLink]="['/login', 'true']" class="btn btn-lg btn-success-outline">Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@ include the remember me checkbox
|
|||
<div class="card card-container">
|
||||
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
|
||||
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="/images/ms-icon-150x150.png" /></div>
|
||||
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-card" [src]="customizationSettings.logo" /></div>
|
||||
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="center" [src]="customizationSettings.logo" /></div>
|
||||
<p id="profile-name" class="profile-name-card"></p>
|
||||
|
||||
<form class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="card card-container">
|
||||
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
|
||||
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="/images/ms-icon-150x150.png" /></div>
|
||||
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-card" [src]="customizationSettings.logo" /></div>
|
||||
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="center" [src]="customizationSettings.logo" /></div>
|
||||
<p id="profile-name" class="profile-name-card"></p>
|
||||
|
||||
|
||||
|
|
|
@ -78,45 +78,32 @@
|
|||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="!request.approved">
|
||||
<form>
|
||||
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
|
||||
<div *ngIf="request.hasQualities" class="btn-group btn-split">
|
||||
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
|
||||
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
|
||||
</form>
|
||||
|
||||
<!--Radarr Root Folder-->
|
||||
<div *ngIf="radarrRootFolders" class="btn-group btn-split">
|
||||
<button type="button" class="btn btn-sm btn-success-outline"><i class="fa fa-plus"></i> Change Root Folder</button>
|
||||
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<!--<ul class="dropdown-menu">
|
||||
{{#each qualities}}
|
||||
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
|
||||
{{/each}}
|
||||
</ul>-->
|
||||
</div>
|
||||
|
||||
|
||||
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
|
||||
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
|
||||
{{#if_eq hasRootFolders true}}
|
||||
<div class="btn-group btn-split">
|
||||
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
|
||||
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{{#each rootFolders}}
|
||||
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
|
||||
{{/each}}
|
||||
<li *ngFor="let folder of radarrRootFolders"><a href="#" (click)="selectRootFolder(request, folder)">{{folder.path}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{{/if_eq}}
|
||||
</form>-->
|
||||
|
||||
|
||||
<!--Radarr Quality Profiles -->
|
||||
<div *ngIf="radarrProfiles" class="btn-group btn-split">
|
||||
<button type="button" class="btn btn-sm btn-success-outline"><i class="fa fa-plus"></i> Change Quality Profile</button>
|
||||
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li *ngFor="let profile of radarrProfiles"><a href="#" (click)="selectQualityProfile(request, profile)">{{profile.name}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!request.denied">
|
||||
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
|
||||
|
|
|
@ -5,9 +5,9 @@ import "rxjs/add/operator/map";
|
|||
import { Subject } from "rxjs/Subject";
|
||||
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { NotificationService, RequestService } from "../services";
|
||||
import { NotificationService, RadarrService, RequestService } from "../services";
|
||||
|
||||
import { IMovieRequests } from "../interfaces";
|
||||
import { IMovieRequests, IRadarrProfile, IRadarrRootFolder } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "movie-requests",
|
||||
|
@ -21,12 +21,16 @@ export class MovieRequestsComponent implements OnInit {
|
|||
|
||||
public isAdmin: boolean;
|
||||
|
||||
public radarrProfiles: IRadarrProfile[];
|
||||
public radarrRootFolders: IRadarrRootFolder[];
|
||||
|
||||
private currentlyLoaded: number;
|
||||
private amountToLoad: number;
|
||||
|
||||
constructor(private requestService: RequestService,
|
||||
private auth: AuthService,
|
||||
private notificationService: NotificationService) {
|
||||
private notificationService: NotificationService,
|
||||
private radarrService: RadarrService) {
|
||||
this.searchChanged
|
||||
.debounceTime(600) // Wait Xms after the last event before emitting last event
|
||||
.distinctUntilChanged() // only emit if value is different from previous value
|
||||
|
@ -42,10 +46,13 @@ export class MovieRequestsComponent implements OnInit {
|
|||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.radarrService.getQualityProfilesFromSettings().subscribe(x => this.radarrProfiles = x);
|
||||
this.radarrService.getRootFoldersFromSettings().subscribe(x => this.radarrRootFolders = x);
|
||||
|
||||
this.amountToLoad = 5;
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
this.isAdmin = this.auth.hasRole("admin");
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
}
|
||||
|
||||
public loadMore() {
|
||||
|
@ -80,6 +87,14 @@ export class MovieRequestsComponent implements OnInit {
|
|||
this.updateRequest(request);
|
||||
}
|
||||
|
||||
public selectRootFolder(searchResult: IMovieRequests, rootFolderSelected: IRadarrRootFolder) {
|
||||
searchResult.rootPathOverride = rootFolderSelected.id;
|
||||
}
|
||||
|
||||
public selectQualityProfile(searchResult: IMovieRequests, profileSelected: IRadarrProfile) {
|
||||
searchResult.qualityOverride = profileSelected.id;
|
||||
}
|
||||
|
||||
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
|
||||
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
|
||||
.subscribe(x => {
|
||||
|
|
|
@ -1,30 +1,25 @@
|
|||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import "rxjs/add/operator/debounceTime";
|
||||
import "rxjs/add/operator/distinctUntilChanged";
|
||||
import "rxjs/add/operator/map";
|
||||
import "rxjs/add/operator/takeUntil";
|
||||
import { Subject } from "rxjs/Subject";
|
||||
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { NotificationService } from "../services";
|
||||
import { RequestService } from "../services";
|
||||
import { SearchService } from "../services";
|
||||
import { NotificationService, RequestService, SearchService } from "../services";
|
||||
|
||||
import { IRequestEngineResult } from "../interfaces";
|
||||
import { ISearchMovieResult } from "../interfaces";
|
||||
import { IRequestEngineResult, ISearchMovieResult } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "movie-search",
|
||||
templateUrl: "./moviesearch.component.html",
|
||||
})
|
||||
export class MovieSearchComponent implements OnInit, OnDestroy {
|
||||
export class MovieSearchComponent implements OnInit {
|
||||
|
||||
public searchText: string;
|
||||
public searchChanged: Subject<string> = new Subject<string>();
|
||||
public movieResults: ISearchMovieResult[];
|
||||
public result: IRequestEngineResult;
|
||||
public searchApplied = false;
|
||||
private subscriptions = new Subject<void>();
|
||||
|
||||
constructor(private searchService: SearchService, private requestService: RequestService,
|
||||
private notificationService: NotificationService, private authService: AuthService) {
|
||||
|
@ -32,7 +27,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
|
|||
this.searchChanged
|
||||
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
|
||||
.distinctUntilChanged() // only emit if value is different from previous value
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.searchText = x as string;
|
||||
if (this.searchText === "") {
|
||||
|
@ -40,7 +34,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
this.searchService.searchMovie(this.searchText)
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.searchApplied = true;
|
||||
|
@ -72,7 +65,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
this.requestService.requestMovie(searchResult)
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.result = x;
|
||||
|
||||
|
@ -90,7 +82,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
|
|||
public popularMovies() {
|
||||
this.clearResults();
|
||||
this.searchService.popularMovies()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.getExtaInfo();
|
||||
|
@ -99,7 +90,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
|
|||
public nowPlayingMovies() {
|
||||
this.clearResults();
|
||||
this.searchService.nowPlayingMovies()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.getExtaInfo();
|
||||
|
@ -108,7 +98,6 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
|
|||
public topRatedMovies() {
|
||||
this.clearResults();
|
||||
this.searchService.topRatedMovies()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.getExtaInfo();
|
||||
|
@ -117,23 +106,16 @@ export class MovieSearchComponent implements OnInit, OnDestroy {
|
|||
public upcomingMovies() {
|
||||
this.clearResults();
|
||||
this.searchService.upcomignMovies()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.getExtaInfo();
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.subscriptions.next();
|
||||
this.subscriptions.complete();
|
||||
}
|
||||
|
||||
private getExtaInfo() {
|
||||
|
||||
this.movieResults.forEach((val, index) => {
|
||||
this.searchService.getMovieInformation(val.id)
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(m => this.updateItem(val, m));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { Injectable } from "@angular/core";
|
||||
import { AuthHttp } from "angular2-jwt";
|
||||
import { Observable } from "rxjs/Rx";
|
||||
|
||||
import { ServiceAuthHelpers } from "../service.helpers";
|
||||
|
||||
import { ICouchPotatoApiKey, ICouchPotatoProfiles, ICouchPotatoSettings } from "../../interfaces";
|
||||
|
||||
@Injectable()
|
||||
export class CouchPotatoService extends ServiceAuthHelpers {
|
||||
constructor(http: AuthHttp, public platformLocation: PlatformLocation) {
|
||||
super(http, "/api/v1/CouchPotato/", platformLocation);
|
||||
}
|
||||
|
||||
public getProfiles(settings: ICouchPotatoSettings): Observable<ICouchPotatoProfiles> {
|
||||
return this.http.post(`${this.url}profile`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
|
||||
public getApiKey(settings: ICouchPotatoSettings): Observable<ICouchPotatoApiKey> {
|
||||
return this.http.post(`${this.url}apikey`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
export * from "./emby.service";
|
||||
export * from "./couchpotato.service";
|
||||
export * from "./emby.service";
|
||||
export * from "./plex.service";
|
||||
export * from "./radarr.service";
|
||||
export * from "./sonarr.service";
|
||||
|
|
|
@ -19,4 +19,11 @@ export class RadarrService extends ServiceAuthHelpers {
|
|||
public getQualityProfiles(settings: IRadarrSettings): Observable<IRadarrProfile[]> {
|
||||
return this.http.post(`${this.url}/Profiles/`, JSON.stringify(settings), { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
|
||||
public getRootFoldersFromSettings(): Observable<IRadarrRootFolder[]> {
|
||||
return this.http.get(`${this.url}/RootFolders/`, { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
public getQualityProfilesFromSettings(): Observable<IRadarrProfile[]> {
|
||||
return this.http.get(`${this.url}/Profiles/`, { headers: this.headers }).map(this.extractData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Observable } from "rxjs/Rx";
|
|||
import {
|
||||
IAbout,
|
||||
IAuthenticationSettings,
|
||||
ICouchPotatoSettings,
|
||||
ICustomizationSettings,
|
||||
IDiscordNotifcationSettings,
|
||||
IEmailNotificationSettings,
|
||||
|
@ -195,4 +196,14 @@ export class SettingsService extends ServiceAuthHelpers {
|
|||
.post(`${this.url}/UserManagement`, JSON.stringify(settings), { headers: this.headers })
|
||||
.map(this.extractData).catch(this.handleError);
|
||||
}
|
||||
|
||||
public getCouchPotatoSettings(): Observable<ICouchPotatoSettings> {
|
||||
return this.httpAuth.get(`${this.url}/UserManagement`).map(this.extractData).catch(this.handleError);
|
||||
}
|
||||
|
||||
public saveCouchPotatoSettings(settings: ICouchPotatoSettings): Observable<boolean> {
|
||||
return this.httpAuth
|
||||
.post(`${this.url}/UserManagement`, JSON.stringify(settings), { headers: this.headers })
|
||||
.map(this.extractData).catch(this.handleError);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<settings-menu>
|
||||
</settings-menu>
|
||||
<div *ngIf="form">
|
||||
<fieldset>
|
||||
<legend>CouchPotato Settings</legend>
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enable" formControlName="enabled" ng-checked="form.enabled">
|
||||
<label for="enable">Enable</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Ip" class="control-label">Hostname or IP</label>
|
||||
<div class="">
|
||||
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('ip').hasError('required')}"
|
||||
id="Ip" name="Ip" placeholder="localhost" formControlName="ip">
|
||||
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="portNumber" class="control-label">Port</label>
|
||||
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('port').hasError('required')}" formControlName="port" id="portNumber" name="Port" placeholder="Port Number">
|
||||
|
||||
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ApiKey" class="control-label">API Key</label>
|
||||
|
||||
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey">
|
||||
|
||||
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
|
||||
<input type="checkbox" id="Ssl" name="Ssl" formControlName="ssl"><label for="Ssl">SSL</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="SubDir" class="control-label">Base Url</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir" name="SubDir">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<input type="text" class="form-control form-control-custom " formControlName="username" name="username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<input type="text" class="form-control form-control-custom " formControlName="password" name="password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary-outline" (click)="requestToken(form)">Request Api Key <i class="fa fa-key"></i></button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button type="submit" (click)="getProfiles(form)" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Quality Profiles</label>
|
||||
<div id="profiles">
|
||||
<select formControlName="defaultProfileId" class="form-control form-control-custom" id="select">
|
||||
<option *ngFor="let profile of profiles?.list" value="{{profile._id}}">{{profile.label}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" (click)="test(form)" class="btn btn-primary-outline">Test Connectivity <span id="spinner"></span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline ">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
</div>
|
|
@ -0,0 +1,90 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
|
||||
import { CouchPotatoService, NotificationService, SettingsService, TesterService } from "../../services";
|
||||
|
||||
import { ICouchPotatoProfiles } from "../../interfaces";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./couchpotato.component.html",
|
||||
})
|
||||
export class CouchPotatoComponent implements OnInit {
|
||||
|
||||
public form: FormGroup;
|
||||
public profiles: ICouchPotatoProfiles;
|
||||
|
||||
public profilesRunning: boolean;
|
||||
|
||||
constructor(private readonly settingsService: SettingsService,
|
||||
private readonly fb: FormBuilder,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly couchPotatoService: CouchPotatoService,
|
||||
private readonly testerService: TesterService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.settingsService.getCouchPotatoSettings().subscribe(x => {
|
||||
this.form = this.fb.group({
|
||||
enabled: [x.enabled],
|
||||
username: [x.username],
|
||||
password: [x.password],
|
||||
apiKey: [x.apiKey, Validators.required],
|
||||
ip: [x.ip, Validators.required],
|
||||
port: [x.port, Validators.required],
|
||||
ssl: [x.ssl],
|
||||
subDir: [x.subDir],
|
||||
defaultProfileId: [x.defaultProfileId],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public getProfiles(form: FormGroup) {
|
||||
this.profilesRunning = true;
|
||||
this.couchPotatoService.getProfiles(form.value).subscribe(x => {
|
||||
this.profiles = x;
|
||||
this.profilesRunning = false;
|
||||
});
|
||||
}
|
||||
|
||||
public onSubmit(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Validation", "Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = form.value;
|
||||
|
||||
this.settingsService.saveCouchPotatoSettings(settings).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Settings Saved", "Successfully saved the CouchPotato settings");
|
||||
} else {
|
||||
this.notificationService.success("Settings Saved", "There was an error when saving the CouchPotato settings");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public test(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Validation", "Please check your entered values");
|
||||
return;
|
||||
}
|
||||
const settings = form.value;
|
||||
this.testerService.radarrTest(settings).subscribe(x => {
|
||||
if (x === true) {
|
||||
this.notificationService.success("Connected", "Successfully connected to Radarr!");
|
||||
} else {
|
||||
this.notificationService.error("Connected", "We could not connect to Radarr!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public requestToken(form: FormGroup) {
|
||||
this.couchPotatoService.getApiKey(form.value).subscribe(x => {
|
||||
if (x.success === true) {
|
||||
(<FormControl>this.form.controls.apiKey).setValue(x.apiKey);
|
||||
this.notificationService.success("Api Key", "Successfully got the Api Key");
|
||||
} else {
|
||||
this.notificationService.error("Api Key", "Could not get the Api Key");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -13,15 +13,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||
<div *ngIf="form.get('webhookUrl').hasError('required')">The Webhook Url is required</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="webhookUrl" class="control-label">Webhook Url</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" formControlName="webhookUrl">
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" formControlName="webhookUrl" [ngClass]="{'form-error': form.get('webhookUrl').hasError('required')}">
|
||||
<small *ngIf="form.get('webhookUrl').hasError('required')" class="error-text">The Webhook Url is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -18,35 +18,27 @@
|
|||
<input type="checkbox" id="Authentication" formControlName="authentication"><label for="Authentication">Enable SMTP Authentication</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="emailForm.invalid && emailForm.dirty" class="alert alert-danger">
|
||||
<div *ngIf="emailForm.get('host').hasError('required')">Host is required</div>
|
||||
<div *ngIf="emailForm.get('port').hasError('required')">The Port is required</div>
|
||||
<div *ngIf="emailForm.get('senderAddress').hasError('required')">The Email Sender Address is required</div>
|
||||
<div *ngIf="emailForm.get('senderAddress').hasError('incorrectMailFormat')">The Email Sender Address needs to be a valid email address</div>
|
||||
<div *ngIf="emailForm.get('adminEmail').hasError('required')">The Email Sender is required</div>
|
||||
<div *ngIf="emailForm.get('adminEmail').hasError('email')">The Admin Email needs to be a valid email address</div>
|
||||
<div *ngIf="emailForm.get('username').hasError('required')">The Username is required</div>
|
||||
<div *ngIf="emailForm.get('password').hasError('required')">The Password is required</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="host" class="control-label">SMTP Host</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="host" name="host" placeholder="localhost" formControlName="host">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="host" name="host" placeholder="localhost" formControlName="host" [ngClass]="{'form-error': emailForm.get('host').hasError('required')}">
|
||||
<small *ngIf="emailForm.get('host').hasError('required')" class="error-text">The Host is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="portNumber" class="control-label">SMTP Port</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" formControlName="port">
|
||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': emailForm.get('port').hasError('required')}" id="portNumber" name="Port" placeholder="Port Number" formControlName="port">
|
||||
<small *ngIf="emailForm.get('port').hasError('required')" class="error-text">The Port is required</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label style="padding-left: 0" for="senderAddress" class="control-label col-md-12">Email Sender</label>
|
||||
<div style="padding-left: 0" class="col-md-6">
|
||||
<input type="text" class="form-control form-control-custom " id="senderAddress" name="senderAddress" formControlName="senderAddress" tooltipPosition="top" placeholder="Sender Address" pTooltip="The email address that the emails will be sent from">
|
||||
<input type="text" class="form-control form-control-custom " id="senderAddress" [ngClass]="{'form-error': emailForm.get('senderAddress').hasError('required'), 'form-error': emailForm.get('senderAddress').hasError('incorrectMailFormat')}" name="senderAddress" formControlName="senderAddress" tooltipPosition="top" placeholder="Sender Address" pTooltip="The email address that the emails will be sent from">
|
||||
<small *ngIf="emailForm.get('senderAddress').hasError('required')" class="error-text">The Email Sender Address is required</small>
|
||||
<small *ngIf="emailForm.get('senderAddress').hasError('email') && !emailForm.get('senderAddress').hasError('required')" class="error-text">The Email Sender Address needs to be a valid email address</small>
|
||||
</div>
|
||||
<div style="padding-left: 0" class="col-md-6">
|
||||
<input type="text" class="form-control form-control-custom " id="senderName" name="senderName" formControlName="senderName" tooltipPosition="top" placeholder="Sender Name" pTooltip="The 'Friendly' name that will appear in the 'FROM:' part of the email">
|
||||
|
@ -58,24 +50,26 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label for="adminEmail" class="control-label">Admin Email</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="adminEmail" name="adminEmail" formControlName="adminEmail" tooltipPosition="top" pTooltip="The administrator email will be used to send emails for admin only notifications (e.g. New Requests that require approvals)">
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': emailForm.get('adminEmail').hasError('required'), 'form-error': emailForm.get('adminEmail').hasError('email')}" id="adminEmail" name="adminEmail" formControlName="adminEmail" tooltipPosition="top" pTooltip="The administrator email will be used to send emails for admin only notifications (e.g. New Requests that require approvals)">
|
||||
<small *ngIf="emailForm.get('adminEmail').hasError('required')" class="error-text">The Admin Email Address is required</small>
|
||||
<small *ngIf="emailForm.get('adminEmail').hasError('email') && !emailForm.get('adminEmail').hasError('required')" class="error-text">The Admin Email needs to be a valid email address</small>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" *ngIf="emailForm.controls['username'].validator">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="username" name="username" formControlName="username" pTooltip="The username if authentication is enabled" tooltipPosition="top">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': emailForm.get('username').hasError('required')}" id="username" name="username" formControlName="username" pTooltip="The username if authentication is enabled" tooltipPosition="top">
|
||||
<small *ngIf="emailForm.get('username').hasError('required')" class="error-text">The Username is required</small>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="emailForm.get('password').validator">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<div>
|
||||
<input type="password" class="form-control form-control-custom " id="password" name="password" formControlName="password" pTooltip="The password if authentication is enabled" tooltipPosition="top">
|
||||
</div>
|
||||
|
||||
<input type="password" class="form-control form-control-custom" [ngClass]="{'form-error': emailForm.get('password').hasError('required')}" id="password" name="password" formControlName="password" pTooltip="The password if authentication is enabled" tooltipPosition="top">
|
||||
<small *ngIf="emailForm.get('password').hasError('required')" class="error-text">The Password is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -13,16 +13,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||
<div *ngIf="form.get('webhookUrl').hasError('required')">The Incoming Webhook Url is required</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<small class="control-label"> Mattermost > Integrations > Incoming Webhook > Add Incoming Webhook. You will then have a Webhook</small>
|
||||
<label for="webhookUrl" class="control-label">Incoming Webhook Url</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" formControlName="webhookUrl">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" formControlName="webhookUrl" [ngClass]="{'form-error': form.get('webhookUrl').hasError('required')}">
|
||||
<small *ngIf="form.get('webhookUrl').hasError('required')" class="error-text">The Webhook Url is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -13,15 +13,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||
<div *ngIf="form.get('accessToken').hasError('required')">The Access Token is required</div>
|
||||
</div>
|
||||
<small>You can find this here: <a href="https://www.pushbullet.com/#settings/account">https://www.pushbullet.com/#settings/account </a></small>
|
||||
<div class="form-group">
|
||||
<label for="accessToken" class="control-label">Access Token</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="accessToken" name="accessToken" formControlName="accessToken">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="accessToken" name="accessToken" formControlName="accessToken" [ngClass]="{'form-error': form.get('accessToken').hasError('required')}">
|
||||
<small *ngIf="form.get('accessToken').hasError('required')" class="error-text">The Access Token is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -13,14 +13,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||
<div *ngIf="form.get('accessToken').hasError('required')">The Access Token is required</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="accessToken" class="control-label">Access Token</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="accessToken" name="accessToken" formControlName="accessToken" pTooltip="Enter your API Key from Pushover.">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="accessToken" name="accessToken" [ngClass]="{'form-error': form.get('accessToken').hasError('required')}" formControlName="accessToken" pTooltip="Enter your API Key from Pushover.">
|
||||
<small *ngIf="form.get('accessToken').hasError('required')" class="error-text">The Access Token is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
@ -14,9 +14,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="form.invalid && form.dirty" class="alert alert-danger">
|
||||
<div *ngIf="form.get('webhookUrl').hasError('required')">The Webhook Url is required</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
|
@ -24,7 +21,8 @@
|
|||
<div>
|
||||
|
||||
<small class="control-label"> Click <a target="_blank" href="https://my.slack.com/services/new/incoming-webhook/">Here</a> and follow the guide. You will then have a Webhook Url</small>
|
||||
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" formControlName="webhookUrl">
|
||||
<input type="text" class="form-control form-control-custom " id="webhookUrl" name="webhookUrl" formControlName="webhookUrl" [ngClass]="{'form-error': form.get('webhookUrl').hasError('required')}">
|
||||
<small *ngIf="form.get('webhookUrl').hasError('required')" class="error-text">The Webhook Url is required</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,19 +8,6 @@
|
|||
<p-inputSwitch id="customInputSwitch" [(ngModel)]="advanced"></p-inputSwitch>
|
||||
</div>
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
||||
<div *ngIf="form.invalid" class="alert alert-danger">
|
||||
<div *ngIf="form.dirty">
|
||||
<div *ngIf="form.get('ip').hasError('required')">The IP/Hostname is required</div>
|
||||
<div *ngIf="form.get('port').hasError('required')">The Port is required</div>
|
||||
<div *ngIf="form.get('apiKey').hasError('required')">The Api Key is required</div>
|
||||
</div>
|
||||
<div>
|
||||
<div *ngIf="form.get('defaultQualityProfile').hasError('required')">A Default Quality Profile is required</div>
|
||||
<div *ngIf="form.get('defaultRootPath').hasError('required')">A Default Root Path is required</div>
|
||||
<div *ngIf="form.get('minimumAvailability').hasError('required')">A Default Minimum Availability is required</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
|
@ -33,25 +20,24 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label for="Ip" class="control-label">Hostname or IP</label>
|
||||
<div class="">
|
||||
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
||||
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="portNumber" class="control-label">Port</label>
|
||||
|
||||
<div class="">
|
||||
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number">
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}">
|
||||
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ApiKey" class="control-label">API Key</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="ApiKey" name="ApiKey" formControlName="apiKey">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey">
|
||||
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
|
@ -76,10 +62,11 @@
|
|||
<div class="form-group">
|
||||
<label for="select" class="control-label">Quality Profiles</label>
|
||||
<div id="profiles">
|
||||
<select formControlName="defaultQualityProfile" class="form-control form-control-custom" id="select">
|
||||
<select formControlName="defaultQualityProfile" class="form-control form-control-custom" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
|
||||
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -92,19 +79,22 @@
|
|||
<div class="form-group">
|
||||
<label for="rootFolders" class="control-label">Default Root Folders</label>
|
||||
<div id="rootFolders">
|
||||
<select formControlName="defaultRootPath" class="form-control form-control-custom">
|
||||
<select formControlName="defaultRootPath" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
|
||||
<option *ngFor="let folder of rootFolders" value="{{folder.path}}">{{folder.path}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rootFolders" class="control-label">Default Minimum Availability</label>
|
||||
<div id="rootFolders">
|
||||
<select formControlName="minimumAvailability" class="form-control form-control-custom">
|
||||
<select formControlName="minimumAvailability" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('minimumAvailability').hasError('required')}">
|
||||
<option *ngFor="let min of minimumAvailabilityOptions" value="{{min.value}}">{{min.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<small *ngIf="form.get('minimumAvailability').hasError('required')" class="error-text">A Default Minimum Availability is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="advanced" style="color:#ff761b">
|
||||
|
|
|
@ -8,10 +8,11 @@ import { ClipboardModule } from "ngx-clipboard/dist";
|
|||
import { AuthGuard } from "../auth/auth.guard";
|
||||
import { AuthModule } from "../auth/auth.module";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { JobService, RadarrService, SonarrService, TesterService, ValidationService } from "../services";
|
||||
import { CouchPotatoService, JobService, RadarrService, SonarrService, TesterService, ValidationService } from "../services";
|
||||
|
||||
import { PipeModule } from "../pipes/pipe.module";
|
||||
import { AboutComponent } from "./about/about.component";
|
||||
import { CouchPotatoComponent } from "./couchpotato/couchpotato.component";
|
||||
import { CustomizationComponent } from "./customization/customization.component";
|
||||
import { EmbyComponent } from "./emby/emby.component";
|
||||
import { LandingPageComponent } from "./landingpage/landingpage.component";
|
||||
|
@ -51,6 +52,7 @@ const routes: Routes = [
|
|||
{ path: "Settings/Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Update", component: UpdateComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/CouchPotato", component: CouchPotatoComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -91,6 +93,7 @@ const routes: Routes = [
|
|||
UpdateComponent,
|
||||
AboutComponent,
|
||||
WikiComponent,
|
||||
CouchPotatoComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
|
@ -103,6 +106,7 @@ const routes: Routes = [
|
|||
ValidationService,
|
||||
TesterService,
|
||||
JobService,
|
||||
CouchPotatoService,
|
||||
],
|
||||
|
||||
})
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<i class="fa fa-film" aria-hidden="true"></i> Movies <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<!--<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/CouchPotato']">CouchPotato</a></li>-->
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/CouchPotato']">CouchPotato (NOT YET READY)</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>
|
||||
|
|
|
@ -8,17 +8,7 @@
|
|||
<p-inputSwitch id="customInputSwitch" [(ngModel)]="advanced"></p-inputSwitch>
|
||||
</div>
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
||||
<div *ngIf="form.invalid" class="alert alert-danger">
|
||||
<div *ngIf="form.dirty">
|
||||
<div *ngIf="form.get('ip').hasError('required')">The IP/Hostname is required</div>
|
||||
<div *ngIf="form.get('port').hasError('required')">The Port is required</div>
|
||||
<div *ngIf="form.get('apiKey').hasError('required')">The Api Key is required</div>
|
||||
</div>
|
||||
<div>
|
||||
<div *ngIf="form.get('qualityProfile').hasError('required')">A Default Quality Profile is required</div>
|
||||
<div *ngIf="form.get('rootPath').hasError('required')">A Default Root Path is required</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
|
@ -29,25 +19,24 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label for="Ip" class="control-label">Sonarr Hostname or IP</label>
|
||||
<div class="">
|
||||
<input type="text" class="form-control form-control-custom " formControlName="ip" id="Ip" name="Ip" placeholder="localhost">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " formControlName="ip" id="Ip" name="Ip" placeholder="localhost" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
||||
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="portNumber" class="control-label">Port</label>
|
||||
|
||||
<div class="">
|
||||
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number">
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('port').hasError('required')}" formControlName="port" id="portNumber" name="Port" placeholder="Port Number">
|
||||
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ApiKey" class="control-label">Sonarr API Key</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " formControlName="apiKey" id="ApiKey" name="ApiKey">
|
||||
</div>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" formControlName="apiKey" id="ApiKey" name="ApiKey">
|
||||
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
|
@ -72,10 +61,12 @@
|
|||
<div class="form-group">
|
||||
<label for="select" class="control-label">Quality Profiles</label>
|
||||
<div id="profiles">
|
||||
<select class="form-control form-control-custom" id="select" formControlName="qualityProfile">
|
||||
<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>
|
||||
</select>
|
||||
</div>
|
||||
<small *ngIf="form.get('qualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -88,10 +79,12 @@
|
|||
<div class="form-group">
|
||||
<label for="rootFolders" class="control-label">Default Root Folders</label>
|
||||
<div id="rootFolders">
|
||||
<select class="form-control form-control-custom" formControlName="rootPath">
|
||||
<select class="form-control form-control-custom" formControlName="rootPath" [ngClass]="{'form-error': form.get('rootPath').hasError('required')}">
|
||||
<option *ngFor="let folder of rootFolders" value="{{folder.id}}">{{folder.path}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<small *ngIf="form.get('rootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ export class UserManagementComponent implements OnInit {
|
|||
|
||||
public runImporter(): void {
|
||||
this.jobService.runPlexImporter().subscribe();
|
||||
this.jobService.runPlexImporter().subscribe();
|
||||
this.jobService.runEmbyImporter().subscribe();
|
||||
}
|
||||
|
||||
private filter(query: string, users: IUsersModel[]): IUsersModel[] {
|
||||
|
|
|
@ -816,3 +816,11 @@ a > h4 {
|
|||
a > h4:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
border: 1px solid #d9534f;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #d9534f;
|
||||
}
|
||||
|
|
37
src/Ombi/Controllers/External/CouchPotatoController.cs
vendored
Normal file
37
src/Ombi/Controllers/External/CouchPotatoController.cs
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Api.CouchPotato;
|
||||
using Ombi.Api.CouchPotato.Models;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
|
||||
namespace Ombi.Controllers.External
|
||||
{
|
||||
[Admin]
|
||||
[ApiV1]
|
||||
[Produces("application/json")]
|
||||
public class CouchPotatoController
|
||||
{
|
||||
public CouchPotatoController(ICouchPotatoApi api)
|
||||
{
|
||||
_api = api;
|
||||
}
|
||||
|
||||
private readonly ICouchPotatoApi _api;
|
||||
|
||||
[HttpPost("profile")]
|
||||
public async Task<CouchPotatoProfiles> GetQualityProfiles([FromBody] CouchPotatoSettings settings)
|
||||
{
|
||||
var profiles = await _api.GetProfiles(settings.FullUri, settings.ApiKey);
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
[HttpPost("apikey")]
|
||||
public async Task<CouchPotatoApiKey> GetApiKey([FromBody] CouchPotatoSettings settings)
|
||||
{
|
||||
var apiKey = await _api.GetApiKey(settings.FullUri, settings.Username, settings.Password);
|
||||
return apiKey;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,9 +13,6 @@ using Ombi.Models.External;
|
|||
|
||||
namespace Ombi.Controllers.External
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Admin]
|
||||
[ApiV1]
|
||||
[Produces("application/json")]
|
||||
|
|
|
@ -1,28 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Ombi.Api.Radarr;
|
||||
using Ombi.Api.Radarr.Models;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
|
||||
namespace Ombi.Controllers.External
|
||||
{
|
||||
[Admin]
|
||||
[PowerUser]
|
||||
[ApiV1]
|
||||
[Produces("application/json")]
|
||||
public class RadarrController : Controller
|
||||
{
|
||||
public RadarrController(IRadarrApi radarr, ISettingsService<RadarrSettings> settings)
|
||||
public RadarrController(IRadarrApi radarr, ISettingsService<RadarrSettings> settings,
|
||||
IMemoryCache mem)
|
||||
{
|
||||
RadarrApi = radarr;
|
||||
RadarrSettings = settings;
|
||||
Cache = mem;
|
||||
}
|
||||
|
||||
private IRadarrApi RadarrApi { get; }
|
||||
private ISettingsService<RadarrSettings> RadarrSettings { get; }
|
||||
|
||||
private IMemoryCache Cache { get; }
|
||||
/// <summary>
|
||||
/// Gets the Radarr profiles.
|
||||
/// </summary>
|
||||
|
@ -44,5 +49,37 @@ namespace Ombi.Controllers.External
|
|||
{
|
||||
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Radarr profiles using the saved settings
|
||||
/// <remarks>The data is cached for an hour</remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("Profiles")]
|
||||
public async Task<IEnumerable<RadarrProfile>> GetProfiles()
|
||||
{
|
||||
return await Cache.GetOrCreate(CacheKeys.RadarrQualityProfiles, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
|
||||
var settings = await RadarrSettings.GetSettingsAsync();
|
||||
return await RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Radar root folders using the saved settings.
|
||||
/// <remarks>The data is cached for an hour</remarks>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("RootFolders")]
|
||||
public async Task<IEnumerable<RadarrRootFolder>> GetRootFolders()
|
||||
{
|
||||
return await Cache.GetOrCreate(CacheKeys.RadarrRootProfiles, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
|
||||
var settings = await RadarrSettings.GetSettingsAsync();
|
||||
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ using Ombi.Api.Sonarr.Models;
|
|||
using Ombi.Attributes;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
|
||||
namespace Ombi.Controllers.External
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@ using AutoMapper;
|
|||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.PlatformAbstractions;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Attributes;
|
||||
|
@ -52,7 +53,8 @@ namespace Ombi.Controllers
|
|||
IEmbyApi embyApi,
|
||||
IPlexContentCacher cacher,
|
||||
IEmbyContentCacher embyCacher,
|
||||
IRadarrCacher radarrCacher)
|
||||
IRadarrCacher radarrCacher,
|
||||
IMemoryCache memCache)
|
||||
{
|
||||
SettingsResolver = resolver;
|
||||
Mapper = mapper;
|
||||
|
@ -61,6 +63,7 @@ namespace Ombi.Controllers
|
|||
_plexContentCacher = cacher;
|
||||
_embyContentCacher = embyCacher;
|
||||
_radarrCacher = radarrCacher;
|
||||
_cache = memCache;
|
||||
}
|
||||
|
||||
private ISettingsResolver SettingsResolver { get; }
|
||||
|
@ -70,6 +73,7 @@ namespace Ombi.Controllers
|
|||
private readonly IPlexContentCacher _plexContentCacher;
|
||||
private readonly IEmbyContentCacher _embyContentCacher;
|
||||
private readonly IRadarrCacher _radarrCacher;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Ombi settings.
|
||||
|
@ -290,6 +294,8 @@ namespace Ombi.Controllers
|
|||
var result = await Save(settings);
|
||||
if (result)
|
||||
{
|
||||
_cache.Remove(CacheKeys.RadarrRootProfiles);
|
||||
_cache.Remove(CacheKeys.RadarrQualityProfiles);
|
||||
BackgroundJob.Enqueue(() => _radarrCacher.CacheContent());
|
||||
}
|
||||
return result;
|
||||
|
@ -337,6 +343,26 @@ namespace Ombi.Controllers
|
|||
return await Get<UpdateSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CouchPotatoSettings Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("CouchPotato")]
|
||||
public async Task<CouchPotatoSettings> CouchPotatoSettings()
|
||||
{
|
||||
return await Get<CouchPotatoSettings>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the CouchPotatoSettings settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("CouchPotato")]
|
||||
public async Task<bool> CouchPotatoSettings([FromBody]CouchPotatoSettings settings)
|
||||
{
|
||||
return await Save(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the email notification settings.
|
||||
|
|
|
@ -9,6 +9,8 @@ using Ombi.Store.Entities;
|
|||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi
|
||||
{
|
||||
|
@ -33,6 +35,8 @@ namespace Ombi
|
|||
UrlArgs = host;
|
||||
|
||||
var urlValue = string.Empty;
|
||||
var instance = StoragePathSingleton.Instance;
|
||||
instance.StoragePath = storagePath ?? string.Empty;
|
||||
using (var ctx = new OmbiContext())
|
||||
{
|
||||
var config = ctx.ApplicationConfigurations.ToList();
|
||||
|
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.HttpOverrides;
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.SpaServices.Webpack;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
@ -36,6 +37,7 @@ namespace Ombi
|
|||
{
|
||||
public class Startup
|
||||
{
|
||||
public static StoragePathSingleton StoragePath => StoragePathSingleton.Instance;
|
||||
public Startup(IHostingEnvironment env)
|
||||
{
|
||||
Console.WriteLine(env.ContentRootPath);
|
||||
|
@ -48,11 +50,24 @@ namespace Ombi
|
|||
|
||||
//if (env.IsDevelopment())
|
||||
//{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
Serilog.ILogger config;
|
||||
if (string.IsNullOrEmpty(StoragePath.StoragePath))
|
||||
{
|
||||
config = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Logs", "log-{Date}.txt"))
|
||||
.WriteTo.SQLite("Ombi.db", "Logs", LogEventLevel.Debug)
|
||||
.CreateLogger();
|
||||
}
|
||||
else
|
||||
{
|
||||
config = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.SQLite(Path.Combine(StoragePath.StoragePath, "Ombi.db"), "Logs", LogEventLevel.Debug)
|
||||
.CreateLogger();
|
||||
}
|
||||
Log.Logger = config;
|
||||
|
||||
|
||||
//}
|
||||
//if (env.IsProduction())
|
||||
//{
|
||||
|
@ -69,6 +84,7 @@ namespace Ombi
|
|||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public IServiceProvider ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
|
||||
// Add framework services.
|
||||
services.AddDbContext<OmbiContext>();
|
||||
|
||||
|
@ -113,6 +129,7 @@ namespace Ombi
|
|||
x.UseConsole();
|
||||
});
|
||||
|
||||
|
||||
// Build the intermediate service provider
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
@ -138,8 +155,17 @@ namespace Ombi
|
|||
});
|
||||
}
|
||||
|
||||
var ombiService =
|
||||
app.ApplicationServices.GetService<ISettingsService<OmbiSettings>>();
|
||||
var settings = ombiService.GetSettings();
|
||||
if (settings.BaseUrl.HasValue())
|
||||
{
|
||||
app.UsePathBase(settings.BaseUrl);
|
||||
}
|
||||
|
||||
app.UseHangfireServer();
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
||||
app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire",
|
||||
new DashboardOptions
|
||||
{
|
||||
Authorization = new[] {new HangfireAuthorizationFilter()}
|
||||
});
|
||||
|
@ -151,14 +177,6 @@ namespace Ombi
|
|||
|
||||
var provider = new FileExtensionContentTypeProvider { Mappings = { [".map"] = "application/octet-stream" } };
|
||||
|
||||
var ombiService =
|
||||
app.ApplicationServices.GetService<ISettingsService<OmbiSettings>>();
|
||||
var settings = ombiService.GetSettings();
|
||||
if (settings.BaseUrl.HasValue())
|
||||
{
|
||||
app.UsePathBase(settings.BaseUrl);
|
||||
}
|
||||
|
||||
app.UseStaticFiles(new StaticFileOptions()
|
||||
{
|
||||
ContentTypeProvider = provider,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue