using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; namespace NzbDrone.Core.Notifications.Plex.Server { public interface IPlexServerProxy { List GetArtistSections(PlexServerSettings settings); void Update(int sectionId, PlexServerSettings settings); void UpdateArtist(int metadataId, PlexServerSettings settings); string Version(PlexServerSettings settings); List Preferences(PlexServerSettings settings); int? GetMetadataId(int sectionId, string mbId, string language, PlexServerSettings settings); } public class PlexServerProxy : IPlexServerProxy { private readonly IHttpClient _httpClient; private readonly IConfigService _configService; private readonly Logger _logger; public PlexServerProxy(IHttpClient httpClient, IConfigService configService, Logger logger) { _httpClient = httpClient; _configService = configService; _logger = logger; } public List GetArtistSections(PlexServerSettings settings) { var request = BuildRequest("library/sections", HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); if (response.Contains("_children")) { return Json.Deserialize(response) .Sections .Where(d => d.Type == "artist") .Select(s => new PlexSection { Id = s.Id, Language = s.Language, Locations = s.Locations, Type = s.Type }) .ToList(); } return Json.Deserialize>(response) .MediaContainer .Sections .Where(d => d.Type == "artist") .ToList(); } public void Update(int sectionId, PlexServerSettings settings) { var resource = $"library/sections/{sectionId}/refresh"; var request = BuildRequest(resource, HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); } public void UpdateArtist(int metadataId, PlexServerSettings settings) { var resource = $"library/metadata/{metadataId}/refresh"; var request = BuildRequest(resource, HttpMethod.Put, settings); var response = ProcessRequest(request); CheckForError(response); } public string Version(PlexServerSettings settings) { var request = BuildRequest("identity", HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); if (response.Contains("_children")) { return Json.Deserialize(response) .Version; } return Json.Deserialize>(response) .MediaContainer .Version; } public List Preferences(PlexServerSettings settings) { var request = BuildRequest(":/prefs", HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); if (response.Contains("_children")) { return Json.Deserialize(response) .Preferences; } return Json.Deserialize>(response) .MediaContainer .Preferences; } public int? GetMetadataId(int sectionId, string mbId, string language, PlexServerSettings settings) { var guid = string.Format("com.plexapp.agents.lastfm://{0}?lang={1}", mbId, language); // TODO Plex Route for MB? LastFM? var resource = $"library/sections/{sectionId}/all?guid={System.Web.HttpUtility.UrlEncode(guid)}"; var request = BuildRequest(resource, HttpMethod.Get, settings); var response = ProcessRequest(request); CheckForError(response); List items; if (response.Contains("_children")) { items = Json.Deserialize(response) .Items; } else { items = Json.Deserialize>(response) .MediaContainer .Items; } if (items == null || items.Empty()) { return null; } return items.First().Id; } private HttpRequestBuilder BuildRequest(string resource, HttpMethod method, PlexServerSettings settings) { var scheme = settings.UseSsl ? "https" : "http"; var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host.ToUrlHost()}:{settings.Port}") .Accept(HttpAccept.Json) .AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier) .AddQueryParam("X-Plex-Product", BuildInfo.AppName) .AddQueryParam("X-Plex-Platform", "Windows") .AddQueryParam("X-Plex-Platform-Version", "7") .AddQueryParam("X-Plex-Device-Name", BuildInfo.AppName) .AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString()); if (settings.AuthToken.IsNotNullOrWhiteSpace()) { requestBuilder.AddQueryParam("X-Plex-Token", settings.AuthToken); } requestBuilder.ResourceUrl = resource; requestBuilder.Method = method; return requestBuilder; } private string ProcessRequest(HttpRequestBuilder requestBuilder) { var httpRequest = requestBuilder.Build(); HttpResponse response; _logger.Debug("Url: {0}", httpRequest.Url); try { response = _httpClient.Execute(httpRequest); } catch (HttpException ex) { if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) { throw new PlexAuthenticationException("Unauthorized - AuthToken is invalid"); } throw new PlexException("Unable to connect to Plex Media Server. Status Code: {0}", ex.Response.StatusCode); } catch (WebException ex) { throw new PlexException("Unable to connect to Plex Media Server", ex); } return response.Content; } private void CheckForError(string response) { _logger.Trace("Checking for error"); if (response.IsNullOrWhiteSpace()) { _logger.Trace("No response body returned, no error detected"); return; } var error = response.Contains("_children") ? Json.Deserialize(response) : Json.Deserialize>(response).MediaContainer; if (error != null && !error.Error.IsNullOrWhiteSpace()) { throw new PlexException(error.Error); } _logger.Trace("No error detected"); } } }