From cdd995652b1cfb0e2ca92f027fee73cd7041f19e Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 1 Feb 2017 21:19:42 +0000 Subject: [PATCH 01/32] Fixed #1038 --- Ombi.UI/Modules/RequestsModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index 699b19b67..08d6d81ce 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -254,7 +254,7 @@ namespace Ombi.UI.Modules Status = tv.Status, ImdbId = tv.ImdbId, Id = tv.Id, - PosterPath = tv.PosterPath.Contains("http:") ? tv.PosterPath.Replace("http:", "https:") : tv.PosterPath, // We make the poster path https on request, but this is just incase + PosterPath = tv.PosterPath?.Contains("http:") ?? false ? tv.PosterPath?.Replace("http:", "https:") : tv.PosterPath ?? string.Empty, // We make the poster path https on request, but this is just incase ReleaseDate = tv.ReleaseDate, ReleaseDateTicks = tv.ReleaseDate.Ticks, RequestedDate = tv.RequestedDate, From 0f121238aaa7f33497787d7227082fcb10e6aa72 Mon Sep 17 00:00:00 2001 From: SuperPotatoMen Date: Wed, 1 Feb 2017 23:38:07 +0100 Subject: [PATCH 02/32] Update README.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 01f0d7ed5..32f49ed8f 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,9 @@ ____ Here some of the features Ombi has: * All your users to Request Movies, TV Shows (Whole series, whole seaons or even single episodes!) and Albums * Easily manage your requests - -* User management system (supports plex.tv accounts and local accounts) [NEW] -* Sending newsletters [NEW] -* Fault Queue for requests (Buffer requests if Sonar/Couchpotato/SickRage is offline) [NEW] - +* User management system (supports plex.tv accounts and local accounts) +* Sending newsletters +* Fault Queue for requests (Buffer requests if Sonar/Couchpotato/SickRage is offline) * Allow your users to report issues and manage them separately * A landing page that will give you the availability of your Plex server and also add custom notification text to inform your users of downtime. * Allow your users to get notifications! @@ -35,9 +33,12 @@ Here some of the features Ombi has: ### Integration We integrate with the following applications: * Plex server 1.2 (and higher) +* Emby (beta) * Sonarr * SickRage * CouchPotato +* Radarr (beta) +* Watcher (beta) * Headphones ### Notifications @@ -46,6 +47,7 @@ Supported notifications: * Pushbullet * Pushover * Slack +* Discord * Weekly Recently Added email notification to all of your Plex Users! # Feature Requests From ace4eecac5d656db4a59a2c042b4502ea04425c7 Mon Sep 17 00:00:00 2001 From: SuperPotatoMen Date: Thu, 2 Feb 2017 19:54:29 +0100 Subject: [PATCH 03/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32f49ed8f..d1cbf8cfb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ____ |----------|:---------------------------:|:----------------------------:|:----------------------------:| | AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/master?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/eap?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/eap) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/dev?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/dev) | Travis | [![Travis](https://img.shields.io/travis/tidusjar/Ombi/master.svg)](https://travis-ci.org/tidusjar/Ombi) | [![Travis](https://img.shields.io/travis/tidusjar/Ombi/EAP.svg)](https://travis-ci.org/tidusjar/Ombi) | [![Travis](https://img.shields.io/travis/tidusjar/Ombi/dev.svg)](https://travis-ci.org/tidusjar/Ombi) - +| Download | Download button | Download button | Download button | # Features Here some of the features Ombi has: * All your users to Request Movies, TV Shows (Whole series, whole seaons or even single episodes!) and Albums From cb7f8b0c23e498cba6229d9793a14015ba23bf6e Mon Sep 17 00:00:00 2001 From: SuperPotatoMen Date: Mon, 13 Feb 2017 00:34:12 +0100 Subject: [PATCH 04/32] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d1cbf8cfb..88a0c4253 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,13 @@ ____ [![Github All Releases](https://img.shields.io/github/downloads/tidusjar/Ombi/total.svg)](https://github.com/tidusjar/Ombi) [![Stories in Progress](https://badge.waffle.io/tidusjar/Ombi.svg?label=in progress&title=In Progress)](http://waffle.io/tidusjar/Ombi) +[![Report a bug](http://i.imgur.com/xSpw482.png)](https://github.com/tidusjar/Ombi/issues/new) [![Feature request](http://i.imgur.com/mFO0OuX.png)](http://feathub.com/tidusjar/Ombi) + | Service | Master | Early Access | Dev | |----------|:---------------------------:|:----------------------------:|:----------------------------:| | AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/master?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/master) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/eap?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/eap) | [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn/branch/dev?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/dev) | Travis | [![Travis](https://img.shields.io/travis/tidusjar/Ombi/master.svg)](https://travis-ci.org/tidusjar/Ombi) | [![Travis](https://img.shields.io/travis/tidusjar/Ombi/EAP.svg)](https://travis-ci.org/tidusjar/Ombi) | [![Travis](https://img.shields.io/travis/tidusjar/Ombi/dev.svg)](https://travis-ci.org/tidusjar/Ombi) -| Download | Download button | Download button | Download button | +| Download |[![Download](http://i.imgur.com/odToka3.png)](https://github.com/tidusjar/Ombi/releases) | [![Download](http://i.imgur.com/odToka3.png)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/eap/artifacts) | [![Download](http://i.imgur.com/odToka3.png)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/dev/artifacts) | # Features Here some of the features Ombi has: * All your users to Request Movies, TV Shows (Whole series, whole seaons or even single episodes!) and Albums From 12ff7378ba3d65b5123c6be1063c9c73f4b2717b Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 17 Feb 2017 21:36:27 +0000 Subject: [PATCH 05/32] The landing page now works for emby #435 --- Ombi.Api.Interfaces/IEmbyApi.cs | 1 + Ombi.Api.Models/Emby/EmbySystemInfo.cs | 63 ++++++++++++++++++++++++++ Ombi.Api.Models/Ombi.Api.Models.csproj | 1 + Ombi.Api/EmbyApi.cs | 20 ++++++++ Ombi.UI/Modules/LandingPageModule.cs | 50 +++++++++++++++----- 5 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 Ombi.Api.Models/Emby/EmbySystemInfo.cs diff --git a/Ombi.Api.Interfaces/IEmbyApi.cs b/Ombi.Api.Interfaces/IEmbyApi.cs index ddc85868c..bc4697140 100644 --- a/Ombi.Api.Interfaces/IEmbyApi.cs +++ b/Ombi.Api.Interfaces/IEmbyApi.cs @@ -14,5 +14,6 @@ namespace Ombi.Api.Interfaces EmbyItemContainer ViewLibrary(string apiKey, string userId, Uri baseUri); EmbyInformation GetInformation(string mediaId, EmbyMediaType type, string apiKey, string userId, Uri baseUri); EmbyUser LogIn(string username, string password, string apiKey, Uri baseUri); + EmbySystemInfo GetSystemInformation(string apiKey, Uri baseUrl); } } \ No newline at end of file diff --git a/Ombi.Api.Models/Emby/EmbySystemInfo.cs b/Ombi.Api.Models/Emby/EmbySystemInfo.cs new file mode 100644 index 000000000..e4b6859fc --- /dev/null +++ b/Ombi.Api.Models/Emby/EmbySystemInfo.cs @@ -0,0 +1,63 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: EmbySystemInfo.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 Ombi.Api.Models.Emby +{ + public class EmbySystemInfo + { + public string SystemUpdateLevel { get; set; } + public string OperatingSystemDisplayName { get; set; } + public bool SupportsRunningAsService { get; set; } + public string MacAddress { get; set; } + public bool HasPendingRestart { get; set; } + public bool SupportsLibraryMonitor { get; set; } + public object[] InProgressInstallations { get; set; } + public int WebSocketPortNumber { get; set; } + public object[] CompletedInstallations { get; set; } + public bool CanSelfRestart { get; set; } + public bool CanSelfUpdate { get; set; } + public object[] FailedPluginAssemblies { get; set; } + public string ProgramDataPath { get; set; } + public string ItemsByNamePath { get; set; } + public string CachePath { get; set; } + public string LogPath { get; set; } + public string InternalMetadataPath { get; set; } + public string TranscodingTempPath { get; set; } + public int HttpServerPortNumber { get; set; } + public bool SupportsHttps { get; set; } + public int HttpsPortNumber { get; set; } + public bool HasUpdateAvailable { get; set; } + public bool SupportsAutoRunAtStartup { get; set; } + public string EncoderLocationType { get; set; } + public string SystemArchitecture { get; set; } + public string LocalAddress { get; set; } + public string WanAddress { get; set; } + public string ServerName { get; set; } + public string Version { get; set; } + public string OperatingSystem { get; set; } + public string Id { get; set; } + } +} \ No newline at end of file diff --git a/Ombi.Api.Models/Ombi.Api.Models.csproj b/Ombi.Api.Models/Ombi.Api.Models.csproj index 3d5fc3460..eb3297999 100644 --- a/Ombi.Api.Models/Ombi.Api.Models.csproj +++ b/Ombi.Api.Models/Ombi.Api.Models.csproj @@ -71,6 +71,7 @@ + diff --git a/Ombi.Api/EmbyApi.cs b/Ombi.Api/EmbyApi.cs index 9625ffa4f..f703c0570 100644 --- a/Ombi.Api/EmbyApi.cs +++ b/Ombi.Api/EmbyApi.cs @@ -71,6 +71,26 @@ namespace Ombi.Api return obj; } + public EmbySystemInfo GetSystemInformation(string apiKey, Uri baseUrl) + { + var request = new RestRequest + { + Resource = "emby/System/Info", + Method = Method.GET + }; + + AddHeaders(request, apiKey); + + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetSystemInformation for Emby, Retrying {0}", timespan), new[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(5) + }); + + var obj = policy.Execute(() => Api.ExecuteJson(request, baseUrl)); + + return obj; + } + public EmbyItemContainer ViewLibrary(string apiKey, string userId, Uri baseUri) { var request = new RestRequest diff --git a/Ombi.UI/Modules/LandingPageModule.cs b/Ombi.UI/Modules/LandingPageModule.cs index 5f1076be8..d544af2c3 100644 --- a/Ombi.UI/Modules/LandingPageModule.cs +++ b/Ombi.UI/Modules/LandingPageModule.cs @@ -40,12 +40,15 @@ namespace Ombi.UI.Modules public class LandingPageModule : BaseModule { public LandingPageModule(ISettingsService settingsService, ISettingsService landing, - ISettingsService ps, IPlexApi pApi, IResourceLinker linker, ISecurityExtensions security) : base("landing", settingsService, security) + ISettingsService ps, IPlexApi pApi, IResourceLinker linker, ISecurityExtensions security, ISettingsService emby, + IEmbyApi embyApi) : base("landing", settingsService, security) { LandingSettings = landing; PlexSettings = ps; PlexApi = pApi; Linker = linker; + EmbySettings = emby; + EmbyApi = embyApi; Get["LandingPageIndex","/", true] = async (x, ct) => { @@ -75,26 +78,49 @@ namespace Ombi.UI.Modules private ISettingsService LandingSettings { get; } private ISettingsService PlexSettings { get; } + private ISettingsService EmbySettings { get; } private IPlexApi PlexApi { get; } + private IEmbyApi EmbyApi { get; } private IResourceLinker Linker { get; } private async Task CheckStatus() { var plexSettings = await PlexSettings.GetSettingsAsync(); - if (string.IsNullOrEmpty(plexSettings.PlexAuthToken) || string.IsNullOrEmpty(plexSettings.Ip)) + if (plexSettings.Enable) { - return Response.AsJson(false); - } - try - { - var status = PlexApi.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri); - return Response.AsJson(status != null); - } - catch (Exception) - { - return Response.AsJson(false); + if (string.IsNullOrEmpty(plexSettings.PlexAuthToken) || string.IsNullOrEmpty(plexSettings.Ip)) + { + return Response.AsJson(false); + } + try + { + var status = PlexApi.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri); + return Response.AsJson(status != null); + } + catch (Exception) + { + return Response.AsJson(false); + } } + var emby = await EmbySettings.GetSettingsAsync(); + if (emby.Enable) + { + if (string.IsNullOrEmpty(emby.AdministratorId) || string.IsNullOrEmpty(emby.Ip)) + { + return Response.AsJson(false); + } + try + { + var status = EmbyApi.GetSystemInformation(emby.ApiKey, emby.FullUri); + return Response.AsJson(status?.Version != null); + } + catch (Exception) + { + return Response.AsJson(false); + } + } + return Response.AsJson(false); } } } \ No newline at end of file From d6684eb1de29728da255571ae652a6a04efd404e Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 17 Feb 2017 22:11:37 +0000 Subject: [PATCH 06/32] Fixed #1133 Do not show shows that we do not ahve any information for. --- Ombi.Api/ApiRequest.cs | 10 ++-------- Ombi.Api/TvMazeApi.cs | 33 +++++++++++++++++++++------------ Ombi.UI/Modules/SearchModule.cs | 17 +++++++++++++++++ 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/Ombi.Api/ApiRequest.cs b/Ombi.Api/ApiRequest.cs index a27d4af28..2de72101d 100644 --- a/Ombi.Api/ApiRequest.cs +++ b/Ombi.Api/ApiRequest.cs @@ -27,6 +27,7 @@ using System; using System.IO; +using System.Net; using System.Xml.Serialization; using Newtonsoft.Json; using NLog; @@ -76,14 +77,7 @@ namespace Ombi.Api var client = new RestClient { BaseUrl = baseUri }; var response = client.Execute(request); - - if (response.ErrorException != null) - { - Log.Error(response.ErrorException); - var message = "Error retrieving response. Check inner details for more info."; - throw new ApiRequestException(message, response.ErrorException); - } - + return response; } diff --git a/Ombi.Api/TvMazeApi.cs b/Ombi.Api/TvMazeApi.cs index 5bb534990..4330a3d1e 100644 --- a/Ombi.Api/TvMazeApi.cs +++ b/Ombi.Api/TvMazeApi.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using NLog; using Ombi.Api.Models.Tv; using RestSharp; @@ -90,21 +91,29 @@ namespace Ombi.Api }; request.AddUrlSegment("id", theTvDbId.ToString()); request.AddHeader("Content-Type", "application/json"); - - var obj = Api.Execute(request, new Uri(Uri)); - - var episodes = EpisodeLookup(obj.id).ToList(); - - foreach (var e in episodes) + try { - obj.Season.Add(new TvMazeCustomSeason + var result = Api.Execute(request, new Uri(Uri)); + var obj = JsonConvert.DeserializeObject(result.Content); + + var episodes = EpisodeLookup(obj.id).ToList(); + + foreach (var e in episodes) { - SeasonNumber = e.season, - EpisodeNumber = e.number - }); + obj.Season.Add(new TvMazeCustomSeason + { + SeasonNumber = e.season, + EpisodeNumber = e.number + }); + } + + return obj; } - - return obj; + catch (Exception e) + { + Log.Error(e); + return null; + } } public List GetSeasons(int id) diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index f11e41b55..2d4a12d49 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -437,6 +437,11 @@ namespace Ombi.UI.Modules { var show = anticipatedShow.Show; var theTvDbId = int.Parse(show.Ids.Tvdb.ToString()); + var result = TvApi.ShowLookupByTheTvDbId(theTvDbId); + if (result == null) + { + continue; + } var model = new SearchTvShowViewModel { @@ -466,6 +471,12 @@ namespace Ombi.UI.Modules { var show = watched.Show; var theTvDbId = int.Parse(show.Ids.Tvdb.ToString()); + var result = TvApi.ShowLookupByTheTvDbId(theTvDbId); + if (result == null) + { + continue; + } + var model = new SearchTvShowViewModel { FirstAired = show.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"), @@ -494,6 +505,12 @@ namespace Ombi.UI.Modules { var show = watched.Show; var theTvDbId = int.Parse(show.Ids.Tvdb.ToString()); + var result = TvApi.ShowLookupByTheTvDbId(theTvDbId); + if (result == null) + { + continue; + } + var model = new SearchTvShowViewModel { FirstAired = show.FirstAired?.ToString("yyyy-MM-ddTHH:mm:ss"), From 10ef372cfd81a76dd270474adc2444fce0d30d9f Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 17 Feb 2017 22:37:19 +0000 Subject: [PATCH 07/32] Added some debugging code around the newsletter for Emby #1116 --- Ombi.Api/EmbyApi.cs | 89 +++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/Ombi.Api/EmbyApi.cs b/Ombi.Api/EmbyApi.cs index f703c0570..83028153a 100644 --- a/Ombi.Api/EmbyApi.cs +++ b/Ombi.Api/EmbyApi.cs @@ -33,6 +33,7 @@ using NLog; using Ombi.Api.Interfaces; using Ombi.Api.Models.Emby; using Ombi.Helpers; +using Polly; using RestSharp; namespace Ombi.Api @@ -78,7 +79,7 @@ namespace Ombi.Api Resource = "emby/System/Info", Method = Method.GET }; - + AddHeaders(request, apiKey); var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetSystemInformation for Emby, Retrying {0}", timespan), new[] { @@ -162,29 +163,71 @@ namespace Ombi.Api TimeSpan.FromSeconds(5) }); - switch (type) + IRestResponse response = null; + try { - case EmbyMediaType.Movie: - return new EmbyInformation - { - MovieInformation = policy.Execute(() => Api.ExecuteJson(request, baseUri)) - }; - case EmbyMediaType.Series: - return new EmbyInformation - { - SeriesInformation = - policy.Execute(() => Api.ExecuteJson(request, baseUri)) - }; - case EmbyMediaType.Music: - break; - case EmbyMediaType.Episode: - return new EmbyInformation - { - EpisodeInformation = - policy.Execute(() => Api.ExecuteJson(request, baseUri)) - }; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); + + switch (type) + { + case EmbyMediaType.Movie: + response = policy.Execute(() => Api.Execute(request, baseUri)); + break; + + case EmbyMediaType.Series: + response = policy.Execute(() => Api.Execute(request, baseUri)); + break; + case EmbyMediaType.Music: + break; + case EmbyMediaType.Episode: + response = policy.Execute(() => Api.Execute(request, baseUri)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + + var info = new EmbyInformation(); + + switch (type) + { + case EmbyMediaType.Movie: + return new EmbyInformation + { + MovieInformation = JsonConvert.DeserializeObject(response.Content) + }; + case EmbyMediaType.Series: + return new EmbyInformation + { + SeriesInformation = JsonConvert.DeserializeObject(response.Content) + }; + case EmbyMediaType.Music: + break; + case EmbyMediaType.Episode: + return new EmbyInformation + { + EpisodeInformation = JsonConvert.DeserializeObject(response.Content) + }; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + + } + catch (Exception e) + { + Log.Error("Could not get the media item's information"); + Log.Error(e); + Log.Debug("ResponseContent"); + Log.Debug(response?.Content ?? "Empty"); + Log.Debug("ResponseStatusCode"); + Log.Debug(response?.StatusCode ?? HttpStatusCode.PreconditionFailed); + + Log.Debug("ResponseError"); + Log.Debug(response?.ErrorMessage ?? "No Error"); + Log.Debug("ResponseException"); + Log.Debug(response?.ErrorException ?? new Exception()); + + + + throw; } return new EmbyInformation(); } From a93c18bc04ea8607d8ae1fcb2c1a842dde456d23 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 17 Feb 2017 23:47:03 +0000 Subject: [PATCH 08/32] Added root folder and approving quality profiles in radarr #1065 --- Ombi.Api.Interfaces/IRadarrApi.cs | 1 + Ombi.Api/RadarrApi.cs | 14 ++ Ombi.Core.Tests/MovieSenderTests.cs | 155 +++++++++++++++++++++ Ombi.Core.Tests/Ombi.Core.Tests.csproj | 17 +++ Ombi.Core/CacheKeys.cs | 1 + Ombi.Core/MovieSender.cs | 17 ++- Ombi.Core/SettingModels/RadarrSettings.cs | 2 +- Ombi.Helpers.Tests/TypeHelperTests.cs | 11 +- Ombi.UI.Tests/AdminModuleTests.cs | 1 + Ombi.UI.Tests/UserLoginModuleTests.cs | 1 + Ombi.UI/Modules/Admin/IntegrationModule.cs | 18 ++- Ombi.UI/Modules/RequestsModule.cs | 87 ++++++++++-- Ombi.UI/Validators/RadarrValidator.cs | 3 +- Ombi.UI/Views/Integration/Radarr.cshtml | 99 ++++++++++++- 14 files changed, 395 insertions(+), 32 deletions(-) create mode 100644 Ombi.Core.Tests/MovieSenderTests.cs diff --git a/Ombi.Api.Interfaces/IRadarrApi.cs b/Ombi.Api.Interfaces/IRadarrApi.cs index 88e6d3028..f1b015d31 100644 --- a/Ombi.Api.Interfaces/IRadarrApi.cs +++ b/Ombi.Api.Interfaces/IRadarrApi.cs @@ -11,5 +11,6 @@ namespace Ombi.Api.Interfaces List GetMovies(string apiKey, Uri baseUrl); List GetProfiles(string apiKey, Uri baseUrl); SystemStatus SystemStatus(string apiKey, Uri baseUrl); + List GetRootFolders(string apiKey, Uri baseUrl); } } \ No newline at end of file diff --git a/Ombi.Api/RadarrApi.cs b/Ombi.Api/RadarrApi.cs index 7eeb98d3f..206023f8e 100644 --- a/Ombi.Api/RadarrApi.cs +++ b/Ombi.Api/RadarrApi.cs @@ -62,6 +62,20 @@ namespace Ombi.Api return obj; } + public List GetRootFolders(string apiKey, Uri baseUrl) + { + var request = new RestRequest { Resource = "/api/rootfolder", Method = Method.GET }; + + request.AddHeader("X-Api-Key", apiKey); + var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling GetRootFolders for Radarr, Retrying {0}", timespan), new TimeSpan[] { + TimeSpan.FromSeconds (1), + TimeSpan.FromSeconds(2) + }); + + var obj = policy.Execute(() => Api.ExecuteJson>(request, baseUrl)); + + return obj; + } public RadarrAddMovie AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, Uri baseUrl, bool searchNow = false) { diff --git a/Ombi.Core.Tests/MovieSenderTests.cs b/Ombi.Core.Tests/MovieSenderTests.cs new file mode 100644 index 000000000..9fa468ac3 --- /dev/null +++ b/Ombi.Core.Tests/MovieSenderTests.cs @@ -0,0 +1,155 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2017 Jamie Rees +// File: MovieSenderTests.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.Threading.Tasks; +using Moq; +using NUnit.Framework; +using Ombi.Api; +using Ombi.Api.Interfaces; +using Ombi.Api.Models.Radarr; +using Ombi.Api.Models.Watcher; +using Ombi.Core.SettingModels; +using Ombi.Store; +using Ploeh.AutoFixture; + +namespace Ombi.Core.Tests +{ + public class MovieSenderTests + { + private MovieSender Sender { get; set; } + private Mock> CpMock { get; set; } + private Mock> WatcherMock { get; set; } + private Mock> RadarrMock { get; set; } + private Mock CpApiMock { get; set; } + private Mock WatcherApiMock { get; set; } + private Mock RadarrApiMock { get; set; } + + private Fixture F { get; set; } + + [SetUp] + public void Setup() + { + F = new Fixture(); + CpMock = new Mock>(); + WatcherMock = new Mock>(); + RadarrApiMock = new Mock(); + RadarrMock = new Mock>(); + CpApiMock = new Mock(); + WatcherApiMock = new Mock(); + + RadarrMock.Setup(x => x.GetSettingsAsync()) + .ReturnsAsync(F.Build().With(x => x.Enabled, false).Create()); + WatcherMock.Setup(x => x.GetSettingsAsync()) + .ReturnsAsync(F.Build().With(x => x.Enabled, false).Create()); + CpMock.Setup(x => x.GetSettingsAsync()) + .ReturnsAsync(F.Build().With(x => x.Enabled, false).Create()); + + Sender = new MovieSender(CpMock.Object, WatcherMock.Object, CpApiMock.Object, WatcherApiMock.Object, RadarrApiMock.Object, RadarrMock.Object); + } + + [Test] + public async Task SendRadarrMovie() + { + RadarrMock.Setup(x => x.GetSettingsAsync()) + .ReturnsAsync(F.Build().With(x => x.Enabled, true).Create()); + RadarrApiMock.Setup(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns(new RadarrAddMovie { title = "Abc" }); + + var model = F.Create(); + + var result = await Sender.Send(model, 2.ToString()); + + + Assert.That(result.Result, Is.True); + Assert.That(result.Error, Is.False); + Assert.That(result.MovieSendingEnabled, Is.True); + + RadarrApiMock.Verify(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), 2, It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public async Task SendRadarrMovie_SendingFailed() + { + RadarrMock.Setup(x => x.GetSettingsAsync()) + .ReturnsAsync(F.Build().With(x => x.Enabled, true).Create()); + RadarrApiMock.Setup(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns(new RadarrAddMovie { Error = new RadarrError{message = "Movie Already Added"}}); + + var model = F.Create(); + + var result = await Sender.Send(model, 2.ToString()); + + + Assert.That(result.Result, Is.False); + Assert.That(result.Error, Is.True); + Assert.That(result.MovieSendingEnabled, Is.True); + + RadarrApiMock.Verify(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), 2, It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public async Task SendCpMovie() + { + CpMock.Setup(x => x.GetSettingsAsync()) + .ReturnsAsync(F.Build().With(x => x.Enabled, true).Create()); + CpApiMock.Setup(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns(true); + + var model = F.Create(); + + var result = await Sender.Send(model); + + Assert.That(result.Result, Is.True); + Assert.That(result.Error, Is.False); + Assert.That(result.MovieSendingEnabled, Is.True); + + CpApiMock.Verify(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny()), Times.Once); + } + + [Test] + public async Task SendWatcherMovie() + { + WatcherMock.Setup(x => x.GetSettingsAsync()) + .ReturnsAsync(F.Build().With(x => x.Enabled, true).Create()); + WatcherApiMock.Setup(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny())).Returns(F.Create()); + + var model = F.Create(); + + var result = await Sender.Send(model); + + Assert.That(result.Result, Is.True); + Assert.That(result.Error, Is.False); + Assert.That(result.MovieSendingEnabled, Is.True); + + WatcherApiMock.Verify(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + } +} \ No newline at end of file diff --git a/Ombi.Core.Tests/Ombi.Core.Tests.csproj b/Ombi.Core.Tests/Ombi.Core.Tests.csproj index 9f4108882..1d5e03e6d 100644 --- a/Ombi.Core.Tests/Ombi.Core.Tests.csproj +++ b/Ombi.Core.Tests/Ombi.Core.Tests.csproj @@ -60,6 +60,7 @@ + @@ -68,6 +69,18 @@ + + {95834072-A675-415D-AA8F-877C91623810} + Ombi.Api.Interfaces + + + {CB37A5F8-6DFC-4554-99D3-A42B502E4591} + Ombi.Api.Models + + + {8CB8D235-2674-442D-9C6A-35FCAEEB160D} + Ombi.Api + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} Ombi.Core @@ -76,6 +89,10 @@ {1252336D-42A3-482A-804C-836E60173DFA} Ombi.Helpers + + {92433867-2B7B-477B-A566-96C382427525} + Ombi.Store + diff --git a/Ombi.Core/CacheKeys.cs b/Ombi.Core/CacheKeys.cs index 32466e897..e78f9a9d0 100644 --- a/Ombi.Core/CacheKeys.cs +++ b/Ombi.Core/CacheKeys.cs @@ -50,5 +50,6 @@ namespace Ombi.Core public const string GetPlexRequestSettings = nameof(GetPlexRequestSettings); public const string LastestProductVersion = nameof(LastestProductVersion); public const string SonarrRootFolders = nameof(SonarrRootFolders); + public const string RadarrRootFolders = nameof(RadarrRootFolders); } } \ No newline at end of file diff --git a/Ombi.Core/MovieSender.cs b/Ombi.Core/MovieSender.cs index 37eeee308..0240a19ce 100644 --- a/Ombi.Core/MovieSender.cs +++ b/Ombi.Core/MovieSender.cs @@ -73,7 +73,7 @@ namespace Ombi.Core if (radarrSettings.Enabled) { - return SendToRadarr(model, radarrSettings); + return SendToRadarr(model, radarrSettings, qualityId); } return new MovieSenderResult { Result = false, MovieSendingEnabled = false }; @@ -102,16 +102,25 @@ namespace Ombi.Core return new MovieSenderResult { Result = result, MovieSendingEnabled = true }; } - private MovieSenderResult SendToRadarr(RequestedModel model, RadarrSettings settings) + private MovieSenderResult SendToRadarr(RequestedModel model, RadarrSettings settings, string qualityId) { var qualityProfile = 0; - int.TryParse(settings.QualityProfile, out qualityProfile); + if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality + { + int.TryParse(qualityId, out qualityProfile); + } + + if (qualityProfile <= 0) + { + int.TryParse(settings.QualityProfile, out qualityProfile); + } + var result = RadarrApi.AddMovie(model.ProviderId, model.Title, model.ReleaseDate.Year, qualityProfile, settings.RootPath, settings.ApiKey, settings.FullUri, true); if (!string.IsNullOrEmpty(result.Error?.message)) { Log.Error(result.Error.message); - return new MovieSenderResult { Result = false, Error = true}; + return new MovieSenderResult { Result = false, Error = true , MovieSendingEnabled = true}; } if (!string.IsNullOrEmpty(result.title)) { diff --git a/Ombi.Core/SettingModels/RadarrSettings.cs b/Ombi.Core/SettingModels/RadarrSettings.cs index b8a6287f7..f5d994535 100644 --- a/Ombi.Core/SettingModels/RadarrSettings.cs +++ b/Ombi.Core/SettingModels/RadarrSettings.cs @@ -32,6 +32,6 @@ namespace Ombi.Core.SettingModels public string ApiKey { get; set; } public string QualityProfile { get; set; } public string RootPath { get; set; } - + public string FullRootPath { get; set; } } } \ No newline at end of file diff --git a/Ombi.Helpers.Tests/TypeHelperTests.cs b/Ombi.Helpers.Tests/TypeHelperTests.cs index cff7d16d5..0390f4087 100644 --- a/Ombi.Helpers.Tests/TypeHelperTests.cs +++ b/Ombi.Helpers.Tests/TypeHelperTests.cs @@ -48,7 +48,7 @@ namespace Ombi.Helpers.Tests var consts = typeof(UserClaims).GetConstantsValues(); Assert.That(consts.Contains("Admin"),Is.True); Assert.That(consts.Contains("PowerUser"),Is.True); - Assert.That(consts.Contains("User"),Is.True); + Assert.That(consts.Contains("RegularUser"),Is.True); } private static IEnumerable TypeData @@ -59,14 +59,7 @@ namespace Ombi.Helpers.Tests yield return new TestCaseData(typeof(int)).Returns(new string[0]).SetName("NoPropeties Class"); yield return new TestCaseData(typeof(IEnumerable<>)).Returns(new string[0]).SetName("Interface"); yield return new TestCaseData(typeof(string)).Returns(new[] { "Chars", "Length" }).SetName("String"); - yield return new TestCaseData(typeof(RequestedModel)).Returns( - new[] - { - "ProviderId", "ImdbId", "TvDbId", "Overview", "Title", "PosterPath", "ReleaseDate", "Type", - "Status", "Approved", "RequestedBy", "RequestedDate", "Available", "Issues", "OtherMessage", "AdminNote", - "SeasonList", "SeasonCount", "SeasonsRequested", "MusicBrainzId", "RequestedUsers","ArtistName", - "ArtistId","IssueId","Episodes", "Denied", "DeniedReason", "AllUsers","CanApprove","Id", - }).SetName("Requested Model"); + } } diff --git a/Ombi.UI.Tests/AdminModuleTests.cs b/Ombi.UI.Tests/AdminModuleTests.cs index 62e4bf514..30da77591 100644 --- a/Ombi.UI.Tests/AdminModuleTests.cs +++ b/Ombi.UI.Tests/AdminModuleTests.cs @@ -48,6 +48,7 @@ using Ombi.UI.Modules.Admin; namespace Ombi.UI.Tests { [TestFixture] + [Ignore("Needs rework")] public class AdminModuleTests { private Mock> PlexRequestMock { get; set; } diff --git a/Ombi.UI.Tests/UserLoginModuleTests.cs b/Ombi.UI.Tests/UserLoginModuleTests.cs index f7e59e86a..a5f68d06b 100644 --- a/Ombi.UI.Tests/UserLoginModuleTests.cs +++ b/Ombi.UI.Tests/UserLoginModuleTests.cs @@ -44,6 +44,7 @@ using Ombi.UI.Modules; namespace Ombi.UI.Tests { [TestFixture] + [Ignore("Needs rewrite")] public class UserLoginModuleTests { private Mock> AuthMock { get; set; } diff --git a/Ombi.UI/Modules/Admin/IntegrationModule.cs b/Ombi.UI/Modules/Admin/IntegrationModule.cs index 4076f9756..57eccfeff 100644 --- a/Ombi.UI/Modules/Admin/IntegrationModule.cs +++ b/Ombi.UI/Modules/Admin/IntegrationModule.cs @@ -69,6 +69,7 @@ namespace Ombi.UI.Modules.Admin Post["/sonarrrootfolders"] = _ => GetSonarrRootFolders(); + Post["/radarrrootfolders"] = _ => GetSonarrRootFolders(); Get["/watcher", true] = async (x, ct) => await Watcher(); Post["/watcher", true] = async (x, ct) => await SaveWatcher(); @@ -191,7 +192,22 @@ namespace Ombi.UI.Modules.Admin { var settings = this.Bind(); - var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); + var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); + + // set the cache + if (rootFolders != null) + { + Cache.Set(CacheKeys.SonarrRootFolders, rootFolders); + } + + return Response.AsJson(rootFolders); + } + + private Response GetRadarrRootFolders() + { + var settings = this.Bind(); + + var rootFolders = RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); // set the cache if (rootFolders != null) diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index 897986635..79065cced 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -69,7 +69,9 @@ namespace Ombi.UI.Modules IEmbyNotificationEngine embyEngine, ISecurityExtensions security, ISettingsService customSettings, - ISettingsService embyS) : base("requests", prSettings, security) + ISettingsService embyS, + ISettingsService radarr, + IRadarrApi radarrApi) : base("requests", prSettings, security) { Service = service; PrSettings = prSettings; @@ -87,6 +89,8 @@ namespace Ombi.UI.Modules EmbyNotificationEngine = embyEngine; CustomizationSettings = customSettings; EmbySettings = embyS; + Radarr = radarr; + RadarrApi = radarrApi; Get["/", true] = async (x, ct) => await LoadRequests(); Get["/movies", true] = async (x, ct) => await GetMovies(); @@ -115,8 +119,10 @@ namespace Ombi.UI.Modules private ISettingsService SickRageSettings { get; } private ISettingsService CpSettings { get; } private ISettingsService CustomizationSettings { get; } + private ISettingsService Radarr { get; } private ISettingsService EmbySettings { get; } private ISonarrApi SonarrApi { get; } + private IRadarrApi RadarrApi { get; } private ISickRageApi SickRageApi { get; } private ICouchPotatoApi CpApi { get; } private ICacheProvider Cache { get; } @@ -144,28 +150,58 @@ namespace Ombi.UI.Modules } List qualities = new List(); + var rootFolders = new List(); + var radarr = await Radarr.GetSettingsAsync(); if (IsAdmin) { - var cpSettings = CpSettings.GetSettings(); - if (cpSettings.Enabled) + try { - try + var cpSettings = await CpSettings.GetSettingsAsync(); + if (cpSettings.Enabled) { - var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () => + try { - return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false); - }); - if (result != null) + var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () => + { + return + await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)) + .ConfigureAwait(false); + }); + if (result != null) + { + qualities = + result.list.Select(x => new QualityModel {Id = x._id, Name = x.label}).ToList(); + } + } + catch (Exception e) { - qualities = result.list.Select(x => new QualityModel { Id = x._id, Name = x.label }).ToList(); + Log.Info(e); } } - catch (Exception e) + if (radarr.Enabled) { - Log.Info(e); + var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.RadarrRootFolders, async () => + { + return await Task.Run(() => RadarrApi.GetRootFolders(radarr.ApiKey, radarr.FullUri)); + }); + + rootFolders = + rootFoldersResult.Select( + x => new RootFolderModel {Id = x.id.ToString(), Path = x.path, FreeSpace = x.freespace}) + .ToList(); + + var result = await Cache.GetOrSetAsync(CacheKeys.RadarrQualityProfiles, async () => + { + return await Task.Run(() => RadarrApi.GetProfiles(radarr.ApiKey, radarr.FullUri)); + }); + qualities = result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList(); } } + catch (Exception e) + { + Log.Error(e); + } } @@ -194,6 +230,9 @@ namespace Ombi.UI.Modules Denied = movie.Denied, DeniedReason = movie.DeniedReason, Qualities = qualities.ToArray(), + HasRootFolders = rootFolders.Any(), + RootFolders = rootFolders.ToArray(), + CurrentRootPath = radarr.Enabled ? GetRootPath(movie.RootFolderSelected, radarr).Result : null }).ToList(); return Response.AsJson(viewModel); @@ -313,6 +352,32 @@ namespace Ombi.UI.Modules } } + private async Task GetRootPath(int pathId, RadarrSettings radarrSettings) + { + var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.RadarrRootFolders, async () => + { + return await Task.Run(() => RadarrApi.GetRootFolders(radarrSettings.ApiKey, radarrSettings.FullUri)); + }); + + foreach (var r in rootFoldersResult.Where(r => r.id == pathId)) + { + return r.path; + } + + int outRoot; + var defaultPath = int.TryParse(radarrSettings.RootPath, out outRoot); + + if (defaultPath) + { + // Return default path + return rootFoldersResult.FirstOrDefault(x => x.id.Equals(outRoot))?.path ?? string.Empty; + } + else + { + return rootFoldersResult.FirstOrDefault()?.path ?? string.Empty; + } + } + private async Task GetAlbumRequests() { var settings = PrSettings.GetSettings(); diff --git a/Ombi.UI/Validators/RadarrValidator.cs b/Ombi.UI/Validators/RadarrValidator.cs index 75550c787..564a054f2 100644 --- a/Ombi.UI/Validators/RadarrValidator.cs +++ b/Ombi.UI/Validators/RadarrValidator.cs @@ -37,7 +37,8 @@ namespace Ombi.UI.Validators RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); - RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile."); + RuleFor(request => request.QualityProfile).NotEmpty().NotNull().WithMessage("You must specify a Quality Profile."); + RuleFor(request => request.RootPath).NotEmpty().NotNull().WithMessage("You must enter a root path."); } } } \ No newline at end of file diff --git a/Ombi.UI/Views/Integration/Radarr.cshtml b/Ombi.UI/Views/Integration/Radarr.cshtml index 1ccccc40f..487a6ade5 100644 --- a/Ombi.UI/Views/Integration/Radarr.cshtml +++ b/Ombi.UI/Views/Integration/Radarr.cshtml @@ -11,6 +11,13 @@ { port = Model.Port; } + + var rootFolder = string.Empty; + if (!string.IsNullOrEmpty(Model.RootPath)) + + { + rootFolder = Model.RootPath.Replace("/", "//"); + } }
@@ -64,10 +71,17 @@
-
- - + + +
+ +
+
+ +
+ +
@@ -128,6 +142,39 @@ } } + @if (!string.IsNullOrEmpty(Model.RootPath)) + { + + + console.log('Hit root folders..'); + + var rootFolderSelected = '@rootFolder'; + if (!rootFolderSelected) { + return; + } + var $form = $("#mainForm"); + $.ajax({ + type: $form.prop("method"), + data: $form.serialize(), + url: "sonarrrootfolders", + dataType: "json", + success: function(response) { + response.forEach(function(result) { + $('#selectedRootFolder').html(""); + if (result.id == rootFolderSelected) { + $("#selectRootFolder").append(""); + } else { + $("#selectRootFolder").append(""); + } + }); + }, + error: function(e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); + + } $('#save').click(function(e) { @@ -138,11 +185,14 @@ return; } var qualityProfile = $("#profiles option:selected").val(); + var rootFolder = $("#rootFolders option:selected").val(); + var rootFolderPath = $('#rootFolders option:selected').text(); + $('#fullRootPath').val(rootFolderPath); var $form = $("#mainForm"); var data = $form.serialize(); - data = data + "&qualityProfile=" + qualityProfile; + data = data + "&qualityProfile=" + qualityProfile + "&rootPath=" + rootFolder; $.ajax({ type: $form.prop("method"), @@ -202,6 +252,45 @@ }); }); + $('#getRootFolders').click(function (e) { + + $('#getRootFolderSpinner').attr("class", "fa fa-spinner fa-spin"); + e.preventDefault(); + if (!$('#Ip').val()) { + generateNotify("Please enter a valid IP/Hostname.", "warning"); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + return; + } + if (!$('#portNumber').val()) { + generateNotify("Please enter a valid Port Number.", "warning"); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + return; + } + if (!$('#ApiKey').val()) { + generateNotify("Please enter a valid ApiKey.", "warning"); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + return; + } + var $form = $("#mainForm"); + $.ajax({ + type: $form.prop("method"), + data: $form.serialize(), + url: "radarrrootfolders", + dataType: "json", + success: function (response) { + response.forEach(function (result) { + $('#getRootFolderSpinner').attr("class", "fa fa-check"); + $("#selectRootFolder").append(""); + }); + }, + error: function (e) { + console.log(e); + $('#getRootFolderSpinner').attr("class", "fa fa-times"); + generateNotify("Something went wrong!", "danger"); + } + }); + }); + var base = '@Html.GetBaseUrl()'; $('#testRadarr').click(function (e) { @@ -213,7 +302,7 @@ var data = $form.serialize(); data = data + "&qualityProfile=" + qualityProfile; - + var url = createBaseUrl(base, '/test/radarr'); $.ajax({ type: $form.prop("method"), From ac1cc942550de9fabec4867f766419e10111def0 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sat, 18 Feb 2017 00:00:59 +0000 Subject: [PATCH 09/32] Fixed a bug when sending to radarr --- Ombi.Core.Tests/MovieSenderTests.cs | 16 +++++++++++++++- Ombi.Core/MovieSender.cs | 23 ++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Ombi.Core.Tests/MovieSenderTests.cs b/Ombi.Core.Tests/MovieSenderTests.cs index 9fa468ac3..8c4e85e39 100644 --- a/Ombi.Core.Tests/MovieSenderTests.cs +++ b/Ombi.Core.Tests/MovieSenderTests.cs @@ -26,14 +26,19 @@ #endregion using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Moq; using NUnit.Framework; using Ombi.Api; using Ombi.Api.Interfaces; using Ombi.Api.Models.Radarr; +using Ombi.Api.Models.Sonarr; using Ombi.Api.Models.Watcher; using Ombi.Core.SettingModels; +using Ombi.Helpers; using Ombi.Store; using Ploeh.AutoFixture; @@ -48,6 +53,7 @@ namespace Ombi.Core.Tests private Mock CpApiMock { get; set; } private Mock WatcherApiMock { get; set; } private Mock RadarrApiMock { get; set; } + private Mock CacheMock { get; set; } private Fixture F { get; set; } @@ -61,6 +67,8 @@ namespace Ombi.Core.Tests RadarrMock = new Mock>(); CpApiMock = new Mock(); WatcherApiMock = new Mock(); + CacheMock = new Mock(); + RadarrMock.Setup(x => x.GetSettingsAsync()) .ReturnsAsync(F.Build().With(x => x.Enabled, false).Create()); @@ -69,7 +77,7 @@ namespace Ombi.Core.Tests CpMock.Setup(x => x.GetSettingsAsync()) .ReturnsAsync(F.Build().With(x => x.Enabled, false).Create()); - Sender = new MovieSender(CpMock.Object, WatcherMock.Object, CpApiMock.Object, WatcherApiMock.Object, RadarrApiMock.Object, RadarrMock.Object); + Sender = new MovieSender(CpMock.Object, WatcherMock.Object, CpApiMock.Object, WatcherApiMock.Object, RadarrApiMock.Object, RadarrMock.Object, CacheMock.Object); } [Test] @@ -80,6 +88,9 @@ namespace Ombi.Core.Tests RadarrApiMock.Setup(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(new RadarrAddMovie { title = "Abc" }); + CacheMock.Setup(x => x.GetOrSet>(CacheKeys.RadarrRootFolders, It.IsAny>>(), It.IsAny())) + .Returns(F.CreateMany().ToList()); + var model = F.Create(); var result = await Sender.Send(model, 2.ToString()); @@ -101,6 +112,9 @@ namespace Ombi.Core.Tests RadarrApiMock.Setup(x => x.AddMovie(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(new RadarrAddMovie { Error = new RadarrError{message = "Movie Already Added"}}); + CacheMock.Setup(x => x.GetOrSet>(CacheKeys.RadarrRootFolders, It.IsAny>>(), It.IsAny())) + .Returns(F.CreateMany().ToList()); + var model = F.Create(); var result = await Sender.Send(model, 2.ToString()); diff --git a/Ombi.Core/MovieSender.cs b/Ombi.Core/MovieSender.cs index 0240a19ce..c7636ba83 100644 --- a/Ombi.Core/MovieSender.cs +++ b/Ombi.Core/MovieSender.cs @@ -26,10 +26,12 @@ #endregion using System; +using System.Linq; using System.Threading.Tasks; using NLog; using Ombi.Api.Interfaces; using Ombi.Core.SettingModels; +using Ombi.Helpers; using Ombi.Store; namespace Ombi.Core @@ -37,7 +39,8 @@ namespace Ombi.Core public class MovieSender : IMovieSender { public MovieSender(ISettingsService cp, ISettingsService watcher, - ICouchPotatoApi cpApi, IWatcherApi watcherApi, IRadarrApi radarrApi, ISettingsService radarrSettings) + ICouchPotatoApi cpApi, IWatcherApi watcherApi, IRadarrApi radarrApi, ISettingsService radarrSettings, + ICacheProvider cache) { CouchPotatoSettings = cp; WatcherSettings = watcher; @@ -45,6 +48,7 @@ namespace Ombi.Core WatcherApi = watcherApi; RadarrSettings = radarrSettings; RadarrApi = radarrApi; + Cache = cache; } private ISettingsService CouchPotatoSettings { get; } @@ -53,6 +57,7 @@ namespace Ombi.Core private IRadarrApi RadarrApi { get; } private ICouchPotatoApi CpApi { get; } private IWatcherApi WatcherApi { get; } + private ICacheProvider Cache { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); public async Task Send(RequestedModel model, string qualityId = "") @@ -114,8 +119,9 @@ namespace Ombi.Core { int.TryParse(settings.QualityProfile, out qualityProfile); } - - var result = RadarrApi.AddMovie(model.ProviderId, model.Title, model.ReleaseDate.Year, qualityProfile, settings.RootPath, settings.ApiKey, settings.FullUri, true); + + var rootFolderPath = model.RootFolderSelected <= 0 ? settings.FullRootPath : GetRootPath(model.RootFolderSelected, settings); + var result = RadarrApi.AddMovie(model.ProviderId, model.Title, model.ReleaseDate.Year, qualityProfile, rootFolderPath, settings.ApiKey, settings.FullUri, true); if (!string.IsNullOrEmpty(result.Error?.message)) { @@ -128,5 +134,16 @@ namespace Ombi.Core } return new MovieSenderResult { Result = false, MovieSendingEnabled = true }; } + + private string GetRootPath(int pathId, RadarrSettings sonarrSettings) + { + var rootFoldersResult = Cache.GetOrSet(CacheKeys.RadarrRootFolders, () => RadarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + + foreach (var r in rootFoldersResult.Where(r => r.id == pathId)) + { + return r.path; + } + return string.Empty; + } } } \ No newline at end of file From e520a9dfd6b1289c2f018510147d1bf7c176eb99 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sat, 18 Feb 2017 22:15:36 +0000 Subject: [PATCH 10/32] Fixed #1123 --- Ombi.UI/Validators/RadarrValidator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Ombi.UI/Validators/RadarrValidator.cs b/Ombi.UI/Validators/RadarrValidator.cs index 564a054f2..cfa1945c6 100644 --- a/Ombi.UI/Validators/RadarrValidator.cs +++ b/Ombi.UI/Validators/RadarrValidator.cs @@ -38,7 +38,6 @@ namespace Ombi.UI.Validators RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); RuleFor(request => request.QualityProfile).NotEmpty().NotNull().WithMessage("You must specify a Quality Profile."); - RuleFor(request => request.RootPath).NotEmpty().NotNull().WithMessage("You must enter a root path."); } } } \ No newline at end of file From d74d88925b85ec50964a06b64e6e7e74c229224a Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 20 Feb 2017 08:08:50 +0000 Subject: [PATCH 11/32] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 3e49d4bca..c02c65295 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -14,6 +14,14 @@ V 1.XX.XX Stable/Early Access Preview/development +#### Media Sever: + +Plex/Emby + +#### Media Server Version: + + + #### Operating System: (Place text here) From 09a06bd6f0d1eb2d3e6a6e849a647e022e3ba4fc Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 21 Feb 2017 21:16:10 +0000 Subject: [PATCH 12/32] Fixed #1152 --- Ombi.UI/Views/Admin/Sonarr.cshtml | 7 ------- Ombi.UI/Views/Integration/Radarr.cshtml | 2 ++ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Ombi.UI/Views/Admin/Sonarr.cshtml b/Ombi.UI/Views/Admin/Sonarr.cshtml index 72498b036..92c667708 100644 --- a/Ombi.UI/Views/Admin/Sonarr.cshtml +++ b/Ombi.UI/Views/Admin/Sonarr.cshtml @@ -103,13 +103,6 @@ - @*
- -
- - -
-
*@
diff --git a/Ombi.UI/Views/Integration/Radarr.cshtml b/Ombi.UI/Views/Integration/Radarr.cshtml index 487a6ade5..00a9d4968 100644 --- a/Ombi.UI/Views/Integration/Radarr.cshtml +++ b/Ombi.UI/Views/Integration/Radarr.cshtml @@ -23,6 +23,8 @@
Radarr Settings + + @Html.Checkbox(Model.Enabled, "Enabled", "Enabled") From a8288a93b0cb6ce39bb5ae3f7e4208aaa63905c4 Mon Sep 17 00:00:00 2001 From: smcpeck Date: Thu, 23 Feb 2017 11:59:37 -0600 Subject: [PATCH 13/32] Enforcing async/await in synchronous methods that were marked async. --- Ombi.Api/RadarrApi.cs | 1 - Ombi.Core/StatusChecker/StatusChecker.cs | 1 + Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs | 2 ++ Ombi.UI/Modules/SearchExtensionModule.cs | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Ombi.Api/RadarrApi.cs b/Ombi.Api/RadarrApi.cs index 7eeb98d3f..b4d74a319 100644 --- a/Ombi.Api/RadarrApi.cs +++ b/Ombi.Api/RadarrApi.cs @@ -94,7 +94,6 @@ namespace Ombi.Api request.AddHeader("X-Api-Key", apiKey); request.AddJsonBody(options); - RadarrAddMovie result; try { var policy = RetryHandler.RetryAndWaitPolicy((exception, timespan) => Log.Error(exception, "Exception when calling AddSeries for Sonarr, Retrying {0}", timespan), new TimeSpan[] { diff --git a/Ombi.Core/StatusChecker/StatusChecker.cs b/Ombi.Core/StatusChecker/StatusChecker.cs index d83e7189c..11710f21b 100644 --- a/Ombi.Core/StatusChecker/StatusChecker.cs +++ b/Ombi.Core/StatusChecker/StatusChecker.cs @@ -202,6 +202,7 @@ namespace Ombi.Core.StatusChecker public async Task OAuth(string url, ISession session) { + await Task.Yield(); var csrf = StringCipher.Encrypt(Guid.NewGuid().ToString("N"), "CSRF"); session[SessionKeys.CSRF] = csrf; diff --git a/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs b/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs index a21ebd16a..7f14e917f 100644 --- a/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs +++ b/Ombi.UI/Modules/Admin/ScheduledJobsRunnerModule.cs @@ -84,6 +84,8 @@ namespace Ombi.UI.Modules.Admin private async Task ScheduleRun(string key) { + await Task.Yield(); + if (key.Equals(JobNames.PlexCacher, StringComparison.CurrentCultureIgnoreCase)) { PlexContentCacher.CacheContent(); diff --git a/Ombi.UI/Modules/SearchExtensionModule.cs b/Ombi.UI/Modules/SearchExtensionModule.cs index 4c0ee4a6f..d9b34c290 100644 --- a/Ombi.UI/Modules/SearchExtensionModule.cs +++ b/Ombi.UI/Modules/SearchExtensionModule.cs @@ -47,6 +47,8 @@ namespace Ombi.UI.Modules public async Task Netflix(string title) { + await Task.Yield(); + var result = NetflixApi.CheckNetflix(title); if (!string.IsNullOrEmpty(result.Message)) From 9380ba3e45b69469dc13122babc61433f9fe5849 Mon Sep 17 00:00:00 2001 From: smcpeck Date: Thu, 23 Feb 2017 12:02:04 -0600 Subject: [PATCH 14/32] API changes to allow for searching movies by actor --- Ombi.Api/TheMovieDbApi.cs | 33 ++++++++++ .../SettingModels/PlexRequestSettings.cs | 1 + Ombi.Core/Setup.cs | 1 + Ombi.UI/Modules/SearchModule.cs | 65 +++++++++++++++---- 4 files changed, 88 insertions(+), 12 deletions(-) diff --git a/Ombi.Api/TheMovieDbApi.cs b/Ombi.Api/TheMovieDbApi.cs index ad3f01251..7a072caa4 100644 --- a/Ombi.Api/TheMovieDbApi.cs +++ b/Ombi.Api/TheMovieDbApi.cs @@ -69,6 +69,11 @@ namespace Ombi.Api return movies?.Results ?? new List(); } + private async Task GetMovie(int id) + { + return await Client.GetMovie(id); + } + public TmdbMovieDetails GetMovieInformationWithVideos(int tmdbId) { var request = new RestRequest { Resource = "movie/{movieId}", Method = Method.GET }; @@ -100,5 +105,33 @@ namespace Ombi.Api var movies = await Client.GetMovie(imdbId); return movies ?? new Movie(); } + + public async Task> SearchActor(string searchTerm) + { + SearchContainer result = await Client.SearchPerson(searchTerm); + var person = result?.Results[0] ?? null; + var movies = new List(); + var counter = 0; + try + { + if (person != null) + { + var credits = await Client.GetPersonMovieCredits(person.Id); + //only get the first 10 movies and delay a bit between each request so we don't overload the API + foreach (var credit in credits.Cast) + { if (counter == 10) + break; + movies.Add(await GetMovie(credit.Id)); + counter++; + await Task.Delay(50); + } + } + } + catch(Exception e) + { + Log.LogException(LogLevel.Error, $"Aggregating movies for {searchTerm} failed.", e); + } + return movies; + } } } diff --git a/Ombi.Core/SettingModels/PlexRequestSettings.cs b/Ombi.Core/SettingModels/PlexRequestSettings.cs index 6c77ba727..026c84c24 100644 --- a/Ombi.Core/SettingModels/PlexRequestSettings.cs +++ b/Ombi.Core/SettingModels/PlexRequestSettings.cs @@ -41,6 +41,7 @@ namespace Ombi.Core.SettingModels public int Port { get; set; } public string BaseUrl { get; set; } public bool SearchForMovies { get; set; } + public bool SearchForActors { get; set; } public bool SearchForTvShows { get; set; } public bool SearchForMusic { get; set; } [Obsolete("Use the user management settings")] diff --git a/Ombi.Core/Setup.cs b/Ombi.Core/Setup.cs index f70749a40..c7df43ed3 100644 --- a/Ombi.Core/Setup.cs +++ b/Ombi.Core/Setup.cs @@ -77,6 +77,7 @@ namespace Ombi.Core { SearchForMovies = true, SearchForTvShows = true, + SearchForActors = true, BaseUrl = baseUrl ?? string.Empty, CollectAnalyticData = true, }; diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 4aa29c316..601ba1914 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -115,6 +115,7 @@ namespace Ombi.UI.Modules Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad(); + Get["actor/{searchTerm}", true] = async (x, ct) => await SearchActor((string)x.searchTerm); 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 SearchAlbum((string)x.searchTerm); @@ -209,6 +210,12 @@ namespace Ombi.UI.Modules return await ProcessMovies(MovieSearchType.Search, searchTerm); } + private async Task SearchActor(string searchTerm) + { + var movies = TransformMovieListToMovieResultList(await MovieApi.SearchActor(searchTerm).ConfigureAwait(false)); + return await TransformMovieResultsToResponse(movies); + } + private Response GetTvPoster(int theTvDbId) { var result = TvApi.ShowLookupByTheTvDbId(theTvDbId); @@ -220,15 +227,10 @@ namespace Ombi.UI.Modules } return banner; } - private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) - { - List apiMovies; - switch (searchType) - { - case MovieSearchType.Search: - var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false); - apiMovies = movies.Select(x => + private List TransformSearchMovieListToMovieResultList(List searchMovies) + { + return searchMovies.Select(x => new MovieResult { Adult = x.Adult, @@ -247,6 +249,39 @@ namespace Ombi.UI.Modules VoteCount = x.VoteCount }) .ToList(); + } + + private List TransformMovieListToMovieResultList(List movies) + { + return movies.Select(x => + new MovieResult + { + Adult = x.Adult, + BackdropPath = x.BackdropPath, + GenreIds = x.Genres.Select(y => y.Id).ToList(), + Id = x.Id, + OriginalLanguage = x.OriginalLanguage, + OriginalTitle = x.OriginalTitle, + Overview = x.Overview, + Popularity = x.Popularity, + PosterPath = x.PosterPath, + ReleaseDate = x.ReleaseDate, + Title = x.Title, + Video = x.Video, + VoteAverage = x.VoteAverage, + VoteCount = x.VoteCount + }) + .ToList(); + } + private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) + { + List apiMovies; + + switch (searchType) + { + case MovieSearchType.Search: + var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false); + apiMovies = TransformSearchMovieListToMovieResultList(movies); break; case MovieSearchType.CurrentlyPlaying: apiMovies = await MovieApi.GetCurrentPlayingMovies(); @@ -259,6 +294,11 @@ namespace Ombi.UI.Modules break; } + return await TransformMovieResultsToResponse(apiMovies); + } + + private async Task TransformMovieResultsToResponse(List movies) + { var allResults = await RequestService.GetAllAsync(); allResults = allResults.Where(x => x.Type == RequestType.Movie); @@ -273,7 +313,7 @@ namespace Ombi.UI.Modules var plexMovies = Checker.GetPlexMovies(content); var viewMovies = new List(); var counter = 0; - foreach (var movie in apiMovies) + foreach (var movie in movies) { var viewMovie = new SearchMovieViewModel { @@ -297,7 +337,7 @@ namespace Ombi.UI.Modules if (counter <= 5) // Let's only do it for the first 5 items { var movieInfo = MovieApi.GetMovieInformationWithVideos(movie.Id); - + // TODO needs to be careful about this, it's adding extra time to search... // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 viewMovie.ImdbId = movieInfo?.imdb_id; @@ -313,7 +353,7 @@ namespace Ombi.UI.Modules counter++; } - + var canSee = CanUserSeeThisRequest(viewMovie.Id, Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests), dbMovies); var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(), imdbId); @@ -335,7 +375,7 @@ namespace Ombi.UI.Modules viewMovie.Approved = true; viewMovie.Requested = true; } - else if(watcherCached.Contains(imdbId) && canSee) // compare to the watcher db + else if (watcherCached.Contains(imdbId) && canSee) // compare to the watcher db { viewMovie.Approved = true; viewMovie.Requested = true; @@ -349,6 +389,7 @@ namespace Ombi.UI.Modules } return Response.AsJson(viewMovies); + } private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, From 0aa00fd888a5b19544ec58249d282a76071b9932 Mon Sep 17 00:00:00 2001 From: smcpeck Date: Thu, 23 Feb 2017 12:02:58 -0600 Subject: [PATCH 15/32] UI changes to consume actor searching API --- Ombi.UI/Content/requests.js | 35 +++++++- Ombi.UI/Content/search.js | 38 +++++--- Ombi.UI/Resources/UI.resx | 143 +++++++++++++++++------------- Ombi.UI/Resources/UI1.Designer.cs | 9 ++ Ombi.UI/Views/Search/Index.cshtml | 20 ++++- 5 files changed, 173 insertions(+), 72 deletions(-) diff --git a/Ombi.UI/Content/requests.js b/Ombi.UI/Content/requests.js index 1d2ad987d..4d9a13b5e 100644 --- a/Ombi.UI/Content/requests.js +++ b/Ombi.UI/Content/requests.js @@ -95,7 +95,10 @@ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { //if ($tvl.mixItUp('isLoaded')) $tvl.mixItUp('destroy'); //$tvl.mixItUp(mixItUpConfig(activeState)); // init or reinit } - if (target === "#MoviesTab") { + if (target === "#MoviesTab" || target === "#ActorsTab") { + if (target === "#ActorsTab") { + actorLoad(); + } $('#approveMovies,#deleteMovies').show(); if ($tvl.mixItUp('isLoaded')) { activeState = $tvl.mixItUp('getState'); @@ -733,6 +736,36 @@ function initLoad() { } + +function actorLoad() { + var $ml = $('#actorMovieList'); + if ($ml.mixItUp('isLoaded')) { + activeState = $ml.mixItUp('getState'); + $ml.mixItUp('destroy'); + } + $ml.html(""); + + var url = createBaseUrl(base, '/requests/actor'); + $.ajax(url).success(function (results) { + if (results.length > 0) { + results.forEach(function (result) { + var context = buildRequestContext(result, "movie"); + var html = searchTemplate(context); + $ml.append(html); + }); + + + $('.customTooltip').tooltipster({ + contentCloning: true + }); + } + else { + $ml.html(noResultsHtml.format("movie")); + } + $ml.mixItUp(mixItUpConfig()); + }); +}; + function movieLoad() { var $ml = $('#movieList'); if ($ml.mixItUp('isLoaded')) { diff --git a/Ombi.UI/Content/search.js b/Ombi.UI/Content/search.js index fde3077e4..51c4011cd 100644 --- a/Ombi.UI/Content/search.js +++ b/Ombi.UI/Content/search.js @@ -63,6 +63,17 @@ $(function () { }); + // Type in actor search + $("#actorSearchContent").on("input", function () { + if (searchTimer) { + clearTimeout(searchTimer); + } + searchTimer = setTimeout(function () { + moviesFromActor(); + }.bind(this), 800); + + }); + $('#moviesComingSoon').on('click', function (e) { e.preventDefault(); moviesComingSoon(); @@ -300,7 +311,7 @@ $(function () { function movieSearch() { var query = $("#movieSearchContent").val(); var url = createBaseUrl(base, '/search/movie/'); - query ? getMovies(url + query) : resetMovies(); + query ? getMovies(url + query) : resetMovies("#movieList"); } function moviesComingSoon() { @@ -313,6 +324,12 @@ $(function () { getMovies(url); } + function moviesFromActor() { + var query = $("#actorSearchContent").val(); + var url = createBaseUrl(base, '/search/actor/'); + query ? getMovies(url + query, "#actorMovieList", "#actorSearchButton") : resetMovies("#actorMovieList"); + } + function popularShows() { var url = createBaseUrl(base, '/search/tv/popular'); getTvShows(url, true); @@ -330,30 +347,31 @@ $(function () { getTvShows(url, true); } - function getMovies(url) { - resetMovies(); - - $('#movieSearchButton').attr("class", "fa fa-spinner fa-spin"); + function getMovies(url, target, button) { + target = target || "#movieList"; + button = button || "#movieSearchButton"; + resetMovies(target); + $(button).attr("class", "fa fa-spinner fa-spin"); $.ajax(url).success(function (results) { if (results.length > 0) { results.forEach(function (result) { var context = buildMovieContext(result); var html = searchTemplate(context); - $("#movieList").append(html); + $(target).append(html); checkNetflix(context.title, context.id); }); } else { - $("#movieList").html(noResultsHtml); + $(target).html(noResultsHtml); } - $('#movieSearchButton').attr("class", "fa fa-search"); + $(button).attr("class", "fa fa-search"); }); }; - function resetMovies() { - $("#movieList").html(""); + function resetMovies(target) { + $(target).html(""); } function tvSearch() { diff --git a/Ombi.UI/Resources/UI.resx b/Ombi.UI/Resources/UI.resx index d24c5ea1c..7144696a1 100644 --- a/Ombi.UI/Resources/UI.resx +++ b/Ombi.UI/Resources/UI.resx @@ -1,76 +1,96 @@  + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + + + + + + + + + + + + + + + + + + - + + @@ -89,13 +109,13 @@ text/microsoft-resx - 1.3 + 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Login @@ -476,4 +496,7 @@ If you are an administrator, please use the other login page + + Actors + \ No newline at end of file diff --git a/Ombi.UI/Resources/UI1.Designer.cs b/Ombi.UI/Resources/UI1.Designer.cs index 56013a95f..598414433 100644 --- a/Ombi.UI/Resources/UI1.Designer.cs +++ b/Ombi.UI/Resources/UI1.Designer.cs @@ -717,6 +717,15 @@ namespace Ombi.UI.Resources { } } + /// + /// Looks up a localized string similar to Actors. + /// + public static string Search_Actors { + get { + return ResourceManager.GetString("Search_Actors", resourceCulture); + } + } + /// /// Looks up a localized string similar to Albums. /// diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index db5b25dd9..001c10db6 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -25,6 +25,10 @@ @UI.Search_Movies +
  • + @UI.Search_Actors + +
  • } @if (Model.Settings.SearchForTvShows) { @@ -70,8 +74,22 @@
    - } + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + } @if (Model.Settings.SearchForTvShows) { From 55e7b50b859884c99e3048a9824217650568b0d4 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 23 Feb 2017 22:31:26 +0000 Subject: [PATCH 16/32] Fixed #1177 --- Ombi.UI/Views/Search/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index 07f9ac358..f28fb2b7b 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -379,7 +379,7 @@
    {{else}} {{#if_eq enableTvRequestsForOnlySeries true}} - + {{else}}