diff --git a/PlexRequests.Core/SettingModels/PlexSettings.cs b/PlexRequests.Core/SettingModels/PlexSettings.cs index e83e61f4d..09be5fb15 100644 --- a/PlexRequests.Core/SettingModels/PlexSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexSettings.cs @@ -24,6 +24,9 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using Newtonsoft.Json; + namespace PlexRequests.Core.SettingModels { public sealed class PlexSettings : ExternalSettings @@ -36,5 +39,6 @@ namespace PlexRequests.Core.SettingModels public bool EnableTvEpisodeSearching { get; set; } public string PlexAuthToken { get; set; } + public string MachineIdentifier { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index 50b27dd2d..dac15a731 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -26,6 +26,7 @@ #endregion using System; +using System.Linq; using System.Text.RegularExpressions; using Mono.Data.Sqlite; @@ -66,6 +67,11 @@ namespace PlexRequests.Core { MigrateToVersion1900(); } + + if(version > 1899 && version <= 1910) + { + MigrateToVersion1910(); + } } return Db.DbConnection().ConnectionString; @@ -244,5 +250,30 @@ namespace PlexRequests.Core Log.Error(e); } } + + /// + /// Migrates to version1910. + /// + public void MigrateToVersion1910() + { + try + { + // Get the new machine Identifier + var settings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); + var plex = settings.GetSettings(); + if (!string.IsNullOrEmpty(plex.PlexAuthToken)) + { + var api = new PlexApi(new ApiRequest()); + var server = api.GetServer(plex.PlexAuthToken); // Get the server info + plex.MachineIdentifier = server.Server.FirstOrDefault(x => x.AccessToken == plex.PlexAuthToken)?.MachineIdentifier; + + settings.SaveSettings(plex); // Save the new settings + } + } + catch (Exception e) + { + Log.Error(e); + } + } } } diff --git a/PlexRequests.Helpers.Tests/PlexHelperTests.cs b/PlexRequests.Helpers.Tests/PlexHelperTests.cs index 6f09d6c39..33a1bf1a3 100644 --- a/PlexRequests.Helpers.Tests/PlexHelperTests.cs +++ b/PlexRequests.Helpers.Tests/PlexHelperTests.cs @@ -61,6 +61,12 @@ namespace PlexRequests.Helpers.Tests return PlexHelper.GetSeasonNumberFromTitle(title); } + [TestCaseSource(nameof(MediaUrls))] + public string GetPlexMediaUrlTest(string machineId, string mediaId) + { + return PlexHelper.GetPlexMediaUrl(machineId, mediaId); + } + private static IEnumerable PlexGuids { get @@ -75,6 +81,15 @@ namespace PlexRequests.Helpers.Tests } } + private static IEnumerable MediaUrls + { + get + { + yield return new TestCaseData("abcd","99").Returns("https://app.plex.tv/web/app#!/server/abcd/details/%2Flibrary%2Fmetadata%2F99").SetName("Test 1"); + yield return new TestCaseData("a54d1db669799308cd704b791f331eca6648b952", "51").Returns("https://app.plex.tv/web/app#!/server/a54d1db669799308cd704b791f331eca6648b952/details/%2Flibrary%2Fmetadata%2F51").SetName("Test 2"); + } + } + private static IEnumerable SeasonNumbers { get diff --git a/PlexRequests.Helpers/PlexHelper.cs b/PlexRequests.Helpers/PlexHelper.cs index baafcd451..1e186ddba 100644 --- a/PlexRequests.Helpers/PlexHelper.cs +++ b/PlexRequests.Helpers/PlexHelper.cs @@ -95,6 +95,13 @@ namespace PlexRequests.Helpers return 0; } + + public static string GetPlexMediaUrl(string machineId, string mediaId) + { + var url = + $"https://app.plex.tv/web/app#!/server/{machineId}/details/%2Flibrary%2Fmetadata%2F{mediaId}"; + return url; + } } public class EpisodeModelHelper diff --git a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs index 5b8ba361f..3b152d7ee 100644 --- a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs +++ b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs @@ -242,6 +242,7 @@ namespace PlexRequests.Services.Tests } }); CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedMovies); + SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create()); var movies = Checker.GetPlexMovies(); Assert.That(movies.Any(x => x.ProviderId == "1212")); @@ -258,6 +259,7 @@ namespace PlexRequests.Services.Tests new Directory1 {Type = "show", Title = "title1", Year = "2016", ProviderId = "1212", Seasons = new List()} } }); + SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create()); CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedTv); var movies = Checker.GetPlexTvShows(); diff --git a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs index 8992e6545..d966e2b5f 100644 --- a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs +++ b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs @@ -42,6 +42,9 @@ namespace PlexRequests.Services.Interfaces List GetPlexAlbums(); bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, string artist); bool IsEpisodeAvailable(string theTvDbId, int season, int episode); + PlexAlbum GetAlbum(PlexAlbum[] plexAlbums, string title, string year, string artist); + PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null); + PlexTvShow GetTvShow(PlexTvShow[] plexShows, string title, string year, string providerId = null, int[] seasons = null); /// /// Gets the episode's stored in the cache. /// diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index c72ed45d5..c8b46b32f 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -162,6 +162,7 @@ namespace PlexRequests.Services.Jobs public List GetPlexMovies() { + var settings = Plex.GetSettings(); var movies = new List(); var libs = Cache.Get>(CacheKeys.PlexLibaries); if (libs != null) @@ -179,6 +180,7 @@ namespace PlexRequests.Services.Jobs ReleaseYear = video.Year, Title = video.Title, ProviderId = video.ProviderId, + Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, video.RatingKey) })); } } @@ -186,6 +188,12 @@ namespace PlexRequests.Services.Jobs } public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year, string providerId = null) + { + var movie = GetMovie(plexMovies, title, year, providerId); + return movie != null; + } + + public PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null) { var advanced = !string.IsNullOrEmpty(providerId); foreach (var movie in plexMovies) @@ -195,20 +203,21 @@ namespace PlexRequests.Services.Jobs if (!string.IsNullOrEmpty(movie.ProviderId) && movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { - return true; + return movie; } } if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && movie.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)) { - return true; + return movie; } } - return false; + return null; } public List GetPlexTvShows() { + var settings = Plex.GetSettings(); var shows = new List(); var libs = Cache.Get>(CacheKeys.PlexLibaries); if (libs != null) @@ -228,7 +237,9 @@ namespace PlexRequests.Services.Jobs Title = x.Title, ReleaseYear = x.Year, ProviderId = x.ProviderId, - Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray() + Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray(), + Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey) + })); } } @@ -236,6 +247,14 @@ namespace PlexRequests.Services.Jobs } public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year, string providerId = null, int[] seasons = null) + { + var show = GetTvShow(plexShows, title, year, providerId, seasons); + return show != null; + } + + + public PlexTvShow GetTvShow(PlexTvShow[] plexShows, string title, string year, string providerId = null, + int[] seasons = null) { var advanced = !string.IsNullOrEmpty(providerId); foreach (var show in plexShows) @@ -246,23 +265,23 @@ namespace PlexRequests.Services.Jobs { if (seasons.Any(season => show.Seasons.Contains(season))) { - return true; + return show; } - return false; + return null; } if (!string.IsNullOrEmpty(show.ProviderId) && show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { - return true; + return show; } } if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && show.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)) { - return true; + return show; } } - return false; + return null; } public bool IsEpisodeAvailable(string theTvDbId, int season, int episode) @@ -332,6 +351,7 @@ namespace PlexRequests.Services.Jobs public List GetPlexAlbums() { + var settings = Plex.GetSettings(); var albums = new List(); var libs = Cache.Get>(CacheKeys.PlexLibaries); if (libs != null) @@ -348,7 +368,8 @@ namespace PlexRequests.Services.Jobs { Title = x.Title, ReleaseYear = x.Year, - Artist = x.ParentTitle + Artist = x.ParentTitle, + Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey) })); } } @@ -359,7 +380,13 @@ namespace PlexRequests.Services.Jobs { return plexAlbums.Any(x => x.Title.Contains(title) && - //x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase) && + x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase)); + } + + public PlexAlbum GetAlbum(PlexAlbum[] plexAlbums, string title, string year, string artist) + { + return plexAlbums.FirstOrDefault(x => + x.Title.Contains(title) && x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase)); } diff --git a/PlexRequests.Services/Models/PlexAlbum.cs b/PlexRequests.Services/Models/PlexAlbum.cs index 5d2bd7254..09d4b2638 100644 --- a/PlexRequests.Services/Models/PlexAlbum.cs +++ b/PlexRequests.Services/Models/PlexAlbum.cs @@ -1,9 +1,10 @@ -namespace PlexRequests.Services.Models -{ - public class PlexAlbum - { - public string Title { get; set; } - public string Artist { get; set; } - public string ReleaseYear { get; set; } - } -} +namespace PlexRequests.Services.Models +{ + public class PlexAlbum + { + public string Title { get; set; } + public string Artist { get; set; } + public string ReleaseYear { get; set; } + public string Url { get; set; } + } +} diff --git a/PlexRequests.Services/Models/PlexMovie.cs b/PlexRequests.Services/Models/PlexMovie.cs index 0149698ba..27eca9948 100644 --- a/PlexRequests.Services/Models/PlexMovie.cs +++ b/PlexRequests.Services/Models/PlexMovie.cs @@ -1,9 +1,10 @@ -namespace PlexRequests.Services.Models -{ - public class PlexMovie - { - public string Title { get; set; } - public string ReleaseYear { get; set; } - public string ProviderId { get; set; } - } -} +namespace PlexRequests.Services.Models +{ + public class PlexMovie + { + public string Title { get; set; } + public string ReleaseYear { get; set; } + public string ProviderId { get; set; } + public string Url { get; set; } + } +} diff --git a/PlexRequests.Services/Models/PlexTvShow.cs b/PlexRequests.Services/Models/PlexTvShow.cs index 5ac629132..aecf6f088 100644 --- a/PlexRequests.Services/Models/PlexTvShow.cs +++ b/PlexRequests.Services/Models/PlexTvShow.cs @@ -6,5 +6,6 @@ public string ReleaseYear { get; set; } public string ProviderId { get; set; } public int[] Seasons { get; set; } + public string Url { get; set; } } } diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 3bbbc43cb..07d5d01b8 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -104,7 +104,14 @@ namespace PlexRequests.Services.Notification private bool ValidateConfiguration(EmailNotificationSettings settings) { - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + if (settings.Authentication) + { + if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) + { + return false; + } + } + if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) { return false; } diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index 932a68cca..81de57573 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -444,7 +444,8 @@ $(function () { imdb: result.imdbId, requested: result.requested, approved: result.approved, - available: result.available + available: result.available, + url: result.plexUrl }; return context; @@ -465,7 +466,8 @@ $(function () { approved: result.approved, available: result.available, episodes: result.episodes, - tvFullyAvailable: result.tvFullyAvailable + tvFullyAvailable: result.tvFullyAvailable, + url: result.plexUrl }; return context; } @@ -485,7 +487,8 @@ $(function () { country: result.country, requested: result.requested, approved: result.approved, - available: result.available + available: result.available, + url: result.plexUrl }; return context; diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index 8c55ccac8..2ce349ec0 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -118,9 +118,10 @@ namespace PlexRequests.UI.Helpers public static IHtmlString LoadDateTimePickerAsset(this HtmlHelpers helper) { - var startUrl = GetBaseUrl(); + var content = GetBaseUrl(); var sb = new StringBuilder(); + var startUrl = $"{content}/Content"; sb.AppendLine($""); sb.AppendLine($""); diff --git a/PlexRequests.UI/Models/SearchViewModel.cs b/PlexRequests.UI/Models/SearchViewModel.cs index 776b9d2b1..9c11d32ef 100644 --- a/PlexRequests.UI/Models/SearchViewModel.cs +++ b/PlexRequests.UI/Models/SearchViewModel.cs @@ -1,37 +1,38 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SearchTvShowViewModel.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 SearchViewModel - { - public bool Approved { get; set; } - public bool Requested { get; set; } - public bool Available { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SearchTvShowViewModel.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 SearchViewModel + { + public bool Approved { get; set; } + public bool Requested { get; set; } + public bool Available { get; set; } + public string PlexUrl { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Models/SessionKeys.cs b/PlexRequests.UI/Models/SessionKeys.cs index fefc7d8bd..766a84bf6 100644 --- a/PlexRequests.UI/Models/SessionKeys.cs +++ b/PlexRequests.UI/Models/SessionKeys.cs @@ -31,5 +31,6 @@ namespace PlexRequests.UI.Models public const string UsernameKey = "Username"; public const string ClientDateTimeOffsetKey = "ClientDateTimeOffset"; public const string UserWizardPlexAuth = nameof(UserWizardPlexAuth); + public const string UserWizardMachineId = nameof(UserWizardMachineId); } } diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 4d6ca7e43..a04edca3c 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -161,7 +161,7 @@ namespace PlexRequests.UI.Modules Post["/couchpotato"] = _ => SaveCouchPotato(); Get["/plex"] = _ => Plex(); - Post["/plex"] = _ => SavePlex(); + Post["/plex", true] = async (x, ct) => await SavePlex(); Get["/sonarr"] = _ => Sonarr(); Post["/sonarr"] = _ => SaveSonarr(); @@ -170,13 +170,13 @@ namespace PlexRequests.UI.Modules Post["/sickrage"] = _ => SaveSickrage(); Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); - Post["/cpprofiles", true] = async (x,ct) => await GetCpProfiles(); + Post["/cpprofiles", true] = async (x, ct) => await GetCpProfiles(); Post["/cpapikey"] = x => GetCpApiKey(); Get["/emailnotification"] = _ => EmailNotifications(); Post["/emailnotification"] = _ => SaveEmailNotifications(); Post["/testemailnotification"] = _ => TestEmailNotifications(); - Get["/status", true] = async (x,ct) => await Status(); + Get["/status", true] = async (x, ct) => await Status(); Get["/pushbulletnotification"] = _ => PushbulletNotifications(); Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); @@ -268,7 +268,7 @@ namespace PlexRequests.UI.Modules Analytics.TrackEventAsync(Category.Admin, Action.Save, "CollectAnalyticData turned off", Username, CookieHelper.GetAnalyticClientId(Cookies)); } var result = PrService.SaveSettings(model); - + Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies)); return Response.AsJson(result ? new JsonResponseModel { Result = true } @@ -377,7 +377,7 @@ namespace PlexRequests.UI.Modules return View["Plex", settings]; } - private Response SavePlex() + private async Task SavePlex() { var plexSettings = this.Bind(); var valid = this.Validate(plexSettings); @@ -386,8 +386,11 @@ namespace PlexRequests.UI.Modules return Response.AsJson(valid.SendJsonError()); } + //Lookup identifier + var server = PlexApi.GetServer(plexSettings.PlexAuthToken); + plexSettings.MachineIdentifier = server.Server.FirstOrDefault(x => x.AccessToken == plexSettings.PlexAuthToken)?.MachineIdentifier; - var result = PlexService.SaveSettings(plexSettings); + var result = await PlexService.SaveSettingsAsync(plexSettings); return Response.AsJson(result ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Plex!" } @@ -517,7 +520,7 @@ namespace PlexRequests.UI.Modules { if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) { - return Response.AsJson(new JsonResponseModel {Result = false, Message = "SMTP Authentication is enabled, please specify a username and password"}); + return Response.AsJson(new JsonResponseModel { Result = false, Message = "SMTP Authentication is enabled, please specify a username and password" }); } } @@ -542,7 +545,7 @@ namespace PlexRequests.UI.Modules { var checker = new StatusChecker(); var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30); - var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true}); + var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true }); status.ReleaseNotes = md.Transform(status.ReleaseNotes); return View["Status", status]; } @@ -711,7 +714,7 @@ namespace PlexRequests.UI.Modules private Response GetCpApiKey() { var settings = this.Bind(); - + if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password)) { return Response.AsJson(new { Message = "Please enter a username and password to request the Api Key", Result = false }); @@ -938,12 +941,12 @@ namespace PlexRequests.UI.Modules { await LogsRepo.DeleteAsync(logEntity); } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Logs cleared successfully."}); + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Logs cleared successfully." }); } catch (Exception e) { Log.Error(e); - return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message }); + return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message }); } } } diff --git a/PlexRequests.UI/Modules/BaseModule.cs b/PlexRequests.UI/Modules/BaseModule.cs index 1a205202b..a01253786 100644 --- a/PlexRequests.UI/Modules/BaseModule.cs +++ b/PlexRequests.UI/Modules/BaseModule.cs @@ -122,7 +122,7 @@ namespace PlexRequests.UI.Modules { get { - if (Context?.CurrentUser == null) + if (!LoggedIn) { return false; } @@ -130,6 +130,9 @@ namespace PlexRequests.UI.Modules return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser); } } + + protected bool LoggedIn => Context?.CurrentUser != null; + protected string Culture { get; set; } protected const string CultureCookieName = "_culture"; protected Response SetCookie() diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs index 8e6a9d39c..8c3bd550d 100644 --- a/PlexRequests.UI/Modules/LoginModule.cs +++ b/PlexRequests.UI/Modules/LoginModule.cs @@ -1,150 +1,154 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: LoginModule.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.Dynamic; - -using Nancy; -using Nancy.Authentication.Forms; -using Nancy.Extensions; -using Nancy.Responses.Negotiation; -using Nancy.Security; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.UI.Models; - -namespace PlexRequests.UI.Modules -{ - public class LoginModule : BaseModule - { - public LoginModule(ISettingsService pr, ICustomUserMapper m) : base(pr) - { - UserMapper = m; - Get["/login"] = _ => - { - { - dynamic model = new ExpandoObject(); - model.Redirect = Request.Query.redirect.Value ?? string.Empty; - model.Errored = Request.Query.error.HasValue; - var adminCreated = UserMapper.DoUsersExist(); - model.AdminExists = adminCreated; - return View["Index", model]; - } - - }; - - Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/"); - - Post["/login"] = x => - { - var username = (string)Request.Form.Username; - var password = (string)Request.Form.Password; - var dtOffset = (int)Request.Form.DateTimeOffset; - var redirect = (string)Request.Form.Redirect; - - var userId = UserMapper.ValidateUser(username, password); - - if (userId == null) - { - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username); - } - DateTime? expiry = null; - if (Request.Form.RememberMe.HasValue) - { - expiry = DateTime.Now.AddDays(7); - } - Session[SessionKeys.UsernameKey] = username; - Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset; - if(redirect.Contains("userlogin")){ - redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search"; - } - return this.LoginAndRedirect(userId.Value, expiry, redirect); - }; - - Get["/register"] = x => - { - { - dynamic model = new ExpandoObject(); - model.Errored = Request.Query.error.HasValue; - - return View["Register", model]; - } - }; - - Post["/register"] = x => - { - var username = (string)Request.Form.Username; - var exists = UserMapper.DoUsersExist(); - if (exists) - { - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true"); - } - var userId = UserMapper.CreateAdmin(username, Request.Form.Password); - Session[SessionKeys.UsernameKey] = username; - return this.LoginAndRedirect((Guid)userId); - }; - - Get["/changepassword"] = _ => ChangePassword(); - Post["/changepassword"] = _ => ChangePasswordPost(); - } - private ICustomUserMapper UserMapper { get; } - - private Negotiator ChangePassword() - { - this.RequiresAuthentication(); - return View["ChangePassword"]; - } - - private Response ChangePasswordPost() - { - var username = Context.CurrentUser.UserName; - var oldPass = Request.Form.OldPassword; - var newPassword = Request.Form.NewPassword; - var newPasswordAgain = Request.Form.NewPasswordAgain; - - if (string.IsNullOrEmpty(oldPass) || string.IsNullOrEmpty(newPassword) || - string.IsNullOrEmpty(newPasswordAgain)) - { - return Response.AsJson(new JsonResponseModel { Message = "Please fill in all fields", Result = false }); - } - - if (!newPassword.Equals(newPasswordAgain)) - { - return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false }); - } - - var result = UserMapper.UpdatePassword(username, oldPass, newPassword); - if (result) - { - return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true }); - } - - return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false }); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: LoginModule.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.Dynamic; + +using Nancy; +using Nancy.Authentication.Forms; +using Nancy.Extensions; +using Nancy.Linker; +using Nancy.Responses.Negotiation; +using Nancy.Security; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules +{ + public class LoginModule : BaseModule + { + public LoginModule(ISettingsService pr, ICustomUserMapper m, IResourceLinker linker) : base(pr) + { + UserMapper = m; + Get["/login"] = _ => + { + if (LoggedIn) + { + var url = linker.BuildRelativeUri(Context, "SearchIndex"); + return Response.AsRedirect(url.ToString()); + } + dynamic model = new ExpandoObject(); + model.Redirect = Request.Query.redirect.Value ?? string.Empty; + model.Errored = Request.Query.error.HasValue; + var adminCreated = UserMapper.DoUsersExist(); + model.AdminExists = adminCreated; + return View["Index", model]; + }; + + Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/"); + + Post["/login"] = x => + { + var username = (string)Request.Form.Username; + var password = (string)Request.Form.Password; + var dtOffset = (int)Request.Form.DateTimeOffset; + var redirect = (string)Request.Form.Redirect; + + var userId = UserMapper.ValidateUser(username, password); + + if (userId == null) + { + return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username); + } + DateTime? expiry = null; + if (Request.Form.RememberMe.HasValue) + { + expiry = DateTime.Now.AddDays(7); + } + Session[SessionKeys.UsernameKey] = username; + Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset; + if (redirect.Contains("userlogin")) + { + redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search"; + } + return this.LoginAndRedirect(userId.Value, expiry, redirect); + }; + + Get["/register"] = x => + { + { + dynamic model = new ExpandoObject(); + model.Errored = Request.Query.error.HasValue; + + return View["Register", model]; + } + }; + + Post["/register"] = x => + { + var username = (string)Request.Form.Username; + var exists = UserMapper.DoUsersExist(); + if (exists) + { + return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true"); + } + var userId = UserMapper.CreateAdmin(username, Request.Form.Password); + Session[SessionKeys.UsernameKey] = username; + return this.LoginAndRedirect((Guid)userId); + }; + + Get["/changepassword"] = _ => ChangePassword(); + Post["/changepassword"] = _ => ChangePasswordPost(); + } + private ICustomUserMapper UserMapper { get; } + + private Negotiator ChangePassword() + { + this.RequiresAuthentication(); + return View["ChangePassword"]; + } + + private Response ChangePasswordPost() + { + var username = Context.CurrentUser.UserName; + var oldPass = Request.Form.OldPassword; + var newPassword = Request.Form.NewPassword; + var newPasswordAgain = Request.Form.NewPasswordAgain; + + if (string.IsNullOrEmpty(oldPass) || string.IsNullOrEmpty(newPassword) || + string.IsNullOrEmpty(newPasswordAgain)) + { + return Response.AsJson(new JsonResponseModel { Message = "Please fill in all fields", Result = false }); + } + + if (!newPassword.Equals(newPasswordAgain)) + { + return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false }); + } + + var result = UserMapper.UpdatePassword(username, oldPass, newPassword); + if (result) + { + return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true }); + } + + return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false }); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 5f0fa4ed1..73f016f24 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -110,7 +110,7 @@ namespace PlexRequests.UI.Modules Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); - Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); + Get["music/{searchTerm}", true] = async (x, ct) => await SearchAlbum((string)x.searchTerm); Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); @@ -252,9 +252,11 @@ namespace PlexRequests.UI.Modules VoteCount = movie.VoteCount }; var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); - if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) + var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString()); + if (plexMovie != null) { viewMovie.Available = true; + viewMovie.PlexUrl = plexMovie.Url; } else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db { @@ -343,9 +345,12 @@ namespace PlexRequests.UI.Modules providerId = viewT.Id.ToString(); } - if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) + var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), + providerId); + if (plexShow != null) { viewT.Available = true; + viewT.PlexUrl = plexShow.Url; } else if (t.show?.externals?.thetvdb != null) { @@ -371,7 +376,7 @@ namespace PlexRequests.UI.Modules return Response.AsJson(viewTv); } - private async Task SearchMusic(string searchTerm) + private async Task SearchAlbum(string searchTerm) { Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); var apiAlbums = new List(); @@ -405,9 +410,11 @@ namespace PlexRequests.UI.Modules DateTime release; DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); var artist = a.ArtistCredit?.FirstOrDefault()?.artist; - if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) + var plexAlbum = Checker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name); + if (plexAlbum != null) { viewA.Available = true; + viewA.PlexUrl = plexAlbum.Url; } if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) { diff --git a/PlexRequests.UI/Modules/UserWizardModule.cs b/PlexRequests.UI/Modules/UserWizardModule.cs index 253288929..1bfbbd679 100644 --- a/PlexRequests.UI/Modules/UserWizardModule.cs +++ b/PlexRequests.UI/Modules/UserWizardModule.cs @@ -34,7 +34,7 @@ using Nancy.Extensions; using Nancy.ModelBinding; using Nancy.Responses.Negotiation; using Nancy.Validation; - +using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Core; using PlexRequests.Core.SettingModels; @@ -84,7 +84,9 @@ namespace PlexRequests.UI.Modules private ICustomUserMapper Mapper { get; } private IAnalytics Analytics { get; } - + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private Response PlexAuth() { var user = this.Bind(); @@ -103,9 +105,10 @@ namespace PlexRequests.UI.Modules // Set the auth token in the session so we can use it in the next form Session[SessionKeys.UserWizardPlexAuth] = model.user.authentication_token; - + var servers = PlexApi.GetServer(model.user.authentication_token); var firstServer = servers.Server.FirstOrDefault(); + return Response.AsJson(new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme }); } @@ -119,6 +122,20 @@ namespace PlexRequests.UI.Modules } form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString(); // Set the auth token from the previous form + // Get the machine ID from the settings (This could have changed) + try + { + var servers = PlexApi.GetServer(form.PlexAuthToken); + var firstServer = servers.Server.FirstOrDefault(x => x.AccessToken == form.PlexAuthToken); + + Session[SessionKeys.UserWizardMachineId] = firstServer?.MachineIdentifier; + } + catch (Exception e) + { + // Probably bad settings, just continue + Log.Error(e); + } + var result = await PlexSettings.SaveSettingsAsync(form); if (result) { diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx index 58e8f71c8..4f23fce97 100644 --- a/PlexRequests.UI/Resources/UI.resx +++ b/PlexRequests.UI/Resources/UI.resx @@ -440,4 +440,7 @@ There is no information available for the release date + + View In Plex + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI1.Designer.cs b/PlexRequests.UI/Resources/UI1.Designer.cs index 20ab447d7..6cd282c32 100644 --- a/PlexRequests.UI/Resources/UI1.Designer.cs +++ b/PlexRequests.UI/Resources/UI1.Designer.cs @@ -987,6 +987,15 @@ namespace PlexRequests.UI.Resources { } } + /// + /// Looks up a localized string similar to View In Plex. + /// + public static string Search_ViewInPlex { + get { + return ResourceManager.GetString("Search_ViewInPlex", resourceCulture); + } + } + /// /// Looks up a localized string similar to You have reached your weekly request limit for Albums! Please contact your admin.. /// diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index 17204abb1..950a7f06d 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -175,6 +175,8 @@ {{#if_eq type "movie"}} {{#if_eq available true}} +
+ @UI.Search_ViewInPlex {{else}} {{#if_eq requested true}} @@ -186,7 +188,8 @@ {{#if_eq type "tv"}} {{#if_eq tvFullyAvailable true}} @*//TODO Not used yet*@ - +
+ @UI.Search_ViewInPlex {{else}}