diff --git a/PlexRequests.Api.Interfaces/IPlexApi.cs b/PlexRequests.Api.Interfaces/IPlexApi.cs index b13f1401b..075628385 100644 --- a/PlexRequests.Api.Interfaces/IPlexApi.cs +++ b/PlexRequests.Api.Interfaces/IPlexApi.cs @@ -40,5 +40,6 @@ namespace PlexRequests.Api.Interfaces PlexAccount GetAccount(string authToken); PlexLibraries GetLibrarySections(string authToken, Uri plexFullHost); PlexSearch GetLibrary(string authToken, Uri plexFullHost, string libraryId); + PlexMetadata GetMetadata(string authToken, Uri plexFullHost, string itemId); } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Plex/PlexMetadata.cs b/PlexRequests.Api.Models/Plex/PlexMetadata.cs new file mode 100644 index 000000000..e2ffc853e --- /dev/null +++ b/PlexRequests.Api.Models/Plex/PlexMetadata.cs @@ -0,0 +1,58 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexMetadata.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; +using System.Xml.Serialization; + +namespace PlexRequests.Api.Models.Plex +{ + [XmlRoot(ElementName = "MediaContainer")] + public class PlexMetadata + { + [XmlElement(ElementName= "Video")] + public Video Video { get; set; } + [XmlElement(ElementName = "Directory")] + public Directory1 Directory { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + [XmlAttribute(AttributeName = "allowSync")] + public string AllowSync { get; set; } + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + [XmlAttribute(AttributeName = "librarySectionID")] + public string LibrarySectionID { get; set; } + [XmlAttribute(AttributeName = "librarySectionTitle")] + public string LibrarySectionTitle { get; set; } + [XmlAttribute(AttributeName = "librarySectionUUID")] + public string LibrarySectionUUID { get; set; } + [XmlAttribute(AttributeName = "mediaTagPrefix")] + public string MediaTagPrefix { get; set; } + [XmlAttribute(AttributeName = "mediaTagVersion")] + public string MediaTagVersion { get; set; } + } + +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/Plex/PlexSearch.cs b/PlexRequests.Api.Models/Plex/PlexSearch.cs index b9fcd89a9..76193251b 100644 --- a/PlexRequests.Api.Models/Plex/PlexSearch.cs +++ b/PlexRequests.Api.Models/Plex/PlexSearch.cs @@ -133,6 +133,9 @@ namespace PlexRequests.Api.Models.Plex [XmlRoot(ElementName = "Video")] public class Video { + public string ProviderId { get; set; } + [XmlAttribute(AttributeName = "guid")] + public string Guid { get; set; } [XmlElement(ElementName = "Media")] public List Media { get; set; } [XmlElement(ElementName = "Genre")] @@ -241,6 +244,9 @@ namespace PlexRequests.Api.Models.Plex [XmlRoot(ElementName = "Directory")] public class Directory1 { + public string ProviderId { get; set; } + [XmlAttribute(AttributeName = "guid")] + public string Guid { get; set; } [XmlElement(ElementName = "Genre")] public List Genre { get; set; } [XmlElement(ElementName = "Role")] @@ -311,6 +317,7 @@ namespace PlexRequests.Api.Models.Plex [XmlRoot(ElementName = "MediaContainer")] public class PlexSearch { + [XmlElement(ElementName = "Directory")] public List Directory { get; set; } [XmlElement(ElementName = "Video")] diff --git a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj index 9b0857e44..86bcfd77f 100644 --- a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj +++ b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj @@ -64,6 +64,7 @@ + diff --git a/PlexRequests.Api/PlexApi.cs b/PlexRequests.Api/PlexApi.cs index a58316b7d..7ef0e6249 100644 --- a/PlexRequests.Api/PlexApi.cs +++ b/PlexRequests.Api/PlexApi.cs @@ -220,6 +220,36 @@ namespace PlexRequests.Api } } + 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); diff --git a/PlexRequests.Core/SettingModels/PlexSettings.cs b/PlexRequests.Core/SettingModels/PlexSettings.cs index fa39af857..e56595c51 100644 --- a/PlexRequests.Core/SettingModels/PlexSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexSettings.cs @@ -37,6 +37,7 @@ namespace PlexRequests.Core.SettingModels public int Port { get; set; } public bool Ssl { get; set; } public string SubDir { get; set; } + public bool AdvancedSearch { get; set; } [JsonIgnore] public Uri FullUri diff --git a/PlexRequests.Helpers.Tests/PlexHelperTests.cs b/PlexRequests.Helpers.Tests/PlexHelperTests.cs new file mode 100644 index 000000000..d690bbbfe --- /dev/null +++ b/PlexRequests.Helpers.Tests/PlexHelperTests.cs @@ -0,0 +1,61 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UriHelperTests.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 NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class PlexHelperTests + { + [TestCaseSource(nameof(PlexGuids))] + public string CreateUriWithSubDir(string guid) + { + return PlexHelper.GetProviderIdFromPlexGuid(guid); + } + + + private static IEnumerable PlexGuids + { + get + { + yield return new TestCaseData("com.plexapp.agents.thetvdb://269586/3/17?lang=en").Returns("269586"); + yield return new TestCaseData("com.plexapp.agents.imdb://tt3300542?lang=en").Returns("tt3300542"); + yield return new TestCaseData("com.plexapp.agents.thetvdb://71326/10/5?lang=en").Returns("71326"); + yield return new TestCaseData("local://3450").Returns("3450"); + yield return new TestCaseData("com.plexapp.agents.imdb://tt1179933?lang=en").Returns("tt1179933"); + yield return new TestCaseData("com.plexapp.agents.imdb://tt0284837?lang=en").Returns("tt0284837"); + yield return new TestCaseData("com.plexapp.agents.imdb://tt0076759?lang=en").Returns("tt0076759"); + } + } + + + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj index 9f3adbd0d..9129651fb 100644 --- a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj +++ b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj @@ -75,6 +75,7 @@ + diff --git a/PlexRequests.Helpers/PlexHelper.cs b/PlexRequests.Helpers/PlexHelper.cs new file mode 100644 index 000000000..06afebba4 --- /dev/null +++ b/PlexRequests.Helpers/PlexHelper.cs @@ -0,0 +1,47 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexHelper.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; + +namespace PlexRequests.Helpers +{ + public class PlexHelper + { + public static string GetProviderIdFromPlexGuid(string guid) + { + if (string.IsNullOrEmpty(guid)) + return guid; + + var guidSplit = guid.Split(new[] {'/', '?'}, StringSplitOptions.RemoveEmptyEntries); + if (guidSplit.Length > 1) + { + return guidSplit[1]; + } + return string.Empty; + } + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers/PlexRequests.Helpers.csproj b/PlexRequests.Helpers/PlexRequests.Helpers.csproj index aad6aa86a..8bdfb5ed8 100644 --- a/PlexRequests.Helpers/PlexRequests.Helpers.csproj +++ b/PlexRequests.Helpers/PlexRequests.Helpers.csproj @@ -79,6 +79,7 @@ + diff --git a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs index 14eda1731..6d8af1846 100644 --- a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs +++ b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs @@ -33,9 +33,9 @@ namespace PlexRequests.Services.Interfaces { void CheckAndUpdateAll(); List GetPlexMovies(); - bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year); + bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year, string providerId = null); List GetPlexTvShows(); - bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year); + bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year, string providerId = null); List GetPlexAlbums(); bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, string artist); } diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index bfe801258..f837427c1 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -111,10 +111,10 @@ namespace PlexRequests.Services.Jobs switch (r.Type) { case RequestType.Movie: - matchResult = IsMovieAvailable(movies, r.Title, releaseDate); + matchResult = IsMovieAvailable(movies, r.Title, releaseDate, r.ImdbId); break; case RequestType.TvShow: - matchResult = IsTvShowAvailable(shows, r.Title, releaseDate); + matchResult = IsTvShowAvailable(shows, r.Title, releaseDate, r.ProviderId.ToString()); break; case RequestType.Album: matchResult = IsAlbumAvailable(albums, r.Title, r.ReleaseDate.Year.ToString(), r.ArtistName); @@ -129,9 +129,9 @@ namespace PlexRequests.Services.Jobs modifiedModel.Add(r); continue; } - + } - + Log.Debug("Requests that will be updated count {0}", modifiedModel.Count); if (modifiedModel.Any()) @@ -158,19 +158,36 @@ namespace PlexRequests.Services.Jobs foreach (var lib in movieLibs) { - movies.AddRange(lib.Video.Select(x => new PlexMovie() // movies are in the Video list + movies.AddRange(lib.Video.Select(video => new PlexMovie { - Title = x.Title, - ReleaseYear = x.Year + ReleaseYear = video.Year, + Title = video.Title, + ProviderId = video.ProviderId, })); } } return movies; } - public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year) + public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year, string providerId = null) { - return plexMovies.Any(x => x.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)); + var advanced = !string.IsNullOrEmpty(providerId); + foreach (var movie in plexMovies) + { + if (advanced) + { + if (movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && + movie.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)) + { + return true; + } + } + return false; } public List GetPlexTvShows() @@ -190,18 +207,33 @@ namespace PlexRequests.Services.Jobs shows.AddRange(lib.Directory.Select(x => new PlexTvShow() // shows are in the directory list { Title = x.Title, - ReleaseYear = x.Year + ReleaseYear = x.Year, + ProviderId = x.ProviderId, })); } } return shows; } - public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year) + public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year, string providerId = null) { - return plexShows.Any(x => - (x.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) || x.Title.StartsWith(title, StringComparison.CurrentCultureIgnoreCase)) && - x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)); + var advanced = !string.IsNullOrEmpty(providerId); + foreach (var show in plexShows) + { + if (advanced) + { + if (show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && + show.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)) + { + return true; + } + } + return false; } public List GetPlexAlbums() @@ -239,12 +271,12 @@ namespace PlexRequests.Services.Jobs private List CachedLibraries(AuthenticationSettings authSettings, PlexSettings plexSettings, bool setCache) { - List results = new List(); + var results = new List(); if (!ValidateSettings(plexSettings, authSettings)) { Log.Warn("The settings are not configured"); - return results; // don't error out here, just let it go! + return results; // don't error out here, just let it go! let it goo!!! } try @@ -252,7 +284,28 @@ namespace PlexRequests.Services.Jobs if (setCache) { results = GetLibraries(authSettings, plexSettings); - + if (plexSettings.AdvancedSearch) + { + for (var i = 0; i < results.Count; i++) + { + for (var j = 0; j < results[i].Directory.Count; j++) + { + var currentItem = results[i].Directory[j]; + var metaData = PlexApi.GetMetadata(authSettings.PlexAuthToken, plexSettings.FullUri, + currentItem.RatingKey); + var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Directory.Guid); + results[i].Directory[j].ProviderId = providerId; + } + for (var j = 0; j < results[i].Video.Count; j++) + { + var currentItem = results[i].Video[j]; + var metaData = PlexApi.GetMetadata(authSettings.PlexAuthToken, plexSettings.FullUri, + currentItem.RatingKey); + var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Video.Guid); + results[i].Video[j].ProviderId = providerId; + } + } + } if (results != null) { Cache.Set(CacheKeys.PlexLibaries, results, CacheKeys.TimeFrameMinutes.SchedulerCaching); @@ -288,7 +341,7 @@ namespace PlexRequests.Services.Jobs } } } - + return libs; } diff --git a/PlexRequests.Services/Models/PlexMovie.cs b/PlexRequests.Services/Models/PlexMovie.cs index 57ca3549b..0149698ba 100644 --- a/PlexRequests.Services/Models/PlexMovie.cs +++ b/PlexRequests.Services/Models/PlexMovie.cs @@ -4,5 +4,6 @@ { public string Title { get; set; } public string ReleaseYear { get; set; } + public string ProviderId { get; set; } } } diff --git a/PlexRequests.Services/Models/PlexTvShow.cs b/PlexRequests.Services/Models/PlexTvShow.cs index 64dfda356..43375f0ca 100644 --- a/PlexRequests.Services/Models/PlexTvShow.cs +++ b/PlexRequests.Services/Models/PlexTvShow.cs @@ -4,5 +4,6 @@ { public string Title { get; set; } public string ReleaseYear { get; set; } + public string ProviderId { get; set; } } } diff --git a/PlexRequests.UI/Views/Admin/Plex.cshtml b/PlexRequests.UI/Views/Admin/Plex.cshtml index d50364b02..430cb6b64 100644 --- a/PlexRequests.UI/Views/Admin/Plex.cshtml +++ b/PlexRequests.UI/Views/Admin/Plex.cshtml @@ -1,4 +1,5 @@ @using PlexRequests.UI.Helpers +@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase @Html.Partial("_Sidebar") @{ int port; @@ -32,18 +33,35 @@
- - @if (Model.Ssl) - { - - } - else - { - - } - + + @if (Model.Ssl) + { + + } + else + { + + } + +
+
+ +
+
+ + @if (Model.AdvancedSearch) + { + + } + else + { + + } +
+ If enabled we will be able to have a 100% accurate match, but we will be querying Plex for every single item in your library. So if you have a big library then this could take some time.
+