Finished the bulk work for Headphones. Needs testing #32

pull/140/head
tidusjar 9 years ago
parent 9211f3db4f
commit 39a5032c3e

@ -33,6 +33,7 @@ namespace PlexRequests.Api.Interfaces
public interface IApiRequest public interface IApiRequest
{ {
T Execute<T>(IRestRequest request, Uri baseUri) where T : new(); T Execute<T>(IRestRequest request, Uri baseUri) where T : new();
IRestResponse Execute(IRestRequest request, Uri baseUri);
T ExecuteXml<T>(IRestRequest request, Uri baseUri) where T : class; T ExecuteXml<T>(IRestRequest request, Uri baseUri) where T : class;
T ExecuteJson<T>(IRestRequest request, Uri baseUri) where T : new(); T ExecuteJson<T>(IRestRequest request, Uri baseUri) where T : new();
} }

@ -25,13 +25,19 @@
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using PlexRequests.Api.Models.Music; using PlexRequests.Api.Models.Music;
namespace PlexRequests.Api.Interfaces namespace PlexRequests.Api.Interfaces
{ {
public interface IHeadphonesApi public interface IHeadphonesApi
{ {
bool AddAlbum(string apiKey, Uri baseUrl, string albumId); Task<bool> AddAlbum(string apiKey, Uri baseUrl, string albumId);
HeadphonesVersion GetVersion(string apiKey, Uri baseUrl); HeadphonesVersion GetVersion(string apiKey, Uri baseUrl);
Task<bool> AddArtist(string apiKey, Uri baseUrl, string artistId);
Task<bool> QueueAlbum(string apiKey, Uri baseUrl, string albumId);
Task<List<HeadphonesGetIndex>> GetIndex(string apiKey, Uri baseUrl);
} }
} }

@ -0,0 +1,49 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: HeadphonesGetIndex.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Api.Models.Music
{
public class HeadphonesGetIndex
{
public string Status { get; set; }
public string ThumbURL { get; set; }
public string DateAdded { get; set; }
public string MetaCritic { get; set; }
public int? TotalTracks { get; set; }
public object Type { get; set; }
public int? IncludeExtras { get; set; }
public string ArtistName { get; set; }
public string LastUpdated { get; set; }
public string ReleaseDate { get; set; }
public string AlbumID { get; set; }
public string ArtistID { get; set; }
public string ArtworkURL { get; set; }
public string Extras { get; set; }
public int? HaveTracks { get; set; }
public string LatestAlbum { get; set; }
public string ArtistSortName { get; set; }
}
}

