New: Add Subsonic library update and notify (#368)
* New: Add Subsonic library update and notify * New: Subsonic setting for to allow connection via SSLpull/371/head
parent
598e3eb23b
commit
3344810653
@ -0,0 +1,95 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Subsonic
|
||||||
|
{
|
||||||
|
public class Subsonic : NotificationBase<SubsonicSettings>
|
||||||
|
{
|
||||||
|
private readonly ISubsonicService _subsonicService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public Subsonic(ISubsonicService subsonicService, Logger logger)
|
||||||
|
{
|
||||||
|
_subsonicService = subsonicService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Link => "http://subsonic.org/";
|
||||||
|
|
||||||
|
public override void OnGrab(GrabMessage grabMessage)
|
||||||
|
{
|
||||||
|
const string header = "Lidarr - Grabbed";
|
||||||
|
|
||||||
|
Notify(Settings, header, grabMessage.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnAlbumDownload(AlbumDownloadMessage message)
|
||||||
|
{
|
||||||
|
const string header = "Lidarr - Downloaded";
|
||||||
|
|
||||||
|
Notify(Settings, header, message.Message);
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDownload(TrackDownloadMessage message)
|
||||||
|
{
|
||||||
|
const string header = "Lidarr - Downloaded";
|
||||||
|
|
||||||
|
Notify(Settings, header, message.Message);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRename(Artist artist)
|
||||||
|
{
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "Subsonic";
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(_subsonicService.Test(Settings, "Success! Subsonic has been successfully configured!"));
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Notify(SubsonicSettings settings, string header, string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Settings.Notify)
|
||||||
|
{
|
||||||
|
_subsonicService.Notify(Settings, $"{header} - {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
var logMessage = $"Unable to connect to Subsonic Host: {Settings.Host}:{Settings.Port}";
|
||||||
|
_logger.Debug(ex, logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Settings.UpdateLibrary)
|
||||||
|
{
|
||||||
|
_subsonicService.Update(Settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
var logMessage = $"Unable to connect to Subsonic Host: {Settings.Host}:{Settings.Port}";
|
||||||
|
_logger.Debug(ex, logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Subsonic
|
||||||
|
{
|
||||||
|
public class SubsonicAuthenticationException : SubsonicException
|
||||||
|
{
|
||||||
|
public SubsonicAuthenticationException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubsonicAuthenticationException(string message, params object[] args)
|
||||||
|
: base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Subsonic
|
||||||
|
{
|
||||||
|
public class SubsonicException : NzbDroneException
|
||||||
|
{
|
||||||
|
public SubsonicException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubsonicException(string message, params object[] args) : base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Rest;
|
||||||
|
using RestSharp;
|
||||||
|
using System.IO;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Subsonic
|
||||||
|
{
|
||||||
|
public interface ISubsonicServerProxy
|
||||||
|
{
|
||||||
|
void Notify(SubsonicSettings settings, string message);
|
||||||
|
void Update(SubsonicSettings settings);
|
||||||
|
string Version(SubsonicSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubsonicServerProxy : ISubsonicServerProxy
|
||||||
|
{
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public SubsonicServerProxy(Logger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Notify(SubsonicSettings settings, string message)
|
||||||
|
{
|
||||||
|
var resource = "addChatMessage";
|
||||||
|
var request = GetSubsonicServerRequest(resource, Method.GET, settings);
|
||||||
|
request.AddParameter("message", message);
|
||||||
|
var client = GetSubsonicServerClient(settings);
|
||||||
|
var response = client.Execute(request);
|
||||||
|
|
||||||
|
_logger.Trace("Update response: {0}", response.Content);
|
||||||
|
CheckForError(response, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(SubsonicSettings settings)
|
||||||
|
{
|
||||||
|
var resource = "startScan";
|
||||||
|
var request = GetSubsonicServerRequest(resource, Method.GET, settings);
|
||||||
|
var client = GetSubsonicServerClient(settings);
|
||||||
|
var response = client.Execute(request);
|
||||||
|
|
||||||
|
_logger.Trace("Update response: {0}", response.Content);
|
||||||
|
CheckForError(response, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Version(SubsonicSettings settings)
|
||||||
|
{
|
||||||
|
var request = GetSubsonicServerRequest("ping", Method.GET, settings);
|
||||||
|
var client = GetSubsonicServerClient(settings);
|
||||||
|
var response = client.Execute(request);
|
||||||
|
|
||||||
|
_logger.Trace("Version response: {0}", response.Content);
|
||||||
|
CheckForError(response, settings);
|
||||||
|
|
||||||
|
var xDoc = XDocument.Load(new StringReader(response.Content.Replace("&", "&")));
|
||||||
|
var version = xDoc.Root?.Attribute("version")?.Value;
|
||||||
|
|
||||||
|
if (version == null)
|
||||||
|
{
|
||||||
|
throw new SubsonicException("Could not read version from Subsonic");
|
||||||
|
}
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestClient GetSubsonicServerClient(SubsonicSettings settings)
|
||||||
|
{
|
||||||
|
var protocol = settings.UseSsl ? "https" : "http";
|
||||||
|
|
||||||
|
return RestClientFactory.BuildClient(string.Format("{0}://{1}:{2}/rest", protocol, settings.Host, settings.Port));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestRequest GetSubsonicServerRequest(string resource, Method method, SubsonicSettings settings)
|
||||||
|
{
|
||||||
|
var request = new RestRequest(resource, method);
|
||||||
|
|
||||||
|
if (settings.Username.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
request.AddParameter("u", settings.Username);
|
||||||
|
request.AddParameter("p", settings.Password);
|
||||||
|
request.AddParameter("c", "Lidarr");
|
||||||
|
request.AddParameter("v", "1.15.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForError(IRestResponse response, SubsonicSettings settings)
|
||||||
|
{
|
||||||
|
_logger.Trace("Checking for error");
|
||||||
|
|
||||||
|
var xDoc = XDocument.Load(new StringReader(response.Content.Replace("&", "&")));
|
||||||
|
var status = xDoc.Root?.Attribute("status")?.Value;
|
||||||
|
|
||||||
|
if (status == null)
|
||||||
|
{
|
||||||
|
throw new SubsonicException("Invalid Response, Check Server Settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "failed")
|
||||||
|
{
|
||||||
|
var ns = xDoc.Root.GetDefaultNamespace();
|
||||||
|
var error = xDoc.Root.Element(XName.Get("error", ns.ToString()));
|
||||||
|
var errorMessage = error?.Attribute("message")?.Value;
|
||||||
|
var errorCode = error?.Attribute("code")?.Value;
|
||||||
|
|
||||||
|
if (errorCode == null)
|
||||||
|
{
|
||||||
|
throw new SubsonicException("Subsonic returned error, check settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorCode == "40")
|
||||||
|
{
|
||||||
|
throw new SubsonicAuthenticationException(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SubsonicException(errorMessage);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.Content.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
_logger.Trace("No response body returned, no error detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Trace("No error detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Subsonic
|
||||||
|
{
|
||||||
|
public interface ISubsonicService
|
||||||
|
{
|
||||||
|
void Notify(SubsonicSettings settings, string message);
|
||||||
|
void Update(SubsonicSettings settings);
|
||||||
|
ValidationFailure Test(SubsonicSettings settings, string message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubsonicService : ISubsonicService
|
||||||
|
{
|
||||||
|
private readonly ISubsonicServerProxy _proxy;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public SubsonicService(ISubsonicServerProxy proxy,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Notify(SubsonicSettings settings, string message)
|
||||||
|
{
|
||||||
|
_proxy.Notify(settings, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(SubsonicSettings settings)
|
||||||
|
{
|
||||||
|
_proxy.Update(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetVersion(SubsonicSettings settings)
|
||||||
|
{
|
||||||
|
var result = _proxy.Version(settings);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationFailure Test(SubsonicSettings settings, string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Debug("Determining version of Host: {0}", settings.Address);
|
||||||
|
var version = GetVersion(settings);
|
||||||
|
_logger.Debug("Version is: {0}", version);
|
||||||
|
}
|
||||||
|
catch (SubsonicAuthenticationException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to connect to Subsonic Server");
|
||||||
|
return new ValidationFailure("Username", "Incorrect username or password");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to connect to Subsonic Server");
|
||||||
|
return new ValidationFailure("Host", "Unable to connect to Subsonic Server");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Subsonic
|
||||||
|
{
|
||||||
|
public class SubsonicSettingsValidator : AbstractValidator<SubsonicSettings>
|
||||||
|
{
|
||||||
|
public SubsonicSettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Host).ValidHost();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubsonicSettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly SubsonicSettingsValidator Validator = new SubsonicSettingsValidator();
|
||||||
|
|
||||||
|
public SubsonicSettings()
|
||||||
|
{
|
||||||
|
Port = 4040;
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Host")]
|
||||||
|
public string Host { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "Port")]
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "Username")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(4, Label = "Notify with Chat Message", Type = FieldType.Checkbox)]
|
||||||
|
public bool Notify { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(5, Label = "Update Library", HelpText = "Update Library on Download & Rename?", Type = FieldType.Checkbox)]
|
||||||
|
public bool UpdateLibrary { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(6, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Connect to Subsonic over HTTPS instead of HTTP")]
|
||||||
|
public bool UseSsl { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string Address => string.Format("{0}:{1}", Host, Port);
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue