Basic Application Syncing

pull/3/head
Qstick 4 years ago
parent 47fbab02c5
commit feba96e8e0

@ -0,0 +1,11 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Applications
{
public class AppIndexerMap : ModelBase
{
public int IndexerId { get; set; }
public int AppId { get; set; }
public int RemoteIndexerId { get; set; }
}
}

@ -0,0 +1,30 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.Applications
{
public interface IAppIndexerMapRepository : IBasicRepository<AppIndexerMap>
{
List<AppIndexerMap> GetMappingsForApp(int appId);
void DeleteAllForApp(int appId);
}
public class TagRepository : BasicRepository<AppIndexerMap>, IAppIndexerMapRepository
{
public TagRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
public void DeleteAllForApp(int appId)
{
Delete(x => x.AppId == appId);
}
public List<AppIndexerMap> GetMappingsForApp(int appId)
{
return Query(x => x.AppId == appId);
}
}
}

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
namespace NzbDrone.Core.Applications
{
public interface IAppIndexerMapService
{
List<AppIndexerMap> GetMappingsForApp(int appId);
AppIndexerMap Insert(AppIndexerMap appIndexerMap);
void DeleteAllForApp(int appId);
}
public class AppIndexerMapService : IAppIndexerMapService
{
private readonly IAppIndexerMapRepository _appIndexerMapRepository;
public AppIndexerMapService(IAppIndexerMapRepository appIndexerMapRepository)
{
_appIndexerMapRepository = appIndexerMapRepository;
}
public void DeleteAllForApp(int appId)
{
_appIndexerMapRepository.DeleteAllForApp(appId);
}
public List<AppIndexerMap> GetMappingsForApp(int appId)
{
return _appIndexerMapRepository.GetMappingsForApp(appId);
}
public AppIndexerMap Insert(AppIndexerMap appIndexerMap)
{
return _appIndexerMapRepository.Insert(appIndexerMap);
}
}
}

