Finished #633 (First part of the queuing)

This commit is contained in:
Jamie.Rees 2016-11-08 14:15:33 +00:00
parent 1c7fb2e93e
commit 2bd7ece9d0
10 changed files with 460 additions and 155 deletions

View file

@ -34,6 +34,7 @@ namespace PlexRequests.Core.Models
RequestApproved, RequestApproved,
AdminNote, AdminNote,
Test, Test,
RequestDeclined,
ItemAddedToFaultQueue
} }
} }

View file

@ -39,6 +39,10 @@
<HintPath>..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll</HintPath> <HintPath>..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Dapper, Version=1.50.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Mono.Data.Sqlite"> <Reference Include="Mono.Data.Sqlite">
<HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath> <HintPath>..\Assemblies\Mono.Data.Sqlite.dll</HintPath>
</Reference> </Reference>
@ -105,6 +109,7 @@
<Compile Include="Notification\Templates\IEmailBasicTemplate.cs" /> <Compile Include="Notification\Templates\IEmailBasicTemplate.cs" />
<Compile Include="Notification\TransportType.cs" /> <Compile Include="Notification\TransportType.cs" />
<Compile Include="PlexReadOnlyDatabase.cs" /> <Compile Include="PlexReadOnlyDatabase.cs" />
<Compile Include="Queue\ITransientFaultQueue.cs" />
<Compile Include="Queue\TransientFaultQueue.cs" /> <Compile Include="Queue\TransientFaultQueue.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" /> <Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\ExternalSettings.cs" /> <Compile Include="SettingModels\ExternalSettings.cs" />

View file

@ -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<RequestQueue> GetQueue();
Task<IEnumerable<RequestQueue>> GetQueueAsync();
void QueueItem(RequestedModel request, RequestType type, FaultType faultType);
Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType);
}
}

View file

@ -26,7 +26,9 @@
#endregion #endregion
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.Store.Models; using PlexRequests.Store.Models;
@ -34,7 +36,7 @@ using PlexRequests.Store.Repository;
namespace PlexRequests.Core.Queue namespace PlexRequests.Core.Queue
{ {
public class TransientFaultQueue public class TransientFaultQueue : ITransientFaultQueue
{ {
public TransientFaultQueue(IRepository<RequestQueue> queue) public TransientFaultQueue(IRepository<RequestQueue> queue)
{ {
@ -44,44 +46,84 @@ namespace PlexRequests.Core.Queue
private IRepository<RequestQueue> RequestQueue { get; } private IRepository<RequestQueue> 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<RequestQueue>("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 var queue = new RequestQueue
{ {
Type = type, Type = type,
Content = ByteConverterHelper.ReturnBytes(request), Content = ByteConverterHelper.ReturnBytes(request),
PrimaryIdentifier = request.ProviderId PrimaryIdentifier = request.ProviderId,
FaultType = faultType
}; };
RequestQueue.Insert(queue); 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<RequestQueue>("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 var queue = new RequestQueue
{ {
Type = type, Type = type,
Content = ByteConverterHelper.ReturnBytes(request), Content = ByteConverterHelper.ReturnBytes(request),
PrimaryIdentifier = request.ProviderId PrimaryIdentifier = request.ProviderId,
FaultType = faultType
}; };
await RequestQueue.InsertAsync(queue); await RequestQueue.InsertAsync(queue);
} }
public IEnumerable<RequestQueue> Dequeue() public IEnumerable<RequestQueue> GetQueue()
{ {
var items = RequestQueue.GetAll(); var items = RequestQueue.GetAll();
RequestQueue.DeleteAll("RequestQueue");
return items; return items;
} }
public async Task<IEnumerable<RequestQueue>> DequeueAsync() public async Task<IEnumerable<RequestQueue>> GetQueueAsync()
{ {
var items = RequestQueue.GetAllAsync(); var items = RequestQueue.GetAllAsync();
await RequestQueue.DeleteAllAsync("RequestQueue");
return await items; return await items;
} }
public void Dequeue()
{
RequestQueue.DeleteAll("RequestQueue");
}
public async Task DequeueAsync()
{
await RequestQueue.DeleteAllAsync("RequestQueue");
}
} }
} }

View file

@ -2,6 +2,7 @@
<packages> <packages>
<package id="Common.Logging" version="3.0.0" targetFramework="net45" /> <package id="Common.Logging" version="3.0.0" targetFramework="net45" />
<package id="Common.Logging.Core" version="3.0.0" targetFramework="net45" /> <package id="Common.Logging.Core" version="3.0.0" targetFramework="net45" />
<package id="Dapper" version="1.50.0-beta8" targetFramework="net45" />
<package id="Nancy" version="1.4.3" targetFramework="net45" /> <package id="Nancy" version="1.4.3" targetFramework="net45" />
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net45" /> <package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net45" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />

View file

@ -87,6 +87,14 @@ namespace PlexRequests.Services.Notification
case NotificationType.Test: case NotificationType.Test:
await EmailTest(model, emailSettings); await EmailTest(model, emailSettings);
break; 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}!", $"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")}", $"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); 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 var message = new MimeMessage
{ {
@ -150,7 +158,7 @@ namespace PlexRequests.Services.Notification
$"Plex Requests: New issue for {model.Title}!", $"Plex Requests: New issue for {model.Title}!",
$"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!",
model.ImgSrc); 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 var message = new MimeMessage
{ {
@ -164,6 +172,27 @@ namespace PlexRequests.Services.Notification
await Send(message, settings); 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) private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings)
{ {
if (!settings.EnableUserEmailNotifications) if (!settings.EnableUserEmailNotifications)
@ -175,7 +204,7 @@ namespace PlexRequests.Services.Notification
$"Plex Requests: {model.Title} is now available!", $"Plex Requests: {model.Title} is now available!",
$"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)", $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)",
model.ImgSrc); 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 var message = new MimeMessage
{ {

View file

@ -38,5 +38,12 @@ namespace PlexRequests.Store.Models
public byte[] Content { get; set; } public byte[] Content { get; set; }
public FaultType FaultType { get; set; }
}
public enum FaultType
{
RequestFault,
MissingInformation
} }
} }

