From 0f121238aaa7f33497787d7227082fcb10e6aa72 Mon Sep 17 00:00:00 2001 From: SuperPotatoMen Date: Wed, 1 Feb 2017 23:38:07 +0100 Subject: [PATCH 01/19] 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 02/19] 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 03/19] 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 d74d88925b85ec50964a06b64e6e7e74c229224a Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 20 Feb 2017 08:08:50 +0000 Subject: [PATCH 04/19] 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 a8288a93b0cb6ce39bb5ae3f7e4208aaa63905c4 Mon Sep 17 00:00:00 2001 From: smcpeck Date: Thu, 23 Feb 2017 11:59:37 -0600 Subject: [PATCH 05/19] 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 06/19] 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 07/19] 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 b51f790493cb51a84ccdf03a4757ac1f688bfa01 Mon Sep 17 00:00:00 2001 From: smcpeck Date: Thu, 23 Feb 2017 17:13:45 -0600 Subject: [PATCH 08/19] REFACTOR: IAvailabilityChecker - changed arrays to IEnumerables --- Ombi.Services/Interfaces/IAvailabilityChecker.cs | 12 ++++++------ Ombi.Services/Jobs/PlexAvailabilityChecker.cs | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Ombi.Services/Interfaces/IAvailabilityChecker.cs b/Ombi.Services/Interfaces/IAvailabilityChecker.cs index f2915faa0..cf602e531 100644 --- a/Ombi.Services/Interfaces/IAvailabilityChecker.cs +++ b/Ombi.Services/Interfaces/IAvailabilityChecker.cs @@ -37,15 +37,15 @@ namespace Ombi.Services.Interfaces void Start(); void CheckAndUpdateAll(); IEnumerable GetPlexMovies(IEnumerable content); - bool IsMovieAvailable(PlexContent[] plexMovies, string title, string year, string providerId = null); + bool IsMovieAvailable(IEnumerable plexMovies, string title, string year, string providerId = null); IEnumerable GetPlexTvShows(IEnumerable content); - bool IsTvShowAvailable(PlexContent[] plexShows, string title, string year, string providerId = null, int[] seasons = null); + bool IsTvShowAvailable(IEnumerable plexShows, string title, string year, string providerId = null, int[] seasons = null); IEnumerable GetPlexAlbums(IEnumerable content); - bool IsAlbumAvailable(PlexContent[] plexAlbums, string title, string year, string artist); + bool IsAlbumAvailable(IEnumerable plexAlbums, string title, string year, string artist); bool IsEpisodeAvailable(string theTvDbId, int season, int episode); - PlexContent GetAlbum(PlexContent[] plexAlbums, string title, string year, string artist); - PlexContent GetMovie(PlexContent[] plexMovies, string title, string year, string providerId = null); - PlexContent GetTvShow(PlexContent[] plexShows, string title, string year, string providerId = null, int[] seasons = null); + PlexContent GetAlbum(IEnumerable plexAlbums, string title, string year, string artist); + PlexContent GetMovie(IEnumerable plexMovies, string title, string year, string providerId = null); + PlexContent GetTvShow(IEnumerable plexShows, string title, string year, string providerId = null, int[] seasons = null); /// /// Gets the episode's stored in the cache. /// diff --git a/Ombi.Services/Jobs/PlexAvailabilityChecker.cs b/Ombi.Services/Jobs/PlexAvailabilityChecker.cs index e205b5b1b..6ab9da967 100644 --- a/Ombi.Services/Jobs/PlexAvailabilityChecker.cs +++ b/Ombi.Services/Jobs/PlexAvailabilityChecker.cs @@ -189,15 +189,15 @@ namespace Ombi.Services.Jobs return content.Where(x => x.Type == Store.Models.Plex.PlexMediaType.Movie); } - public bool IsMovieAvailable(PlexContent[] plexMovies, string title, string year, string providerId = null) + public bool IsMovieAvailable(IEnumerable plexMovies, string title, string year, string providerId = null) { var movie = GetMovie(plexMovies, title, year, providerId); return movie != null; } - public PlexContent GetMovie(PlexContent[] plexMovies, string title, string year, string providerId = null) + public PlexContent GetMovie(IEnumerable plexMovies, string title, string year, string providerId = null) { - if (plexMovies.Length == 0) + if (plexMovies.Count() == 0) { return null; } @@ -231,14 +231,14 @@ namespace Ombi.Services.Jobs return content.Where(x => x.Type == Store.Models.Plex.PlexMediaType.Show); } - public bool IsTvShowAvailable(PlexContent[] plexShows, string title, string year, string providerId = null, int[] seasons = null) + public bool IsTvShowAvailable(IEnumerable plexShows, string title, string year, string providerId = null, int[] seasons = null) { var show = GetTvShow(plexShows, title, year, providerId, seasons); return show != null; } - public PlexContent GetTvShow(PlexContent[] plexShows, string title, string year, string providerId = null, + public PlexContent GetTvShow(IEnumerable plexShows, string title, string year, string providerId = null, int[] seasons = null) { var advanced = !string.IsNullOrEmpty(providerId); @@ -340,14 +340,14 @@ namespace Ombi.Services.Jobs return content.Where(x => x.Type == Store.Models.Plex.PlexMediaType.Artist); } - public bool IsAlbumAvailable(PlexContent[] plexAlbums, string title, string year, string artist) + public bool IsAlbumAvailable(IEnumerable plexAlbums, string title, string year, string artist) { return plexAlbums.Any(x => x.Title.Contains(title) && x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase)); } - public PlexContent GetAlbum(PlexContent[] plexAlbums, string title, string year, string artist) + public PlexContent GetAlbum(IEnumerable plexAlbums, string title, string year, string artist) { return plexAlbums.FirstOrDefault(x => x.Title.Contains(title) && From 0d3ff966d64038ac27501096072b6e70d49adc0c Mon Sep 17 00:00:00 2001 From: smcpeck Date: Thu, 23 Feb 2017 17:15:58 -0600 Subject: [PATCH 09/19] Added API endpoint for /actor/new/ to support searching for movies not already available/requested --- Ombi.Api/TheMovieDbApi.cs | 23 +++++++-- Ombi.UI/Modules/SearchModule.cs | 86 ++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/Ombi.Api/TheMovieDbApi.cs b/Ombi.Api/TheMovieDbApi.cs index 7a072caa4..1dee00d62 100644 --- a/Ombi.Api/TheMovieDbApi.cs +++ b/Ombi.Api/TheMovieDbApi.cs @@ -107,9 +107,16 @@ namespace Ombi.Api } public async Task> SearchActor(string searchTerm) + { + return await SearchActor(searchTerm, null); + } + + public async Task> SearchActor(string searchTerm, Func> alreadyAvailable) { SearchContainer result = await Client.SearchPerson(searchTerm); - var person = result?.Results[0] ?? null; + + var people = result?.Results ?? new List(); + var person = (people.Count != 0 ? people[0] : null); var movies = new List(); var counter = 0; try @@ -121,15 +128,23 @@ namespace Ombi.Api foreach (var credit in credits.Cast) { if (counter == 10) break; - movies.Add(await GetMovie(credit.Id)); - counter++; + if (alreadyAvailable == null || !(await alreadyAvailable(credit.Id, credit.Title, credit.ReleaseDate.Value.Year.ToString()))) + { + movies.Add(await GetMovie(credit.Id)); + counter++; + } + else + { + //if we didn't add the movie, delay longer since we know we'll be making additional API calls + await Task.Delay(75); + } await Task.Delay(50); } } } catch(Exception e) { - Log.LogException(LogLevel.Error, $"Aggregating movies for {searchTerm} failed.", e); + Log.Log(LogLevel.Error, e); } return movies; } diff --git a/Ombi.UI/Modules/SearchModule.cs b/Ombi.UI/Modules/SearchModule.cs index 601ba1914..77aea51fc 100644 --- a/Ombi.UI/Modules/SearchModule.cs +++ b/Ombi.UI/Modules/SearchModule.cs @@ -116,6 +116,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["actor/new/{searchTerm}", true] = async (x, ct) => await SearchActor((string)x.searchTerm, true); 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); @@ -173,6 +174,8 @@ namespace Ombi.UI.Modules private IRadarrCacher RadarrCacher { get; } private ISettingsService CustomizationSettings { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); + private IEnumerable _plexMovies = null; + private Dictionary _dbMovies = null; private async Task RequestLoad() { @@ -216,6 +219,29 @@ namespace Ombi.UI.Modules return await TransformMovieResultsToResponse(movies); } + private async Task SearchActor(string searchTerm, bool filterExisting) + { + var movies = TransformMovieListToMovieResultList(await MovieApi.SearchActor(searchTerm, AlreadyAvailable).ConfigureAwait(false)); + return await TransformMovieResultsToResponse(movies); + } + + private async Task AlreadyAvailable(int id, string title, string year) + { + await Task.Yield(); + return IsMovieInCache(id, String.Empty) || Checker.IsMovieAvailable(plexMovies(), title, year); + } + + private IEnumerable plexMovies() + { + if(_plexMovies == null) + { + var content = PlexContentRepository.GetAll(); + _plexMovies = Checker.GetPlexMovies(content); + } + + return _plexMovies; + } + private Response GetTvPoster(int theTvDbId) { var result = TvApi.ShowLookupByTheTvDbId(theTvDbId); @@ -297,22 +323,25 @@ namespace Ombi.UI.Modules return await TransformMovieResultsToResponse(apiMovies); } - private async Task TransformMovieResultsToResponse(List movies) + private async Task> requestedMovies() { - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Movie); - - var distinctResults = allResults.DistinctBy(x => x.ProviderId); - var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); + if (_dbMovies == null) + { + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Movie); + var distinctResults = allResults.DistinctBy(x => x.ProviderId); + _dbMovies = distinctResults.ToDictionary(x => x.ProviderId); + } + return _dbMovies; + } - var cpCached = CpCacher.QueuedIds(); - var watcherCached = WatcherCacher.QueuedIds(); - var radarrCached = RadarrCacher.QueuedIds(); - var content = PlexContentRepository.GetAll(); - var plexMovies = Checker.GetPlexMovies(content); + private async Task TransformMovieResultsToResponse(List movies) + { + await Task.Yield(); var viewMovies = new List(); var counter = 0; + Dictionary dbMovies = await requestedMovies(); foreach (var movie in movies) { var viewMovie = new SearchMovieViewModel @@ -353,10 +382,8 @@ 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); + var canSee = CanUserSeeThisRequest(viewMovie.Id, Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests), await requestedMovies()); + var plexMovie = Checker.GetMovie(plexMovies(), movie.Title, movie.ReleaseDate?.Year.ToString(), imdbId); if (plexMovie != null) { viewMovie.Available = true; @@ -370,26 +397,29 @@ namespace Ombi.UI.Modules viewMovie.Approved = dbm.Approved; viewMovie.Available = dbm.Available; } - else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db - { - viewMovie.Approved = true; - viewMovie.Requested = true; - } - else if (watcherCached.Contains(imdbId) && canSee) // compare to the watcher db + else if (canSee) { - viewMovie.Approved = true; - viewMovie.Requested = true; - } - else if (radarrCached.Contains(movie.Id) && canSee) - { - viewMovie.Approved = true; - viewMovie.Requested = true; + bool exists = IsMovieInCache(movie, imdbId); + viewMovie.Approved = exists; + viewMovie.Requested = exists; } viewMovies.Add(viewMovie); } return Response.AsJson(viewMovies); + } + + private bool IsMovieInCache(MovieResult movie, string imdbId) + { int id = movie.Id; + return IsMovieInCache(id, imdbId); + } + + private bool IsMovieInCache(int id, string imdbId) + { var cpCached = CpCacher.QueuedIds(); + var watcherCached = WatcherCacher.QueuedIds(); + var radarrCached = RadarrCacher.QueuedIds(); + return cpCached.Contains(id) || watcherCached.Contains(imdbId) || radarrCached.Contains(id); } private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, From 39871802b51c35869fa08fcd1bf0e152d334b50d Mon Sep 17 00:00:00 2001 From: smcpeck Date: Thu, 23 Feb 2017 17:17:27 -0600 Subject: [PATCH 10/19] UI changes to add checkbox and support searching for only new matches via new API. --- Ombi.UI/Content/requests.js | 3 +- Ombi.UI/Content/search.js | 16 ++++++++-- Ombi.UI/Resources/UI.resx | 3 ++ Ombi.UI/Resources/UI1.Designer.cs | 9 ++++++ Ombi.UI/Views/Admin/Settings.cshtml | 1 + Ombi.UI/Views/Search/Index.cshtml | 47 +++++++++++++++++------------ 6 files changed, 56 insertions(+), 23 deletions(-) diff --git a/Ombi.UI/Content/requests.js b/Ombi.UI/Content/requests.js index 4d9a13b5e..132bb7232 100644 --- a/Ombi.UI/Content/requests.js +++ b/Ombi.UI/Content/requests.js @@ -745,7 +745,8 @@ function actorLoad() { } $ml.html(""); - var url = createBaseUrl(base, '/requests/actor'); + var $newOnly = $('#searchNewOnly').val(); + var url = createBaseUrl(base, '/requests/actor' + (!!$newOnly ? '/new' : '')); $.ajax(url).success(function (results) { if (results.length > 0) { results.forEach(function (result) { diff --git a/Ombi.UI/Content/search.js b/Ombi.UI/Content/search.js index 51c4011cd..cc4849ecd 100644 --- a/Ombi.UI/Content/search.js +++ b/Ombi.UI/Content/search.js @@ -65,14 +65,23 @@ $(function () { // Type in actor search $("#actorSearchContent").on("input", function () { + triggerActorSearch(); + }); + + // if they toggle the checkbox, we want to refresh our search + $("#actorsSearchNew").click(function () { + triggerActorSearch(); + }); + + function triggerActorSearch() + { if (searchTimer) { clearTimeout(searchTimer); } searchTimer = setTimeout(function () { moviesFromActor(); }.bind(this), 800); - - }); + } $('#moviesComingSoon').on('click', function (e) { e.preventDefault(); @@ -326,7 +335,8 @@ $(function () { function moviesFromActor() { var query = $("#actorSearchContent").val(); - var url = createBaseUrl(base, '/search/actor/'); + var $newOnly = $('#actorsSearchNew')[0].checked; + var url = createBaseUrl(base, '/search/actor/' + (!!$newOnly ? 'new/' : '')); query ? getMovies(url + query, "#actorMovieList", "#actorSearchButton") : resetMovies("#actorMovieList"); } diff --git a/Ombi.UI/Resources/UI.resx b/Ombi.UI/Resources/UI.resx index 7144696a1..d87972cff 100644 --- a/Ombi.UI/Resources/UI.resx +++ b/Ombi.UI/Resources/UI.resx @@ -211,6 +211,9 @@ Albums + + Don't include titles that are already requested/available + Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it! diff --git a/Ombi.UI/Resources/UI1.Designer.cs b/Ombi.UI/Resources/UI1.Designer.cs index 598414433..d7d92aea8 100644 --- a/Ombi.UI/Resources/UI1.Designer.cs +++ b/Ombi.UI/Resources/UI1.Designer.cs @@ -897,6 +897,15 @@ namespace Ombi.UI.Resources { } } + /// + /// Looks up a localized string similar to Don't include titles that are already requested/available. + /// + public static string Search_NewOnly { + get { + return ResourceManager.GetString("Search_NewOnly", resourceCulture); + } + } + /// /// Looks up a localized string similar to Not Requested yet. /// diff --git a/Ombi.UI/Views/Admin/Settings.cshtml b/Ombi.UI/Views/Admin/Settings.cshtml index 54e588c20..9a6010964 100644 --- a/Ombi.UI/Views/Admin/Settings.cshtml +++ b/Ombi.UI/Views/Admin/Settings.cshtml @@ -60,6 +60,7 @@ @Html.Checkbox(Model.SearchForMovies,"SearchForMovies","Search for Movies") + @Html.Checkbox(Model.SearchForActors,"SearchForActors","Search for Movies by Actor")
    diff --git a/Ombi.UI/Views/Search/Index.cshtml b/Ombi.UI/Views/Search/Index.cshtml index 001c10db6..965cfe62f 100644 --- a/Ombi.UI/Views/Search/Index.cshtml +++ b/Ombi.UI/Views/Search/Index.cshtml @@ -25,10 +25,13 @@ @UI.Search_Movies -
  • - @UI.Search_Actors + @if (Model.Settings.SearchForActors) + { +
  • + @UI.Search_Actors -
  • + + } } @if (Model.Settings.SearchForTvShows) { @@ -74,21 +77,27 @@
    + @if (Model.Settings.SearchForActors) + { -
    -
    - -
    - +
    +
    + +
    + +
    +
    +
    +
    -
    -
    -
    - -
    -
    -
    +
    +
    + +
    +
    +
    + } } @if (Model.Settings.SearchForTvShows) @@ -139,7 +148,7 @@ } - -