@ -1,13 +1,18 @@
using System;
using System.Collections.Generic;
using FluentValidation.Results;
using NLog;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Applications
{
public abstract class ApplicationBase<TSettings> : IApplications
public abstract class ApplicationBase<TSettings> : IApplication
where TSettings : IProviderConfig, new()
{
protected readonly IAppIndexerMapService _appIndexerMapService;
protected readonly Logger _logger;
public abstract string Name { get; }
public Type ConfigContract => typeof(TSettings);
@ -19,6 +24,12 @@ namespace NzbDrone.Core.Applications
protected TSettings Settings => (TSettings)Definition.Settings;
public ApplicationBase(IAppIndexerMapService appIndexerMapService, Logger logger)
{
_appIndexerMapService = appIndexerMapService;
_logger = logger;
}
public override string ToString()
{
return GetType().Name;
@ -40,6 +51,11 @@ namespace NzbDrone.Core.Applications
}
}
public abstract void AddIndexer(IndexerDefinition indexer);
public abstract void UpdateIndexer(IndexerDefinition indexer);
public abstract void RemoveIndexer(int indexerId);
public abstract void SyncIndexers();
public virtual object RequestAction(string action, IDictionary<string, string> query)
{
return null;

@ -7,13 +7,13 @@ using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Applications
{
public interface IApplicationsFactory : IProviderFactory<IApplications, ApplicationDefinition>
public interface IApplicationFactory : IProviderFactory<IApplication, ApplicationDefinition>
{
}
public class ApplicationFactory : ProviderFactory<IApplications, ApplicationDefinition>, IApplicationsFactory
public class ApplicationFactory : ProviderFactory<IApplication, ApplicationDefinition>, IApplicationFactory
{
public ApplicationFactory(IApplicationsRepository providerRepository, IEnumerable<IApplications> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
public ApplicationFactory(IApplicationsRepository providerRepository, IEnumerable<IApplication> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger)
{
}

@ -1,16 +1,65 @@
using NLog;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider.Events;
namespace NzbDrone.Core.Applications
{
public class ApplicationService
public class ApplicationService : IHandle<ProviderAddedEvent<IIndexer>>, IHandle<ProviderDeletedEvent<IIndexer>>, IHandle<ProviderAddedEvent<IApplication>>, IHandle<ProviderUpdatedEvent<IIndexer>>
{
private readonly IApplicationsFactory _applicationsFactory;
private readonly IApplicationFactory _applicationsFactory;
private readonly Logger _logger;
public ApplicationService(IApplicationsFactory applicationsFactory, Logger logger)
public ApplicationService(IApplicationFactory applicationsFactory, Logger logger)
{
_applicationsFactory = applicationsFactory;
_logger = logger;
}
// Sync Indexers on App Add if Sync Enabled
public void Handle(ProviderAddedEvent<IApplication> message)
{
var appDefinition = (ApplicationDefinition)message.Definition;
if (message.Definition.Enable)
{
var app = _applicationsFactory.GetInstance(appDefinition);
app.SyncIndexers();
}
}
public void Handle(ProviderAddedEvent<IIndexer> message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders();
// TODO: Only apps with Sync enabled
foreach (var app in enabledApps)
{
app.AddIndexer((IndexerDefinition)message.Definition);
}
}
public void Handle(ProviderDeletedEvent<IIndexer> message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders();
// TODO: Only remove indexers when Sync is Full
foreach (var app in enabledApps)
{
app.RemoveIndexer(message.ProviderId);
}
}
public void Handle(ProviderUpdatedEvent<IIndexer> message)
{
var enabledApps = _applicationsFactory.GetAvailableProviders();
// TODO: Only upate indexers when Sync is Full
foreach (var app in enabledApps)
{
app.UpdateIndexer((IndexerDefinition)message.Definition);
}
}
}
}

@ -0,0 +1,13 @@
using NzbDrone.Core.Indexers;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Applications
{
public interface IApplication : IProvider
{
void SyncIndexers();
void AddIndexer(IndexerDefinition indexer);
void UpdateIndexer(IndexerDefinition indexer);
void RemoveIndexer(int indexerId);
}
}

@ -1,8 +0,0 @@
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Applications
{
public interface IApplications : IProvider
{
}
}

@ -1,6 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Core.Applications.Radarr
{
@ -9,10 +12,13 @@ namespace NzbDrone.Core.Applications.Radarr
public override string Name => "Radarr";
private readonly IRadarrV3Proxy _radarrV3Proxy;
private readonly IIndexerFactory _indexerFactory;
public Radarr(IRadarrV3Proxy radarrV3Proxy)
public Radarr(IRadarrV3Proxy radarrV3Proxy, IIndexerFactory indexerFactory, IAppIndexerMapService appIndexerMapService, Logger logger)
: base(appIndexerMapService, logger)
{
_radarrV3Proxy = radarrV3Proxy;
_indexerFactory = indexerFactory;
}
public override ValidationResult Test()
@ -23,5 +29,85 @@ namespace NzbDrone.Core.Applications.Radarr
return new ValidationResult(failures);
}
public override void AddIndexer(IndexerDefinition indexer)
{
var schema = _radarrV3Proxy.GetIndexerSchema(Settings);
var newznab = schema.Where(i => i.Implementation == "Newznab").First();
var torznab = schema.Where(i => i.Implementation == "Torznab").First();
var radarrIndexer = BuildRadarrIndexer(indexer, indexer.Protocol == DownloadProtocol.Usenet ? newznab : torznab);
var remoteIndexer = _radarrV3Proxy.AddIndexer(radarrIndexer, Settings);
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = indexer.Id, RemoteIndexerId = remoteIndexer.Id });
}
public override void RemoveIndexer(int indexerId)
{
//Use the Id mapping here to delete the correct indexer
throw new System.NotImplementedException();
}
public override void UpdateIndexer(IndexerDefinition indexer)
{
//Use the Id mapping here to delete the correct indexer
throw new System.NotImplementedException();
}
public override void SyncIndexers()
{
// Pull Schema so we get the field mapping right
var schema = _radarrV3Proxy.GetIndexerSchema(Settings);
var newznab = schema.Where(i => i.Implementation == "Newznab").First();
var torznab = schema.Where(i => i.Implementation == "Torznab").First();
// Pull existing indexers from Radarr
var indexers = _radarrV3Proxy.GetIndexers(Settings);
//Pull all local indexers (TODO only those that support movie categories.)
var prowlarrIndexers = _indexerFactory.GetAvailableProviders();
//Pull mapping so we can check the mapping to see what already exists.
var indexerMappings = _appIndexerMapService.GetMappingsForApp(Definition.Id);
//Add new Indexers
foreach (var indexer in prowlarrIndexers)
{
//Don't add if it already exists in our mappings for this app (TODO should we check that it exists remote?)
if (indexerMappings.Any(x => x.IndexerId == indexer.Definition.Id))
{
continue;
}
var definition = (IndexerDefinition)indexer.Definition;
var radarrIndexer = BuildRadarrIndexer(definition, definition.Protocol == DownloadProtocol.Usenet ? newznab : torznab);
var remoteIndexer = _radarrV3Proxy.AddIndexer(radarrIndexer, Settings);
_appIndexerMapService.Insert(new AppIndexerMap { AppId = Definition.Id, IndexerId = definition.Id, RemoteIndexerId = remoteIndexer.Id });
}
//Delete Indexers that need Deleting.
}
private RadarrIndexer BuildRadarrIndexer(IndexerDefinition indexer, RadarrIndexer schema)
{
var radarrIndexer = new RadarrIndexer
{
Id = 0,
Name = $"{indexer.Name} (Prowlarr)",
EnableRss = indexer.EnableRss,
EnableAutomaticSearch = indexer.EnableAutomaticSearch,
EnableInteractiveSearch = indexer.EnableInteractiveSearch,
Priority = indexer.Priority,
Implementation = indexer.Protocol == DownloadProtocol.Usenet ? "Newznab" : "Torznab",
ConfigContract = schema.ConfigContract,
Fields = schema.Fields,
};
radarrIndexer.Fields.FirstOrDefault(x => x.Name == "baseUrl").Value = $"{Settings.ProwlarrUrl}/api/v1/indexer/1/newznab";
return radarrIndexer;
}
}
}

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NzbDrone.Core.Applications.Radarr
{
public class RadarrField
{
public int Order { get; set; }
public string Name { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public string HelpText { get; set; }
public string HelpLink { get; set; }
public object Value { get; set; }
public string Type { get; set; }
public bool Advanced { get; set; }
public string Section { get; set; }
public string Hidden { get; set; }
public RadarrField Clone()
{
return (RadarrField)MemberwiseClone();
}
}
}

@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace NzbDrone.Core.Applications.Radarr
{
public class RadarrIndexer
{
public int Id { get; set; }
public bool EnableRss { get; set; }
public bool EnableAutomaticSearch { get; set; }
public bool EnableInteractiveSearch { get; set; }
public int Priority { get; set; }
public string Name { get; set; }
public string ImplementationName { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public string InfoLink { get; set; }
public HashSet<int> Tags { get; set; }
public List<RadarrField> Fields { get; set; }
}
}

@ -10,6 +10,7 @@ namespace NzbDrone.Core.Applications.Radarr
public RadarrSettingsValidator()
{
RuleFor(c => c.BaseUrl).IsValidUrl();
RuleFor(c => c.ProwlarrUrl).IsValidUrl();
RuleFor(c => c.ApiKey).NotEmpty();
}
}
@ -20,12 +21,17 @@ namespace NzbDrone.Core.Applications.Radarr
public RadarrSettings()
{
ProwlarrUrl = "http://localhost:9696";
BaseUrl = "http://localhost:7878";
}
[FieldDefinition(0, Label = "Radarr Server", HelpText = "Radarr server URL, including http(s):// and port if needed")]
[FieldDefinition(0, Label = "Prowlarr Server", HelpText = "Prowlarr server URL as Radarr sees it, including http(s):// and port if needed")]
public string ProwlarrUrl { get; set; }
[FieldDefinition(1, Label = "Radarr Server", HelpText = "Radarr server URL, including http(s):// and port if needed")]
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]
[FieldDefinition(2, Label = "ApiKey", Privacy = PrivacyLevel.ApiKey, HelpText = "The ApiKey generated by Radarr in Settings/General")]
public string ApiKey { get; set; }
public NzbDroneValidationResult Validate()

@ -1,15 +1,19 @@
using System;
using System.Collections.Generic;
using System.Net;
using FluentValidation.Results;
using Newtonsoft.Json;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Applications.Radarr
{
public interface IRadarrV3Proxy
{
RadarrIndexer AddIndexer(RadarrIndexer indexer, RadarrSettings settings);
List<RadarrIndexer> GetIndexers(RadarrSettings settings);
List<RadarrIndexer> GetIndexerSchema(RadarrSettings settings);
ValidationFailure Test(RadarrSettings settings);
}
@ -26,7 +30,35 @@ namespace NzbDrone.Core.Applications.Radarr
public RadarrStatus GetStatus(RadarrSettings settings)
{
return Execute<RadarrStatus>("/api/v3/system/status", settings);
var request = BuildRequest(settings, "/api/v3/system/status", HttpMethod.GET);
return Execute<RadarrStatus>(request);
}
public List<RadarrIndexer> GetIndexers(RadarrSettings settings)
{
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.GET);
return Execute<List<RadarrIndexer>>(request);
}
public void RemoveIndexer(int indexerId, RadarrSettings settings)
{
var request = BuildRequest(settings, $"/api/v3/indexer/{indexerId}", HttpMethod.DELETE);
var response = _httpClient.Execute(request);
}
public List<RadarrIndexer> GetIndexerSchema(RadarrSettings settings)
{
var request = BuildRequest(settings, "/api/v3/indexer/schema", HttpMethod.GET);
return Execute<List<RadarrIndexer>>(request);
}
public RadarrIndexer AddIndexer(RadarrIndexer indexer, RadarrSettings settings)
{
var request = BuildRequest(settings, "/api/v3/indexer", HttpMethod.POST);
request.SetContent(indexer.ToJson());
return Execute<RadarrIndexer>(request);
}
public ValidationFailure Test(RadarrSettings settings)
@ -55,20 +87,25 @@ namespace NzbDrone.Core.Applications.Radarr
return null;
}
private TResource Execute<TResource>(string resource, RadarrSettings settings)
where TResource : new()
private HttpRequest BuildRequest(RadarrSettings settings, string resource, HttpMethod method)
{
if (settings.BaseUrl.IsNullOrWhiteSpace() || settings.ApiKey.IsNullOrWhiteSpace())
{
return new TResource();
}
var baseUrl = settings.BaseUrl.TrimEnd('/');
var request = new HttpRequestBuilder(baseUrl).Resource(resource).Accept(HttpAccept.Json)
.SetHeader("X-Api-Key", settings.ApiKey).Build();
var request = new HttpRequestBuilder(baseUrl).Resource(resource)
.SetHeader("X-Api-Key", settings.ApiKey)
.Build();
var response = _httpClient.Get(request);
request.Headers.ContentType = "application/json";
request.Method = method;
return request;
}
private TResource Execute<TResource>(HttpRequest request)
where TResource : new()
{
var response = _httpClient.Execute(request);
var results = JsonConvert.DeserializeObject<TResource>(response.Content);

@ -48,6 +48,11 @@ namespace NzbDrone.Core.Datastore.Migration
.WithColumn("Priority").AsInt32().NotNullable().WithDefaultValue(25)
.WithColumn("Added").AsDateTime();
Create.TableForModel("ApplicationIndexerMapping")
.WithColumn("IndexerId").AsInt32()
.WithColumn("AppId").AsInt32()
.WithColumn("RemoteIndexerId").AsInt32();
Create.TableForModel("Applications")
.WithColumn("Name").AsString().Unique()
.WithColumn("Implementation").AsString()

@ -61,6 +61,8 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<Tag>("Tags").RegisterModel();
Mapper.Entity<AppIndexerMap>("ApplicationIndexerMapping").RegisterModel();
Mapper.Entity<User>("Users").RegisterModel();
Mapper.Entity<CommandModel>("Commands").RegisterModel()
.Ignore(c => c.Message);

@ -2,7 +2,7 @@ using NzbDrone.Core.Applications;
namespace Prowlarr.Api.V1.Application
{
public class ApplicationModule : ProviderModuleBase<ApplicationResource, IApplications, ApplicationDefinition>
public class ApplicationModule : ProviderModuleBase<ApplicationResource, IApplication, ApplicationDefinition>
{
public static readonly ApplicationResourceMapper ResourceMapper = new ApplicationResourceMapper();

Loading…
Cancel
Save