diff --git a/PlexRequests.Api.Interfaces/ISonarrApi.cs b/PlexRequests.Api.Interfaces/ISonarrApi.cs index 7e888f91a..eddc51c9f 100644 --- a/PlexRequests.Api.Interfaces/ISonarrApi.cs +++ b/PlexRequests.Api.Interfaces/ISonarrApi.cs @@ -1,45 +1,51 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ISonarrApi.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; -using System.Collections.Generic; - -using PlexRequests.Api.Models.Sonarr; - -namespace PlexRequests.Api.Interfaces -{ - public interface ISonarrApi - { - List GetProfiles(string apiKey, Uri baseUrl); - - SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, - int seasonCount, int[] seasons, string apiKey, Uri baseUrl); - - SystemStatus SystemStatus(string apiKey, Uri baseUrl); - - List GetSeries(string apiKey, Uri baseUrl); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ISonarrApi.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; +using System.Collections.Generic; + +using PlexRequests.Api.Models.Sonarr; + +namespace PlexRequests.Api.Interfaces +{ + public interface ISonarrApi + { + List GetProfiles(string apiKey, Uri baseUrl); + + SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, + int seasonCount, int[] seasons, string apiKey, Uri baseUrl); + + SystemStatus SystemStatus(string apiKey, Uri baseUrl); + + List GetSeries(string apiKey, Uri baseUrl); + Series GetSeries(string seriesId, string apiKey, Uri baseUrl); + IEnumerable GetEpisodes(string seriesId, string apiKey, Uri baseUrl); + SonarrEpisode GetEpisode(string episodeId, string apiKey, Uri baseUrl); + SonarrEpisode UpdateEpisode(SonarrEpisode episodeInfo, string apiKey, Uri baseUrl); + SonarrAddEpisodeResult SearchForEpisodes(int[] episodeIds, string apiKey, Uri baseUrl); + + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj index ecd891927..36a97335b 100644 --- a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj +++ b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj @@ -78,8 +78,12 @@ + + + + diff --git a/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeBody.cs b/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeBody.cs new file mode 100644 index 000000000..265d881d1 --- /dev/null +++ b/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeBody.cs @@ -0,0 +1,34 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrAddEpisodeBody.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.Sonarr +{ + public class SonarrAddEpisodeBody + { + public string name { get; set; } + public int[] episodeIds { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeResult.cs b/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeResult.cs new file mode 100644 index 000000000..953503ea2 --- /dev/null +++ b/PlexRequests.Api.Models/Sonarr/SonarrAddEpisodeResult.cs @@ -0,0 +1,58 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrAddEpisodeResult.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.Collections.Generic; + +namespace PlexRequests.Api.Models.Sonarr +{ + public class Body + { + public List episodeIds { get; set; } + public bool sendUpdatesToClient { get; set; } + public bool updateScheduledTask { get; set; } + public string completionMessage { get; set; } + public string name { get; set; } + public string trigger { get; set; } + } + + public class SonarrAddEpisodeResult + { + public string name { get; set; } + public Body body { get; set; } + public string priority { get; set; } + public string status { get; set; } + public string queued { get; set; } + public string trigger { get; set; } + public string state { get; set; } + public bool manual { get; set; } + public string startedOn { get; set; } + public bool sendUpdatesToClient { get; set; } + public bool updateScheduledTask { get; set; } + public int id { get; set; } + } + +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SonarrEpisode.cs b/PlexRequests.Api.Models/Sonarr/SonarrEpisode.cs new file mode 100644 index 000000000..f982eb780 --- /dev/null +++ b/PlexRequests.Api.Models/Sonarr/SonarrEpisode.cs @@ -0,0 +1,73 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrEpisode.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.Sonarr +{ + + public class Revision + { + public int version { get; set; } + public int real { get; set; } + } + + public class EpisodeFile + { + public int seriesId { get; set; } + public int seasonNumber { get; set; } + public string relativePath { get; set; } + public string path { get; set; } + public long size { get; set; } + public string dateAdded { get; set; } + public Quality quality { get; set; } + public bool qualityCutoffNotMet { get; set; } + public int id { get; set; } + } + + + + public class SonarrEpisode + { + public int seriesId { get; set; } + public int episodeFileId { get; set; } + public int seasonNumber { get; set; } + public int episodeNumber { get; set; } + public string title { get; set; } + public string airDate { get; set; } + public string airDateUtc { get; set; } + public string overview { get; set; } + public EpisodeFile episodeFile { get; set; } + public bool hasFile { get; set; } + public bool monitored { get; set; } + public int absoluteEpisodeNumber { get; set; } + public bool unverifiedSceneNumbering { get; set; } + public Series series { get; set; } + public int id { get; set; } + } + + + +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SonarrEpisodes.cs b/PlexRequests.Api.Models/Sonarr/SonarrEpisodes.cs new file mode 100644 index 000000000..05f6e4607 --- /dev/null +++ b/PlexRequests.Api.Models/Sonarr/SonarrEpisodes.cs @@ -0,0 +1,47 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrEpisodes.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.Sonarr +{ + public class SonarrEpisodes + { + + public int seriesId { get; set; } + public int episodeFileId { get; set; } + public int seasonNumber { get; set; } + public int episodeNumber { get; set; } + public string title { get; set; } + public string overview { get; set; } + public bool hasFile { get; set; } + public bool monitored { get; set; } + public bool unverifiedSceneNumbering { get; set; } + public int id { get; set; } + public string airDate { get; set; } + public string airDateUtc { get; set; } + public int? absoluteEpisodeNumber { get; set; } + } + +} \ No newline at end of file diff --git a/PlexRequests.Api/CouchPotatoApi.cs b/PlexRequests.Api/CouchPotatoApi.cs index 9f5bb7887..f9f886fbb 100644 --- a/PlexRequests.Api/CouchPotatoApi.cs +++ b/PlexRequests.Api/CouchPotatoApi.cs @@ -63,11 +63,11 @@ namespace PlexRequests.Api request.AddUrlSegment("imdbid", imdbid); request.AddUrlSegment("title", title); - var obj = RetryHandler.Execute(() => Api.ExecuteJson(request, baseUrl), new[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10)}, - (exception, timespan) => Log.Error(exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan)); + var obj = RetryHandler.Execute(() => Api.ExecuteJson(request, baseUrl), + (exception, timespan) => Log.Error(exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10)}); if (obj.Count > 0) @@ -103,11 +103,11 @@ namespace PlexRequests.Api request.AddUrlSegment("apikey", apiKey); - var obj = RetryHandler.Execute(() => Api.Execute(request, url), new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10)}, - (exception, timespan) => Log.Error(exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan)); + var obj = RetryHandler.Execute(() => Api.Execute(request, url), + (exception, timespan) => Log.Error(exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10)}); return obj; } @@ -122,8 +122,8 @@ namespace PlexRequests.Api request.AddUrlSegment("apikey", apiKey); - var obj = RetryHandler.Execute(() => Api.Execute(request, url), null, - (exception, timespan) => Log.Error(exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan)); + var obj = RetryHandler.Execute(() => Api.Execute(request, url), + (exception, timespan) => Log.Error(exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan), null); return obj; } @@ -144,12 +144,11 @@ namespace PlexRequests.Api try { var obj = RetryHandler.Execute(() => Api.Execute(request, baseUrl), - new[] { + (exception, timespan) => Log.Error(exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan), new[] { TimeSpan.FromSeconds (5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) - }, - (exception, timespan) => Log.Error(exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan)); + }); return obj; } @@ -172,8 +171,8 @@ namespace PlexRequests.Api request.AddUrlSegment("username", StringHasher.CalcuateMd5Hash(username)); request.AddUrlSegment("password", StringHasher.CalcuateMd5Hash(password)); - var obj = RetryHandler.Execute(() => Api.Execute(request, baseUrl), null, - (exception, timespan) => Log.Error(exception, "Exception when calling GetApiKey for CP, Retrying {0}", timespan)); + var obj = RetryHandler.Execute(() => Api.Execute(request, baseUrl), + (exception, timespan) => Log.Error(exception, "Exception when calling GetApiKey for CP, Retrying {0}", timespan), null); return obj; } diff --git a/PlexRequests.Api/Mocks/MockSonarrApi.cs b/PlexRequests.Api/Mocks/MockSonarrApi.cs index abe8e793b..17f77ea91 100644 --- a/PlexRequests.Api/Mocks/MockSonarrApi.cs +++ b/PlexRequests.Api/Mocks/MockSonarrApi.cs @@ -1,64 +1,89 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: MockSonarrApi.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; -using System.Collections.Generic; - -using Newtonsoft.Json; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Sonarr; - -namespace PlexRequests.Api.Mocks -{ - public class MockSonarrApi : ISonarrApi - { - public List GetProfiles(string apiKey, Uri baseUrl) - { - var json = MockApiData.Sonarr_Profiles; - var obj = JsonConvert.DeserializeObject>(json); - return obj; - } - - public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int seasonCount, int[] seasons, - string apiKey, Uri baseUrl) - { - var json = MockApiData.Sonarr_AddSeriesResult; - var obj = JsonConvert.DeserializeObject(json); - return obj; - } - - public SystemStatus SystemStatus(string apiKey, Uri baseUrl) - { - throw new NotImplementedException(); - } - - public List GetSeries(string apiKey, Uri baseUrl) - { - throw new NotImplementedException(); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: MockSonarrApi.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; +using System.Collections.Generic; + +using Newtonsoft.Json; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Sonarr; + +namespace PlexRequests.Api.Mocks +{ + public class MockSonarrApi : ISonarrApi + { + public List GetProfiles(string apiKey, Uri baseUrl) + { + var json = MockApiData.Sonarr_Profiles; + var obj = JsonConvert.DeserializeObject>(json); + return obj; + } + + public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int seasonCount, int[] seasons, + string apiKey, Uri baseUrl) + { + var json = MockApiData.Sonarr_AddSeriesResult; + var obj = JsonConvert.DeserializeObject(json); + return obj; + } + + public SystemStatus SystemStatus(string apiKey, Uri baseUrl) + { + throw new NotImplementedException(); + } + + public List GetSeries(string apiKey, Uri baseUrl) + { + throw new NotImplementedException(); + } + + public Series GetSeries(string seriesId, string apiKey, Uri baseUrl) + { + throw new NotImplementedException(); + } + + public IEnumerable GetEpisodes(string seriesId, string apiKey, Uri baseUrl) + { + throw new NotImplementedException(); + } + + public SonarrEpisode GetEpisode(string episodeId, string apiKey, Uri baseUrl) + { + throw new NotImplementedException(); + } + + public SonarrEpisode UpdateEpisode(SonarrEpisode episodeInfo, string apiKey, Uri baseUrl) + { + throw new NotImplementedException(); + } + + public SonarrAddEpisodeResult SearchForEpisodes(int[] episodeIds, string apiKey, Uri baseUrl) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/PlexRequests.Api/PlexApi.cs b/PlexRequests.Api/PlexApi.cs index 7ef0e6249..c38de8c87 100644 --- a/PlexRequests.Api/PlexApi.cs +++ b/PlexRequests.Api/PlexApi.cs @@ -1,268 +1,260 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexApi.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. -// ************************************************************************/ -using Polly; - - -#endregion -using System; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Plex; -using PlexRequests.Helpers; -using PlexRequests.Helpers.Exceptions; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class PlexApi : IPlexApi - { - static PlexApi() - { - Version = AssemblyHelper.GetAssemblyVersion(); - } - - public PlexApi (IApiRequest api) - { - Api = api; - } - - private IApiRequest Api { get; } - - private const string SignInUri = "https://plex.tv/users/sign_in.json"; - private const string FriendsUri = "https://plex.tv/pms/friends/all"; - private const string GetAccountUri = "https://plex.tv/users/account"; - - private static Logger Log = LogManager.GetCurrentClassLogger(); - private static string Version { get; } - - public PlexAuthentication SignIn(string username, string password) - { - var userModel = new PlexUserRequest - { - user = new UserRequest - { - password = password, - login = username - } - }; - var request = new RestRequest - { - Method = Method.POST - }; - - AddHeaders(ref request); - - request.AddJsonBody(userModel); - - var obj = RetryHandler.Execute(() => Api.Execute (request, new Uri(SignInUri)), - null, - (exception, timespan) => Log.Error (exception, "Exception when calling SignIn for Plex, Retrying {0}", timespan)); - - return obj; - } - - public PlexFriends GetUsers(string authToken) - { - var request = new RestRequest - { - Method = Method.GET, - }; - - AddHeaders(ref request, authToken); - - var users = RetryHandler.Execute(() => Api.ExecuteXml (request, new Uri(FriendsUri)), - null, - (exception, timespan) => Log.Error (exception, "Exception when calling GetUsers for Plex, Retrying {0}", timespan)); - - - return users; - } - - /// - /// Gets the users. - /// - /// The authentication token. - /// The search term. - /// The full plex host. - /// - public PlexSearch SearchContent(string authToken, string searchTerm, Uri plexFullHost) - { - var request = new RestRequest - { - Method = Method.GET, - Resource = "search?query={searchTerm}" - }; - - request.AddUrlSegment("searchTerm", searchTerm); - AddHeaders(ref request, authToken); - - var search = RetryHandler.Execute(() => Api.ExecuteXml (request, plexFullHost), - null, - (exception, timespan) => Log.Error (exception, "Exception when calling SearchContent for Plex, Retrying {0}", timespan)); - - return search; - } - - public PlexStatus GetStatus(string authToken, Uri uri) - { - var request = new RestRequest - { - Method = Method.GET, - }; - - AddHeaders(ref request, authToken); - - var users = RetryHandler.Execute(() => Api.ExecuteXml (request, uri), - null, - (exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for Plex, Retrying {0}", timespan)); - - return users; - } - - public PlexAccount GetAccount(string authToken) - { - var request = new RestRequest - { - Method = Method.GET, - }; - - AddHeaders(ref request, authToken); - - var account = RetryHandler.Execute(() => Api.ExecuteXml (request, new Uri(GetAccountUri)), - null, - (exception, timespan) => Log.Error (exception, "Exception when calling GetAccount for Plex, Retrying {0}", timespan)); - - return account; - } - - public PlexLibraries GetLibrarySections(string authToken, Uri plexFullHost) - { - var request = new RestRequest - { - Method = Method.GET, - Resource = "library/sections" - }; - - AddHeaders(ref request, authToken); - - try - { - var lib = RetryHandler.Execute(() => Api.ExecuteXml (request, plexFullHost), - new TimeSpan[] { - TimeSpan.FromSeconds (5), - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30) - }, - (exception, timespan) => Log.Error (exception, "Exception when calling GetLibrarySections for Plex, Retrying {0}", timespan)); - - return lib; - } - catch (Exception e) - { - Log.Error(e,"There has been a API Exception when attempting to get the Plex Libraries"); - return new PlexLibraries(); - } - } - - public PlexSearch GetLibrary(string authToken, Uri plexFullHost, string libraryId) - { - var request = new RestRequest - { - Method = Method.GET, - Resource = "library/sections/{libraryId}/all" - }; - - request.AddUrlSegment("libraryId", libraryId); - AddHeaders(ref request, authToken); - - try - { - var lib = RetryHandler.Execute(() => Api.ExecuteXml (request, plexFullHost), - new TimeSpan[] { - TimeSpan.FromSeconds (5), - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30) - }, - (exception, timespan) => Log.Error (exception, "Exception when calling GetLibrary for Plex, Retrying {0}", timespan)); - - return lib; - } - catch (Exception e) - { - Log.Error(e,"There has been a API Exception when attempting to get the Plex Library"); - return new PlexSearch(); - } - } - - public PlexMetadata GetMetadata(string authToken, Uri plexFullHost, string itemId) - { - var request = new RestRequest - { - Method = Method.GET, - Resource = "library/metadata/{itemId}" - }; - - request.AddUrlSegment("itemId", itemId); - AddHeaders(ref request, authToken); - - try - { - var lib = RetryHandler.Execute(() => Api.ExecuteXml(request, plexFullHost), - new[] { - TimeSpan.FromSeconds (5), - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30) - }, - (exception, timespan) => Log.Error(exception, "Exception when calling GetMetadata for Plex, Retrying {0}", timespan)); - - return lib; - } - catch (Exception e) - { - Log.Error(e, "There has been a API Exception when attempting to get the Plex GetMetadata"); - return new PlexMetadata(); - } - } - - private void AddHeaders(ref RestRequest request, string authToken) - { - request.AddHeader("X-Plex-Token", authToken); - AddHeaders(ref request); - } - - private void AddHeaders(ref RestRequest request) - { - request.AddHeader("X-Plex-Client-Identifier", "Test213"); - request.AddHeader("X-Plex-Product", "Request Plex"); - request.AddHeader("X-Plex-Version", Version); - request.AddHeader("Content-Type", "application/xml"); - } - } -} - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexApi.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. +// ************************************************************************/ +using Polly; + + +#endregion +using System; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Exceptions; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class PlexApi : IPlexApi + { + static PlexApi() + { + Version = AssemblyHelper.GetAssemblyVersion(); + } + + public PlexApi (IApiRequest api) + { + Api = api; + } + + private IApiRequest Api { get; } + + private const string SignInUri = "https://plex.tv/users/sign_in.json"; + private const string FriendsUri = "https://plex.tv/pms/friends/all"; + private const string GetAccountUri = "https://plex.tv/users/account"; + + private static Logger Log = LogManager.GetCurrentClassLogger(); + private static string Version { get; } + + public PlexAuthentication SignIn(string username, string password) + { + var userModel = new PlexUserRequest + { + user = new UserRequest + { + password = password, + login = username + } + }; + var request = new RestRequest + { + Method = Method.POST + }; + + AddHeaders(ref request); + + request.AddJsonBody(userModel); + + var obj = RetryHandler.Execute(() => Api.Execute (request, new Uri(SignInUri)), + (exception, timespan) => Log.Error (exception, "Exception when calling SignIn for Plex, Retrying {0}", timespan), null); + + return obj; + } + + public PlexFriends GetUsers(string authToken) + { + var request = new RestRequest + { + Method = Method.GET, + }; + + AddHeaders(ref request, authToken); + + var users = RetryHandler.Execute(() => Api.ExecuteXml (request, new Uri(FriendsUri)), + (exception, timespan) => Log.Error (exception, "Exception when calling GetUsers for Plex, Retrying {0}", timespan), null); + + + return users; + } + + /// + /// Gets the users. + /// + /// The authentication token. + /// The search term. + /// The full plex host. + /// + public PlexSearch SearchContent(string authToken, string searchTerm, Uri plexFullHost) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "search?query={searchTerm}" + }; + + request.AddUrlSegment("searchTerm", searchTerm); + AddHeaders(ref request, authToken); + + var search = RetryHandler.Execute(() => Api.ExecuteXml (request, plexFullHost), + (exception, timespan) => Log.Error (exception, "Exception when calling SearchContent for Plex, Retrying {0}", timespan), null); + + return search; + } + + public PlexStatus GetStatus(string authToken, Uri uri) + { + var request = new RestRequest + { + Method = Method.GET, + }; + + AddHeaders(ref request, authToken); + + var users = RetryHandler.Execute(() => Api.ExecuteXml (request, uri), + (exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for Plex, Retrying {0}", timespan), null); + + return users; + } + + public PlexAccount GetAccount(string authToken) + { + var request = new RestRequest + { + Method = Method.GET, + }; + + AddHeaders(ref request, authToken); + + var account = RetryHandler.Execute(() => Api.ExecuteXml (request, new Uri(GetAccountUri)), + (exception, timespan) => Log.Error (exception, "Exception when calling GetAccount for Plex, Retrying {0}", timespan), null); + + return account; + } + + public PlexLibraries GetLibrarySections(string authToken, Uri plexFullHost) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "library/sections" + }; + + AddHeaders(ref request, authToken); + + try + { + var lib = RetryHandler.Execute(() => Api.ExecuteXml (request, plexFullHost), + (exception, timespan) => Log.Error (exception, "Exception when calling GetLibrarySections for Plex, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30) + }); + + return lib; + } + catch (Exception e) + { + Log.Error(e,"There has been a API Exception when attempting to get the Plex Libraries"); + return new PlexLibraries(); + } + } + + public PlexSearch GetLibrary(string authToken, Uri plexFullHost, string libraryId) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "library/sections/{libraryId}/all" + }; + + request.AddUrlSegment("libraryId", libraryId); + AddHeaders(ref request, authToken); + + try + { + var lib = RetryHandler.Execute(() => Api.ExecuteXml (request, plexFullHost), + (exception, timespan) => Log.Error (exception, "Exception when calling GetLibrary for Plex, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30) + }); + + return lib; + } + catch (Exception e) + { + Log.Error(e,"There has been a API Exception when attempting to get the Plex Library"); + return new PlexSearch(); + } + } + + public PlexMetadata GetMetadata(string authToken, Uri plexFullHost, string itemId) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "library/metadata/{itemId}" + }; + + request.AddUrlSegment("itemId", itemId); + AddHeaders(ref request, authToken); + + try + { + var lib = RetryHandler.Execute(() => Api.ExecuteXml(request, plexFullHost), + (exception, timespan) => Log.Error(exception, "Exception when calling GetMetadata for Plex, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30) + }); + + return lib; + } + catch (Exception e) + { + Log.Error(e, "There has been a API Exception when attempting to get the Plex GetMetadata"); + return new PlexMetadata(); + } + } + + private void AddHeaders(ref RestRequest request, string authToken) + { + request.AddHeader("X-Plex-Token", authToken); + AddHeaders(ref request); + } + + private void AddHeaders(ref RestRequest request) + { + request.AddHeader("X-Plex-Client-Identifier", "Test213"); + request.AddHeader("X-Plex-Product", "Request Plex"); + request.AddHeader("X-Plex-Version", Version); + request.AddHeader("Content-Type", "application/xml"); + } + } +} + diff --git a/PlexRequests.Api/RetryHandler.cs b/PlexRequests.Api/RetryHandler.cs index dc34f6f59..898c2370c 100644 --- a/PlexRequests.Api/RetryHandler.cs +++ b/PlexRequests.Api/RetryHandler.cs @@ -35,25 +35,29 @@ namespace PlexRequests.Api { private static readonly TimeSpan[] DefaultTime = { TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }; - public static T Execute(Func action, TimeSpan[] timeSpan) - { - var policy = RetryAndWaitPolicy(timeSpan); - - return policy.Execute(action); - } - - public static T Execute(Func func, TimeSpan[] timeSpan, Action action) + public static T Execute(Func action, TimeSpan[] timeSpan = null) { if (timeSpan == null) { timeSpan = DefaultTime; } - var policy = RetryAndWaitPolicy(timeSpan, action); + var policy = RetryAndWaitPolicy(timeSpan); + + return policy.Execute(action); + } + + public static T Execute(Func func, Action action, TimeSpan[] timeSpan = null) + { + if (timeSpan == null) + { + timeSpan = DefaultTime; + } + var policy = RetryAndWaitPolicy(action, timeSpan); return policy.Execute(func); } - public static RetryPolicy RetryAndWaitPolicy(TimeSpan[] timeSpan, Action action) + public static RetryPolicy RetryAndWaitPolicy(Action action, TimeSpan[] timeSpan = null) { if (timeSpan == null) { @@ -75,7 +79,7 @@ namespace PlexRequests.Api return policy; } - public static RetryPolicy RetryAndWaitPolicy(TimeSpan[] timeSpan, Action action) + public static RetryPolicy RetryAndWaitPolicy(Action action, TimeSpan[] timeSpan = null) { if (timeSpan == null) { diff --git a/PlexRequests.Api/SickrageApi.cs b/PlexRequests.Api/SickrageApi.cs index 4caa6033b..df5f859e5 100644 --- a/PlexRequests.Api/SickrageApi.cs +++ b/PlexRequests.Api/SickrageApi.cs @@ -1,227 +1,217 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SickrageApi.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; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.SickRage; -using PlexRequests.Helpers; -using PlexRequests.Helpers.Exceptions; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class SickrageApi : ISickRageApi - { - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - - public SickrageApi() - { - Api = new ApiRequest(); - } - - private ApiRequest Api { get; } - - public SickRageSeasonList VerifyShowHasLoaded(int tvdbId, string apiKey, Uri baseUrl) - { - Log.Trace("Entered `VerifyShowHasLoaded({0} <- id)`", tvdbId); - var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=show.seasonlist", Method = Method.GET }; - request.AddUrlSegment("apiKey", apiKey); - request.AddQueryParameter("tvdbid", tvdbId.ToString()); - - try - { - var policy = RetryHandler.RetryAndWaitPolicy( - null, - (exception, timespan) => Log.Error(exception, "Exception when calling VerifyShowHasLoaded for SR, Retrying {0}", timespan)); - - var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); - return obj; - } - catch (Exception e) - { - Log.Error(e); - return new SickRageSeasonList(); - } - } - - public async Task AddSeries(int tvdbId, int seasonCount, int[] seasons, string quality, string apiKey, Uri baseUrl) - { - var futureStatus = seasons.Length > 0 && !seasons.Any(x => x == seasonCount) ? SickRageStatus.Skipped : SickRageStatus.Wanted; - var status = seasons.Length > 0 ? SickRageStatus.Skipped : SickRageStatus.Wanted; - - Log.Trace("Future Status: {0}", futureStatus); - Log.Trace("Current Status: {0}", status); - - var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=show.addnew", Method = Method.GET }; - request.AddUrlSegment("apiKey", apiKey); - request.AddQueryParameter("tvdbid", tvdbId.ToString()); - request.AddQueryParameter("status", status); - request.AddQueryParameter("future_status", futureStatus); - if (!quality.Equals("default", StringComparison.CurrentCultureIgnoreCase)) - { - Log.Trace("Settings quality to {0}", quality); - request.AddQueryParameter("initial", quality); - } - - var policy = RetryHandler.RetryAndWaitPolicy( - null, - (exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for SR, Retrying {0}", timespan)); - - var obj = policy.Execute(() => Api.Execute(request, baseUrl)); - Log.Trace("obj Result:"); - Log.Trace(obj.DumpJson()); - - if (obj.result != "failure") - { - var sw = new Stopwatch(); - sw.Start(); - - var seasonIncrement = 0; - var seasonList = new SickRageSeasonList(); - try - { - while (seasonIncrement < seasonCount) - { - seasonList = VerifyShowHasLoaded(tvdbId, apiKey, baseUrl); - if (seasonList.result.Equals("failure")) - { - Thread.Sleep(3000); - continue; - } - seasonIncrement = seasonList.Data?.Length ?? 0; - Log.Trace("New seasonIncrement -> {0}", seasonIncrement); - - if (sw.ElapsedMilliseconds > 30000) // Break out after 30 seconds, it's not going to get added - { - Log.Warn("Couldn't find out if the show had been added after 10 seconds. I doubt we can change the status to wanted."); - break; - } - } - sw.Stop(); - } - catch (Exception e) - { - Log.Error("Exception thrown when getting the seasonList"); - Log.Error(e); - } - } - Log.Trace("seasons.Length > 0 where seasons.Len -> {0}", seasons.Length); - try - { - if (seasons.Length > 0) - { - //handle the seasons requested - foreach (var s in seasons) - { - Log.Trace("Adding season {0}", s); - - var result = await AddSeason(tvdbId, s, apiKey, baseUrl); - Log.Trace("SickRage adding season results: "); - Log.Trace(result.DumpJson()); - } - } - } - catch (Exception e) - { - Log.Trace("Exception when adding seasons:"); - Log.Error(e); - throw; - } - - Log.Trace("Finished with the API, returning the obj"); - return obj; - } - - public SickRagePing Ping(string apiKey, Uri baseUrl) - { - var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=sb.ping", Method = Method.GET }; - - var policy = RetryHandler.RetryAndWaitPolicy( - null, - (exception, timespan) => Log.Error(exception, "Exception when calling Ping for SR, Retrying {0}", timespan)); - - request.AddUrlSegment("apiKey", apiKey); - var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); - - return obj; - } - - public async Task AddSeason(int tvdbId, int season, string apiKey, Uri baseUrl) - { - var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=episode.setstatus", Method = Method.GET }; - request.AddUrlSegment("apiKey", apiKey); - request.AddQueryParameter("tvdbid", tvdbId.ToString()); - request.AddQueryParameter("season", season.ToString()); - request.AddQueryParameter("status", SickRageStatus.Wanted); - - await Task.Run(() => Thread.Sleep(2000)); - return await Task.Run( - () => - { - var policy = RetryHandler.RetryAndWaitPolicy( - null, - (exception, timespan) => Log.Error(exception, "Exception when calling AddSeason for SR, Retrying {0}", timespan)); - - var result = policy.Execute(() => Api.Execute(request, baseUrl)); - - return result; - }).ConfigureAwait(false); - } - - public async Task GetShows(string apiKey, Uri baseUrl) - { - var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=shows", Method = Method.GET }; - request.AddUrlSegment("apiKey", apiKey); - - return await Task.Run( - () => - { - try - { - var policy = RetryHandler.RetryAndWaitPolicy( - new[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) }, - (exception, timespan) => Log.Error(exception, "Exception when calling GetShows for SR, Retrying {0}", timespan)); - - return policy.Execute(() => Api.Execute(request, baseUrl)); - } - catch (ApiRequestException) - { - Log.Error("There has been a API exception when Getting the Sickrage shows"); - return null; - } - }).ConfigureAwait(false); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SickrageApi.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; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.SickRage; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Exceptions; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class SickrageApi : ISickRageApi + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public SickrageApi() + { + Api = new ApiRequest(); + } + + private ApiRequest Api { get; } + + public SickRageSeasonList VerifyShowHasLoaded(int tvdbId, string apiKey, Uri baseUrl) + { + Log.Trace("Entered `VerifyShowHasLoaded({0} <- id)`", tvdbId); + var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=show.seasonlist", Method = Method.GET }; + request.AddUrlSegment("apiKey", apiKey); + request.AddQueryParameter("tvdbid", tvdbId.ToString()); + + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling VerifyShowHasLoaded for SR, Retrying {0}", timespan), null); + + var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + return obj; + } + catch (Exception e) + { + Log.Error(e); + return new SickRageSeasonList(); + } + } + + public async Task AddSeries(int tvdbId, int seasonCount, int[] seasons, string quality, string apiKey, Uri baseUrl) + { + var futureStatus = seasons.Length > 0 && !seasons.Any(x => x == seasonCount) ? SickRageStatus.Skipped : SickRageStatus.Wanted; + var status = seasons.Length > 0 ? SickRageStatus.Skipped : SickRageStatus.Wanted; + + Log.Trace("Future Status: {0}", futureStatus); + Log.Trace("Current Status: {0}", status); + + var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=show.addnew", Method = Method.GET }; + request.AddUrlSegment("apiKey", apiKey); + request.AddQueryParameter("tvdbid", tvdbId.ToString()); + request.AddQueryParameter("status", status); + request.AddQueryParameter("future_status", futureStatus); + if (!quality.Equals("default", StringComparison.CurrentCultureIgnoreCase)) + { + Log.Trace("Settings quality to {0}", quality); + request.AddQueryParameter("initial", quality); + } + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for SR, Retrying {0}", timespan), null); + + var obj = policy.Execute(() => Api.Execute(request, baseUrl)); + Log.Trace("obj Result:"); + Log.Trace(obj.DumpJson()); + + if (obj.result != "failure") + { + var sw = new Stopwatch(); + sw.Start(); + + var seasonIncrement = 0; + var seasonList = new SickRageSeasonList(); + try + { + while (seasonIncrement < seasonCount) + { + seasonList = VerifyShowHasLoaded(tvdbId, apiKey, baseUrl); + if (seasonList.result.Equals("failure")) + { + Thread.Sleep(3000); + continue; + } + seasonIncrement = seasonList.Data?.Length ?? 0; + Log.Trace("New seasonIncrement -> {0}", seasonIncrement); + + if (sw.ElapsedMilliseconds > 30000) // Break out after 30 seconds, it's not going to get added + { + Log.Warn("Couldn't find out if the show had been added after 10 seconds. I doubt we can change the status to wanted."); + break; + } + } + sw.Stop(); + } + catch (Exception e) + { + Log.Error("Exception thrown when getting the seasonList"); + Log.Error(e); + } + } + Log.Trace("seasons.Length > 0 where seasons.Len -> {0}", seasons.Length); + try + { + if (seasons.Length > 0) + { + //handle the seasons requested + foreach (var s in seasons) + { + Log.Trace("Adding season {0}", s); + + var result = await AddSeason(tvdbId, s, apiKey, baseUrl); + Log.Trace("SickRage adding season results: "); + Log.Trace(result.DumpJson()); + } + } + } + catch (Exception e) + { + Log.Trace("Exception when adding seasons:"); + Log.Error(e); + throw; + } + + Log.Trace("Finished with the API, returning the obj"); + return obj; + } + + public SickRagePing Ping(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=sb.ping", Method = Method.GET }; + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling Ping for SR, Retrying {0}", timespan), null); + + request.AddUrlSegment("apiKey", apiKey); + var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + + return obj; + } + + public async Task AddSeason(int tvdbId, int season, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=episode.setstatus", Method = Method.GET }; + request.AddUrlSegment("apiKey", apiKey); + request.AddQueryParameter("tvdbid", tvdbId.ToString()); + request.AddQueryParameter("season", season.ToString()); + request.AddQueryParameter("status", SickRageStatus.Wanted); + + await Task.Run(() => Thread.Sleep(2000)); + return await Task.Run( + () => + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling AddSeason for SR, Retrying {0}", timespan), null); + + var result = policy.Execute(() => Api.Execute(request, baseUrl)); + + return result; + }).ConfigureAwait(false); + } + + public async Task GetShows(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/{apiKey}/?cmd=shows", Method = Method.GET }; + request.AddUrlSegment("apiKey", apiKey); + + return await Task.Run( + () => + { + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetShows for SR, Retrying {0}", timespan), new[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) }); + + return policy.Execute(() => Api.Execute(request, baseUrl)); + } + catch (ApiRequestException) + { + Log.Error("There has been a API exception when Getting the Sickrage shows"); + return null; + } + }).ConfigureAwait(false); + } + } } \ No newline at end of file diff --git a/PlexRequests.Api/SonarrApi.cs b/PlexRequests.Api/SonarrApi.cs index b56e969a7..9ac35258b 100644 --- a/PlexRequests.Api/SonarrApi.cs +++ b/PlexRequests.Api/SonarrApi.cs @@ -1,167 +1,299 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: CouchPotatoApi.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; -using System.Collections.Generic; -using System.Linq; - -using Newtonsoft.Json; - -using NLog; -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Sonarr; -using PlexRequests.Helpers; - -using RestSharp; -using Newtonsoft.Json.Linq; - -using PlexRequests.Helpers.Exceptions; - -namespace PlexRequests.Api -{ - public class SonarrApi : ISonarrApi - { - public SonarrApi() - { - Api = new ApiRequest(); - } - private ApiRequest Api { get; set; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - public List GetProfiles(string apiKey, Uri baseUrl) - { - var request = new RestRequest { Resource = "/api/profile", Method = Method.GET }; - - request.AddHeader("X-Api-Key", apiKey); - var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10) - }, (exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for Sonarr, Retrying {0}", timespan)); - - var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); - - return obj; - } - - public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int seasonCount, int[] seasons, string apiKey, Uri baseUrl) - { - Log.Debug("Adding series {0}", title); - Log.Debug("Seasons = {0}, out of {1} seasons", seasons.DumpJson(), seasonCount); - var request = new RestRequest - { - Resource = "/api/Series?", - Method = Method.POST - }; - - var options = new SonarrAddSeries - { - seasonFolder = seasonFolders, - title = title, - qualityProfileId = qualityId, - tvdbId = tvdbId, - titleSlug = title, - seasons = new List(), - rootFolderPath = rootPath - }; - - for (var i = 1; i <= seasonCount; i++) - { - var season = new Season - { - seasonNumber = i, - monitored = seasons.Length == 0 || seasons.Any(x => x == i) - }; - options.seasons.Add(season); - } - - Log.Debug("Sonarr API Options:"); - Log.Debug(options.DumpJson()); - - request.AddHeader("X-Api-Key", apiKey); - request.AddJsonBody(options); - - SonarrAddSeries result; - try - { - var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10) - }, (exception, timespan) => Log.Error (exception, "Exception when calling AddSeries for Sonarr, Retrying {0}", timespan)); - - result = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); - } - catch (JsonSerializationException jse) - { - Log.Error(jse); - var error = Api.ExecuteJson>(request, baseUrl); - var messages = error?.Select(x => x.errorMessage).ToList(); - messages?.ForEach(x => Log.Error(x)); - result = new SonarrAddSeries { ErrorMessages = messages }; - } - - return result; - } - - public SystemStatus SystemStatus(string apiKey, Uri baseUrl) - { - var request = new RestRequest { Resource = "/api/system/status", Method = Method.GET }; - request.AddHeader("X-Api-Key", apiKey); - - var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10) - }, (exception, timespan) => Log.Error (exception, "Exception when calling SystemStatus for Sonarr, Retrying {0}", timespan)); - - var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); - - return obj; - } - - public List GetSeries(string apiKey, Uri baseUrl) - { - var request = new RestRequest { Resource = "/api/series", Method = Method.GET }; - request.AddHeader("X-Api-Key", apiKey); - try - { - var policy = RetryHandler.RetryAndWaitPolicy (new TimeSpan[] { - TimeSpan.FromSeconds (5), - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30) - }, (exception, timespan) => Log.Error (exception, "Exception when calling GetSeries for Sonarr, Retrying {0}", timespan)); - - return policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); - } - catch (Exception e) - { - Log.Error(e, "There has been an API exception when getting the Sonarr Series"); - return null; - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CouchPotatoApi.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; +using System.Collections.Generic; +using System.Linq; + +using Newtonsoft.Json; + +using NLog; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Sonarr; +using PlexRequests.Helpers; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class SonarrApi : ISonarrApi + { + public SonarrApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; set; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public List GetProfiles(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/profile", Method = Method.GET }; + + request.AddHeader("X-Api-Key", apiKey); + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetProfiles for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + + return obj; + } + + public SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, int seasonCount, int[] seasons, string apiKey, Uri baseUrl) + { + Log.Debug("Adding series {0}", title); + Log.Debug("Seasons = {0}, out of {1} seasons", seasons.DumpJson(), seasonCount); + var request = new RestRequest + { + Resource = "/api/Series?", + Method = Method.POST + }; + + var options = new SonarrAddSeries + { + seasonFolder = seasonFolders, + title = title, + qualityProfileId = qualityId, + tvdbId = tvdbId, + titleSlug = title, + seasons = new List(), + rootFolderPath = rootPath + }; + + for (var i = 1; i <= seasonCount; i++) + { + var season = new Season + { + seasonNumber = i, + monitored = seasons.Length == 0 || seasons.Any(x => x == i) + }; + options.seasons.Add(season); + } + + Log.Debug("Sonarr API Options:"); + Log.Debug(options.DumpJson()); + + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(options); + + SonarrAddSeries result; + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + result = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (JsonSerializationException jse) + { + Log.Error(jse); + var error = Api.ExecuteJson>(request, baseUrl); + var messages = error?.Select(x => x.errorMessage).ToList(); + messages?.ForEach(x => Log.Error(x)); + result = new SonarrAddSeries { ErrorMessages = messages }; + } + + return result; + } + + public SystemStatus SystemStatus(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/system/status", Method = Method.GET }; + request.AddHeader("X-Api-Key", apiKey); + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling SystemStatus for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10) + }); + + var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + + return obj; + } + + public List GetSeries(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/series", Method = Method.GET }; + request.AddHeader("X-Api-Key", apiKey); + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetSeries for Sonarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30) + }); + + return policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when getting the Sonarr Series"); + return null; + } + } + + public Series GetSeries(string seriesId, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/series/{seriesId}", Method = Method.GET }; + request.AddHeader("X-Api-Key", apiKey); + request.AddUrlSegment("seriesId", seriesId); + try + { + var policy = + RetryHandler.RetryAndWaitPolicy( + (exception, timespan) => + Log.Error(exception, "Exception when calling GetSeries by ID for Sonarr, Retrying {0}", + timespan)); + + return policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when getting the Sonarr Series by ID"); + return null; + } + } + + /// + /// Returns all episodes for the given series. + /// + /// The series identifier. + /// The API key. + /// The base URL. + /// + public IEnumerable GetEpisodes(string seriesId, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/Episode", Method = Method.GET }; + request.AddHeader("X-Api-Key", apiKey); + request.AddQueryParameter("seriesId", seriesId); + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception when calling GetEpisodes for Sonarr, Retrying {0}", timespan)); + + return policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when getting the Sonarr GetEpisodes"); + return null; + } + } + + /// + /// Returns the episode with the matching id. + /// + /// The episode identifier. + /// The API key. + /// The base URL. + /// + public SonarrEpisode GetEpisode(string episodeId, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/Episode/{episodeId}", Method = Method.GET }; + request.AddHeader("X-Api-Key", apiKey); + request.AddUrlSegment("episodeId", episodeId); + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception when calling GetEpisode by ID for Sonarr, Retrying {0}", timespan)); + + return policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when getting the Sonarr GetEpisode by ID"); + return null; + } + } + + /// + /// Update the given episodes, currently only monitored is changed, all other modifications are ignored. + /// Required: All parameters (you should perform a GET/{id} and submit the full body with the changes, as other values may be editable in the future. + /// + /// The episode information. + /// The API key. + /// The base URL. + /// + public SonarrEpisode UpdateEpisode(SonarrEpisode episodeInfo, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/Episode", Method = Method.PUT }; + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(episodeInfo); + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception when calling UpdateEpisode for Sonarr, Retrying {0}", timespan)); + + return policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when put the Sonarr UpdateEpisode"); + return null; + } + } + + /// + /// Search for one or more episodes + /// + /// The episode ids. + /// The API key. + /// The base URL. + /// + public SonarrAddEpisodeResult SearchForEpisodes(int[] episodeIds, string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/Command", Method = Method.POST }; + request.AddHeader("X-Api-Key", apiKey); + + var body = new SonarrAddEpisodeBody + { + name = "EpisodeSearch", + episodeIds = episodeIds + }; + + request.AddJsonBody(body); + + try + { + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => + Log.Error(exception, "Exception when calling SearchForEpisodes for Sonarr, Retrying {0}", timespan)); + + return policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + } + catch (Exception e) + { + Log.Error(e, "There has been an API exception when put the Sonarr SearchForEpisodes"); + return null; + } + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Bootstrapper.cs b/PlexRequests.UI/Bootstrapper.cs index bc8d24665..f1e8ebc1a 100644 --- a/PlexRequests.UI/Bootstrapper.cs +++ b/PlexRequests.UI/Bootstrapper.cs @@ -69,14 +69,14 @@ namespace PlexRequests.UI private IKernel _kernel; protected override IKernel GetApplicationContainer() { - Debug.WriteLine("GetAppContainer"); + Debug.WriteLine("GetAppContainer"); _kernel.Load(); return _kernel; } protected override void ApplicationStartup(IKernel container, IPipelines pipelines) { - Debug.WriteLine("Bootstrapper.ApplicationStartup"); + Debug.WriteLine("Bootstrapper.ApplicationStartup"); ConfigureContainer(container); JsonSettings.MaxJsonLength = int.MaxValue; @@ -119,21 +119,22 @@ namespace PlexRequests.UI #endif protected override void ConfigureConventions(NancyConventions nancyConventions) { - Debug.WriteLine("Configuring the conventions"); + Debug.WriteLine("Configuring the conventions"); base.ConfigureConventions(nancyConventions); -Debug.WriteLine("Finished BASE"); + var settingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); var settings = settingsService.GetSettings(); - var assetLocation = settings.BaseUrl ?? string.Empty; + var assetLocation = string.Empty; + if (!string.IsNullOrEmpty(settings.BaseUrl)) + { + assetLocation = $"{settings.BaseUrl}/"; + } - Debug.WriteLine($"AssetLocation {assetLocation}"); - nancyConventions.StaticContentsConventions.Add( - StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content_{AssemblyHelper.GetProductVersion()}", "Content") - ); -Debug.WriteLine("Added Content"); - nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/docs", "swagger-ui"); + Debug.WriteLine($"AssetLocation {assetLocation}"); - nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/fonts", "Content/fonts"); + nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}Content", "Content"); + nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}docs", "swagger-ui"); + nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}fonts", "Content/fonts"); } protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" }; @@ -170,10 +171,9 @@ Debug.WriteLine("Added Content"); notificationService.Subscribe(new SlackNotification(container.Get(), slackService)); } } - + protected override void RequestStartup(IKernel container, IPipelines pipelines, NancyContext context) { - Debug.WriteLine("RequestStartup"); //CORS Enable pipelines.AfterRequest.AddItemToEndOfPipeline((ctx) => { @@ -187,7 +187,7 @@ Debug.WriteLine("Added Content"); private void ConfigureContainer(IKernel container) { - Debug.WriteLine("Configuring ServiceLoc/Container"); + Debug.WriteLine("Configuring ServiceLoc/Container"); var loc = ServiceLocator.Instance; loc.SetContainer(container); } diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index b149c2d42..6965d1351 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -538,6 +538,7 @@ $(function () { }); $('#episodesModal').on('show.bs.modal', function (event) { + finishLoading("episodesRequest", "primary"); var button = $(event.relatedTarget); // Button that triggered the modal var id = button.data('identifier'); // Extract info from data-* attributes var url = createBaseUrl(base, '/search/episodes/'); @@ -552,8 +553,6 @@ $(function () { $content.html(""); $('#selectedEpisodeId').val(id); results.forEach(function (result) { - - var episodes = buildEpisodesView(result); if (!seenSeasons.find(x => x === episodes.season)) { @@ -572,6 +571,42 @@ $(function () { } }); + // Save Modal click + $("#episodesRequest").click(function (e) { + e.preventDefault(); + + var tvId = $('#selectedEpisodeId').val(); + + $("#episodesRequest").prop("disabled", true); + loadingButton("episodesRequest", "primary"); + + + var $form = $('#form' + tvId); + + var model = []; + + + + var $checkedEpisodes = $('.selectedEpisodes:checkbox:checked'); + $checkedEpisodes.each(function (index, element) { + var $element = $('#' + element.id); + var tempObj = {}; + tempObj.episodeNumber = $element.attr("epNumber"); + tempObj.seasonNumber = $element.attr("epSeason"); + model.push(tempObj); + }); + + var finalObj = { + ShowId: tvId, + Episodes: model + } + + var url = createBaseUrl(mainBaseUrl, "search/request/tvEpisodes"); + var type = $form.prop('method'); + + sendRequestAjax(JSON.stringify(finalObj), type, url, tvId); + }); + function buildSeasonsContext(result) { var context = { id: result diff --git a/PlexRequests.UI/Content/site.js b/PlexRequests.UI/Content/site.js index 13b81ddde..7978839c1 100644 --- a/PlexRequests.UI/Content/site.js +++ b/PlexRequests.UI/Content/site.js @@ -8,6 +8,8 @@ return s; } +var mainBaseUrl = $('#baseUrl').text(); + function Humanize(date) { var mNow = moment(); var mDate = moment(date).local(); diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index 283956b1b..f2d42a432 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -73,7 +73,7 @@ namespace PlexRequests.UI.Helpers if (settings.ThemeName == "PlexBootstrap.css") settings.ThemeName = Themes.PlexTheme; if (settings.ThemeName == "OriginalBootstrap.css") settings.ThemeName = Themes.OriginalTheme; - var startUrl = $"{content}/Content_{Assembly}"; + var startUrl = $"{content}/Content"; var styleAssets = new List { @@ -123,7 +123,7 @@ namespace PlexRequests.UI.Helpers var content = GetContentUrl(assetLocation); - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -135,7 +135,7 @@ namespace PlexRequests.UI.Helpers var content = GetContentUrl(assetLocation); - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -147,7 +147,7 @@ namespace PlexRequests.UI.Helpers var content = GetContentUrl(assetLocation); - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -157,7 +157,7 @@ namespace PlexRequests.UI.Helpers var assetLocation = GetBaseUrl(); var content = GetContentUrl(assetLocation); - var asset = $""; + var asset = $""; return helper.Raw(asset); } @@ -169,8 +169,8 @@ namespace PlexRequests.UI.Helpers var content = GetContentUrl(assetLocation); - sb.AppendLine($""); - sb.AppendLine($""); + sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -186,7 +186,7 @@ namespace PlexRequests.UI.Helpers var assetLocation = GetBaseUrl(); var content = GetContentUrl(assetLocation); - var asset = $""; + var asset = $""; return helper.Raw(asset); } diff --git a/PlexRequests.UI/Helpers/ContravariantBindingResolver.cs b/PlexRequests.UI/Helpers/ContravariantBindingResolver.cs index 0416d8c90..0b606429d 100644 --- a/PlexRequests.UI/Helpers/ContravariantBindingResolver.cs +++ b/PlexRequests.UI/Helpers/ContravariantBindingResolver.cs @@ -44,24 +44,34 @@ namespace PlexRequests.UI.Helpers /// public IEnumerable Resolve(Multimap bindings, Type service) { - Debug.WriteLine("Contrar thing"); - if (service.IsGenericType) { var genericType = service.GetGenericTypeDefinition(); var genericArguments = genericType.GetGenericArguments(); - if (!genericArguments.Any()) - { - return Enumerable.Empty(); - } + if (!genericArguments.Any()) + { + return Enumerable.Empty(); + } if (genericArguments.Length == 1 && genericArguments.Single().GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant)) { - var argument = service.GetGenericArguments().Single(); + var argument = service.GetGenericArguments().FirstOrDefault(); + if (argument == null) + { + return Enumerable.Empty(); + } + var matches = bindings.Where( kvp => - kvp.Key.IsGenericType && kvp.Key.GetGenericTypeDefinition() == genericType && kvp.Key.GetGenericArguments().Single() != argument - && kvp.Key.GetGenericArguments().Single().IsAssignableFrom(argument)).SelectMany(kvp => kvp.Value); + { + var assignableFrom = kvp.Key.GetGenericArguments().FirstOrDefault(); + + return kvp.Key.IsGenericType && kvp.Key.GetGenericTypeDefinition() == genericType && + kvp.Key.GetGenericArguments().FirstOrDefault() != argument + && assignableFrom.IsAssignableFrom(argument); + + + }).SelectMany(kvp => kvp.Value); return matches; } } diff --git a/PlexRequests.UI/Models/EpisodeRequestModel.cs b/PlexRequests.UI/Models/EpisodeRequestModel.cs new file mode 100644 index 000000000..1d431e6b3 --- /dev/null +++ b/PlexRequests.UI/Models/EpisodeRequestModel.cs @@ -0,0 +1,41 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: EpisodeRequestModel.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.UI.Models +{ + public class EpisodeRequestModel + { + public int ShowId { get; set; } + public EpisodesModel[] Episodes { get; set; } + + } + + public class EpisodesModel + { + public int SeasonNumber { get; set; } + public int EpisodeNumber { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index f6275ef2e..8fab31ea0 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -49,8 +49,10 @@ using PlexRequests.UI.Models; using System.Threading.Tasks; using Nancy.Extensions; +using Nancy.ModelBinding; using Nancy.Responses; - +using Newtonsoft.Json; +using PlexRequests.Api.Models.Sonarr; using PlexRequests.Api.Models.Tv; using PlexRequests.Core.Models; using PlexRequests.Helpers.Analytics; @@ -103,7 +105,7 @@ namespace PlexRequests.UI.Modules TvApi = new TvMazeApi(); - Get["SearchIndex","/", true] = async (x, ct) => await RequestLoad(); + Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); @@ -115,6 +117,7 @@ namespace PlexRequests.UI.Modules Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); + Post["request/tvEpisodes", true] = async (x, ct) => await RequestEpisodes(); Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); @@ -436,7 +439,7 @@ namespace PlexRequests.UI.Modules Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, CookieHelper.GetAnalyticClientId(Cookies)); var movieInfo = MovieApi.GetMovieInformation(movieId).Result; var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; - + var existingRequest = await RequestService.CheckRequestAsync(movieId); if (existingRequest != null) { @@ -449,7 +452,7 @@ namespace PlexRequests.UI.Modules return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" }); } - + try { var movies = Checker.GetPlexMovies(); @@ -533,9 +536,8 @@ namespace PlexRequests.UI.Modules return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_WeeklyRequestLimitTVShow }); } Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, CookieHelper.GetAnalyticClientId(Cookies)); - var tvApi = new TvMazeApi(); - var showInfo = tvApi.ShowLookupByTheTvDbId(showId); + var showInfo = TvApi.ShowLookupByTheTvDbId(showId); DateTime firstAir; DateTime.TryParse(showInfo.premiered, out firstAir); string fullShowName = $"{showInfo.name} ({firstAir.Year})"; @@ -687,7 +689,7 @@ namespace PlexRequests.UI.Modules } Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, CookieHelper.GetAnalyticClientId(Cookies)); var existingRequest = await RequestService.CheckRequestAsync(releaseId); - + if (existingRequest != null) { if (!existingRequest.UserHasRequested(Username)) @@ -697,7 +699,7 @@ namespace PlexRequests.UI.Modules } return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" }); } - + var albumInfo = MusicBrainzApi.GetAlbum(releaseId); DateTime release; DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); @@ -954,5 +956,65 @@ namespace PlexRequests.UI.Modules return Response.AsJson(new JsonResponseModel { Result = true, Message = message }); } + + private async Task RequestEpisodes() + { + var req = (Dictionary.ValueCollection)this.Request.Form.Values; + var json = req.FirstOrDefault().ToString(); + var model = JsonConvert.DeserializeObject(json); + //var model = this.Bind(); + if (model == null) + { + return Nancy.Response.NoBody; + } + var sonarrSettings = await SonarrService.GetSettingsAsync(); + if (!sonarrSettings.Enabled) + { + return Response.AsJson("Need sonarr"); + } + + var existingRequest = await RequestService.CheckRequestAsync(model.ShowId); + + + + + // Find the correct series + var task = await Task.Run(() => SonarrApi.GetSeries(sonarrSettings.ApiKey, sonarrSettings.FullUri)).ConfigureAwait(false); + var selectedSeries = task.FirstOrDefault(series => series.tvdbId == model.ShowId); + if (selectedSeries == null) + { + + // Need to add the series as unmonitored. + return Response.AsJson(""); + } + + // Show Exists + // Look up all episodes + var episodes = SonarrApi.GetEpisodes(selectedSeries.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri).ToList(); + var internalEpisodeIds = new List(); + var tasks = new List(); + foreach (var r in model.Episodes) + { + var episode = + episodes.FirstOrDefault( + x => x.episodeNumber == r.EpisodeNumber && x.seasonNumber == r.SeasonNumber); + if (episode == null) + { + continue; + } + var episodeInfo = SonarrApi.GetEpisode(episode.id.ToString(), sonarrSettings.ApiKey, sonarrSettings.FullUri); + episodeInfo.monitored = true; // Set the episode to monitored + tasks.Add(Task.Run(() => SonarrApi.UpdateEpisode(episodeInfo, sonarrSettings.ApiKey, + sonarrSettings.FullUri))); + internalEpisodeIds.Add(episode.id); + } + Task.WaitAll(tasks.ToArray()); + + SonarrApi.SearchForEpisodes(internalEpisodeIds.ToArray(), sonarrSettings.ApiKey, sonarrSettings.FullUri); + + + return Response.AsJson(new JsonResponseModel() { Result = true }); + } + } } diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 401b0c3e1..08f6b5b7a 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -200,6 +200,7 @@ + diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index b9cb4c633..2eed7b4dd 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -172,33 +172,34 @@
- {{#if_eq available true}} - - {{else}} - {{#if_eq requested true}} - - {{else}} {{#if_eq type "movie"}} - + {{#if_eq available true}} + + {{else}} + {{#if_eq requested true}} + + {{else}} + + {{/if_eq}} + {{/if_eq}} {{/if_eq}} {{#if_eq type "tv"}} - - {{/if_eq}} - {{/if_eq}} + {{/if_eq}} +
{{#if_eq available true}} @@ -304,6 +305,7 @@
+