diff --git a/PlexRequests.Core/Models/NotificationType.cs b/PlexRequests.Core/Models/NotificationType.cs index a01d153dd..eace7b018 100644 --- a/PlexRequests.Core/Models/NotificationType.cs +++ b/PlexRequests.Core/Models/NotificationType.cs @@ -34,6 +34,7 @@ namespace PlexRequests.Core.Models RequestApproved, AdminNote, Test, - + RequestDeclined, + ItemAddedToFaultQueue } } diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 2800771ca..8faf4e83a 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -39,6 +39,10 @@ ..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll True + + ..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll + True + ..\Assemblies\Mono.Data.Sqlite.dll @@ -105,6 +109,7 @@ + diff --git a/PlexRequests.Core/Queue/ITransientFaultQueue.cs b/PlexRequests.Core/Queue/ITransientFaultQueue.cs new file mode 100644 index 000000000..ac62add0f --- /dev/null +++ b/PlexRequests.Core/Queue/ITransientFaultQueue.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using PlexRequests.Store; +using PlexRequests.Store.Models; + +namespace PlexRequests.Core.Queue +{ + public interface ITransientFaultQueue + { + void Dequeue(); + Task DequeueAsync(); + IEnumerable GetQueue(); + Task> GetQueueAsync(); + void QueueItem(RequestedModel request, RequestType type, FaultType faultType); + Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType); + } +} \ No newline at end of file diff --git a/PlexRequests.Core/Queue/TransientFaultQueue.cs b/PlexRequests.Core/Queue/TransientFaultQueue.cs index e3621ee0f..108387f03 100644 --- a/PlexRequests.Core/Queue/TransientFaultQueue.cs +++ b/PlexRequests.Core/Queue/TransientFaultQueue.cs @@ -26,7 +26,9 @@ #endregion using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Dapper; using PlexRequests.Helpers; using PlexRequests.Store; using PlexRequests.Store.Models; @@ -34,7 +36,7 @@ using PlexRequests.Store.Repository; namespace PlexRequests.Core.Queue { - public class TransientFaultQueue + public class TransientFaultQueue : ITransientFaultQueue { public TransientFaultQueue(IRepository queue) { @@ -44,44 +46,84 @@ namespace PlexRequests.Core.Queue private IRepository RequestQueue { get; } - public void QueueItem(RequestedModel request, RequestType type) + public void QueueItem(RequestedModel request, RequestType type, FaultType faultType) { + //Ensure there is not a duplicate queued item + var existingItem = RequestQueue.Custom( + connection => + { + connection.Open(); + var result = connection.Query("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + + return result; + }).FirstOrDefault(); + + if (existingItem != null) + { + // It's already in the queue + return; + } + var queue = new RequestQueue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId + PrimaryIdentifier = request.ProviderId, + FaultType = faultType }; RequestQueue.Insert(queue); } - public async Task QueueItemAsync(RequestedModel request, RequestType type) + public async Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType) { + //Ensure there is not a duplicate queued item + var existingItem = await RequestQueue.CustomAsync(async connection => + { + connection.Open(); + var result = await connection.QueryAsync("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + + return result; + }); + + if (existingItem.FirstOrDefault() != null) + { + // It's already in the queue + return; + } + var queue = new RequestQueue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId + PrimaryIdentifier = request.ProviderId, + FaultType = faultType }; await RequestQueue.InsertAsync(queue); } - public IEnumerable Dequeue() + public IEnumerable GetQueue() { var items = RequestQueue.GetAll(); - RequestQueue.DeleteAll("RequestQueue"); return items; } - public async Task> DequeueAsync() + public async Task> GetQueueAsync() { var items = RequestQueue.GetAllAsync(); - - await RequestQueue.DeleteAllAsync("RequestQueue"); - + return await items; } + + public void Dequeue() + { + RequestQueue.DeleteAll("RequestQueue"); + } + + public async Task DequeueAsync() + { + await RequestQueue.DeleteAllAsync("RequestQueue"); + } } } \ No newline at end of file diff --git a/PlexRequests.Core/packages.config b/PlexRequests.Core/packages.config index e8f81f5a9..d698d0fd5 100644 --- a/PlexRequests.Core/packages.config +++ b/PlexRequests.Core/packages.config @@ -2,6 +2,7 @@ + diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 3f6bf9c91..52727dba8 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -87,6 +87,14 @@ namespace PlexRequests.Services.Notification case NotificationType.Test: await EmailTest(model, emailSettings); break; + case NotificationType.RequestDeclined: + throw new NotImplementedException(); + + case NotificationType.ItemAddedToFaultQueue: + await EmailAddedToRequestQueue(model, emailSettings); + break; + default: + throw new ArgumentOutOfRangeException(); } } @@ -129,7 +137,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }; var message = new MimeMessage { @@ -150,7 +158,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: New issue for {model.Title}!", $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!"}; var message = new MimeMessage { @@ -164,6 +172,27 @@ namespace PlexRequests.Services.Notification await Send(message, settings); } + private async Task EmailAddedToRequestQueue(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Plex Requests: A request could not be added.", + $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying", + model.ImgSrc); + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying" }; + + var message = new MimeMessage + { + Body = body.ToMessageBody(), + Subject = $"Plex Requests: A request could not be added" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) { if (!settings.EnableUserEmailNotifications) @@ -175,7 +204,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: {model.Title} is now available!", $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }; var message = new MimeMessage { diff --git a/PlexRequests.Store/Models/RequestQueue.cs b/PlexRequests.Store/Models/RequestQueue.cs index bdbf25a2f..15067fbc7 100644 --- a/PlexRequests.Store/Models/RequestQueue.cs +++ b/PlexRequests.Store/Models/RequestQueue.cs @@ -38,5 +38,12 @@ namespace PlexRequests.Store.Models public byte[] Content { get; set; } + public FaultType FaultType { get; set; } + } + + public enum FaultType + { + RequestFault, + MissingInformation } } \ No newline at end of file diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index a22258a5d..220664ba1 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -138,6 +138,7 @@ CREATE TABLE IF NOT EXISTS RequestQueue Id INTEGER PRIMARY KEY AUTOINCREMENT, PrimaryIdentifier INTEGER NOT NULL, Type INTEGER NOT NULL, + FaultType INTEGER NOT NULL, Content BLOB NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index e10e5b92d..ef35e08b6 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -1,4 +1,5 @@ #region Copyright + // /************************************************************************ // Copyright (c) 2016 Jamie Rees // File: SearchModule.cs @@ -23,7 +24,9 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ + #endregion + using System; using System.Collections.Generic; using System.Globalization; @@ -54,13 +57,13 @@ using Newtonsoft.Json; using PlexRequests.Api.Models.Sonarr; using PlexRequests.Api.Models.Tv; using PlexRequests.Core.Models; +using PlexRequests.Core.Queue; using PlexRequests.Helpers.Analytics; using PlexRequests.Helpers.Permissions; using PlexRequests.Store.Models; using PlexRequests.Store.Repository; using TMDbLib.Objects.General; -using TMDbLib.Objects.Search; using Action = PlexRequests.Helpers.Analytics.Action; using EpisodesModel = PlexRequests.Store.EpisodesModel; @@ -73,10 +76,13 @@ namespace PlexRequests.UI.Modules ISettingsService prSettings, IAvailabilityChecker checker, IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, - INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, + INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, + ISettingsService hpService, ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, - ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, - IIssueService issue, IAnalytics a, IRepository rl) : base("search", prSettings) + ISettingsService plexService, ISettingsService auth, + IRepository u, ISettingsService email, + IIssueService issue, IAnalytics a, IRepository rl, ITransientFaultQueue tfQueue) + : base("search", prSettings) { Auth = auth; PlexService = plexService; @@ -104,6 +110,7 @@ namespace PlexRequests.UI.Modules IssueService = issue; Analytics = a; RequestLimitRepo = rl; + FaultQueue = tfQueue; TvApi = new TvMazeApi(); @@ -118,7 +125,8 @@ namespace PlexRequests.UI.Modules Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); - Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); + Post["request/tv", true] = + async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); Post["request/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0, "episode"); Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); @@ -128,6 +136,7 @@ namespace PlexRequests.UI.Modules Get["/seasons"] = x => GetSeasons(); Get["/episodes", true] = async (x, ct) => await GetEpisodes(); } + private TvMazeApi TvApi { get; } private IPlexApi PlexApi { get; } private TheMovieDbApi MovieApi { get; } @@ -154,6 +163,7 @@ namespace PlexRequests.UI.Modules private IRepository UsersToNotifyRepo { get; } private IIssueService IssueService { get; } private IAnalytics Analytics { get; } + private ITransientFaultQueue FaultQueue { get; } private IRepository RequestLimitRepo { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -167,19 +177,22 @@ namespace PlexRequests.UI.Modules private async Task UpcomingMovies() { - Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, + CookieHelper.GetAnalyticClientId(Cookies)); return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); } private async Task CurrentlyPlayingMovies() { - Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, + CookieHelper.GetAnalyticClientId(Cookies)); return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); } private async Task SearchMovie(string searchTerm) { - Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); return await ProcessMovies(MovieSearchType.Search, searchTerm); } @@ -192,24 +205,24 @@ namespace PlexRequests.UI.Modules case MovieSearchType.Search: var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false); apiMovies = movies.Select(x => - new MovieResult - { - Adult = x.Adult, - BackdropPath = x.BackdropPath, - GenreIds = x.GenreIds, - 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(); + new MovieResult + { + Adult = x.Adult, + BackdropPath = x.BackdropPath, + GenreIds = x.GenreIds, + 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(); break; case MovieSearchType.CurrentlyPlaying: apiMovies = await MovieApi.GetCurrentPlayingMovies(); @@ -239,8 +252,9 @@ namespace PlexRequests.UI.Modules var imdbId = string.Empty; if (counter <= 5) // Let's only do it for the first 5 items { - var movieInfoTask = await MovieApi.GetMovieInformation(movie.Id).ConfigureAwait(false); // TODO needs to be careful about this, it's adding extra time to search... - // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 + var movieInfoTask = await MovieApi.GetMovieInformation(movie.Id).ConfigureAwait(false); + // TODO needs to be careful about this, it's adding extra time to search... + // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 imdbId = movieInfoTask.ImdbId; counter++; } @@ -263,7 +277,8 @@ namespace PlexRequests.UI.Modules VoteCount = movie.VoteCount }; var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); - var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(), imdbId); + var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(), + imdbId); if (plexMovie != null) { viewMovie.Available = true; @@ -288,7 +303,8 @@ namespace PlexRequests.UI.Modules return Response.AsJson(viewMovies); } - private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) + private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, + Dictionary moviesInDb) { if (usersCanViewOnlyOwnRequests) { @@ -302,7 +318,8 @@ namespace PlexRequests.UI.Modules private async Task SearchTvShow(string searchTerm) { - Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); var plexSettings = await PlexService.GetSettingsAsync(); var prSettings = await PrService.GetSettingsAsync(); var providerId = string.Empty; @@ -360,7 +377,8 @@ namespace PlexRequests.UI.Modules providerId = viewT.Id.ToString(); } - var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId); + var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), + providerId); if (plexShow != null) { viewT.Available = true; @@ -377,7 +395,8 @@ namespace PlexRequests.UI.Modules viewT.Episodes = dbt.Episodes.ToList(); viewT.Approved = dbt.Approved; } - if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db + if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid)) + // compare to the sonarr/sickrage db { viewT.Requested = true; } @@ -391,7 +410,8 @@ namespace PlexRequests.UI.Modules private async Task SearchAlbum(string searchTerm) { - Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); var apiAlbums = new List(); await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => { @@ -448,7 +468,7 @@ namespace PlexRequests.UI.Modules if (Security.DoesNotHavePermissions(Permissions.ReadOnlyUser, User)) { return - Response.AsJson(new JsonResponseModel() + Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to request a movie!" @@ -457,12 +477,19 @@ namespace PlexRequests.UI.Modules var settings = await PrService.GetSettingsAsync(); if (!await CheckRequestLimit(settings, RequestType.Movie)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for Movies! Please contact your admin." }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "You have reached your weekly request limit for Movies! Please contact your admin." + }); } - Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, + CookieHelper.GetAnalyticClientId(Cookies)); var movieInfo = await MovieApi.GetMovieInformation(movieId); - var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; + var fullMovieName = + $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; var existingRequest = await RequestService.CheckRequestAsync(movieId); if (existingRequest != null) @@ -474,7 +501,15 @@ namespace PlexRequests.UI.Modules await RequestService.UpdateRequestAsync(existingRequest); } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = + settings.UsersCanViewOnlyOwnRequests + ? $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" + : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" + }); } try @@ -482,13 +517,23 @@ namespace PlexRequests.UI.Modules var movies = Checker.GetPlexMovies(); if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullMovieName} is already in Plex!" + }); } } catch (Exception e) { Log.Error(e); - return Response.AsJson(new JsonResponseModel { Result = false, Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName) }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName) + }); } //#endif @@ -508,41 +553,58 @@ namespace PlexRequests.UI.Modules Issues = IssueState.None, }; - - if (ShouldAutoApprove(RequestType.Movie, settings)) - { - var cpSettings = await CpService.GetSettingsAsync(); - model.Approved = true; - if (cpSettings.Enabled) - { - Log.Info("Adding movie to CP (No approval required)"); - var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, - cpSettings.FullUri, cpSettings.ProfileId); - Log.Debug("Adding movie to CP result {0}", result); - if (result) - { - return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); - } - - return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = Resources.UI.Search_CouchPotatoError - }); - } - model.Approved = true; - return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); - } - try { + if (ShouldAutoApprove(RequestType.Movie, settings)) + { + var cpSettings = await CpService.GetSettingsAsync(); + model.Approved = true; + if (cpSettings.Enabled) + { + Log.Info("Adding movie to CP (No approval required)"); + var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, + cpSettings.FullUri, cpSettings.ProfileId); + Log.Debug("Adding movie to CP result {0}", result); + if (result) + { + return + await + AddRequest(model, settings, + $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); + } + + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_CouchPotatoError + }); + } + model.Approved = true; + return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); + } + + return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); } catch (Exception e) { Log.Fatal(e); + await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_CouchPotatoError }); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.Movie, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" + }); } } @@ -576,9 +638,15 @@ namespace PlexRequests.UI.Modules var settings = await PrService.GetSettingsAsync(); if (!await CheckRequestLimit(settings, RequestType.TvShow)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_WeeklyRequestLimitTVShow }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_WeeklyRequestLimitTVShow + }); } - Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, + CookieHelper.GetAnalyticClientId(Cookies)); var sonarrSettings = SonarrService.GetSettingsAsync(); @@ -590,7 +658,13 @@ namespace PlexRequests.UI.Modules var s = await sonarrSettings; if (!s.Enabled) { - return Response.AsJson(new JsonResponseModel { Message = "This is currently only supported with Sonarr, Please enable Sonarr for this feature", Result = false }); + return + Response.AsJson(new JsonResponseModel + { + Message = + "This is currently only supported with Sonarr, Please enable Sonarr for this feature", + Result = false + }); } } @@ -599,14 +673,8 @@ namespace PlexRequests.UI.Modules DateTime.TryParse(showInfo.premiered, out firstAir); string fullShowName = $"{showInfo.name} ({firstAir.Year})"; - if (showInfo.externals?.thetvdb == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Our TV Provider (TVMaze) doesn't have a TheTVDBId for this TV Show :( We cannot add the TV Show automatically sorry! Please report this problem to the server admin so he/she can sort it out!" }); - } - var model = new RequestedModel { - ProviderId = showInfo.externals?.thetvdb ?? 0, Type = RequestType.TvShow, Overview = showInfo.summary.RemoveHtml(), PosterPath = showInfo.image?.medium, @@ -622,6 +690,25 @@ namespace PlexRequests.UI.Modules TvDbId = showId.ToString() }; + if (showInfo.externals?.thetvdb == null) + { + await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.MissingInformation); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + model.ProviderId = showInfo.externals?.thetvdb ?? 0; var seasonsList = new List(); switch (seasons) @@ -642,9 +729,14 @@ namespace PlexRequests.UI.Modules foreach (var ep in episodeModel?.Episodes ?? new Models.EpisodesModel[0]) { - model.Episodes.Add(new EpisodesModel { EpisodeNumber = ep.EpisodeNumber, SeasonNumber = ep.SeasonNumber }); + model.Episodes.Add(new EpisodesModel + { + EpisodeNumber = ep.EpisodeNumber, + SeasonNumber = ep.SeasonNumber + }); } - Analytics.TrackEventAsync(Category.Requests, Action.TvShow, $"Episode request for {model.Title}", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Requests, Action.TvShow, $"Episode request for {model.Title}", + Username, CookieHelper.GetAnalyticClientId(Cookies)); break; default: model.SeasonsRequested = seasons; @@ -691,7 +783,12 @@ namespace PlexRequests.UI.Modules else { // We no episodes to approve - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); } } else if (model.SeasonList.Except(existingRequest.SeasonList).Any()) @@ -720,9 +817,19 @@ namespace PlexRequests.UI.Modules var cachedEpisodes = cachedEpisodesTask.ToList(); foreach (var d in difference) // difference is from an existing request { - if (cachedEpisodes.Any(x => x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && x.ProviderId == providerId)) + if ( + cachedEpisodes.Any( + x => + x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && + x.ProviderId == providerId)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" + }); } } @@ -738,66 +845,118 @@ namespace PlexRequests.UI.Modules var result = Checker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, s.EpisodeNumber); if (result) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); } } } - else if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId, model.SeasonList)) + else if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), + providerId, model.SeasonList)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); } } } catch (Exception) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) + }); } - - if (ShouldAutoApprove(RequestType.TvShow, settings)) + try { - model.Approved = true; - var s = await sonarrSettings; - var sender = new TvSenderOld(SonarrApi, SickrageApi); // TODO put back - if (s.Enabled) + if (ShouldAutoApprove(RequestType.TvShow, settings)) { - var result = await sender.SendToSonarr(s, model); - if (!string.IsNullOrEmpty(result?.title)) + model.Approved = true; + var s = await sonarrSettings; + var sender = new TvSenderOld(SonarrApi, SickrageApi); // TODO put back + if (s.Enabled) { - if (existingRequest != null) + var result = await sender.SendToSonarr(s, model); + if (!string.IsNullOrEmpty(result?.title)) { - return await UpdateRequest(model, settings, + if (existingRequest != null) + { + return await UpdateRequest(model, settings, + $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + return + await + AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + Log.Debug("Error with sending to sonarr."); + return + Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages ?? new List())); } - Log.Debug("Error with sending to sonarr."); - return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages ?? new List())); - } - var srSettings = SickRageService.GetSettings(); - if (srSettings.Enabled) - { - var result = sender.SendToSickRage(srSettings, model); - if (result?.result == "success") + var srSettings = SickRageService.GetSettings(); + if (srSettings.Enabled) { - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + var result = sender.SendToSickRage(srSettings, model); + if (result?.result == "success") + { + return + await + AddRequest(model, settings, + $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = result?.message ?? Resources.UI.Search_SickrageError + }); } - return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message ?? Resources.UI.Search_SickrageError }); + + if (!srSettings.Enabled && !s.Enabled) + { + return + await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + + return + Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp }); + } - - if (!srSettings.Enabled && !s.Enabled) - { - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); - } - - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp }); - + return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + catch (Exception e) + { + await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.RequestFault); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + Log.Error(e); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); } - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } - private async Task AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, string fullShowName, bool episodeReq = false) + private async Task AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, + string fullShowName, bool episodeReq = false) { // check if the current user is already marked as a requester for this show, if not, add them if (!existingRequest.UserHasRequested(Username)) @@ -812,12 +971,15 @@ namespace PlexRequests.UI.Modules $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } - return await UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_AlreadyRequested}"); + return + await UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_AlreadyRequested}"); } private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) { - var sendNotification = ShouldAutoApprove(type, prSettings) ? !prSettings.IgnoreNotifyForAutoApprovedRequests : true; + var sendNotification = ShouldAutoApprove(type, prSettings) + ? !prSettings.IgnoreNotifyForAutoApprovedRequests + : true; var claims = Context.CurrentUser?.Claims; if (claims != null) { @@ -836,9 +998,15 @@ namespace PlexRequests.UI.Modules var settings = await PrService.GetSettingsAsync(); if (!await CheckRequestLimit(settings, RequestType.Album)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_WeeklyRequestLimitAlbums }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_WeeklyRequestLimitAlbums + }); } - Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, + CookieHelper.GetAnalyticClientId(Cookies)); var existingRequest = await RequestService.CheckRequestAsync(releaseId); if (existingRequest != null) @@ -848,7 +1016,15 @@ namespace PlexRequests.UI.Modules existingRequest.RequestedUsers.Add(Username); await RequestService.UpdateRequestAsync(existingRequest); } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = + settings.UsersCanViewOnlyOwnRequests + ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" + : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" + }); } var albumInfo = MusicBrainzApi.GetAlbum(releaseId); @@ -858,11 +1034,17 @@ namespace PlexRequests.UI.Modules var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; if (artist == null) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_MusicBrainzError }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_MusicBrainzError + }); } var albums = Checker.GetPlexAlbums(); - var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); + var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), + artist.name); if (alreadyInPlex) { @@ -892,28 +1074,46 @@ namespace PlexRequests.UI.Modules ArtistId = artist.id }; - if (ShouldAutoApprove(RequestType.Album, settings)) + try { - model.Approved = true; - var hpSettings = HeadphonesService.GetSettings(); - - if (!hpSettings.Enabled) + if (ShouldAutoApprove(RequestType.Album, settings)) { - await RequestService.AddRequestAsync(model); - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}" - }); + model.Approved = true; + var hpSettings = HeadphonesService.GetSettings(); + + if (!hpSettings.Enabled) + { + await RequestService.AddRequestAsync(model); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); + await sender.AddAlbum(model); + return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); } - var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); - await sender.AddAlbum(model); return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); } + catch (Exception e) + { + Log.Error(e); + await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); - return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.Album, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + throw; + } } private string GetMusicBrainzCoverArt(string id) diff --git a/PlexRequests.UI/NinjectModules/ServicesModule.cs b/PlexRequests.UI/NinjectModules/ServicesModule.cs index a180335d6..a7ba11dea 100644 --- a/PlexRequests.UI/NinjectModules/ServicesModule.cs +++ b/PlexRequests.UI/NinjectModules/ServicesModule.cs @@ -25,7 +25,7 @@ // ************************************************************************/ #endregion using Ninject.Modules; - +using PlexRequests.Core.Queue; using PlexRequests.Helpers.Analytics; using PlexRequests.Services.Interfaces; using PlexRequests.Services.Jobs; @@ -51,6 +51,8 @@ namespace PlexRequests.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + + Bind().To(); } } } \ No newline at end of file