diff --git a/src/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs b/src/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs
index bb1dea1f0..90b0d2c5f 100644
--- a/src/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs
+++ b/src/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs
@@ -33,79 +33,6 @@ namespace NzbDrone.Core.Test.NotificationTests
};
}
- [Test]
- public void GetSectionKeys_should_return_single_section_key_when_only_one_show_section()
- {
- var response = "";
- Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
-
- Mocker.GetMock().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
- .Returns(stream);
-
- var result = Mocker.Resolve().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
-
-
- result.Should().HaveCount(1);
- result.First().Should().Be(5);
- }
-
- [Test]
- public void GetSectionKeys_should_return_single_section_key_when_only_one_show_section_with_other_sections()
- {
-
-
- var response = "";
- Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
-
- Mocker.GetMock().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
- .Returns(stream);
-
-
- var result = Mocker.Resolve().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
-
-
- result.Should().HaveCount(1);
- result.First().Should().Be(5);
- }
-
- [Test]
- public void GetSectionKeys_should_return_multiple_section_keys_when_there_are_multiple_show_sections()
- {
-
-
- var response = "";
- Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
-
- Mocker.GetMock().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
- .Returns(stream);
-
-
- var result = Mocker.Resolve().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
-
-
- result.Should().HaveCount(2);
- result.First().Should().Be(5);
- result.Last().Should().Be(6);
- }
-
- [Test]
- public void UpdateSection_should_update_section()
- {
-
-
- var response = "";
- Stream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
-
- Mocker.GetMock().Setup(s => s.DownloadString("http://localhost:32400/library/sections/5/refresh"))
- .Returns(response);
-
-
- Mocker.Resolve().UpdateSection(new PlexServerSettings { Host = "localhost", Port = 32400 }, 5);
-
-
-
- }
-
[Test]
public void Notify_should_send_notification()
{
diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexError.cs b/src/NzbDrone.Core/Notifications/Plex/PlexError.cs
new file mode 100644
index 000000000..78e733050
--- /dev/null
+++ b/src/NzbDrone.Core/Notifications/Plex/PlexError.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace NzbDrone.Core.Notifications.Plex
+{
+ public class PlexError
+ {
+ public String Error { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexException.cs b/src/NzbDrone.Core/Notifications/Plex/PlexException.cs
new file mode 100644
index 000000000..5447c563b
--- /dev/null
+++ b/src/NzbDrone.Core/Notifications/Plex/PlexException.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NzbDrone.Common.Exceptions;
+
+namespace NzbDrone.Core.Notifications.Plex
+{
+ public class PlexException : NzbDroneException
+ {
+ public PlexException(string message) : base(message)
+ {
+ }
+
+ public PlexException(string message, params object[] args) : base(message, args)
+ {
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexSection.cs b/src/NzbDrone.Core/Notifications/Plex/PlexSection.cs
new file mode 100644
index 000000000..d43fb53f6
--- /dev/null
+++ b/src/NzbDrone.Core/Notifications/Plex/PlexSection.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace NzbDrone.Core.Notifications.Plex
+{
+ public class PlexSection
+ {
+ public Int32 Id { get; set; }
+ public String Path { get; set; }
+ }
+
+ public class PlexDirectory
+ {
+ public String Type { get; set; }
+
+ [JsonProperty("_children")]
+ public List Sections { get; set; }
+ }
+
+ public class PlexMediaContainer
+ {
+ [JsonProperty("_children")]
+ public List Directories { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServerProxy.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServerProxy.cs
new file mode 100644
index 000000000..e0cf3e061
--- /dev/null
+++ b/src/NzbDrone.Core/Notifications/Plex/PlexServerProxy.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Newtonsoft.Json.Linq;
+using NzbDrone.Common;
+using NzbDrone.Common.Cache;
+using NzbDrone.Common.Serializer;
+using RestSharp;
+
+namespace NzbDrone.Core.Notifications.Plex
+{
+ public interface IPlexServerProxy
+ {
+ List GetTvSections(PlexServerSettings settings);
+ void Update(int sectionId, PlexServerSettings settings);
+ }
+
+ public class PlexServerProxy : IPlexServerProxy
+ {
+ private readonly ICached _authCache;
+
+ public PlexServerProxy(ICacheManager cacheManager)
+ {
+ _authCache = cacheManager.GetCache(GetType(), "authCache");
+ }
+
+ public List GetTvSections(PlexServerSettings settings)
+ {
+ var request = GetPlexServerRequest("library/sections", Method.GET, settings);
+ var client = GetPlexServerClient(settings);
+
+ var response = client.Execute(request);
+
+ return Json.Deserialize(response.Content)
+ .Directories
+ .Where(d => d.Type == "show")
+ .SelectMany(d => d.Sections)
+ .ToList();
+ }
+
+ public void Update(int sectionId, PlexServerSettings settings)
+ {
+ var resource = String.Format("library/sections/{2}/refresh");
+ var request = GetPlexServerRequest(resource, Method.GET, settings);
+ var client = GetPlexServerClient(settings);
+
+ var response = client.Execute(request);
+ }
+
+ private String Authenticate(string username, string password)
+ {
+ var request = GetMyPlexRequest("users/sign_in.json", Method.POST);
+ var client = GetMyPlexClient(username, password);
+
+ var response = client.Execute(request);
+ CheckForError(response.Content);
+
+ var user = Json.Deserialize(JObject.Parse(response.Content).SelectToken("user").ToString());
+
+ _authCache.Set(username, user.AuthenticationToken);
+
+ return user.AuthenticationToken;
+ }
+
+ private RestClient GetMyPlexClient(string username, string password)
+ {
+ var client = new RestClient("https://my.plexapp.com");
+ client.Authenticator = new HttpBasicAuthenticator(username, password);
+
+ return client;
+ }
+
+ private RestRequest GetMyPlexRequest(string resource, Method method)
+ {
+ var request = new RestRequest(resource, method);
+ request.AddHeader("X-Plex-Platform", "Windows");
+ request.AddHeader("X-Plex-Platform-Version", "7");
+ request.AddHeader("X-Plex-Provides", "player");
+ request.AddHeader("X-Plex-Client-Identifier", "AB6CCCC7-5CF5-4523-826A-B969E0FFD8A0");
+ request.AddHeader("X-Plex-Product", "PlexWMC");
+ request.AddHeader("X-Plex-Version", "0");
+
+ return request;
+
+ }
+
+ private RestClient GetPlexServerClient(PlexServerSettings settings)
+ {
+ return new RestClient(String.Format("http://{0}:{1}", settings.Host, settings.Port));
+ }
+
+ private RestRequest GetPlexServerRequest(string resource, Method method, PlexServerSettings settings)
+ {
+ var request = new RestRequest(resource, method);
+ request.AddHeader("Accept", "application/json");
+
+ if (!settings.Username.IsNullOrWhiteSpace())
+ {
+ request.AddParameter("X-Plex-Token", GetAuthenticationToken(settings.Username, settings.Password));
+ }
+
+ return request;
+ }
+
+ private string GetAuthenticationToken(string username, string password)
+ {
+ return _authCache.Get(username, () => Authenticate(username, password));
+ }
+
+ private void CheckForError(string response)
+ {
+ var error = Json.Deserialize(response);
+
+ if (error != null && !error.Error.IsNullOrWhiteSpace())
+ {
+ throw new PlexException(error.Error);
+ }
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs
index 272d0efcc..89be29eb0 100644
--- a/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs
+++ b/src/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Notifications.Plex
[FieldDefinition(2, Label = "Username")]
public String Username { get; set; }
- [FieldDefinition(3, Label = "Password")]
+ [FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; }
[FieldDefinition(4, Label = "Update Library", Type = FieldType.Checkbox)]
diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexService.cs b/src/NzbDrone.Core/Notifications/Plex/PlexService.cs
index 0d4c6608b..a27b55d3a 100644
--- a/src/NzbDrone.Core/Notifications/Plex/PlexService.cs
+++ b/src/NzbDrone.Core/Notifications/Plex/PlexService.cs
@@ -18,11 +18,13 @@ namespace NzbDrone.Core.Notifications.Plex
public class PlexService : IPlexService, IExecute, IExecute
{
private readonly IHttpProvider _httpProvider;
+ private readonly IPlexServerProxy _plexServerProxy;
private readonly Logger _logger;
- public PlexService(IHttpProvider httpProvider, Logger logger)
+ public PlexService(IHttpProvider httpProvider, IPlexServerProxy plexServerProxy, Logger logger)
{
_httpProvider = httpProvider;
+ _plexServerProxy = plexServerProxy;
_logger = logger;
}
@@ -45,7 +47,7 @@ namespace NzbDrone.Core.Notifications.Plex
{
_logger.Debug("Sending Update Request to Plex Server");
var sections = GetSectionKeys(settings);
- sections.ForEach(s => UpdateSection(settings, s));
+ sections.ForEach(s => UpdateSection(s, settings));
}
catch(Exception ex)
@@ -55,26 +57,21 @@ namespace NzbDrone.Core.Notifications.Plex
}
}
- public List GetSectionKeys(PlexServerSettings settings)
+ private List GetSectionKeys(PlexServerSettings settings)
{
_logger.Debug("Getting sections from Plex host: {0}", settings.Host);
- var url = String.Format("http://{0}:{1}/library/sections", settings.Host, settings.Port);
- var xmlStream = _httpProvider.DownloadStream(url, GetCredentials(settings));
- var xDoc = XDocument.Load(xmlStream);
- var mediaContainer = xDoc.Descendants("MediaContainer").FirstOrDefault();
- var directories = mediaContainer.Descendants("Directory").Where(x => x.Attribute("type").Value == "show");
- return directories.Select(d => Int32.Parse(d.Attribute("key").Value)).ToList();
+ return _plexServerProxy.GetTvSections(settings).Select(s => s.Id).ToList();
}
- public void UpdateSection(PlexServerSettings settings, int key)
+ private void UpdateSection(int key, PlexServerSettings settings)
{
_logger.Debug("Updating Plex host: {0}, Section: {1}", settings.Host, key);
- var url = String.Format("http://{0}:{1}/library/sections/{2}/refresh", settings.Host, settings.Port, key);
- _httpProvider.DownloadString(url, GetCredentials(settings));
+
+ _plexServerProxy.Update(key, settings);
}
- public string SendCommand(string host, int port, string command, string username, string password)
+ private string SendCommand(string host, int port, string command, string username, string password)
{
var url = String.Format("http://{0}:{1}/xbmcCmds/xbmcHttp?command={2}", host, port, command);
@@ -86,13 +83,6 @@ namespace NzbDrone.Core.Notifications.Plex
return _httpProvider.DownloadString(url);
}
- private NetworkCredential GetCredentials(PlexServerSettings settings)
- {
- if (settings.Username.IsNullOrWhiteSpace()) return null;
-
- return new NetworkCredential(settings.Username, settings.Password);
- }
-
public void Execute(TestPlexClientCommand message)
{
_logger.Debug("Sending Test Notifcation to Plex Client: {0}", message.Host);
diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexUser.cs b/src/NzbDrone.Core/Notifications/Plex/PlexUser.cs
new file mode 100644
index 000000000..4ca9a642a
--- /dev/null
+++ b/src/NzbDrone.Core/Notifications/Plex/PlexUser.cs
@@ -0,0 +1,11 @@
+using System;
+using Newtonsoft.Json;
+
+namespace NzbDrone.Core.Notifications.Plex
+{
+ public class PlexUser
+ {
+ [JsonProperty("authentication_token")]
+ public String AuthenticationToken { get; set; }
+ }
+}
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index 9ec718804..a27327dba 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -377,6 +377,11 @@
+
+
+
+
+