@ -42,6 +42,8 @@ namespace PlexRequests.Api.Models.Music
public class MusicBrainzReleaseInfo public class MusicBrainzReleaseInfo
{ {
[JsonProperty(PropertyName = "artist-credit")]
public List<ArtistCredit> ArtistCredits { get; set; }
public string date { get; set; } public string date { get; set; }
public string status { get; set; } public string status { get; set; }
public string asin { get; set; } public string asin { get; set; }

@ -63,6 +63,8 @@ namespace PlexRequests.Api.Models.Music
public class ArtistCredit public class ArtistCredit
{ {
public Artist artist { get; set; } public Artist artist { get; set; }
public string name { get; set; }
public string joinphrase { get; set; }
} }
public class ReleaseGroup public class ReleaseGroup

@ -50,6 +50,7 @@
<Compile Include="Movie\CouchPotatoStatus.cs" /> <Compile Include="Movie\CouchPotatoStatus.cs" />
<Compile Include="Music\HeadphonesAlbumSearchResult.cs" /> <Compile Include="Music\HeadphonesAlbumSearchResult.cs" />
<Compile Include="Music\HeadphonesArtistSearchResult.cs" /> <Compile Include="Music\HeadphonesArtistSearchResult.cs" />
<Compile Include="Music\HeadphonesGetIndex.cs" />
<Compile Include="Music\HeadphonesVersion.cs" /> <Compile Include="Music\HeadphonesVersion.cs" />
<Compile Include="Music\MusicBrainzCoverArt.cs" /> <Compile Include="Music\MusicBrainzCoverArt.cs" />
<Compile Include="Music\MusicBrainzReleaseInfo.cs" /> <Compile Include="Music\MusicBrainzReleaseInfo.cs" />

@ -64,6 +64,21 @@ namespace PlexRequests.Api
} }
public IRestResponse Execute(IRestRequest request, Uri baseUri)
{
var client = new RestClient { BaseUrl = baseUri };
var response = client.Execute(request);
if (response.ErrorException != null)
{
var message = "Error retrieving response. Check inner details for more info.";
throw new ApplicationException(message, response.ErrorException);
}
return response;
}
public T ExecuteXml<T>(IRestRequest request, Uri baseUri) where T : class public T ExecuteXml<T>(IRestRequest request, Uri baseUri) where T : class
{ {
var client = new RestClient { BaseUrl = baseUri }; var client = new RestClient { BaseUrl = baseUri };

@ -26,6 +26,7 @@
#endregion #endregion
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -33,6 +34,7 @@ using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Music; using PlexRequests.Api.Models.Music;
using PlexRequests.Helpers;
using RestSharp; using RestSharp;
@ -47,7 +49,7 @@ namespace PlexRequests.Api
private ApiRequest Api { get; } private ApiRequest Api { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public bool AddAlbum(string apiKey, Uri baseUrl, string albumId) public async Task<bool> AddAlbum(string apiKey, Uri baseUrl, string albumId)
{ {
Log.Trace("Adding album: {0}", albumId); Log.Trace("Adding album: {0}", albumId);
var request = new RestRequest var request = new RestRequest
@ -61,12 +63,91 @@ namespace PlexRequests.Api
try try
{ {
//var result = Api.Execute<string>(request, baseUrl); var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false);
return false; Log.Trace("Add Album Result: {0}", result.DumpJson());
var albumResult = result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase);
Log.Info("Album add result {0}", albumResult);
return albumResult;
}
catch (JsonSerializationException jse)
{
Log.Error(jse);
return false; // If there is no matching result we do not get returned a JSON string, it just returns "false".
}
}
public async Task<List<HeadphonesGetIndex>> GetIndex(string apiKey, Uri baseUrl)
{
var request = new RestRequest
{
Resource = "/api",
Method = Method.GET
};
request.AddQueryParameter("apikey", apiKey);
request.AddQueryParameter("cmd", "getIndex");
try
{
var result = await Task.Run(() => Api.ExecuteJson<List<HeadphonesGetIndex>>(request, baseUrl)).ConfigureAwait(false);
return result;
}
catch (JsonSerializationException jse)
{
Log.Error(jse);
return new List<HeadphonesGetIndex>();
}
}
public async Task<bool> AddArtist(string apiKey, Uri baseUrl, string artistId)
{
Log.Trace("Adding Artist: {0}", artistId);
var request = new RestRequest
{
Resource = "/api",
Method = Method.GET
};
request.AddQueryParameter("apikey", apiKey);
request.AddQueryParameter("cmd", "addArtist");
request.AddQueryParameter("id", artistId);
try
{
var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false);
Log.Info("Add Artist Result: {0}", result.Content);
Log.Trace("Add Artist Result: {0}", result.DumpJson());
return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase);
}
catch (JsonSerializationException jse)
{
Log.Error(jse);
return false; // If there is no matching result we do not get returned a JSON string, it just returns "false".
}
}
public async Task<bool> QueueAlbum(string apiKey, Uri baseUrl, string albumId)
{
Log.Trace("Queing album: {0}", albumId);
var request = new RestRequest
{
Resource = "/api?cmd=queueAlbum&id={albumId}",
Method = Method.GET
};
request.AddQueryParameter("apikey", apiKey);
request.AddUrlSegment("albumId", albumId);
try
{
var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false);
Log.Info("Queue Result: {0}", result.Content);
Log.Trace("Queue Result: {0}", result.DumpJson());
return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase);
} }
catch (JsonSerializationException jse) catch (JsonSerializationException jse)
{ {
Log.Warn(jse); Log.Error(jse);
return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". return false; // If there is no matching result we do not get returned a JSON string, it just returns "false".
} }
} }
@ -88,7 +169,7 @@ namespace PlexRequests.Api
} }
catch (JsonSerializationException jse) catch (JsonSerializationException jse)
{ {
Log.Warn(jse); Log.Error(jse);
return new HeadphonesVersion(); // If there is no matching result we do not get returned a JSON string, it just returns "false". return new HeadphonesVersion(); // If there is no matching result we do not get returned a JSON string, it just returns "false".
} }
} }