View file

@ -138,6 +138,7 @@ CREATE TABLE IF NOT EXISTS RequestQueue
Id INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER PRIMARY KEY AUTOINCREMENT,
PrimaryIdentifier INTEGER NOT NULL, PrimaryIdentifier INTEGER NOT NULL,
Type INTEGER NOT NULL, Type INTEGER NOT NULL,
FaultType INTEGER NOT NULL,
Content BLOB NOT NULL Content BLOB NOT NULL
); );
CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id);

View file

@ -1,4 +1,5 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: SearchModule.cs // File: SearchModule.cs
@ -23,7 +24,9 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -54,13 +57,13 @@ using Newtonsoft.Json;
using PlexRequests.Api.Models.Sonarr; using PlexRequests.Api.Models.Sonarr;
using PlexRequests.Api.Models.Tv; using PlexRequests.Api.Models.Tv;
using PlexRequests.Core.Models; using PlexRequests.Core.Models;
using PlexRequests.Core.Queue;
using PlexRequests.Helpers.Analytics; using PlexRequests.Helpers.Analytics;
using PlexRequests.Helpers.Permissions; using PlexRequests.Helpers.Permissions;
using PlexRequests.Store.Models; using PlexRequests.Store.Models;
using PlexRequests.Store.Repository; using PlexRequests.Store.Repository;
using TMDbLib.Objects.General; using TMDbLib.Objects.General;
using TMDbLib.Objects.Search;
using Action = PlexRequests.Helpers.Analytics.Action; using Action = PlexRequests.Helpers.Analytics.Action;
using EpisodesModel = PlexRequests.Store.EpisodesModel; using EpisodesModel = PlexRequests.Store.EpisodesModel;
@ -73,10 +76,13 @@ namespace PlexRequests.UI.Modules
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker, ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings, IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<SickRageSettings> sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, ISettingsService<SickRageSettings> sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi,
INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService<HeadphonesSettings> hpService, INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi,
ISettingsService<HeadphonesSettings> hpService,
ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi,
ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth, IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email, ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth,
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl) : base("search", prSettings) IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email,
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl, ITransientFaultQueue tfQueue)
: base("search", prSettings)
{ {
Auth = auth; Auth = auth;
PlexService = plexService; PlexService = plexService;
@ -104,6 +110,7 @@ namespace PlexRequests.UI.Modules
IssueService = issue; IssueService = issue;
Analytics = a; Analytics = a;
RequestLimitRepo = rl; RequestLimitRepo = rl;
FaultQueue = tfQueue;
TvApi = new TvMazeApi(); TvApi = new TvMazeApi();
@ -118,7 +125,8 @@ namespace PlexRequests.UI.Modules
Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies();
Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); 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/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0, "episode");
Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); 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["/seasons"] = x => GetSeasons();
Get["/episodes", true] = async (x, ct) => await GetEpisodes(); Get["/episodes", true] = async (x, ct) => await GetEpisodes();
} }
private TvMazeApi TvApi { get; } private TvMazeApi TvApi { get; }
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
private TheMovieDbApi MovieApi { get; } private TheMovieDbApi MovieApi { get; }
@ -154,6 +163,7 @@ namespace PlexRequests.UI.Modules
private IRepository<UsersToNotify> UsersToNotifyRepo { get; } private IRepository<UsersToNotify> UsersToNotifyRepo { get; }
private IIssueService IssueService { get; } private IIssueService IssueService { get; }
private IAnalytics Analytics { get; } private IAnalytics Analytics { get; }
private ITransientFaultQueue FaultQueue { get; }
private IRepository<RequestLimit> RequestLimitRepo { get; } private IRepository<RequestLimit> RequestLimitRepo { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
@ -167,19 +177,22 @@ namespace PlexRequests.UI.Modules
private async Task<Response> UpcomingMovies() private async Task<Response> 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); return await ProcessMovies(MovieSearchType.Upcoming, string.Empty);
} }
private async Task<Response> CurrentlyPlayingMovies() private async Task<Response> 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); return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty);
} }
private async Task<Response> SearchMovie(string searchTerm) private async Task<Response> 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); return await ProcessMovies(MovieSearchType.Search, searchTerm);
} }
@ -239,7 +252,8 @@ namespace PlexRequests.UI.Modules
var imdbId = string.Empty; var imdbId = string.Empty;
if (counter <= 5) // Let's only do it for the first 5 items 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... 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 // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2
imdbId = movieInfoTask.ImdbId; imdbId = movieInfoTask.ImdbId;
counter++; counter++;
@ -263,7 +277,8 @@ namespace PlexRequests.UI.Modules
VoteCount = movie.VoteCount VoteCount = movie.VoteCount
}; };
var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); 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) if (plexMovie != null)
{ {
viewMovie.Available = true; viewMovie.Available = true;
@ -288,7 +303,8 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(viewMovies); return Response.AsJson(viewMovies);
} }
private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary<int, RequestedModel> moviesInDb) private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests,
Dictionary<int, RequestedModel> moviesInDb)
{ {
if (usersCanViewOnlyOwnRequests) if (usersCanViewOnlyOwnRequests)
{ {
@ -302,7 +318,8 @@ namespace PlexRequests.UI.Modules
private async Task<Response> SearchTvShow(string searchTerm) private async Task<Response> 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 plexSettings = await PlexService.GetSettingsAsync();
var prSettings = await PrService.GetSettingsAsync(); var prSettings = await PrService.GetSettingsAsync();
var providerId = string.Empty; var providerId = string.Empty;
@ -360,7 +377,8 @@ namespace PlexRequests.UI.Modules
providerId = viewT.Id.ToString(); 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) if (plexShow != null)
{ {
viewT.Available = true; viewT.Available = true;
@ -377,7 +395,8 @@ namespace PlexRequests.UI.Modules
viewT.Episodes = dbt.Episodes.ToList(); viewT.Episodes = dbt.Episodes.ToList();
viewT.Approved = dbt.Approved; 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; viewT.Requested = true;
} }
@ -391,7 +410,8 @@ namespace PlexRequests.UI.Modules
private async Task<Response> SearchAlbum(string searchTerm) private async Task<Response> 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<Release>(); var apiAlbums = new List<Release>();
await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) =>
{ {
@ -448,7 +468,7 @@ namespace PlexRequests.UI.Modules
if (Security.DoesNotHavePermissions(Permissions.ReadOnlyUser, User)) if (Security.DoesNotHavePermissions(Permissions.ReadOnlyUser, User))
{ {
return return
Response.AsJson(new JsonResponseModel() Response.AsJson(new JsonResponseModel
{ {
Result = false, Result = false,
Message = "Sorry, you do not have the correct permissions to request a movie!" 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(); var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.Movie)) 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 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); var existingRequest = await RequestService.CheckRequestAsync(movieId);
if (existingRequest != null) if (existingRequest != null)
@ -474,7 +501,15 @@ namespace PlexRequests.UI.Modules
await RequestService.UpdateRequestAsync(existingRequest); 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 try
@ -482,13 +517,23 @@ namespace PlexRequests.UI.Modules
var movies = Checker.GetPlexMovies(); var movies = Checker.GetPlexMovies();
if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) 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) catch (Exception e)
{ {
Log.Error(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 //#endif
@ -508,7 +553,8 @@ namespace PlexRequests.UI.Modules
Issues = IssueState.None, Issues = IssueState.None,
}; };
try
{
if (ShouldAutoApprove(RequestType.Movie, settings)) if (ShouldAutoApprove(RequestType.Movie, settings))
{ {
var cpSettings = await CpService.GetSettingsAsync(); var cpSettings = await CpService.GetSettingsAsync();
@ -521,7 +567,10 @@ namespace PlexRequests.UI.Modules
Log.Debug("Adding movie to CP result {0}", result); Log.Debug("Adding movie to CP result {0}", result);
if (result) if (result)
{ {
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); return
await
AddRequest(model, settings,
$"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
} }
return Response.AsJson(new JsonResponseModel return Response.AsJson(new JsonResponseModel
@ -534,15 +583,28 @@ namespace PlexRequests.UI.Modules
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
} }
try
{
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
} }
catch (Exception e) catch (Exception e)
{ {
Log.Fatal(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(); var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.TvShow)) 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(); var sonarrSettings = SonarrService.GetSettingsAsync();
@ -590,7 +658,13 @@ namespace PlexRequests.UI.Modules
var s = await sonarrSettings; var s = await sonarrSettings;
if (!s.Enabled) 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); DateTime.TryParse(showInfo.premiered, out firstAir);
string fullShowName = $"{showInfo.name} ({firstAir.Year})"; 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 var model = new RequestedModel
{ {
ProviderId = showInfo.externals?.thetvdb ?? 0,
Type = RequestType.TvShow, Type = RequestType.TvShow,
Overview = showInfo.summary.RemoveHtml(), Overview = showInfo.summary.RemoveHtml(),
PosterPath = showInfo.image?.medium, PosterPath = showInfo.image?.medium,
@ -622,6 +690,25 @@ namespace PlexRequests.UI.Modules
TvDbId = showId.ToString() 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<int>(); var seasonsList = new List<int>();
switch (seasons) switch (seasons)
@ -642,9 +729,14 @@ namespace PlexRequests.UI.Modules
foreach (var ep in episodeModel?.Episodes ?? new Models.EpisodesModel[0]) 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; break;
default: default:
model.SeasonsRequested = seasons; model.SeasonsRequested = seasons;
@ -691,7 +783,12 @@ namespace PlexRequests.UI.Modules
else else
{ {
// We no episodes to approve // 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()) else if (model.SeasonList.Except(existingRequest.SeasonList).Any())
@ -720,9 +817,19 @@ namespace PlexRequests.UI.Modules
var cachedEpisodes = cachedEpisodesTask.ToList(); var cachedEpisodes = cachedEpisodesTask.ToList();
foreach (var d in difference) // difference is from an existing request 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,22 +845,39 @@ namespace PlexRequests.UI.Modules
var result = Checker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, s.EpisodeNumber); var result = Checker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, s.EpisodeNumber);
if (result) if (result)
{ {
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); return
} Response.AsJson(new JsonResponseModel
}
}
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}" }); Result = false,
Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}"
});
}
}
}
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}"
});
} }
} }
} }
catch (Exception) 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)
});
} }
try
{
if (ShouldAutoApprove(RequestType.TvShow, settings)) if (ShouldAutoApprove(RequestType.TvShow, settings))
{ {
model.Approved = true; model.Approved = true;
@ -769,10 +893,14 @@ namespace PlexRequests.UI.Modules
return await UpdateRequest(model, settings, return await UpdateRequest(model, settings,
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); $"{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."); Log.Debug("Error with sending to sonarr.");
return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages ?? new List<string>())); return
Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages ?? new List<string>()));
} }
var srSettings = SickRageService.GetSettings(); var srSettings = SickRageService.GetSettings();
@ -781,23 +909,54 @@ namespace PlexRequests.UI.Modules
var result = sender.SendToSickRage(srSettings, model); var result = sender.SendToSickRage(srSettings, model);
if (result?.result == "success") if (result?.result == "success")
{ {
return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); 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) if (!srSettings.Enabled && !s.Enabled)
{ {
return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); return
await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}");
} }
return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp }); return
Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp });
} }
return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); 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}"
});
}
}
private async Task<Response> AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, string fullShowName, bool episodeReq = false) private async Task<Response> 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 // check if the current user is already marked as a requester for this show, if not, add them
if (!existingRequest.UserHasRequested(Username)) if (!existingRequest.UserHasRequested(Username))
@ -812,12 +971,15 @@ namespace PlexRequests.UI.Modules
$"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); $"{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) 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; var claims = Context.CurrentUser?.Claims;
if (claims != null) if (claims != null)
{ {
@ -836,9 +998,15 @@ namespace PlexRequests.UI.Modules
var settings = await PrService.GetSettingsAsync(); var settings = await PrService.GetSettingsAsync();
if (!await CheckRequestLimit(settings, RequestType.Album)) 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); var existingRequest = await RequestService.CheckRequestAsync(releaseId);
if (existingRequest != null) if (existingRequest != null)
@ -848,7 +1016,15 @@ namespace PlexRequests.UI.Modules
existingRequest.RequestedUsers.Add(Username); existingRequest.RequestedUsers.Add(Username);
await RequestService.UpdateRequestAsync(existingRequest); 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); var albumInfo = MusicBrainzApi.GetAlbum(releaseId);
@ -858,11 +1034,17 @@ namespace PlexRequests.UI.Modules
var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist;
if (artist == null) 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 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) if (alreadyInPlex)
{ {
@ -892,6 +1074,8 @@ namespace PlexRequests.UI.Modules
ArtistId = artist.id ArtistId = artist.id
}; };
try
{
if (ShouldAutoApprove(RequestType.Album, settings)) if (ShouldAutoApprove(RequestType.Album, settings))
{ {
model.Approved = true; model.Approved = true;
@ -915,6 +1099,22 @@ namespace PlexRequests.UI.Modules
return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); 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);
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) private string GetMusicBrainzCoverArt(string id)
{ {

View file

@ -25,7 +25,7 @@
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using Ninject.Modules; using Ninject.Modules;
using PlexRequests.Core.Queue;
using PlexRequests.Helpers.Analytics; using PlexRequests.Helpers.Analytics;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Jobs; using PlexRequests.Services.Jobs;
@ -51,6 +51,8 @@ namespace PlexRequests.UI.NinjectModules
Bind<IAnalytics>().To<Analytics>(); Bind<IAnalytics>().To<Analytics>();
Bind<ISchedulerFactory>().To<StdSchedulerFactory>(); Bind<ISchedulerFactory>().To<StdSchedulerFactory>();
Bind<IJobScheduler>().To<Scheduler>(); Bind<IJobScheduler>().To<Scheduler>();
Bind<ITransientFaultQueue>().To<TransientFaultQueue>();
} }
} }
} }