@ -73,10 +73,12 @@ namespace PlexRequests.Api
Log.Trace("Getting album: {0}", releaseId); Log.Trace("Getting album: {0}", releaseId);
var request = new RestRequest var request = new RestRequest
{ {
Resource = "release/{albumId}?fmt=json", Resource = "release/{albumId}",
Method = Method.GET Method = Method.GET
}; };
request.AddUrlSegment("albumId", releaseId); request.AddUrlSegment("albumId", releaseId);
request.AddQueryParameter("fmt", "json");
request.AddQueryParameter("inc", "artists");
try try
{ {

@ -38,7 +38,8 @@ namespace PlexRequests.Store
public string SeasonsRequested { get; set; } public string SeasonsRequested { get; set; }
public string MusicBrainzId { get; set; } public string MusicBrainzId { get; set; }
public List<string> RequestedUsers { get; set; } public List<string> RequestedUsers { get; set; }
public string Artist { get; set; } public string ArtistName { get; set; }
public string ArtistId { get; set; }
[JsonIgnore] [JsonIgnore]
public List<string> AllUsers public List<string> AllUsers

@ -0,0 +1,151 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: HeadphonesSender.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Store;
namespace PlexRequests.UI.Helpers
{
public class HeadphonesSender
{
public HeadphonesSender(IHeadphonesApi api, HeadphonesSettings settings, IRequestService request)
{
Api = api;
Settings = settings;
RequestService = request;
}
private int WaitTime => 1000;
private int CounterMax => 30;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private IHeadphonesApi Api { get; }
private IRequestService RequestService { get; }
private HeadphonesSettings Settings { get; }
public async Task<bool> AddAlbum(RequestedModel request)
{
var addArtistResult = await AddArtist(request);
if (!addArtistResult)
{
return false;
}
// Artist is now active
// Add album
var albumResult = await Api.AddAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId);
if (!albumResult)
{
Log.Error("Couldn't add the album to headphones");
}
// Set the status to wanted and search
var status = await SetAlbumStatus(request);
if (!status)
{
return false;
}
// Approve it
request.Approved = true;
// Update the record
var updated = RequestService.UpdateRequest(request);
return updated;
}
private async Task<bool> AddArtist(RequestedModel request)
{
var index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri);
var artistExists = index.Any(x => x.ArtistID == request.ArtistId);
if (!artistExists)
{
var artistAdd = Api.AddArtist(Settings.ApiKey, Settings.FullUri, request.ArtistId);
Log.Info("Artist add result : {0}", artistAdd);
}
var counter = 0;
while (index.All(x => x.ArtistID != request.ArtistId))
{
Thread.Sleep(WaitTime);
counter++;
Log.Trace("Artist is still not present in the index. Counter = {0}", counter);
index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri);
if (counter > CounterMax)
{
Log.Trace("Artist is still not present in the index. Counter = {0}. Returning false", counter);
return false;
}
}
counter = 0;
var artistStatus = index.Where(x => x.ArtistID == request.ArtistId).Select(x => x.Status).FirstOrDefault();
while (artistStatus != "Active")
{
Thread.Sleep(WaitTime);
counter++;
Log.Trace("Artist status {1}. Counter = {0}", counter, artistStatus);
index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri);
artistStatus = index.Where(x => x.ArtistID == request.ArtistId).Select(x => x.Status).FirstOrDefault();
if (counter > CounterMax)
{
Log.Trace("Artist status is still not active. Counter = {0}. Returning false", counter);
return false;
}
}
return true;
}
private async Task<bool> SetAlbumStatus(RequestedModel request)
{
var counter = 0;
var setStatus = await Api.QueueAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId);
while (!setStatus)
{
Thread.Sleep(WaitTime);
counter++;
Log.Trace("Setting Album status. Counter = {0}", counter);
setStatus = await Api.QueueAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId);
if (counter > CounterMax)
{
Log.Trace("Album status is still not active. Counter = {0}. Returning false", counter);
return false;
}
}
return true;
}
}
}

@ -47,7 +47,8 @@ namespace PlexRequests.UI.Modules
{ {
public ApprovalModule(IRequestService service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi, public ApprovalModule(IRequestService service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings) : base("approval") ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings,
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi) : base("approval")
{ {
this.RequiresAuthentication(); this.RequiresAuthentication();
@ -58,6 +59,8 @@ namespace PlexRequests.UI.Modules
SonarrSettings = sonarrSettings; SonarrSettings = sonarrSettings;
SickRageApi = srApi; SickRageApi = srApi;
SickRageSettings = srSettings; SickRageSettings = srSettings;
HeadphonesSettings = hpSettings;
HeadphoneApi = hpApi;
Post["/approve"] = parameters => Approve((int)Request.Form.requestid, (string)Request.Form.qualityId); Post["/approve"] = parameters => Approve((int)Request.Form.requestid, (string)Request.Form.qualityId);
Post["/approveall"] = x => ApproveAll(); Post["/approveall"] = x => ApproveAll();
@ -71,9 +74,11 @@ namespace PlexRequests.UI.Modules
private ISettingsService<SonarrSettings> SonarrSettings { get; } private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; } private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; } private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISettingsService<HeadphonesSettings> HeadphonesSettings { get; }
private ISonarrApi SonarrApi { get; } private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; } private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; } private ICouchPotatoApi CpApi { get; }
private IHeadphonesApi HeadphoneApi { get; }
/// <summary> /// <summary>
/// Approves the specified request identifier. /// Approves the specified request identifier.
@ -102,6 +107,8 @@ namespace PlexRequests.UI.Modules
return RequestMovieAndUpdateStatus(request, qualityId); return RequestMovieAndUpdateStatus(request, qualityId);
case RequestType.TvShow: case RequestType.TvShow:
return RequestTvAndUpdateStatus(request, qualityId); return RequestTvAndUpdateStatus(request, qualityId);
case RequestType.Album:
return RequestAlbumAndUpdateStatus(request);
default: default:
throw new ArgumentOutOfRangeException(nameof(request)); throw new ArgumentOutOfRangeException(nameof(request));
} }
@ -219,6 +226,34 @@ namespace PlexRequests.UI.Modules
}); });
} }
private Response RequestAlbumAndUpdateStatus(RequestedModel request)
{
var hpSettings = HeadphonesSettings.GetSettings();
Log.Info("Adding album to Headphones : {0}", request.Title);
if (!hpSettings.Enabled)
{
// Approve it
request.Approved = true;
Log.Warn("We approved Album: {0} but could not add it to Headphones because it has not been setup", request.Title);
// Update the record
var inserted = Service.UpdateRequest(request);
return Response.AsJson(inserted
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to Headphones because it has not been configured." }
: new JsonResponseModel
{
Result = false,
Message = "We could not approve this request. Please try again or check the logs."
});
}
var sender = new HeadphonesSender(HeadphoneApi, hpSettings, Service);
var result = sender.AddAlbum(request);
return Response.AsJson( new JsonResponseModel { Result = true, Message = "We have sent the approval to Headphones for processing, This can take a few minutes."} );
}
private Response ApproveAllMovies() private Response ApproveAllMovies()
{ {
if (!Context.CurrentUser.IsAuthenticated()) if (!Context.CurrentUser.IsAuthenticated())

@ -505,6 +505,12 @@ namespace PlexRequests.UI.Modules
DateTime release; DateTime release;
DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release);
var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist;
if (artist == null)
{
return Response.AsJson("We could not find the artist on MusicBrainz. Please try again later or contact your admin");
}
var model = new RequestedModel var model = new RequestedModel
{ {
Title = albumInfo.title, Title = albumInfo.title,
@ -513,12 +519,13 @@ namespace PlexRequests.UI.Modules
PosterPath = img, PosterPath = img,
Type = RequestType.Album, Type = RequestType.Album,
ProviderId = 0, ProviderId = 0,
RequestedUsers = new List<string>() { Username }, RequestedUsers = new List<string> { Username },
Status = albumInfo.status, Status = albumInfo.status,
Issues = IssueState.None, Issues = IssueState.None,
RequestedDate = DateTime.UtcNow, RequestedDate = DateTime.UtcNow,
ReleaseDate = release, ReleaseDate = release,
// Artist = albumInfo. ArtistName = artist.name,
ArtistId = artist.id
}; };
@ -542,11 +549,9 @@ namespace PlexRequests.UI.Modules
}); });
} }
var headphonesResult = HeadphonesApi.AddAlbum(hpSettings.ApiKey, hpSettings.FullUri, model.MusicBrainzId); var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService);
Log.Info("Result from adding album to Headphones = {0}", headphonesResult); sender.AddAlbum(model);
RequestService.AddRequest(model);
if (headphonesResult)
{
return return
Response.AsJson(new JsonResponseModel Response.AsJson(new JsonResponseModel
{ {
@ -555,14 +560,6 @@ namespace PlexRequests.UI.Modules
}); });
} }
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"There was a problem adding {model.Title}. Please contact your admin!"
});
}
var result = RequestService.AddRequest(model); var result = RequestService.AddRequest(model);
return Response.AsJson(new JsonResponseModel return Response.AsJson(new JsonResponseModel
{ {

@ -168,6 +168,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Bootstrapper.cs" /> <Compile Include="Bootstrapper.cs" />
<Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\TvSender.cs" /> <Compile Include="Helpers\TvSender.cs" />
<Compile Include="Helpers\ValidationHelper.cs" /> <Compile Include="Helpers\ValidationHelper.cs" />
<Compile Include="Models\DatatablesModel.cs" /> <Compile Include="Models\DatatablesModel.cs" />

@ -54,11 +54,11 @@
} }
@if (Context.Request.Path == "/admin/headphones") @if (Context.Request.Path == "/admin/headphones")
{ {
<a class="list-group-item active" href="/admin/headphones">Headphones</a> <a class="list-group-item active" href="/admin/headphones">Headphones (Beta)</a>
} }
else else
{ {
<a class="list-group-item" href="/admin/headphones">Headphones</a> <a class="list-group-item" href="/admin/headphones">Headphones (Beta)</a>
} }
@if (Context.Request.Path == "/admin/emailnotification") @if (Context.Request.Path == "/admin/emailnotification")

Loading…
Cancel
Save