From 5b90fa9089a5743110710c317cead6154d59fe7b Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 22 Mar 2016 15:36:21 +0000 Subject: [PATCH 01/94] Lowercase logs folder, because you know, linux. #59 --- PlexRequests.UI/NLog.config | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/PlexRequests.UI/NLog.config b/PlexRequests.UI/NLog.config index 4fb8be6cb..4810bdc88 100644 --- a/PlexRequests.UI/NLog.config +++ b/PlexRequests.UI/NLog.config @@ -7,23 +7,27 @@ internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log" > - - - - - - - + + + + \ No newline at end of file From 0585ff73ecd88b0eb1fd43d812e58b5f40a18592 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 22 Mar 2016 17:13:14 +0000 Subject: [PATCH 02/94] Added a notification model to the notifiers. Added the backend work for sending a notification for an issue report #75 --- .../NotificationServiceTests.cs | 10 +- .../Notification/EmailMessageNotification.cs | 99 ++++++++++++++----- .../Notification/INotification.cs | 5 +- .../Notification/NotificationModel.cs | 39 ++++++++ .../Notification/NotificationService.cs | 4 +- .../Notification/NotificationType.cs | 37 +++++++ .../Notification/PushbulletNotification.cs | 68 ++++++++++--- .../PlexRequests.Services.csproj | 2 + PlexRequests.UI/Modules/SearchModule.cs | 15 +-- PlexRequests.UI/NLog.config | 15 +-- PlexRequests.UI/Program.cs | 3 +- 11 files changed, 238 insertions(+), 59 deletions(-) create mode 100644 PlexRequests.Services/Notification/NotificationModel.cs create mode 100644 PlexRequests.Services/Notification/NotificationType.cs diff --git a/PlexRequests.Services.Tests/NotificationServiceTests.cs b/PlexRequests.Services.Tests/NotificationServiceTests.cs index ffbd75bfe..877ddcbbf 100644 --- a/PlexRequests.Services.Tests/NotificationServiceTests.cs +++ b/PlexRequests.Services.Tests/NotificationServiceTests.cs @@ -97,7 +97,7 @@ namespace PlexRequests.Services.Tests { Assert.DoesNotThrow( () => - { NotificationService.Publish(string.Empty, string.Empty); }); + { NotificationService.Publish(new NotificationModel()); }); } [Test] @@ -112,11 +112,11 @@ namespace PlexRequests.Services.Tests NotificationService.Subscribe(notificationMock2.Object); Assert.That(NotificationService.Observers.Count, Is.EqualTo(2)); + var model = new NotificationModel {Title = "abc", Body = "test"}; + NotificationService.Publish(model); - NotificationService.Publish("a","b"); - - notificationMock1.Verify(x => x.Notify("a","b"), Times.Once); - notificationMock2.Verify(x => x.Notify("a","b"), Times.Once); + notificationMock1.Verify(x => x.Notify(model), Times.Once); + notificationMock2.Verify(x => x.Notify(model), Times.Once); } } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 2c5c84158..43d567cd8 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -42,10 +42,12 @@ namespace PlexRequests.Services.Notification EmailNotificationSettings = settings; } - private static Logger Log = LogManager.GetCurrentClassLogger(); + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); private ISettingsService EmailNotificationSettings { get; } + private EmailNotificationSettings Settings => GetConfiguration(); public string NotificationName => "EmailMessageNotification"; - public bool Notify(string title, string requester) + + public bool Notify(NotificationModel model) { var configuration = GetConfiguration(); if (!ValidateConfiguration(configuration)) @@ -53,21 +55,62 @@ namespace PlexRequests.Services.Notification return false; } + switch (model.NotificationType) + { + case NotificationType.NewRequest: + return EmailNewRequest(model); + case NotificationType.Issue: + return EmailIssue(model); + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return false; + } + + private EmailNotificationSettings GetConfiguration() + { + var settings = EmailNotificationSettings.GetSettings(); + return settings; + } + + private bool ValidateConfiguration(EmailNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + { + return false; + } + + return true; + } + + private bool EmailNewRequest(NotificationModel model) + { var message = new MailMessage { IsBodyHtml = true, - To = { new MailAddress(configuration.RecipientEmail) }, - Body = $"User {requester} has requested {title}!", - From = new MailAddress(configuration.EmailUsername), - Subject = $"New Request for {title}!" + To = { new MailAddress(Settings.RecipientEmail) }, + Body = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}", + From = new MailAddress(Settings.EmailUsername), + Subject = $"Plex Requests: New request for {model.Title}!" }; try { - using (var smtp = new SmtpClient(configuration.EmailHost, configuration.EmailPort)) + using (var smtp = new SmtpClient(Settings.EmailHost, Settings.EmailPort)) { - smtp.Credentials = new NetworkCredential(configuration.EmailUsername, configuration.EmailPassword); - smtp.EnableSsl = configuration.Ssl; + smtp.Credentials = new NetworkCredential(Settings.EmailUsername, Settings.EmailPassword); + smtp.EnableSsl = Settings.Ssl; smtp.Send(message); return true; } @@ -83,26 +126,36 @@ namespace PlexRequests.Services.Notification return false; } - private EmailNotificationSettings GetConfiguration() + private bool EmailIssue(NotificationModel model) { - var settings = EmailNotificationSettings.GetSettings(); - return settings; - } + var message = new MailMessage + { + IsBodyHtml = true, + To = { new MailAddress(Settings.RecipientEmail) }, + Body = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", + From = new MailAddress(Settings.EmailUsername), + Subject = $"Plex Requests: New issue for {model.Title}!" + }; - private bool ValidateConfiguration(EmailNotificationSettings settings) - { - if (!settings.Enabled) + try { - return false; + using (var smtp = new SmtpClient(Settings.EmailHost, Settings.EmailPort)) + { + smtp.Credentials = new NetworkCredential(Settings.EmailUsername, Settings.EmailPassword); + smtp.EnableSsl = Settings.Ssl; + smtp.Send(message); + return true; + } } - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) - || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) - || string.IsNullOrEmpty(settings.EmailPort.ToString())) + catch (SmtpException smtp) { - return false; + Log.Fatal(smtp); } - - return true; + catch (Exception e) + { + Log.Fatal(e); + } + return false; } } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/INotification.cs b/PlexRequests.Services/Notification/INotification.cs index 6b85bd178..dd099747d 100644 --- a/PlexRequests.Services/Notification/INotification.cs +++ b/PlexRequests.Services/Notification/INotification.cs @@ -38,9 +38,8 @@ namespace PlexRequests.Services.Notification /// /// Notifies the specified title. /// - /// The title. - /// The requester. + /// The model. /// - bool Notify(string title, string requester); + bool Notify(NotificationModel model); } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/NotificationModel.cs b/PlexRequests.Services/Notification/NotificationModel.cs new file mode 100644 index 000000000..264d3e609 --- /dev/null +++ b/PlexRequests.Services/Notification/NotificationModel.cs @@ -0,0 +1,39 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: NotificationModel.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; + +namespace PlexRequests.Services.Notification +{ + public class NotificationModel + { + public string Title { get; set; } + public string Body { get; set; } + public DateTime DateTime { get; set; } + public NotificationType NotificationType { get; set; } + public string User { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Notification/NotificationService.cs b/PlexRequests.Services/Notification/NotificationService.cs index 81968c28d..817dcd2d9 100644 --- a/PlexRequests.Services/Notification/NotificationService.cs +++ b/PlexRequests.Services/Notification/NotificationService.cs @@ -44,7 +44,7 @@ namespace PlexRequests.Services.Notification Observers = new Dictionary(); } - public static void Publish(string title, string requester) + public static void Publish(NotificationModel model) { Log.Trace("Notifying all observers: "); Log.Trace(Observers.DumpJson()); @@ -55,7 +55,7 @@ namespace PlexRequests.Services.Notification new Thread(() => { Thread.CurrentThread.IsBackground = true; - notification.Notify(title, requester); + notification.Notify(model); }).Start(); } } diff --git a/PlexRequests.Services/Notification/NotificationType.cs b/PlexRequests.Services/Notification/NotificationType.cs new file mode 100644 index 000000000..bf919fe39 --- /dev/null +++ b/PlexRequests.Services/Notification/NotificationType.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: NotificationType.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +namespace PlexRequests.Services.Notification +{ + public enum NotificationType + { + NewRequest, + Issue, + RequestAvailable, + RequestApproved, + AdminNote, + } +} diff --git a/PlexRequests.Services/Notification/PushbulletNotification.cs b/PlexRequests.Services/Notification/PushbulletNotification.cs index 5000919f7..be6af1ef2 100644 --- a/PlexRequests.Services/Notification/PushbulletNotification.cs +++ b/PlexRequests.Services/Notification/PushbulletNotification.cs @@ -39,27 +39,59 @@ namespace PlexRequests.Services.Notification public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService settings) { PushbulletApi = pushbulletApi; - Settings = settings; + SettingsService = settings; } private IPushbulletApi PushbulletApi { get; } - private ISettingsService Settings { get; } + private ISettingsService SettingsService { get; } + private PushbulletNotificationSettings Settings => GetSettings(); private static Logger Log = LogManager.GetCurrentClassLogger(); public string NotificationName => "PushbulletNotification"; - public bool Notify(string title, string requester) + public bool Notify(NotificationModel model) { - var settings = GetSettings(); - - if (!settings.Enabled) + if (!ValidateConfiguration()) { return false; } - var message = $"{title} has been requested by {requester}"; - var pushTitle = $"Plex Requests: {title}"; + switch (model.NotificationType) + { + case NotificationType.NewRequest: + return PushNewRequest(model); + + case NotificationType.Issue: + return PushIssue(model); + + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + default: + throw new ArgumentOutOfRangeException(); + } + return false; + + } + + private bool ValidateConfiguration() + { + return !Settings.Enabled && !string.IsNullOrEmpty(Settings.AccessToken); + } + + private PushbulletNotificationSettings GetSettings() + { + return SettingsService.GetSettings(); + } + + private bool PushNewRequest(NotificationModel model) + { + var message = $"{model.Title} has been requested by user: {model.User}"; + var pushTitle = $"Plex Requests: {model.Title} has been requested!"; try { - var result = PushbulletApi.Push(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier); + var result = PushbulletApi.Push(Settings.AccessToken, pushTitle, message, Settings.DeviceIdentifier); if (result != null) { return true; @@ -72,9 +104,23 @@ namespace PlexRequests.Services.Notification return false; } - private PushbulletNotificationSettings GetSettings() + private bool PushIssue(NotificationModel model) { - return Settings.GetSettings(); + var message = $"A new issue: {model.Title} has been reported by user: {model.User} for the title: {model.Body}"; + var pushTitle = $"Plex Requests: A new issue has been reported for {model.Body}"; + try + { + var result = PushbulletApi.Push(Settings.AccessToken, pushTitle, message, Settings.DeviceIdentifier); + if (result != null) + { + return true; + } + } + catch (Exception e) + { + Log.Fatal(e); + } + return false; } } } \ No newline at end of file diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj index 29ad390d5..0317cf36b 100644 --- a/PlexRequests.Services/PlexRequests.Services.csproj +++ b/PlexRequests.Services/PlexRequests.Services.csproj @@ -79,7 +79,9 @@ + + diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 7d1d0dcab..5fce72960 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -182,7 +182,7 @@ namespace PlexRequests.UI.Modules var movieInfo = movieApi.GetMovieInformation(movieId).Result; Log.Trace("Getting movie info from TheMovieDb"); Log.Trace(movieInfo.DumpJson); -//#if !DEBUG + //#if !DEBUG try { if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) @@ -194,7 +194,7 @@ namespace PlexRequests.UI.Modules { return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {movieInfo.Title} is in Plex, are you sure it's correctly setup?" }); } -//#endif + //#endif var model = new RequestedModel { @@ -241,7 +241,8 @@ namespace PlexRequests.UI.Modules Log.Debug("Adding movie to database requests"); var id = RequestService.AddRequest(model); - NotificationService.Publish(model.Title, model.RequestedBy); + var notificationModel = new NotificationModel { Title = model.Title, User = model.RequestedBy, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + NotificationService.Publish(notificationModel); return Response.AsJson(new JsonResponseModel { Result = true }); } @@ -269,7 +270,7 @@ namespace PlexRequests.UI.Modules var tvApi = new TvMazeApi(); var showInfo = tvApi.ShowLookupByTheTvDbId(showId); -//#if !DEBUG + //#if !DEBUG try { if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01 @@ -281,7 +282,7 @@ namespace PlexRequests.UI.Modules { return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {showInfo.name} is in Plex, are you sure it's correctly setup?" }); } -//#endif + //#endif DateTime firstAir; DateTime.TryParse(showInfo.premiered, out firstAir); @@ -344,7 +345,9 @@ namespace PlexRequests.UI.Modules } RequestService.AddRequest(model); - NotificationService.Publish(model.Title, model.RequestedBy); + + var notificationModel = new NotificationModel { Title = model.Title, User = model.RequestedBy, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + NotificationService.Publish(notificationModel); return Response.AsJson(new { Result = true }); } diff --git a/PlexRequests.UI/NLog.config b/PlexRequests.UI/NLog.config index 4810bdc88..39605a1f7 100644 --- a/PlexRequests.UI/NLog.config +++ b/PlexRequests.UI/NLog.config @@ -13,21 +13,22 @@ layout="${date} ${logger} ${level}: ${message}" /> - + + - + \ No newline at end of file diff --git a/PlexRequests.UI/Program.cs b/PlexRequests.UI/Program.cs index a16a5c393..80f0adebb 100644 --- a/PlexRequests.UI/Program.cs +++ b/PlexRequests.UI/Program.cs @@ -61,7 +61,6 @@ namespace PlexRequests.UI } port = portResult; } - Log.Trace("Getting product version"); WriteOutVersion(); @@ -126,7 +125,7 @@ namespace PlexRequests.UI { CommandType = CommandType.Text, ConnectionString = connectionString, - DBProvider = "Mono.Data.Sqlite, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756", + DBProvider = "Mono.Data.Sqlite.SqliteConnection, Mono.Data.Sqlite, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756", Name = "database" }; From f64ccd73272f44cee9fb2ac08f5a9f47cefdb4b9 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 22 Mar 2016 21:31:52 +0000 Subject: [PATCH 03/94] Resolved #75 --- .../Notification/PushbulletNotification.cs | 14 +++++++++++--- PlexRequests.UI/Modules/RequestsModule.cs | 13 +++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/PlexRequests.Services/Notification/PushbulletNotification.cs b/PlexRequests.Services/Notification/PushbulletNotification.cs index be6af1ef2..20c4b9f35 100644 --- a/PlexRequests.Services/Notification/PushbulletNotification.cs +++ b/PlexRequests.Services/Notification/PushbulletNotification.cs @@ -77,7 +77,15 @@ namespace PlexRequests.Services.Notification private bool ValidateConfiguration() { - return !Settings.Enabled && !string.IsNullOrEmpty(Settings.AccessToken); + if (!Settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(Settings.AccessToken)) + { + return false; + } + return true; } private PushbulletNotificationSettings GetSettings() @@ -106,8 +114,8 @@ namespace PlexRequests.Services.Notification private bool PushIssue(NotificationModel model) { - var message = $"A new issue: {model.Title} has been reported by user: {model.User} for the title: {model.Body}"; - var pushTitle = $"Plex Requests: A new issue has been reported for {model.Body}"; + var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}"; try { var result = PushbulletApi.Push(Settings.AccessToken, pushTitle, message, Settings.DeviceIdentifier); diff --git a/PlexRequests.UI/Modules/RequestsModule.cs b/PlexRequests.UI/Modules/RequestsModule.cs index 4f534e0b9..228a0d752 100644 --- a/PlexRequests.UI/Modules/RequestsModule.cs +++ b/PlexRequests.UI/Modules/RequestsModule.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System; using System.Collections.Generic; using System.Linq; @@ -37,6 +38,7 @@ using Nancy.Security; using PlexRequests.Api; using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Notification; using PlexRequests.Store; using PlexRequests.UI.Models; @@ -166,6 +168,17 @@ namespace PlexRequests.UI.Modules var result = Service.UpdateRequest(originalRequest); + + var model = new NotificationModel + { + User = Session[SessionKeys.UsernameKey].ToString(), + NotificationType = NotificationType.Issue, + Title = originalRequest.Title, + DateTime = DateTime.Now, + Body = issue == IssueState.Other ? comment : issue.Humanize() + }; + NotificationService.Publish(model); + return Response.AsJson(result ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); From 2d5612a045a62dca1670da45e51aabf7623bf5ec Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 22 Mar 2016 22:17:06 +0000 Subject: [PATCH 04/94] Finished adding pushover support. #44 --- PlexRequests.Api.Interfaces/IPushoverApi.cs | 36 +++++ .../PlexRequests.Api.Interfaces.csproj | 1 + .../Notifications/PushoverResponse.cs | 34 +++++ .../PlexRequests.Api.Models.csproj | 1 + PlexRequests.Api/PlexRequests.Api.csproj | 1 + PlexRequests.Api/PushoverApi.cs | 56 ++++++++ PlexRequests.Core/PlexRequests.Core.csproj | 1 + .../PushoverNotificationSettings.cs | 9 ++ .../Notification/PushoverNotification.cs | 132 ++++++++++++++++++ .../PlexRequests.Services.csproj | 1 + PlexRequests.UI.Tests/AdminModuleTests.cs | 6 + PlexRequests.UI/Bootstrapper.cs | 11 +- PlexRequests.UI/Modules/AdminModule.cs | 45 +++++- PlexRequests.UI/PlexRequests.UI.csproj | 4 + .../Validators/PushoverSettingsValidator.cs | 41 ++++++ .../Views/Admin/PushoverNotifications.cshtml | 74 ++++++++++ PlexRequests.UI/Views/Admin/_Sidebar.cshtml | 9 ++ 17 files changed, 459 insertions(+), 3 deletions(-) create mode 100644 PlexRequests.Api.Interfaces/IPushoverApi.cs create mode 100644 PlexRequests.Api.Models/Notifications/PushoverResponse.cs create mode 100644 PlexRequests.Api/PushoverApi.cs create mode 100644 PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs create mode 100644 PlexRequests.Services/Notification/PushoverNotification.cs create mode 100644 PlexRequests.UI/Validators/PushoverSettingsValidator.cs create mode 100644 PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml diff --git a/PlexRequests.Api.Interfaces/IPushoverApi.cs b/PlexRequests.Api.Interfaces/IPushoverApi.cs new file mode 100644 index 000000000..42e3f2217 --- /dev/null +++ b/PlexRequests.Api.Interfaces/IPushoverApi.cs @@ -0,0 +1,36 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IPushoverApi.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using PlexRequests.Api.Models.Notifications; + +namespace PlexRequests.Api.Interfaces +{ + public interface IPushoverApi + { + PushoverResponse Push(string accessToken, string message, string userToken); + } +} \ No newline at end of file diff --git a/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj b/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj index 3ea7be621..7522c8156 100644 --- a/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj +++ b/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj @@ -49,6 +49,7 @@ + diff --git a/PlexRequests.Api.Models/Notifications/PushoverResponse.cs b/PlexRequests.Api.Models/Notifications/PushoverResponse.cs new file mode 100644 index 000000000..94849fba1 --- /dev/null +++ b/PlexRequests.Api.Models/Notifications/PushoverResponse.cs @@ -0,0 +1,34 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushoverResponse.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +namespace PlexRequests.Api.Models.Notifications +{ + public class PushoverResponse + { + public int status { get; set; } + public string request { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj index bd3151c55..8a4da4222 100644 --- a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj +++ b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj @@ -50,6 +50,7 @@ + diff --git a/PlexRequests.Api/PlexRequests.Api.csproj b/PlexRequests.Api/PlexRequests.Api.csproj index 2f204c975..6422dfd6f 100644 --- a/PlexRequests.Api/PlexRequests.Api.csproj +++ b/PlexRequests.Api/PlexRequests.Api.csproj @@ -72,6 +72,7 @@ MockApiData.resx + diff --git a/PlexRequests.Api/PushoverApi.cs b/PlexRequests.Api/PushoverApi.cs new file mode 100644 index 000000000..1de5694aa --- /dev/null +++ b/PlexRequests.Api/PushoverApi.cs @@ -0,0 +1,56 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexApi.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using Nancy.Helpers; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Notifications; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class PushoverApi : IPushoverApi + { + public PushoverResponse Push(string accessToken, string message, string userToken) + { + var request = new RestRequest + { + Method = Method.POST, + Resource = "messages.json?token={token}&user={user}&message={message}" + }; + + request.AddUrlSegment("token", accessToken); + request.AddUrlSegment("message", message); + request.AddUrlSegment("user", userToken); + + + var api = new ApiRequest(); + return api.ExecuteJson(request, new Uri("https://api.pushover.net/1")); + } + } +} + diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index e1c4ee1f3..9b698ab96 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -73,6 +73,7 @@ + diff --git a/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs b/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs new file mode 100644 index 000000000..ac6c4c435 --- /dev/null +++ b/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs @@ -0,0 +1,9 @@ +namespace PlexRequests.Core.SettingModels +{ + public class PushoverNotificationSettings : Settings + { + public bool Enabled { get; set; } + public string AccessToken { get; set; } + public string UserToken { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Notification/PushoverNotification.cs b/PlexRequests.Services/Notification/PushoverNotification.cs new file mode 100644 index 000000000..2c3bf6bfe --- /dev/null +++ b/PlexRequests.Services/Notification/PushoverNotification.cs @@ -0,0 +1,132 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushbulletNotification.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Services.Notification +{ + public class PushoverNotification : INotification + { + public PushoverNotification(IPushoverApi pushoverApi, ISettingsService settings) + { + PushoverApi = pushoverApi; + SettingsService = settings; + } + private IPushoverApi PushoverApi { get; } + private ISettingsService SettingsService { get; } + private PushoverNotificationSettings Settings => GetSettings(); + + private static Logger Log = LogManager.GetCurrentClassLogger(); + public string NotificationName => "PushoverNotification"; + public bool Notify(NotificationModel model) + { + if (!ValidateConfiguration()) + { + return false; + } + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + return PushNewRequest(model); + + case NotificationType.Issue: + return PushIssue(model); + + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + default: + throw new ArgumentOutOfRangeException(); + } + return false; + + } + + private bool ValidateConfiguration() + { + if (!Settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(Settings.AccessToken) || string.IsNullOrEmpty(Settings.UserToken)) + { + return false; + } + return true; + } + + private PushoverNotificationSettings GetSettings() + { + return SettingsService.GetSettings(); + } + + private bool PushNewRequest(NotificationModel model) + { + var message = $"Plex Requests: {model.Title} has been requested by user: {model.User}"; + try + { + var result = PushoverApi.Push(Settings.AccessToken, message, Settings.UserToken); + if (result?.status == 1) + { + return true; + } + } + catch (Exception e) + { + Log.Fatal(e); + } + return false; + } + + private bool PushIssue(NotificationModel model) + { + var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + try + { + var result = PushoverApi.Push(Settings.AccessToken, message, Settings.UserToken); + if (result != null) + { + return true; + } + } + catch (Exception e) + { + Log.Fatal(e); + } + return false; + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj index 0317cf36b..e1ba81532 100644 --- a/PlexRequests.Services/PlexRequests.Services.csproj +++ b/PlexRequests.Services/PlexRequests.Services.csproj @@ -82,6 +82,7 @@ + diff --git a/PlexRequests.UI.Tests/AdminModuleTests.cs b/PlexRequests.UI.Tests/AdminModuleTests.cs index e42eb4a13..08c84235b 100644 --- a/PlexRequests.UI.Tests/AdminModuleTests.cs +++ b/PlexRequests.UI.Tests/AdminModuleTests.cs @@ -55,9 +55,11 @@ namespace PlexRequests.UI.Tests private Mock> SickRageSettingsMock { get; set; } private Mock> EmailMock { get; set; } private Mock> PushbulletSettings { get; set; } + private Mock> PushoverSettings { get; set; } private Mock PlexMock { get; set; } private Mock SonarrApiMock { get; set; } private Mock PushbulletApi { get; set; } + private Mock PushoverApi { get; set; } private Mock CpApi { get; set; } private ConfigurableBootstrapper Bootstrapper { get; set; } @@ -83,6 +85,8 @@ namespace PlexRequests.UI.Tests PushbulletSettings = new Mock>(); CpApi = new Mock(); SickRageSettingsMock = new Mock>(); + PushoverSettings = new Mock>(); + PushoverApi = new Mock(); Bootstrapper = new ConfigurableBootstrapper(with => { @@ -99,6 +103,8 @@ namespace PlexRequests.UI.Tests with.Dependency(PushbulletSettings.Object); with.Dependency(CpApi.Object); with.Dependency(SickRageSettingsMock.Object); + with.Dependency(PushoverSettings.Object); + with.Dependency(PushoverApi.Object); with.RootPathProvider(); with.RequestStartup((container, pipelines, context) => { diff --git a/PlexRequests.UI/Bootstrapper.cs b/PlexRequests.UI/Bootstrapper.cs index 8fd5aad2b..9c07266e9 100644 --- a/PlexRequests.UI/Bootstrapper.cs +++ b/PlexRequests.UI/Bootstrapper.cs @@ -74,6 +74,7 @@ namespace PlexRequests.UI container.Register, SettingsServiceV2>(); container.Register, SettingsServiceV2>(); container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); // Repo's container.Register, GenericRepository>(); @@ -88,6 +89,7 @@ namespace PlexRequests.UI // Api's container.Register(); container.Register(); + container.Register(); container.Register(); container.Register(); container.Register(); @@ -137,7 +139,14 @@ namespace PlexRequests.UI var pushbulletSettings = pushbulletService.GetSettings(); if (pushbulletSettings.Enabled) { - NotificationService.Subscribe(new PushbulletNotification(container.Resolve(), container.Resolve>())); + NotificationService.Subscribe(new PushbulletNotification(container.Resolve(), pushbulletService)); + } + + var pushoverService = container.Resolve>(); + var pushoverSettings = pushoverService.GetSettings(); + if (pushoverSettings.Enabled) + { + NotificationService.Subscribe(new PushoverNotification(container.Resolve(), pushoverService)); } } } diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 7876aa41c..d97d06129 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -59,9 +59,11 @@ namespace PlexRequests.UI.Modules private ISettingsService SickRageService { get; } private ISettingsService EmailService { get; } private ISettingsService PushbulletService { get; } + private ISettingsService PushoverService { get; } private IPlexApi PlexApi { get; } private ISonarrApi SonarrApi { get; } - private PushbulletApi PushbulletApi { get; } + private IPushbulletApi PushbulletApi { get; } + private IPushoverApi PushoverApi { get; } private ICouchPotatoApi CpApi { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -76,7 +78,9 @@ namespace PlexRequests.UI.Modules IPlexApi plexApi, ISettingsService pbSettings, PushbulletApi pbApi, - ICouchPotatoApi cpApi) : base("admin") + ICouchPotatoApi cpApi, + ISettingsService pushoverSettings, + IPushoverApi pushoverApi) : base("admin") { RpService = rpService; CpService = cpService; @@ -90,6 +94,8 @@ namespace PlexRequests.UI.Modules PushbulletApi = pbApi; CpApi = cpApi; SickRageService = sickrage; + PushoverService = pushoverSettings; + PushoverApi = pushoverApi; #if !DEBUG this.RequiresAuthentication(); @@ -126,6 +132,9 @@ namespace PlexRequests.UI.Modules Get["/pushbulletnotification"] = _ => PushbulletNotifications(); Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); + + Get["/pushovernotification"] = _ => PushoverNotifications(); + Post["/pushovernotification"] = _ => SavePushoverNotifications(); } private Negotiator Authentication() @@ -415,6 +424,38 @@ namespace PlexRequests.UI.Modules : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } + private Negotiator PushoverNotifications() + { + var settings = PushoverService.GetSettings(); + return View["PushoverNotifications", settings]; + } + + private Response SavePushoverNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + Log.Trace(settings.DumpJson()); + + var result = PushoverService.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + else + { + NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + + Log.Info("Saved email settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushbullet Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + private Response GetCpProfiles() { var settings = this.Bind(); diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 44ec9805c..a62ab2592 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -166,6 +166,7 @@ + @@ -335,6 +336,9 @@ Always + + Always + web.config diff --git a/PlexRequests.UI/Validators/PushoverSettingsValidator.cs b/PlexRequests.UI/Validators/PushoverSettingsValidator.cs new file mode 100644 index 000000000..55a218aa0 --- /dev/null +++ b/PlexRequests.UI/Validators/PushoverSettingsValidator.cs @@ -0,0 +1,41 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrValidator.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using FluentValidation; + +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.UI.Validators +{ + public class PushoverSettingsValidator : AbstractValidator + { + public PushoverSettingsValidator() + { + RuleFor(request => request.AccessToken).NotEmpty().WithMessage("You must specify a API Token."); + RuleFor(request => request.UserToken).NotEmpty().WithMessage("You must specify a User Token."); + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml b/PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml new file mode 100644 index 000000000..0877739d0 --- /dev/null +++ b/PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml @@ -0,0 +1,74 @@ +@Html.Partial("_Sidebar") + +
+
+
+ Pushover Notifications + +
+
+ +
+
+ +
+ + Enter your API Key from Pushover. +
+ +
+
+ +
+ + Your user or group key from Pushover. +
+ +
+
+ +
+
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/_Sidebar.cshtml b/PlexRequests.UI/Views/Admin/_Sidebar.cshtml index 2877729c7..51e181921 100644 --- a/PlexRequests.UI/Views/Admin/_Sidebar.cshtml +++ b/PlexRequests.UI/Views/Admin/_Sidebar.cshtml @@ -71,6 +71,15 @@ Pushbullet Notifications } + @if (Context.Request.Path == "/admin/pushovernotification") + { + Pushover Notifications + } + else + { + Pushover Notifications + } + @if (Context.Request.Path == "/admin/status") { Status From 95fad3c33ca5bc5625e6f2766fd7427618001b9c Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 23 Mar 2016 09:49:33 +0000 Subject: [PATCH 05/94] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f6ac4c89..db7bdba52 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet) ###### A massive thanks to everyone below! -[heartisall](https://github.com/heartisall) +[heartisall](https://github.com/heartisall), Stuke00, [shiitake](https://github.com/shiitake) # Sponsors From c7ac8a7d99cde8f01cc07005e02c4015f5acec4a Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 23 Mar 2016 13:43:27 +0000 Subject: [PATCH 06/94] Done most on #59 --- PlexRequests.Core/JsonRequestService.cs | 1 + PlexRequests.Core/PlexRequests.Core.csproj | 1 - PlexRequests.Core/RequestService.cs | 84 - PlexRequests.Core/SettingsServiceV2.cs | 1 + PlexRequests.Core/Setup.cs | 2 +- PlexRequests.Core/UserMapper.cs | 4 +- PlexRequests.Helpers/LoggingHelper.cs | 57 + .../PlexRequests.Helpers.csproj | 4 + PlexRequests.Helpers/packages.config | 1 + PlexRequests.Store/Models/LogEntity.cs | 4 +- PlexRequests.Store/PlexRequests.Store.csproj | 8 +- .../{ => Repository}/GenericRepository.cs | 37 +- .../{ => Repository}/IRepository.cs | 2 +- .../{ => Repository}/IRequestRepository.cs | 2 +- .../{ => Repository}/ISettingsRepository.cs | 2 +- PlexRequests.Store/UserRepository.cs | 2 + PlexRequests.Store/UsersModel.cs | 1 + PlexRequests.UI.Tests/AdminModuleTests.cs | 5 + .../PlexRequests.UI.Tests.csproj | 4 + PlexRequests.UI/Bootstrapper.cs | 2 + PlexRequests.UI/Content/jquery.mixitup.js | 2086 +---------------- PlexRequests.UI/Modules/AdminModule.cs | 35 +- PlexRequests.UI/Modules/ApprovalModule.cs | 13 +- PlexRequests.UI/Modules/LoginModule.cs | 13 +- PlexRequests.UI/NLog.config | 6 +- PlexRequests.UI/PlexRequests.UI.csproj | 7 + PlexRequests.UI/Program.cs | 90 +- PlexRequests.UI/Views/Admin/Logs.cshtml | 118 + PlexRequests.UI/Views/Admin/_Sidebar.cshtml | 9 +- PlexRequests.UI/Views/Shared/_Layout.cshtml | 24 +- PlexRequests.UI/compilerconfig.json | 4 + PlexRequests.UI/packages.config | 1 + 32 files changed, 346 insertions(+), 2284 deletions(-) delete mode 100644 PlexRequests.Core/RequestService.cs rename PlexRequests.Store/{ => Repository}/GenericRepository.cs (77%) rename PlexRequests.Store/{ => Repository}/IRepository.cs (95%) rename PlexRequests.Store/{ => Repository}/IRequestRepository.cs (95%) rename PlexRequests.Store/{ => Repository}/ISettingsRepository.cs (95%) create mode 100644 PlexRequests.UI/Views/Admin/Logs.cshtml diff --git a/PlexRequests.Core/JsonRequestService.cs b/PlexRequests.Core/JsonRequestService.cs index 1a99569d3..533bc1c56 100644 --- a/PlexRequests.Core/JsonRequestService.cs +++ b/PlexRequests.Core/JsonRequestService.cs @@ -32,6 +32,7 @@ using Newtonsoft.Json; using PlexRequests.Store; using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; namespace PlexRequests.Core { diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index e1c4ee1f3..a7903bd4f 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -81,7 +81,6 @@ - diff --git a/PlexRequests.Core/RequestService.cs b/PlexRequests.Core/RequestService.cs deleted file mode 100644 index 7788b6436..000000000 --- a/PlexRequests.Core/RequestService.cs +++ /dev/null @@ -1,84 +0,0 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: RequestService.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using System.Collections.Generic; -using System.Linq; -using PlexRequests.Store; - -namespace PlexRequests.Core -{ - public class RequestService : IRequestService - { - public RequestService(IRepository db) - { - Repo = db; - } - - private IRepository Repo { get; set; } - - public long AddRequest(RequestedModel model) - { - return Repo.Insert(model); - } - - public bool CheckRequest(int providerId) - { - return Repo.GetAll().Any(x => x.ProviderId == providerId); - } - - public void DeleteRequest(RequestedModel model) - { - var entity = Repo.Get(model.Id); - Repo.Delete(entity); - } - - public bool UpdateRequest(RequestedModel model) - { - return Repo.Update(model); - } - - /// - /// Updates all the entities. NOTE: we need to Id to be the original entity - /// - /// The model. - /// - public bool BatchUpdate(List model) - { - return Repo.UpdateAll(model); - } - - public RequestedModel Get(int id) - { - return Repo.Get(id); - } - - public IEnumerable GetAll() - { - return Repo.GetAll(); - } - } -} diff --git a/PlexRequests.Core/SettingsServiceV2.cs b/PlexRequests.Core/SettingsServiceV2.cs index 067b3b44b..3fb53cd35 100644 --- a/PlexRequests.Core/SettingsServiceV2.cs +++ b/PlexRequests.Core/SettingsServiceV2.cs @@ -30,6 +30,7 @@ using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Store; using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; namespace PlexRequests.Core { diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index 04ce5b9db..c467dfac3 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -75,7 +75,7 @@ namespace PlexRequests.Core { var result = new List(); RequestedModel[] requestedModels; - var repo = new GenericRepository(Db); + var repo = new GenericRepository(Db, new MemoryCacheProvider()); try { var records = repo.GetAll(); diff --git a/PlexRequests.Core/UserMapper.cs b/PlexRequests.Core/UserMapper.cs index c073661b4..6bacbdca9 100644 --- a/PlexRequests.Core/UserMapper.cs +++ b/PlexRequests.Core/UserMapper.cs @@ -88,12 +88,12 @@ namespace PlexRequests.Core return users.Any(); } - public static Guid? CreateUser(string username, string password) + public static Guid? CreateUser(string username, string password, string[] claims = default(string[])) { var repo = new UserRepository(Db); var salt = PasswordHasher.GenerateSalt(); - var userModel = new UsersModel { UserName = username, UserGuid = Guid.NewGuid().ToString(), Salt = salt, Hash = PasswordHasher.ComputeHash(password, salt)}; + var userModel = new UsersModel { UserName = username, UserGuid = Guid.NewGuid().ToString(), Salt = salt, Hash = PasswordHasher.ComputeHash(password, salt), Claims = claims}; repo.Insert(userModel); var userRecord = repo.Get(userModel.UserGuid); diff --git a/PlexRequests.Helpers/LoggingHelper.cs b/PlexRequests.Helpers/LoggingHelper.cs index b3ee3dc1b..5487a90df 100644 --- a/PlexRequests.Helpers/LoggingHelper.cs +++ b/PlexRequests.Helpers/LoggingHelper.cs @@ -25,9 +25,14 @@ // ************************************************************************/ #endregion using System; +using System.Data; using Newtonsoft.Json; +using NLog; +using NLog.Config; +using NLog.Targets; + namespace PlexRequests.Helpers { public static class LoggingHelper @@ -55,5 +60,57 @@ namespace PlexRequests.Helpers } return dumpTarget.ToString(); } + + public static void ConfigureLogging(string connectionString) + { + LogManager.ThrowExceptions = true; + // Step 1. Create configuration object + var config = new LoggingConfiguration(); + + // Step 2. Create targets and add them to the configuration + var databaseTarget = new DatabaseTarget + { + CommandType = CommandType.Text, + ConnectionString = connectionString, + DBProvider = "Mono.Data.Sqlite.SqliteConnection, Mono.Data.Sqlite, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756", + Name = "database" + }; + + var messageParam = new DatabaseParameterInfo { Name = "@Message", Layout = "${message}" }; + var callsiteParam = new DatabaseParameterInfo { Name = "@Callsite", Layout = "${callsite}" }; + var levelParam = new DatabaseParameterInfo { Name = "@Level", Layout = "${level}" }; + var dateParam = new DatabaseParameterInfo { Name = "@Date", Layout = "${date}" }; + var loggerParam = new DatabaseParameterInfo { Name = "@Logger", Layout = "${logger}" }; + var exceptionParam = new DatabaseParameterInfo { Name = "@Exception", Layout = "${exception:tostring}" }; + + databaseTarget.Parameters.Add(messageParam); + databaseTarget.Parameters.Add(callsiteParam); + databaseTarget.Parameters.Add(levelParam); + databaseTarget.Parameters.Add(dateParam); + databaseTarget.Parameters.Add(loggerParam); + databaseTarget.Parameters.Add(exceptionParam); + + databaseTarget.CommandText = "INSERT INTO Logs (Date,Level,Logger, Message, Callsite, Exception) VALUES(@Date,@Level,@Logger, @Message, @Callsite, @Exception);"; + config.AddTarget("database", databaseTarget); + + // Step 4. Define rules + var rule1 = new LoggingRule("*", LogLevel.Info, databaseTarget); + config.LoggingRules.Add(rule1); + + // Step 5. Activate the configuration + LogManager.Configuration = config; + } + + public static void ReconfigureLogLevel(LogLevel level) + { + foreach (var rule in LogManager.Configuration.LoggingRules) + { + rule.EnableLoggingForLevel(level); + } + + //Call to update existing Loggers created with GetLogger() or + //GetCurrentClassLogger() + LogManager.ReconfigExistingLoggers(); + } } } diff --git a/PlexRequests.Helpers/PlexRequests.Helpers.csproj b/PlexRequests.Helpers/PlexRequests.Helpers.csproj index beaf7c117..6af45a9b5 100644 --- a/PlexRequests.Helpers/PlexRequests.Helpers.csproj +++ b/PlexRequests.Helpers/PlexRequests.Helpers.csproj @@ -35,6 +35,10 @@ ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll True + + ..\packages\NLog.4.2.3\lib\net45\NLog.dll + True + diff --git a/PlexRequests.Helpers/packages.config b/PlexRequests.Helpers/packages.config index 47cebb403..dc63c2a11 100644 --- a/PlexRequests.Helpers/packages.config +++ b/PlexRequests.Helpers/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/PlexRequests.Store/Models/LogEntity.cs b/PlexRequests.Store/Models/LogEntity.cs index 694b6ce8d..e0a275c0a 100644 --- a/PlexRequests.Store/Models/LogEntity.cs +++ b/PlexRequests.Store/Models/LogEntity.cs @@ -26,11 +26,13 @@ #endregion using System; +using Dapper.Contrib.Extensions; + namespace PlexRequests.Store.Models { + [Table("Logs")] public class LogEntity : Entity { - public string Username { get; set; } public DateTime Date { get; set; } public string Level { get; set; } public string Logger { get; set; } diff --git a/PlexRequests.Store/PlexRequests.Store.csproj b/PlexRequests.Store/PlexRequests.Store.csproj index d9586aba3..7207fd555 100644 --- a/PlexRequests.Store/PlexRequests.Store.csproj +++ b/PlexRequests.Store/PlexRequests.Store.csproj @@ -58,17 +58,17 @@ - - + + - + - + diff --git a/PlexRequests.Store/GenericRepository.cs b/PlexRequests.Store/Repository/GenericRepository.cs similarity index 77% rename from PlexRequests.Store/GenericRepository.cs rename to PlexRequests.Store/Repository/GenericRepository.cs index 331567463..17e7b9c0f 100644 --- a/PlexRequests.Store/GenericRepository.cs +++ b/PlexRequests.Store/Repository/GenericRepository.cs @@ -34,20 +34,23 @@ using NLog; using PlexRequests.Helpers; -namespace PlexRequests.Store +namespace PlexRequests.Store.Repository { public class GenericRepository : IRepository where T : Entity { - public GenericRepository(ISqliteConfiguration config) + private ICacheProvider Cache { get; } + public GenericRepository(ISqliteConfiguration config, ICacheProvider cache) { Config = config; + Cache = cache; } private static Logger Log = LogManager.GetCurrentClassLogger(); - private ISqliteConfiguration Config { get; set; } + private ISqliteConfiguration Config { get; } public long Insert(T entity) { + ResetCache(); using (var cnn = Config.DbConnection()) { cnn.Open(); @@ -57,12 +60,14 @@ namespace PlexRequests.Store public IEnumerable GetAll() { + using (var db = Config.DbConnection()) { db.Open(); var result = db.GetAll(); return result; } + } public T Get(string id) @@ -72,15 +77,23 @@ namespace PlexRequests.Store public T Get(int id) { - using (var db = Config.DbConnection()) - { - db.Open(); - return db.Get(id); - } + var key = "Get" + id; + var item = Cache.GetOrSet( + key, + () => + { + using (var db = Config.DbConnection()) + { + db.Open(); + return db.Get(id); + } + }); + return item; } public void Delete(T entity) { + ResetCache(); using (var db = Config.DbConnection()) { db.Open(); @@ -90,6 +103,7 @@ namespace PlexRequests.Store public bool Update(T entity) { + ResetCache(); Log.Trace("Updating entity"); Log.Trace(entity.DumpJson()); using (var db = Config.DbConnection()) @@ -101,6 +115,7 @@ namespace PlexRequests.Store public bool UpdateAll(IEnumerable entity) { + ResetCache(); Log.Trace("Updating all entities"); var result = new HashSet(); @@ -114,5 +129,11 @@ namespace PlexRequests.Store } return result.All(x => true); } + + private void ResetCache() + { + Cache.Remove("Get"); + Cache.Remove("GetAll"); + } } } diff --git a/PlexRequests.Store/IRepository.cs b/PlexRequests.Store/Repository/IRepository.cs similarity index 95% rename from PlexRequests.Store/IRepository.cs rename to PlexRequests.Store/Repository/IRepository.cs index d88f80aa0..4d301047c 100644 --- a/PlexRequests.Store/IRepository.cs +++ b/PlexRequests.Store/Repository/IRepository.cs @@ -26,7 +26,7 @@ #endregion using System.Collections.Generic; -namespace PlexRequests.Store +namespace PlexRequests.Store.Repository { public interface IRepository { diff --git a/PlexRequests.Store/IRequestRepository.cs b/PlexRequests.Store/Repository/IRequestRepository.cs similarity index 95% rename from PlexRequests.Store/IRequestRepository.cs rename to PlexRequests.Store/Repository/IRequestRepository.cs index e2a07b6b5..809628c51 100644 --- a/PlexRequests.Store/IRequestRepository.cs +++ b/PlexRequests.Store/Repository/IRequestRepository.cs @@ -28,7 +28,7 @@ using System.Collections.Generic; using PlexRequests.Store.Models; -namespace PlexRequests.Store +namespace PlexRequests.Store.Repository { public interface IRequestRepository { diff --git a/PlexRequests.Store/ISettingsRepository.cs b/PlexRequests.Store/Repository/ISettingsRepository.cs similarity index 95% rename from PlexRequests.Store/ISettingsRepository.cs rename to PlexRequests.Store/Repository/ISettingsRepository.cs index c0a8a866a..393da6ef7 100644 --- a/PlexRequests.Store/ISettingsRepository.cs +++ b/PlexRequests.Store/Repository/ISettingsRepository.cs @@ -28,7 +28,7 @@ using System.Collections.Generic; using PlexRequests.Store.Models; -namespace PlexRequests.Store +namespace PlexRequests.Store.Repository { public interface ISettingsRepository { diff --git a/PlexRequests.Store/UserRepository.cs b/PlexRequests.Store/UserRepository.cs index 3d431908e..467e0487c 100644 --- a/PlexRequests.Store/UserRepository.cs +++ b/PlexRequests.Store/UserRepository.cs @@ -30,6 +30,8 @@ using System.Linq; using Dapper.Contrib.Extensions; +using PlexRequests.Store.Repository; + namespace PlexRequests.Store { public class UserRepository : IRepository where T : UserEntity diff --git a/PlexRequests.Store/UsersModel.cs b/PlexRequests.Store/UsersModel.cs index e0376b7ba..cf5eec764 100644 --- a/PlexRequests.Store/UsersModel.cs +++ b/PlexRequests.Store/UsersModel.cs @@ -33,5 +33,6 @@ namespace PlexRequests.Store { public byte[] Hash { get; set; } public byte[] Salt { get; set; } + public string[] Claims { get; set; } } } diff --git a/PlexRequests.UI.Tests/AdminModuleTests.cs b/PlexRequests.UI.Tests/AdminModuleTests.cs index e42eb4a13..d505e5261 100644 --- a/PlexRequests.UI.Tests/AdminModuleTests.cs +++ b/PlexRequests.UI.Tests/AdminModuleTests.cs @@ -39,6 +39,8 @@ using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.Plex; using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; using PlexRequests.UI.Models; using PlexRequests.UI.Modules; @@ -59,6 +61,7 @@ namespace PlexRequests.UI.Tests private Mock SonarrApiMock { get; set; } private Mock PushbulletApi { get; set; } private Mock CpApi { get; set; } + private Mock> LogRepo { get; set; } private ConfigurableBootstrapper Bootstrapper { get; set; } @@ -83,6 +86,7 @@ namespace PlexRequests.UI.Tests PushbulletSettings = new Mock>(); CpApi = new Mock(); SickRageSettingsMock = new Mock>(); + LogRepo = new Mock>(); Bootstrapper = new ConfigurableBootstrapper(with => { @@ -99,6 +103,7 @@ namespace PlexRequests.UI.Tests with.Dependency(PushbulletSettings.Object); with.Dependency(CpApi.Object); with.Dependency(SickRageSettingsMock.Object); + with.Dependency(LogRepo.Object); with.RootPathProvider(); with.RequestStartup((container, pipelines, context) => { diff --git a/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj b/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj index 96823f840..e79b01dc4 100644 --- a/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj +++ b/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj @@ -117,6 +117,10 @@ {566EFA49-68F8-4716-9693-A6B3F2624DEA} PlexRequests.Services + + {92433867-2B7B-477B-A566-96C382427525} + PlexRequests.Store + {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6} PlexRequests.UI diff --git a/PlexRequests.UI/Bootstrapper.cs b/PlexRequests.UI/Bootstrapper.cs index 8fd5aad2b..c47cb40c1 100644 --- a/PlexRequests.UI/Bootstrapper.cs +++ b/PlexRequests.UI/Bootstrapper.cs @@ -47,6 +47,7 @@ using PlexRequests.Services; using PlexRequests.Services.Interfaces; using PlexRequests.Services.Notification; using PlexRequests.Store; +using PlexRequests.Store.Models; using PlexRequests.Store.Repository; using PlexRequests.UI.Jobs; using TaskFactory = FluentScheduler.TaskFactory; @@ -77,6 +78,7 @@ namespace PlexRequests.UI // Repo's container.Register, GenericRepository>(); + container.Register, GenericRepository>(); container.Register(); container.Register(); diff --git a/PlexRequests.UI/Content/jquery.mixitup.js b/PlexRequests.UI/Content/jquery.mixitup.js index b23a60279..7acfbe489 100644 --- a/PlexRequests.UI/Content/jquery.mixitup.js +++ b/PlexRequests.UI/Content/jquery.mixitup.js @@ -11,2088 +11,4 @@ * Non-commercial use permitted under terms of CC-BY-NC license. * http://creativecommons.org/licenses/by-nc/3.0/ */ - -(function($, undf){ - 'use strict'; - - /** - * MixItUp Constructor Function - * @constructor - * @extends jQuery - */ - - $.MixItUp = function(){ - var self = this; - - self._execAction('_constructor', 0); - - $.extend(self, { - - /* Public Properties - ---------------------------------------------------------------------- */ - - selectors: { - target: '.mix', - filter: '.filter', - sort: '.sort' - }, - - animation: { - enable: true, - effects: 'fade scale', - duration: 600, - easing: 'ease', - perspectiveDistance: '3000', - perspectiveOrigin: '50% 50%', - queue: true, - queueLimit: 1, - animateChangeLayout: false, - animateResizeContainer: true, - animateResizeTargets: false, - staggerSequence: false, - reverseOut: false - }, - - callbacks: { - onMixLoad: false, - onMixStart: false, - onMixBusy: false, - onMixEnd: false, - onMixFail: false, - _user: false - }, - - controls: { - enable: true, - live: false, - toggleFilterButtons: false, - toggleLogic: 'or', - activeClass: 'active' - }, - - layout: { - display: 'inline-block', - containerClass: '', - containerClassFail: 'fail' - }, - - load: { - filter: 'all', - sort: false - }, - - /* Private Properties - ---------------------------------------------------------------------- */ - - _$body: null, - _$container: null, - _$targets: null, - _$parent: null, - _$sortButtons: null, - _$filterButtons: null, - - _suckMode: false, - _mixing: false, - _sorting: false, - _clicking: false, - _loading: true, - _changingLayout: false, - _changingClass: false, - _changingDisplay: false, - - _origOrder: [], - _startOrder: [], - _newOrder: [], - _activeFilter: null, - _toggleArray: [], - _toggleString: '', - _activeSort: 'default:asc', - _newSort: null, - _startHeight: null, - _newHeight: null, - _incPadding: true, - _newDisplay: null, - _newClass: null, - _targetsBound: 0, - _targetsDone: 0, - _queue: [], - - _$show: $(), - _$hide: $() - }); - - self._execAction('_constructor', 1); - }; - - /** - * MixItUp Prototype - * @override - */ - - $.MixItUp.prototype = { - constructor: $.MixItUp, - - /* Static Properties - ---------------------------------------------------------------------- */ - - _instances: {}, - _handled: { - _filter: {}, - _sort: {} - }, - _bound: { - _filter: {}, - _sort: {} - }, - _actions: {}, - _filters: {}, - - /* Static Methods - ---------------------------------------------------------------------- */ - - /** - * Extend - * @since 2.1.0 - * @param {object} new properties/methods - * @extends {object} prototype - */ - - extend: function(extension){ - for(var key in extension){ - $.MixItUp.prototype[key] = extension[key]; - } - }, - - /** - * Add Action - * @since 2.1.0 - * @param {string} hook name - * @param {string} namespace - * @param {function} function to execute - * @param {number} priority - * @extends {object} $.MixItUp.prototype._actions - */ - - addAction: function(hook, name, func, priority){ - $.MixItUp.prototype._addHook('_actions', hook, name, func, priority); - }, - - /** - * Add Filter - * @since 2.1.0 - * @param {string} hook name - * @param {string} namespace - * @param {function} function to execute - * @param {number} priority - * @extends {object} $.MixItUp.prototype._filters - */ - - addFilter: function(hook, name, func, priority){ - $.MixItUp.prototype._addHook('_filters', hook, name, func, priority); - }, - - /** - * Add Hook - * @since 2.1.0 - * @param {string} type of hook - * @param {string} hook name - * @param {function} function to execute - * @param {number} priority - * @extends {object} $.MixItUp.prototype._filters - */ - - _addHook: function(type, hook, name, func, priority){ - var collection = $.MixItUp.prototype[type], - obj = {}; - - priority = (priority === 1 || priority === 'post') ? 'post' : 'pre'; - - obj[hook] = {}; - obj[hook][priority] = {}; - obj[hook][priority][name] = func; - - $.extend(true, collection, obj); - }, - - - /* Private Methods - ---------------------------------------------------------------------- */ - - /** - * Initialise - * @since 2.0.0 - * @param {object} domNode - * @param {object} config - */ - - _init: function(domNode, config){ - var self = this; - - self._execAction('_init', 0, arguments); - - config && $.extend(true, self, config); - - self._$body = $('body'); - self._domNode = domNode; - self._$container = $(domNode); - self._$container.addClass(self.layout.containerClass); - self._id = domNode.id; - - self._platformDetect(); - - self._brake = self._getPrefixedCSS('transition', 'none'); - - self._refresh(true); - - self._$parent = self._$targets.parent().length ? self._$targets.parent() : self._$container; - - if(self.load.sort){ - self._newSort = self._parseSort(self.load.sort); - self._newSortString = self.load.sort; - self._activeSort = self.load.sort; - self._sort(); - self._printSort(); - } - - self._activeFilter = self.load.filter === 'all' ? - self.selectors.target : - self.load.filter === 'none' ? - '' : - self.load.filter; - - self.controls.enable && self._bindHandlers(); - - if(self.controls.toggleFilterButtons){ - self._buildToggleArray(); - - for(var i = 0; i < self._toggleArray.length; i++){ - self._updateControls({filter: self._toggleArray[i], sort: self._activeSort}, true); - }; - } else if(self.controls.enable){ - self._updateControls({filter: self._activeFilter, sort: self._activeSort}); - } - - self._filter(); - - self._init = true; - - self._$container.data('mixItUp',self); - - self._execAction('_init', 1, arguments); - - self._buildState(); - - self._$targets.css(self._brake); - - self._goMix(self.animation.enable); - }, - - /** - * Platform Detect - * @since 2.0.0 - */ - - _platformDetect: function(){ - var self = this, - vendorsTrans = ['Webkit', 'Moz', 'O', 'ms'], - vendorsRAF = ['webkit', 'moz'], - chrome = window.navigator.appVersion.match(/Chrome\/(\d+)\./) || false, - ff = typeof InstallTrigger !== 'undefined', - prefix = function(el){ - for (var i = 0; i < vendorsTrans.length; i++){ - if (vendorsTrans[i] + 'Transition' in el.style){ - return { - prefix: '-'+vendorsTrans[i].toLowerCase()+'-', - vendor: vendorsTrans[i] - }; - }; - }; - return 'transition' in el.style ? '' : false; - }, - transPrefix = prefix(self._domNode); - - self._execAction('_platformDetect', 0); - - self._chrome = chrome ? parseInt(chrome[1], 10) : false; - self._ff = ff ? parseInt(window.navigator.userAgent.match(/rv:([^)]+)\)/)[1]) : false; - self._prefix = transPrefix.prefix; - self._vendor = transPrefix.vendor; - self._suckMode = window.atob && self._prefix ? false : true; - - self._suckMode && (self.animation.enable = false); - (self._ff && self._ff <= 4) && (self.animation.enable = false); - - /* Polyfills - ---------------------------------------------------------------------- */ - - /** - * window.requestAnimationFrame - */ - - for(var x = 0; x < vendorsRAF.length && !window.requestAnimationFrame; x++){ - window.requestAnimationFrame = window[vendorsRAF[x]+'RequestAnimationFrame']; - } - - /** - * Object.getPrototypeOf - */ - - if(typeof Object.getPrototypeOf !== 'function'){ - if(typeof 'test'.__proto__ === 'object'){ - Object.getPrototypeOf = function(object){ - return object.__proto__; - }; - } else { - Object.getPrototypeOf = function(object){ - return object.constructor.prototype; - }; - } - } - - /** - * Element.nextElementSibling - */ - - if(self._domNode.nextElementSibling === undf){ - Object.defineProperty(Element.prototype, 'nextElementSibling',{ - get: function(){ - var el = this.nextSibling; - - while(el){ - if(el.nodeType ===1){ - return el; - } - el = el.nextSibling; - } - return null; - } - }); - } - - self._execAction('_platformDetect', 1); - }, - - /** - * Refresh - * @since 2.0.0 - * @param {boolean} init - * @param {boolean} force - */ - - _refresh: function(init, force){ - var self = this; - - self._execAction('_refresh', 0, arguments); - - self._$targets = self._$container.find(self.selectors.target); - - for(var i = 0; i < self._$targets.length; i++){ - var target = self._$targets[i]; - - if(target.dataset === undf || force){ - - target.dataset = {}; - - for(var j = 0; j < target.attributes.length; j++){ - - var attr = target.attributes[j], - name = attr.name, - val = attr.value; - - if(name.indexOf('data-') > -1){ - var dataName = self._helpers._camelCase(name.substring(5,name.length)); - target.dataset[dataName] = val; - } - } - } - - if(target.mixParent === undf){ - target.mixParent = self._id; - } - } - - if( - (self._$targets.length && init) || - (!self._origOrder.length && self._$targets.length) - ){ - self._origOrder = []; - - for(var i = 0; i < self._$targets.length; i++){ - var target = self._$targets[i]; - - self._origOrder.push(target); - } - } - - self._execAction('_refresh', 1, arguments); - }, - - /** - * Bind Handlers - * @since 2.0.0 - */ - - _bindHandlers: function(){ - var self = this, - filters = $.MixItUp.prototype._bound._filter, - sorts = $.MixItUp.prototype._bound._sort; - - self._execAction('_bindHandlers', 0); - - if(self.controls.live){ - self._$body - .on('click.mixItUp.'+self._id, self.selectors.sort, function(){ - self._processClick($(this), 'sort'); - }) - .on('click.mixItUp.'+self._id, self.selectors.filter, function(){ - self._processClick($(this), 'filter'); - }); - } else { - self._$sortButtons = $(self.selectors.sort); - self._$filterButtons = $(self.selectors.filter); - - self._$sortButtons.on('click.mixItUp.'+self._id, function(){ - self._processClick($(this), 'sort'); - }); - - self._$filterButtons.on('click.mixItUp.'+self._id, function(){ - self._processClick($(this), 'filter'); - }); - } - - filters[self.selectors.filter] = (filters[self.selectors.filter] === undf) ? 1 : filters[self.selectors.filter] + 1; - sorts[self.selectors.sort] = (sorts[self.selectors.sort] === undf) ? 1 : sorts[self.selectors.sort] + 1; - - self._execAction('_bindHandlers', 1); - }, - - /** - * Process Click - * @since 2.0.0 - * @param {object} $button - * @param {string} type - */ - - _processClick: function($button, type){ - var self = this, - trackClick = function($button, type, off){ - var proto = $.MixItUp.prototype; - - proto._handled['_'+type][self.selectors[type]] = (proto._handled['_'+type][self.selectors[type]] === undf) ? - 1 : - proto._handled['_'+type][self.selectors[type]] + 1; - - if(proto._handled['_'+type][self.selectors[type]] === proto._bound['_'+type][self.selectors[type]]){ - $button[(off ? 'remove' : 'add')+'Class'](self.controls.activeClass); - delete proto._handled['_'+type][self.selectors[type]]; - } - }; - - self._execAction('_processClick', 0, arguments); - - if(!self._mixing || (self.animation.queue && self._queue.length < self.animation.queueLimit)){ - self._clicking = true; - - if(type === 'sort'){ - var sort = $button.attr('data-sort'); - - if(!$button.hasClass(self.controls.activeClass) || sort.indexOf('random') > -1){ - $(self.selectors.sort).removeClass(self.controls.activeClass); - trackClick($button, type); - self.sort(sort); - } - } - - if(type === 'filter') { - var filter = $button.attr('data-filter'), - ndx, - seperator = self.controls.toggleLogic === 'or' ? ',' : ''; - - if(!self.controls.toggleFilterButtons){ - if(!$button.hasClass(self.controls.activeClass)){ - $(self.selectors.filter).removeClass(self.controls.activeClass); - trackClick($button, type); - self.filter(filter); - } - } else { - self._buildToggleArray(); - - if(!$button.hasClass(self.controls.activeClass)){ - trackClick($button, type); - - self._toggleArray.push(filter); - } else { - trackClick($button, type, true); - ndx = self._toggleArray.indexOf(filter); - self._toggleArray.splice(ndx, 1); - } - - self._toggleArray = $.grep(self._toggleArray,function(n){return(n);}); - - self._toggleString = self._toggleArray.join(seperator); - - self.filter(self._toggleString); - } - } - - self._execAction('_processClick', 1, arguments); - } else { - if(typeof self.callbacks.onMixBusy === 'function'){ - self.callbacks.onMixBusy.call(self._domNode, self._state, self); - } - self._execAction('_processClickBusy', 1, arguments); - } - }, - - /** - * Build Toggle Array - * @since 2.0.0 - */ - - _buildToggleArray: function(){ - var self = this, - activeFilter = self._activeFilter.replace(/\s/g, ''); - - self._execAction('_buildToggleArray', 0, arguments); - - if(self.controls.toggleLogic === 'or'){ - self._toggleArray = activeFilter.split(','); - } else { - self._toggleArray = activeFilter.split('.'); - - !self._toggleArray[0] && self._toggleArray.shift(); - - for(var i = 0, filter; filter = self._toggleArray[i]; i++){ - self._toggleArray[i] = '.'+filter; - } - } - - self._execAction('_buildToggleArray', 1, arguments); - }, - - /** - * Update Controls - * @since 2.0.0 - * @param {object} command - * @param {boolean} multi - */ - - _updateControls: function(command, multi){ - var self = this, - output = { - filter: command.filter, - sort: command.sort - }, - update = function($el, filter){ - try { - (multi && type === 'filter' && !(output.filter === 'none' || output.filter === '')) ? - $el.filter(filter).addClass(self.controls.activeClass) : - $el.removeClass(self.controls.activeClass).filter(filter).addClass(self.controls.activeClass); - } catch(e) {} - }, - type = 'filter', - $el = null; - - self._execAction('_updateControls', 0, arguments); - - (command.filter === undf) && (output.filter = self._activeFilter); - (command.sort === undf) && (output.sort = self._activeSort); - (output.filter === self.selectors.target) && (output.filter = 'all'); - - for(var i = 0; i < 2; i++){ - $el = self.controls.live ? $(self.selectors[type]) : self['_$'+type+'Buttons']; - $el && update($el, '[data-'+type+'="'+output[type]+'"]'); - type = 'sort'; - } - - self._execAction('_updateControls', 1, arguments); - }, - - /** - * Filter (private) - * @since 2.0.0 - */ - - _filter: function(){ - var self = this; - - self._execAction('_filter', 0); - - for(var i = 0; i < self._$targets.length; i++){ - var $target = $(self._$targets[i]); - - if($target.is(self._activeFilter)){ - self._$show = self._$show.add($target); - } else { - self._$hide = self._$hide.add($target); - } - } - - self._execAction('_filter', 1); - }, - - /** - * Sort (private) - * @since 2.0.0 - */ - - _sort: function(){ - var self = this, - arrayShuffle = function(oldArray){ - var newArray = oldArray.slice(), - len = newArray.length, - i = len; - - while(i--){ - var p = parseInt(Math.random()*len); - var t = newArray[i]; - newArray[i] = newArray[p]; - newArray[p] = t; - }; - return newArray; - }; - - self._execAction('_sort', 0); - - self._startOrder = []; - - for(var i = 0; i < self._$targets.length; i++){ - var target = self._$targets[i]; - - self._startOrder.push(target); - } - - switch(self._newSort[0].sortBy){ - case 'default': - self._newOrder = self._origOrder; - break; - case 'random': - self._newOrder = arrayShuffle(self._startOrder); - break; - case 'custom': - self._newOrder = self._newSort[0].order; - break; - default: - self._newOrder = self._startOrder.concat().sort(function(a, b){ - return self._compare(a, b); - }); - } - - self._execAction('_sort', 1); - }, - - /** - * Compare Algorithm - * @since 2.0.0 - * @param {string|number} a - * @param {string|number} b - * @param {number} depth (recursion) - * @return {number} - */ - - _compare: function(a, b, depth){ - depth = depth ? depth : 0; - - var self = this, - order = self._newSort[depth].order, - getData = function(el){ - return el.dataset[self._newSort[depth].sortBy] || 0; - }, - attrA = isNaN(getData(a) * 1) ? getData(a).toLowerCase() : getData(a) * 1, - attrB = isNaN(getData(b) * 1) ? getData(b).toLowerCase() : getData(b) * 1; - - if(attrA < attrB) - return order === 'asc' ? -1 : 1; - if(attrA > attrB) - return order === 'asc' ? 1 : -1; - if(attrA === attrB && self._newSort.length > depth+1) - return self._compare(a, b, depth+1); - - return 0; - }, - - /** - * Print Sort - * @since 2.0.0 - * @param {boolean} reset - */ - - _printSort: function(reset){ - var self = this, - order = reset ? self._startOrder : self._newOrder, - targets = self._$parent[0].querySelectorAll(self.selectors.target), - nextSibling = targets.length ? targets[targets.length -1].nextElementSibling : null, - frag = document.createDocumentFragment(); - - self._execAction('_printSort', 0, arguments); - - for(var i = 0; i < targets.length; i++){ - var target = targets[i], - whiteSpace = target.nextSibling; - - if(target.style.position === 'absolute') continue; - - if(whiteSpace && whiteSpace.nodeName === '#text'){ - self._$parent[0].removeChild(whiteSpace); - } - - self._$parent[0].removeChild(target); - } - - for(var i = 0; i < order.length; i++){ - var el = order[i]; - - if(self._newSort[0].sortBy === 'default' && self._newSort[0].order === 'desc' && !reset){ - var firstChild = frag.firstChild; - frag.insertBefore(el, firstChild); - frag.insertBefore(document.createTextNode(' '), el); - } else { - frag.appendChild(el); - frag.appendChild(document.createTextNode(' ')); - } - } - - nextSibling ? - self._$parent[0].insertBefore(frag, nextSibling) : - self._$parent[0].appendChild(frag); - - self._execAction('_printSort', 1, arguments); - }, - - /** - * Parse Sort - * @since 2.0.0 - * @param {string} sortString - * @return {array} newSort - */ - - _parseSort: function(sortString){ - var self = this, - rules = typeof sortString === 'string' ? sortString.split(' ') : [sortString], - newSort = []; - - for(var i = 0; i < rules.length; i++){ - var rule = typeof sortString === 'string' ? rules[i].split(':') : ['custom', rules[i]], - ruleObj = { - sortBy: self._helpers._camelCase(rule[0]), - order: rule[1] || 'asc' - }; - - newSort.push(ruleObj); - - if(ruleObj.sortBy === 'default' || ruleObj.sortBy === 'random') break; - } - - return self._execFilter('_parseSort', newSort, arguments); - }, - - /** - * Parse Effects - * @since 2.0.0 - * @return {object} effects - */ - - _parseEffects: function(){ - var self = this, - effects = { - opacity: '', - transformIn: '', - transformOut: '', - filter: '' - }, - parse = function(effect, extract, reverse){ - if(self.animation.effects.indexOf(effect) > -1){ - if(extract){ - var propIndex = self.animation.effects.indexOf(effect+'('); - if(propIndex > -1){ - var str = self.animation.effects.substring(propIndex), - match = /\(([^)]+)\)/.exec(str), - val = match[1]; - - return {val: val}; - } - } - return true; - } else { - return false; - } - }, - negate = function(value, invert){ - if(invert){ - return value.charAt(0) === '-' ? value.substr(1, value.length) : '-'+value; - } else { - return value; - } - }, - buildTransform = function(key, invert){ - var transforms = [ - ['scale', '.01'], - ['translateX', '20px'], - ['translateY', '20px'], - ['translateZ', '20px'], - ['rotateX', '90deg'], - ['rotateY', '90deg'], - ['rotateZ', '180deg'], - ]; - - for(var i = 0; i < transforms.length; i++){ - var prop = transforms[i][0], - def = transforms[i][1], - inverted = invert && prop !== 'scale'; - - effects[key] += parse(prop) ? prop+'('+negate(parse(prop, true).val || def, inverted)+') ' : ''; - } - }; - - effects.opacity = parse('fade') ? parse('fade',true).val || '0' : '1'; - - buildTransform('transformIn'); - - self.animation.reverseOut ? buildTransform('transformOut', true) : (effects.transformOut = effects.transformIn); - - effects.transition = {}; - - effects.transition = self._getPrefixedCSS('transition','all '+self.animation.duration+'ms '+self.animation.easing+', opacity '+self.animation.duration+'ms linear'); - - self.animation.stagger = parse('stagger') ? true : false; - self.animation.staggerDuration = parseInt(parse('stagger') ? (parse('stagger',true).val ? parse('stagger',true).val : 100) : 100); - - return self._execFilter('_parseEffects', effects); - }, - - /** - * Build State - * @since 2.0.0 - * @param {boolean} future - * @return {object} futureState - */ - - _buildState: function(future){ - var self = this, - state = {}; - - self._execAction('_buildState', 0); - - state = { - activeFilter: self._activeFilter === '' ? 'none' : self._activeFilter, - activeSort: future && self._newSortString ? self._newSortString : self._activeSort, - fail: !self._$show.length && self._activeFilter !== '', - $targets: self._$targets, - $show: self._$show, - $hide: self._$hide, - totalTargets: self._$targets.length, - totalShow: self._$show.length, - totalHide: self._$hide.length, - display: future && self._newDisplay ? self._newDisplay : self.layout.display - }; - - if(future){ - return self._execFilter('_buildState', state); - } else { - self._state = state; - - self._execAction('_buildState', 1); - } - }, - - /** - * Go Mix - * @since 2.0.0 - * @param {boolean} animate - */ - - _goMix: function(animate){ - var self = this, - phase1 = function(){ - if(self._chrome && (self._chrome === 31)){ - chromeFix(self._$parent[0]); - } - - self._setInter(); - - phase2(); - }, - phase2 = function(){ - var scrollTop = window.pageYOffset, - scrollLeft = window.pageXOffset, - docHeight = document.documentElement.scrollHeight; - - self._getInterMixData(); - - self._setFinal(); - - self._getFinalMixData(); - - (window.pageYOffset !== scrollTop) && window.scrollTo(scrollLeft, scrollTop); - - self._prepTargets(); - - if(window.requestAnimationFrame){ - requestAnimationFrame(phase3); - } else { - setTimeout(function(){ - phase3(); - },20); - } - }, - phase3 = function(){ - self._animateTargets(); - - if(self._targetsBound === 0){ - self._cleanUp(); - } - }, - chromeFix = function(grid){ - var parent = grid.parentElement, - placeholder = document.createElement('div'), - frag = document.createDocumentFragment(); - - parent.insertBefore(placeholder, grid); - frag.appendChild(grid); - parent.replaceChild(grid, placeholder); - }, - futureState = self._buildState(true); - - self._execAction('_goMix', 0, arguments); - - !self.animation.duration && (animate = false); - - self._mixing = true; - - self._$container.removeClass(self.layout.containerClassFail); - - if(typeof self.callbacks.onMixStart === 'function'){ - self.callbacks.onMixStart.call(self._domNode, self._state, futureState, self); - } - - self._$container.trigger('mixStart', [self._state, futureState, self]); - - self._getOrigMixData(); - - if(animate && !self._suckMode){ - - window.requestAnimationFrame ? - requestAnimationFrame(phase1) : - phase1(); - - } else { - self._cleanUp(); - } - - self._execAction('_goMix', 1, arguments); - }, - - /** - * Get Target Data - * @since 2.0.0 - */ - - _getTargetData: function(el, stage){ - var self = this, - elStyle; - - el.dataset[stage+'PosX'] = el.offsetLeft; - el.dataset[stage+'PosY'] = el.offsetTop; - - if(self.animation.animateResizeTargets){ - elStyle = !self._suckMode ? - window.getComputedStyle(el) : - { - marginBottom: '', - marginRight: '' - }; - - el.dataset[stage+'MarginBottom'] = parseInt(elStyle.marginBottom); - el.dataset[stage+'MarginRight'] = parseInt(elStyle.marginRight); - el.dataset[stage+'Width'] = el.offsetWidth; - el.dataset[stage+'Height'] = el.offsetHeight; - } - }, - - /** - * Get Original Mix Data - * @since 2.0.0 - */ - - _getOrigMixData: function(){ - var self = this, - parentStyle = !self._suckMode ? window.getComputedStyle(self._$parent[0]) : {boxSizing: ''}, - parentBS = parentStyle.boxSizing || parentStyle[self._vendor+'BoxSizing']; - - self._incPadding = (parentBS === 'border-box'); - - self._execAction('_getOrigMixData', 0); - - !self._suckMode && (self.effects = self._parseEffects()); - - self._$toHide = self._$hide.filter(':visible'); - self._$toShow = self._$show.filter(':hidden'); - self._$pre = self._$targets.filter(':visible'); - - self._startHeight = self._incPadding ? - self._$parent.outerHeight() : - self._$parent.height(); - - for(var i = 0; i < self._$pre.length; i++){ - var el = self._$pre[i]; - - self._getTargetData(el, 'orig'); - } - - self._execAction('_getOrigMixData', 1); - }, - - /** - * Set Intermediate Positions - * @since 2.0.0 - */ - - _setInter: function(){ - var self = this; - - self._execAction('_setInter', 0); - - if(self._changingLayout && self.animation.animateChangeLayout){ - self._$toShow.css('display',self._newDisplay); - - if(self._changingClass){ - self._$container - .removeClass(self.layout.containerClass) - .addClass(self._newClass); - } - } else { - self._$toShow.css('display', self.layout.display); - } - - self._execAction('_setInter', 1); - }, - - /** - * Get Intermediate Mix Data - * @since 2.0.0 - */ - - _getInterMixData: function(){ - var self = this; - - self._execAction('_getInterMixData', 0); - - for(var i = 0; i < self._$toShow.length; i++){ - var el = self._$toShow[i]; - - self._getTargetData(el, 'inter'); - } - - for(var i = 0; i < self._$pre.length; i++){ - var el = self._$pre[i]; - - self._getTargetData(el, 'inter'); - } - - self._execAction('_getInterMixData', 1); - }, - - /** - * Set Final Positions - * @since 2.0.0 - */ - - _setFinal: function(){ - var self = this; - - self._execAction('_setFinal', 0); - - self._sorting && self._printSort(); - - self._$toHide.removeStyle('display'); - - if(self._changingLayout && self.animation.animateChangeLayout){ - self._$pre.css('display',self._newDisplay); - } - - self._execAction('_setFinal', 1); - }, - - /** - * Get Final Mix Data - * @since 2.0.0 - */ - - _getFinalMixData: function(){ - var self = this; - - self._execAction('_getFinalMixData', 0); - - for(var i = 0; i < self._$toShow.length; i++){ - var el = self._$toShow[i]; - - self._getTargetData(el, 'final'); - } - - for(var i = 0; i < self._$pre.length; i++){ - var el = self._$pre[i]; - - self._getTargetData(el, 'final'); - } - - self._newHeight = self._incPadding ? - self._$parent.outerHeight() : - self._$parent.height(); - - self._sorting && self._printSort(true); - - self._$toShow.removeStyle('display'); - - self._$pre.css('display',self.layout.display); - - if(self._changingClass && self.animation.animateChangeLayout){ - self._$container - .removeClass(self._newClass) - .addClass(self.layout.containerClass); - } - - self._execAction('_getFinalMixData', 1); - }, - - /** - * Prepare Targets - * @since 2.0.0 - */ - - _prepTargets: function(){ - var self = this, - transformCSS = { - _in: self._getPrefixedCSS('transform', self.effects.transformIn), - _out: self._getPrefixedCSS('transform', self.effects.transformOut) - }; - - self._execAction('_prepTargets', 0); - - if(self.animation.animateResizeContainer){ - self._$parent.css('height',self._startHeight+'px'); - } - - for(var i = 0; i < self._$toShow.length; i++){ - var el = self._$toShow[i], - $el = $(el); - - el.style.opacity = self.effects.opacity; - el.style.display = (self._changingLayout && self.animation.animateChangeLayout) ? - self._newDisplay : - self.layout.display; - - $el.css(transformCSS._in); - - if(self.animation.animateResizeTargets){ - el.style.width = el.dataset.finalWidth+'px'; - el.style.height = el.dataset.finalHeight+'px'; - el.style.marginRight = -(el.dataset.finalWidth - el.dataset.interWidth) + (el.dataset.finalMarginRight * 1)+'px'; - el.style.marginBottom = -(el.dataset.finalHeight - el.dataset.interHeight) + (el.dataset.finalMarginBottom * 1)+'px'; - } - } - - for(var i = 0; i < self._$pre.length; i++){ - var el = self._$pre[i], - $el = $(el), - translate = { - x: el.dataset.origPosX - el.dataset.interPosX, - y: el.dataset.origPosY - el.dataset.interPosY - }, - transformCSS = self._getPrefixedCSS('transform','translate('+translate.x+'px,'+translate.y+'px)'); - - $el.css(transformCSS); - - if(self.animation.animateResizeTargets){ - el.style.width = el.dataset.origWidth+'px'; - el.style.height = el.dataset.origHeight+'px'; - - if(el.dataset.origWidth - el.dataset.finalWidth){ - el.style.marginRight = -(el.dataset.origWidth - el.dataset.interWidth) + (el.dataset.origMarginRight * 1)+'px'; - } - - if(el.dataset.origHeight - el.dataset.finalHeight){ - el.style.marginBottom = -(el.dataset.origHeight - el.dataset.interHeight) + (el.dataset.origMarginBottom * 1) +'px'; - } - } - } - - self._execAction('_prepTargets', 1); - }, - - /** - * Animate Targets - * @since 2.0.0 - */ - - _animateTargets: function(){ - var self = this; - - self._execAction('_animateTargets', 0); - - self._targetsDone = 0; - self._targetsBound = 0; - - self._$parent - .css(self._getPrefixedCSS('perspective', self.animation.perspectiveDistance+'px')) - .css(self._getPrefixedCSS('perspective-origin', self.animation.perspectiveOrigin)); - - if(self.animation.animateResizeContainer){ - self._$parent - .css(self._getPrefixedCSS('transition','height '+self.animation.duration+'ms ease')) - .css('height',self._newHeight+'px'); - } - - for(var i = 0; i < self._$toShow.length; i++){ - var el = self._$toShow[i], - $el = $(el), - translate = { - x: el.dataset.finalPosX - el.dataset.interPosX, - y: el.dataset.finalPosY - el.dataset.interPosY - }, - delay = self._getDelay(i), - toShowCSS = {}; - - el.style.opacity = ''; - - for(var j = 0; j < 2; j++){ - var a = j === 0 ? a = self._prefix : ''; - - if(self._ff && self._ff <= 20){ - toShowCSS[a+'transition-property'] = 'all'; - toShowCSS[a+'transition-timing-function'] = self.animation.easing+'ms'; - toShowCSS[a+'transition-duration'] = self.animation.duration+'ms'; - } - - toShowCSS[a+'transition-delay'] = delay+'ms'; - toShowCSS[a+'transform'] = 'translate('+translate.x+'px,'+translate.y+'px)'; - } - - if(self.effects.transform || self.effects.opacity){ - self._bindTargetDone($el); - } - - (self._ff && self._ff <= 20) ? - $el.css(toShowCSS) : - $el.css(self.effects.transition).css(toShowCSS); - } - - for(var i = 0; i < self._$pre.length; i++){ - var el = self._$pre[i], - $el = $(el), - translate = { - x: el.dataset.finalPosX - el.dataset.interPosX, - y: el.dataset.finalPosY - el.dataset.interPosY - }, - delay = self._getDelay(i); - - if(!( - el.dataset.finalPosX === el.dataset.origPosX && - el.dataset.finalPosY === el.dataset.origPosY - )){ - self._bindTargetDone($el); - } - - $el.css(self._getPrefixedCSS('transition', 'all '+self.animation.duration+'ms '+self.animation.easing+' '+delay+'ms')); - $el.css(self._getPrefixedCSS('transform', 'translate('+translate.x+'px,'+translate.y+'px)')); - - if(self.animation.animateResizeTargets){ - if(el.dataset.origWidth - el.dataset.finalWidth && el.dataset.finalWidth * 1){ - el.style.width = el.dataset.finalWidth+'px'; - el.style.marginRight = -(el.dataset.finalWidth - el.dataset.interWidth)+(el.dataset.finalMarginRight * 1)+'px'; - } - - if(el.dataset.origHeight - el.dataset.finalHeight && el.dataset.finalHeight * 1){ - el.style.height = el.dataset.finalHeight+'px'; - el.style.marginBottom = -(el.dataset.finalHeight - el.dataset.interHeight)+(el.dataset.finalMarginBottom * 1) +'px'; - } - } - } - - if(self._changingClass){ - self._$container - .removeClass(self.layout.containerClass) - .addClass(self._newClass); - } - - for(var i = 0; i < self._$toHide.length; i++){ - var el = self._$toHide[i], - $el = $(el), - delay = self._getDelay(i), - toHideCSS = {}; - - for(var j = 0; j<2; j++){ - var a = j === 0 ? a = self._prefix : ''; - - toHideCSS[a+'transition-delay'] = delay+'ms'; - toHideCSS[a+'transform'] = self.effects.transformOut; - toHideCSS.opacity = self.effects.opacity; - } - - $el.css(self.effects.transition).css(toHideCSS); - - if(self.effects.transform || self.effects.opacity){ - self._bindTargetDone($el); - }; - } - - self._execAction('_animateTargets', 1); - - }, - - /** - * Bind Targets TransitionEnd - * @since 2.0.0 - * @param {object} $el - */ - - _bindTargetDone: function($el){ - var self = this, - el = $el[0]; - - self._execAction('_bindTargetDone', 0, arguments); - - if(!el.dataset.bound){ - - el.dataset.bound = true; - self._targetsBound++; - - $el.on('webkitTransitionEnd.mixItUp transitionend.mixItUp',function(e){ - if( - (e.originalEvent.propertyName.indexOf('transform') > -1 || - e.originalEvent.propertyName.indexOf('opacity') > -1) && - $(e.originalEvent.target).is(self.selectors.target) - ){ - $el.off('.mixItUp'); - el.dataset.bound = ''; - self._targetDone(); - } - }); - } - - self._execAction('_bindTargetDone', 1, arguments); - }, - - /** - * Target Done - * @since 2.0.0 - */ - - _targetDone: function(){ - var self = this; - - self._execAction('_targetDone', 0); - - self._targetsDone++; - - (self._targetsDone === self._targetsBound) && self._cleanUp(); - - self._execAction('_targetDone', 1); - }, - - /** - * Clean Up - * @since 2.0.0 - */ - - _cleanUp: function(){ - var self = this, - targetStyles = self.animation.animateResizeTargets ? - 'transform opacity width height margin-bottom margin-right' : - 'transform opacity', - unBrake = function(){ - self._$targets.removeStyle('transition', self._prefix); - }; - - self._execAction('_cleanUp', 0); - - !self._changingLayout ? - self._$show.css('display',self.layout.display) : - self._$show.css('display',self._newDisplay); - - self._$targets.css(self._brake); - - self._$targets - .removeStyle(targetStyles, self._prefix) - .removeAttr('data-inter-pos-x data-inter-pos-y data-final-pos-x data-final-pos-y data-orig-pos-x data-orig-pos-y data-orig-height data-orig-width data-final-height data-final-width data-inter-width data-inter-height data-orig-margin-right data-orig-margin-bottom data-inter-margin-right data-inter-margin-bottom data-final-margin-right data-final-margin-bottom'); - - self._$hide.removeStyle('display'); - - self._$parent.removeStyle('height transition perspective-distance perspective perspective-origin-x perspective-origin-y perspective-origin perspectiveOrigin', self._prefix); - - if(self._sorting){ - self._printSort(); - self._activeSort = self._newSortString; - self._sorting = false; - } - - if(self._changingLayout){ - if(self._changingDisplay){ - self.layout.display = self._newDisplay; - self._changingDisplay = false; - } - - if(self._changingClass){ - self._$parent.removeClass(self.layout.containerClass).addClass(self._newClass); - self.layout.containerClass = self._newClass; - self._changingClass = false; - } - - self._changingLayout = false; - } - - self._refresh(); - - self._buildState(); - - if(self._state.fail){ - self._$container.addClass(self.layout.containerClassFail); - } - - self._$show = $(); - self._$hide = $(); - - if(window.requestAnimationFrame){ - requestAnimationFrame(unBrake); - } - - self._mixing = false; - - if(typeof self.callbacks._user === 'function'){ - self.callbacks._user.call(self._domNode, self._state, self); - } - - if(typeof self.callbacks.onMixEnd === 'function'){ - self.callbacks.onMixEnd.call(self._domNode, self._state, self); - } - - self._$container.trigger('mixEnd', [self._state, self]); - - if(self._state.fail){ - (typeof self.callbacks.onMixFail === 'function') && self.callbacks.onMixFail.call(self._domNode, self._state, self); - self._$container.trigger('mixFail', [self._state, self]); - } - - if(self._loading){ - (typeof self.callbacks.onMixLoad === 'function') && self.callbacks.onMixLoad.call(self._domNode, self._state, self); - self._$container.trigger('mixLoad', [self._state, self]); - } - - if(self._queue.length){ - self._execAction('_queue', 0); - - self.multiMix(self._queue[0][0],self._queue[0][1],self._queue[0][2]); - self._queue.splice(0, 1); - } - - self._execAction('_cleanUp', 1); - - self._loading = false; - }, - - /** - * Get Prefixed CSS - * @since 2.0.0 - * @param {string} property - * @param {string} value - * @param {boolean} prefixValue - * @return {object} styles - */ - - _getPrefixedCSS: function(property, value, prefixValue){ - var self = this, - styles = {}, - prefix = '', - i = -1; - - for(i = 0; i < 2; i++){ - prefix = i === 0 ? self._prefix : ''; - prefixValue ? styles[prefix+property] = prefix+value : styles[prefix+property] = value; - } - - return self._execFilter('_getPrefixedCSS', styles, arguments); - }, - - /** - * Get Delay - * @since 2.0.0 - * @param {number} i - * @return {number} delay - */ - - _getDelay: function(i){ - var self = this, - n = typeof self.animation.staggerSequence === 'function' ? self.animation.staggerSequence.call(self._domNode, i, self._state) : i, - delay = self.animation.stagger ? n * self.animation.staggerDuration : 0; - - return self._execFilter('_getDelay', delay, arguments); - }, - - /** - * Parse MultiMix Arguments - * @since 2.0.0 - * @param {array} args - * @return {object} output - */ - - _parseMultiMixArgs: function(args){ - var self = this, - output = { - command: null, - animate: self.animation.enable, - callback: null - }; - - for(var i = 0; i < args.length; i++){ - var arg = args[i]; - - if(arg !== null){ - if(typeof arg === 'object' || typeof arg === 'string'){ - output.command = arg; - } else if(typeof arg === 'boolean'){ - output.animate = arg; - } else if(typeof arg === 'function'){ - output.callback = arg; - } - } - } - - return self._execFilter('_parseMultiMixArgs', output, arguments); - }, - - /** - * Parse Insert Arguments - * @since 2.0.0 - * @param {array} args - * @return {object} output - */ - - _parseInsertArgs: function(args){ - var self = this, - output = { - index: 0, - $object: $(), - multiMix: {filter: self._state.activeFilter}, - callback: null - }; - - for(var i = 0; i < args.length; i++){ - var arg = args[i]; - - if(typeof arg === 'number'){ - output.index = arg; - } else if(typeof arg === 'object' && arg instanceof $){ - output.$object = arg; - } else if(typeof arg === 'object' && self._helpers._isElement(arg)){ - output.$object = $(arg); - } else if(typeof arg === 'object' && arg !== null){ - output.multiMix = arg; - } else if(typeof arg === 'boolean' && !arg){ - output.multiMix = false; - } else if(typeof arg === 'function'){ - output.callback = arg; - } - } - - return self._execFilter('_parseInsertArgs', output, arguments); - }, - - /** - * Execute Action - * @since 2.0.0 - * @param {string} methodName - * @param {boolean} isPost - * @param {array} args - */ - - _execAction: function(methodName, isPost, args){ - var self = this, - context = isPost ? 'post' : 'pre'; - - if(!self._actions.isEmptyObject && self._actions.hasOwnProperty(methodName)){ - for(var key in self._actions[methodName][context]){ - self._actions[methodName][context][key].call(self, args); - } - } - }, - - /** - * Execute Filter - * @since 2.0.0 - * @param {string} methodName - * @param {mixed} value - * @return {mixed} value - */ - - _execFilter: function(methodName, value, args){ - var self = this; - - if(!self._filters.isEmptyObject && self._filters.hasOwnProperty(methodName)){ - for(var key in self._filters[methodName]){ - return self._filters[methodName][key].call(self, args); - } - } else { - return value; - } - }, - - /* Helpers - ---------------------------------------------------------------------- */ - - _helpers: { - - /** - * CamelCase - * @since 2.0.0 - * @param {string} - * @return {string} - */ - - _camelCase: function(string){ - return string.replace(/-([a-z])/g, function(g){ - return g[1].toUpperCase(); - }); - }, - - /** - * Is Element - * @since 2.1.3 - * @param {object} element to test - * @return {boolean} - */ - - _isElement: function(el){ - if(window.HTMLElement){ - return el instanceof HTMLElement; - } else { - return ( - el !== null && - el.nodeType === 1 && - el.nodeName === 'string' - ); - } - } - }, - - /* Public Methods - ---------------------------------------------------------------------- */ - - /** - * Is Mixing - * @since 2.0.0 - * @return {boolean} - */ - - isMixing: function(){ - var self = this; - - return self._execFilter('isMixing', self._mixing); - }, - - /** - * Filter (public) - * @since 2.0.0 - * @param {array} arguments - */ - - filter: function(){ - var self = this, - args = self._parseMultiMixArgs(arguments); - - self._clicking && (self._toggleString = ''); - - self.multiMix({filter: args.command}, args.animate, args.callback); - }, - - /** - * Sort (public) - * @since 2.0.0 - * @param {array} arguments - */ - - sort: function(){ - var self = this, - args = self._parseMultiMixArgs(arguments); - - self.multiMix({sort: args.command}, args.animate, args.callback); - }, - - /** - * Change Layout (public) - * @since 2.0.0 - * @param {array} arguments - */ - - changeLayout: function(){ - var self = this, - args = self._parseMultiMixArgs(arguments); - - self.multiMix({changeLayout: args.command}, args.animate, args.callback); - }, - - /** - * MultiMix - * @since 2.0.0 - * @param {array} arguments - */ - - multiMix: function(){ - var self = this, - args = self._parseMultiMixArgs(arguments); - - self._execAction('multiMix', 0, arguments); - - if(!self._mixing){ - if(self.controls.enable && !self._clicking){ - self.controls.toggleFilterButtons && self._buildToggleArray(); - self._updateControls(args.command, self.controls.toggleFilterButtons); - } - - (self._queue.length < 2) && (self._clicking = false); - - delete self.callbacks._user; - if(args.callback) self.callbacks._user = args.callback; - - var sort = args.command.sort, - filter = args.command.filter, - changeLayout = args.command.changeLayout; - - self._refresh(); - - if(sort){ - self._newSort = self._parseSort(sort); - self._newSortString = sort; - - self._sorting = true; - self._sort(); - } - - if(filter !== undf){ - filter = (filter === 'all') ? self.selectors.target : filter; - - self._activeFilter = filter; - } - - self._filter(); - - if(changeLayout){ - self._newDisplay = (typeof changeLayout === 'string') ? changeLayout : changeLayout.display || self.layout.display; - self._newClass = changeLayout.containerClass || ''; - - if( - self._newDisplay !== self.layout.display || - self._newClass !== self.layout.containerClass - ){ - self._changingLayout = true; - - self._changingClass = (self._newClass !== self.layout.containerClass); - self._changingDisplay = (self._newDisplay !== self.layout.display); - } - } - - self._$targets.css(self._brake); - - self._goMix(args.animate ^ self.animation.enable ? args.animate : self.animation.enable); - - self._execAction('multiMix', 1, arguments); - - } else { - if(self.animation.queue && self._queue.length < self.animation.queueLimit){ - self._queue.push(arguments); - - (self.controls.enable && !self._clicking) && self._updateControls(args.command); - - self._execAction('multiMixQueue', 1, arguments); - - } else { - if(typeof self.callbacks.onMixBusy === 'function'){ - self.callbacks.onMixBusy.call(self._domNode, self._state, self); - } - self._$container.trigger('mixBusy', [self._state, self]); - - self._execAction('multiMixBusy', 1, arguments); - } - } - }, - - /** - * Insert - * @since 2.0.0 - * @param {array} arguments - */ - - insert: function(){ - var self = this, - args = self._parseInsertArgs(arguments), - callback = (typeof args.callback === 'function') ? args.callback : null, - frag = document.createDocumentFragment(), - target = (function(){ - self._refresh(); - - if(self._$targets.length){ - return (args.index < self._$targets.length || !self._$targets.length) ? - self._$targets[args.index] : - self._$targets[self._$targets.length-1].nextElementSibling; - } else { - return self._$parent[0].children[0]; - } - })(); - - self._execAction('insert', 0, arguments); - - if(args.$object){ - for(var i = 0; i < args.$object.length; i++){ - var el = args.$object[i]; - - frag.appendChild(el); - frag.appendChild(document.createTextNode(' ')); - } - - self._$parent[0].insertBefore(frag, target); - } - - self._execAction('insert', 1, arguments); - - if(typeof args.multiMix === 'object'){ - self.multiMix(args.multiMix, callback); - } - }, - - /** - * Prepend - * @since 2.0.0 - * @param {array} arguments - */ - - prepend: function(){ - var self = this, - args = self._parseInsertArgs(arguments); - - self.insert(0, args.$object, args.multiMix, args.callback); - }, - - /** - * Append - * @since 2.0.0 - * @param {array} arguments - */ - - append: function(){ - var self = this, - args = self._parseInsertArgs(arguments); - - self.insert(self._state.totalTargets, args.$object, args.multiMix, args.callback); - }, - - /** - * Get Option - * @since 2.0.0 - * @param {string} string - * @return {mixed} value - */ - - getOption: function(string){ - var self = this, - getProperty = function(obj, prop){ - var parts = prop.split('.'), - last = parts.pop(), - l = parts.length, - i = 1, - current = parts[0] || prop; - - while((obj = obj[current]) && i < l){ - current = parts[i]; - i++; - } - - if(obj !== undf){ - return obj[last] !== undf ? obj[last] : obj; - } - }; - - return string ? self._execFilter('getOption', getProperty(self, string), arguments) : self; - }, - - /** - * Set Options - * @since 2.0.0 - * @param {object} config - */ - - setOptions: function(config){ - var self = this; - - self._execAction('setOptions', 0, arguments); - - typeof config === 'object' && $.extend(true, self, config); - - self._execAction('setOptions', 1, arguments); - }, - - /** - * Get State - * @since 2.0.0 - * @return {object} state - */ - - getState: function(){ - var self = this; - - return self._execFilter('getState', self._state, self); - }, - - /** - * Force Refresh - * @since 2.1.2 - */ - - forceRefresh: function(){ - var self = this; - - self._refresh(false, true); - }, - - /** - * Destroy - * @since 2.0.0 - * @param {boolean} hideAll - */ - - destroy: function(hideAll){ - var self = this, - filters = $.MixItUp.prototype._bound._filter, - sorts = $.MixItUp.prototype._bound._sort; - - self._execAction('destroy', 0, arguments); - - self._$body - .add($(self.selectors.sort)) - .add($(self.selectors.filter)) - .off('.mixItUp'); - - for(var i = 0; i < self._$targets.length; i++){ - var target = self._$targets[i]; - - hideAll && (target.style.display = ''); - - delete target.mixParent; - } - - self._execAction('destroy', 1, arguments); - - if(filters[self.selectors.filter] && filters[self.selectors.filter] > 1) { - filters[self.selectors.filter]--; - } else if(filters[self.selectors.filter] === 1) { - delete filters[self.selectors.filter]; - } - - if(sorts[self.selectors.sort] && sorts[self.selectors.sort] > 1) { - sorts[self.selectors.sort]--; - } else if(sorts[self.selectors.sort] === 1) { - delete sorts[self.selectors.sort]; - } - - delete $.MixItUp.prototype._instances[self._id]; - } - - }; - - /* jQuery Methods - ---------------------------------------------------------------------- */ - - /** - * jQuery .mixItUp() method - * @since 2.0.0 - * @extends $.fn - */ - - $.fn.mixItUp = function(){ - var args = arguments, - dataReturn = [], - eachReturn, - _instantiate = function(domNode, settings){ - var instance = new $.MixItUp(), - rand = function(){ - return ('00000'+(Math.random()*16777216<<0).toString(16)).substr(-6).toUpperCase(); - }; - - instance._execAction('_instantiate', 0, arguments); - - domNode.id = !domNode.id ? 'MixItUp'+rand() : domNode.id; - - if(!instance._instances[domNode.id]){ - instance._instances[domNode.id] = instance; - instance._init(domNode, settings); - } - - instance._execAction('_instantiate', 1, arguments); - }; - - eachReturn = this.each(function(){ - if(args && typeof args[0] === 'string'){ - var instance = $.MixItUp.prototype._instances[this.id]; - if(args[0] === 'isLoaded'){ - dataReturn.push(instance ? true : false); - } else { - var data = instance[args[0]](args[1], args[2], args[3]); - if(data !== undf)dataReturn.push(data); - } - } else { - _instantiate(this, args[0]); - } - }); - - if(dataReturn.length){ - return dataReturn.length > 1 ? dataReturn : dataReturn[0]; - } else { - return eachReturn; - } - }; - - /** - * jQuery .removeStyle() method - * @since 2.0.0 - * @extends $.fn - */ - - $.fn.removeStyle = function(style, prefix){ - prefix = prefix ? prefix : ''; - - return this.each(function(){ - var el = this, - styles = style.split(' '); - - for(var i = 0; i < styles.length; i++){ - for(var j = 0; j < 4; j++){ - switch (j) { - case 0: - var prop = styles[i]; - break; - case 1: - var prop = $.MixItUp.prototype._helpers._camelCase(prop); - break; - case 2: - var prop = prefix+styles[i]; - break; - case 3: - var prop = $.MixItUp.prototype._helpers._camelCase(prefix+styles[i]); - } - - if( - el.style[prop] !== undf && - typeof el.style[prop] !== 'unknown' && - el.style[prop].length > 0 - ){ - el.style[prop] = ''; - } - - if(!prefix && j === 1)break; - } - } - - if(el.attributes && el.attributes.style && el.attributes.style !== undf && el.attributes.style.value === ''){ - el.attributes.removeNamedItem('style'); - } - }); - }; - -})(jQuery); \ No newline at end of file +(function(n,t){"use strict";n.MixItUp=function(){var t=this;t._execAction("_constructor",0);n.extend(t,{selectors:{target:".mix",filter:".filter",sort:".sort"},animation:{enable:!0,effects:"fade scale",duration:600,easing:"ease",perspectiveDistance:"3000",perspectiveOrigin:"50% 50%",queue:!0,queueLimit:1,animateChangeLayout:!1,animateResizeContainer:!0,animateResizeTargets:!1,staggerSequence:!1,reverseOut:!1},callbacks:{onMixLoad:!1,onMixStart:!1,onMixBusy:!1,onMixEnd:!1,onMixFail:!1,_user:!1},controls:{enable:!0,live:!1,toggleFilterButtons:!1,toggleLogic:"or",activeClass:"active"},layout:{display:"inline-block",containerClass:"",containerClassFail:"fail"},load:{filter:"all",sort:!1},_$body:null,_$container:null,_$targets:null,_$parent:null,_$sortButtons:null,_$filterButtons:null,_suckMode:!1,_mixing:!1,_sorting:!1,_clicking:!1,_loading:!0,_changingLayout:!1,_changingClass:!1,_changingDisplay:!1,_origOrder:[],_startOrder:[],_newOrder:[],_activeFilter:null,_toggleArray:[],_toggleString:"",_activeSort:"default:asc",_newSort:null,_startHeight:null,_newHeight:null,_incPadding:!0,_newDisplay:null,_newClass:null,_targetsBound:0,_targetsDone:0,_queue:[],_$show:n(),_$hide:n()});t._execAction("_constructor",1)};n.MixItUp.prototype={constructor:n.MixItUp,_instances:{},_handled:{_filter:{},_sort:{}},_bound:{_filter:{},_sort:{}},_actions:{},_filters:{},extend:function(t){for(var i in t)n.MixItUp.prototype[i]=t[i]},addAction:function(t,i,r,u){n.MixItUp.prototype._addHook("_actions",t,i,r,u)},addFilter:function(t,i,r,u){n.MixItUp.prototype._addHook("_filters",t,i,r,u)},_addHook:function(t,i,r,u,f){var o=n.MixItUp.prototype[t],e={};f=f===1||f==="post"?"post":"pre";e[i]={};e[i][f]={};e[i][f][r]=u;n.extend(!0,o,e)},_init:function(t,i){var r=this,u;if(r._execAction("_init",0,arguments),i&&n.extend(!0,r,i),r._$body=n("body"),r._domNode=t,r._$container=n(t),r._$container.addClass(r.layout.containerClass),r._id=t.id,r._platformDetect(),r._brake=r._getPrefixedCSS("transition","none"),r._refresh(!0),r._$parent=r._$targets.parent().length?r._$targets.parent():r._$container,r.load.sort&&(r._newSort=r._parseSort(r.load.sort),r._newSortString=r.load.sort,r._activeSort=r.load.sort,r._sort(),r._printSort()),r._activeFilter=r.load.filter==="all"?r.selectors.target:r.load.filter==="none"?"":r.load.filter,r.controls.enable&&r._bindHandlers(),r.controls.toggleFilterButtons)for(r._buildToggleArray(),u=0;u-1&&(h=r._helpers._camelCase(o.substring(5,o.length)),u.dataset[h]=c)}u.mixParent===t&&(u.mixParent=r._id)}if(r._$targets.length&&n||!r._origOrder.length&&r._$targets.length)for(r._origOrder=[],f=0;f-1)&&(n(u.selectors.sort).removeClass(u.controls.activeClass),f(i,r),u.sort(o))),r==="filter"&&(e=i.attr("data-filter"),h=u.controls.toggleLogic==="or"?",":"",u.controls.toggleFilterButtons?(u._buildToggleArray(),i.hasClass(u.controls.activeClass)?(f(i,r,!0),s=u._toggleArray.indexOf(e),u._toggleArray.splice(s,1)):(f(i,r),u._toggleArray.push(e)),u._toggleArray=n.grep(u._toggleArray,function(n){return n}),u._toggleString=u._toggleArray.join(h),u.filter(u._toggleString)):i.hasClass(u.controls.activeClass)||(n(u.selectors.filter).removeClass(u.controls.activeClass),f(i,r),u.filter(e))),u._execAction("_processClick",1,arguments)):(typeof u.callbacks.onMixBusy=="function"&&u.callbacks.onMixBusy.call(u._domNode,u._state,u),u._execAction("_processClickBusy",1,arguments))},_buildToggleArray:function(){var n=this,i=n._activeFilter.replace(/\s/g,""),t,r;if(n._execAction("_buildToggleArray",0,arguments),n.controls.toggleLogic==="or")n._toggleArray=i.split(",");else for(n._toggleArray=i.split("."),n._toggleArray[0]||n._toggleArray.shift(),t=0;r=n._toggleArray[t];t++)n._toggleArray[t]="."+r;n._execAction("_buildToggleArray",1,arguments)},_updateControls:function(i,r){var u=this,f={filter:i.filter,sort:i.sort},h=function(n,t){try{r&&e==="filter"&&!(f.filter==="none"||f.filter==="")?n.filter(t).addClass(u.controls.activeClass):n.removeClass(u.controls.activeClass).filter(t).addClass(u.controls.activeClass)}catch(i){}},e="filter",o=null,s;for(u._execAction("_updateControls",0,arguments),i.filter===t&&(f.filter=u._activeFilter),i.sort===t&&(f.sort=u._activeSort),f.filter===u.selectors.target&&(f.filter="all"),s=0;s<2;s++)o=u.controls.live?n(u.selectors[e]):u["_$"+e+"Buttons"],o&&h(o,"[data-"+e+'="'+f[e]+'"]'),e="sort";u._execAction("_updateControls",1,arguments)},_filter:function(){var t=this,i,r;for(t._execAction("_filter",0),i=0;ie?o==="asc"?1:-1:f===e&&u._newSort.length>i+1?u._compare(n,t,i+1):0},_printSort:function(n){var t=this,s=n?t._startOrder:t._newOrder,u=t._$parent[0].querySelectorAll(t.selectors.target),h=u.length?u[u.length-1].nextElementSibling:null,r=document.createDocumentFragment(),f,e,i,o,c;for(t._execAction("_printSort",0,arguments),i=0;i-1){if(i&&(r=n.animation.effects.indexOf(t+"("),r>-1)){var u=n.animation.effects.substring(r),f=/\(([^)]+)\)/.exec(u),e=f[1];return{val:e}}return!0}return!1},u=function(n,t){return t?n.charAt(0)==="-"?n.substr(1,n.length):"-"+n:n},r=function(n,r){for(var o=[["scale",".01"],["translateX","20px"],["translateY","20px"],["translateZ","20px"],["rotateX","90deg"],["rotateY","90deg"],["rotateZ","180deg"],],f=0;f-1||u.originalEvent.propertyName.indexOf("opacity")>-1)&&n(u.originalEvent.target).is(i.selectors.target)&&(t.off(".mixItUp"),r.dataset.bound="",i._targetDone())})}i._execAction("_bindTargetDone",1,arguments)},_targetDone:function(){var n=this;n._execAction("_targetDone",0);n._targetsDone++;n._targetsDone===n._targetsBound&&n._cleanUp();n._execAction("_targetDone",1)},_cleanUp:function(){var t=this,i=t.animation.animateResizeTargets?"transform opacity width height margin-bottom margin-right":"transform opacity",r=function(){t._$targets.removeStyle("transition",t._prefix)};t._execAction("_cleanUp",0);t._changingLayout?t._$show.css("display",t._newDisplay):t._$show.css("display",t.layout.display);t._$targets.css(t._brake);t._$targets.removeStyle(i,t._prefix).removeAttr("data-inter-pos-x data-inter-pos-y data-final-pos-x data-final-pos-y data-orig-pos-x data-orig-pos-y data-orig-height data-orig-width data-final-height data-final-width data-inter-width data-inter-height data-orig-margin-right data-orig-margin-bottom data-inter-margin-right data-inter-margin-bottom data-final-margin-right data-final-margin-bottom");t._$hide.removeStyle("display");t._$parent.removeStyle("height transition perspective-distance perspective perspective-origin-x perspective-origin-y perspective-origin perspectiveOrigin",t._prefix);t._sorting&&(t._printSort(),t._activeSort=t._newSortString,t._sorting=!1);t._changingLayout&&(t._changingDisplay&&(t.layout.display=t._newDisplay,t._changingDisplay=!1),t._changingClass&&(t._$parent.removeClass(t.layout.containerClass).addClass(t._newClass),t.layout.containerClass=t._newClass,t._changingClass=!1),t._changingLayout=!1);t._refresh();t._buildState();t._state.fail&&t._$container.addClass(t.layout.containerClassFail);t._$show=n();t._$hide=n();window.requestAnimationFrame&&requestAnimationFrame(r);t._mixing=!1;typeof t.callbacks._user=="function"&&t.callbacks._user.call(t._domNode,t._state,t);typeof t.callbacks.onMixEnd=="function"&&t.callbacks.onMixEnd.call(t._domNode,t._state,t);t._$container.trigger("mixEnd",[t._state,t]);t._state.fail&&(typeof t.callbacks.onMixFail=="function"&&t.callbacks.onMixFail.call(t._domNode,t._state,t),t._$container.trigger("mixFail",[t._state,t]));t._loading&&(typeof t.callbacks.onMixLoad=="function"&&t.callbacks.onMixLoad.call(t._domNode,t._state,t),t._$container.trigger("mixLoad",[t._state,t]));t._queue.length&&(t._execAction("_queue",0),t.multiMix(t._queue[0][0],t._queue[0][1],t._queue[0][2]),t._queue.splice(0,1));t._execAction("_cleanUp",1);t._loading=!1},_getPrefixedCSS:function(n,t,i){for(var f=this,e={},u="",r=-1,r=0;r<2;r++)u=r===0?f._prefix:"",e[u+n]=i?u+t:t;return f._execFilter("_getPrefixedCSS",e,arguments)},_getDelay:function(n){var t=this,i=typeof t.animation.staggerSequence=="function"?t.animation.staggerSequence.call(t._domNode,n,t._state):n,r=t.animation.stagger?i*t.animation.staggerDuration:0;return t._execFilter("_getDelay",r,arguments)},_parseMultiMixArgs:function(n){for(var t,u=this,i={command:null,animate:u.animation.enable,callback:null},r=0;r1?r[i.selectors.filter]--:r[i.selectors.filter]===1&&delete r[i.selectors.filter];u[i.selectors.sort]&&u[i.selectors.sort]>1?u[i.selectors.sort]--:u[i.selectors.sort]===1&&delete u[i.selectors.sort];delete n.MixItUp.prototype._instances[i._id]}};n.fn.mixItUp=function(){var i=arguments,r=[],u,f=function(t,i){var r=new n.MixItUp,u=function(){return("00000"+(Math.random()*16777216<<0).toString(16)).substr(-6).toUpperCase()};r._execAction("_instantiate",0,arguments);t.id=t.id?t.id:"MixItUp"+u();r._instances[t.id]||(r._instances[t.id]=r,r._init(t,i));r._execAction("_instantiate",1,arguments)};return u=this.each(function(){var u,e;i&&typeof i[0]=="string"?(u=n.MixItUp.prototype._instances[this.id],i[0]==="isLoaded"?r.push(u?!0:!1):(e=u[i[0]](i[1],i[2],i[3]),e!==t&&r.push(e))):f(this,i[0])}),r.length?r.length>1?r:r[0]:u};n.fn.removeStyle=function(i,r){return r=r?r:"",this.each(function(){for(var o,f,u=this,s=i.split(" "),e=0;e0&&(u.style[f]=""),!r&&o===1)break}u.attributes&&u.attributes.style&&u.attributes.style!==t&&u.attributes.style.value===""&&u.attributes.removeNamedItem("style")})}})(jQuery); \ No newline at end of file diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 7876aa41c..d15198709 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -26,6 +26,7 @@ #endregion using System.Dynamic; using System.Linq; +using System.Web.UI.WebControls; using MarkdownSharp; @@ -44,6 +45,8 @@ using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Services.Notification; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; using PlexRequests.UI.Helpers; using PlexRequests.UI.Models; @@ -63,6 +66,7 @@ namespace PlexRequests.UI.Modules private ISonarrApi SonarrApi { get; } private PushbulletApi PushbulletApi { get; } private ICouchPotatoApi CpApi { get; } + private IRepository LogsRepo { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); public AdminModule(ISettingsService rpService, @@ -76,7 +80,8 @@ namespace PlexRequests.UI.Modules IPlexApi plexApi, ISettingsService pbSettings, PushbulletApi pbApi, - ICouchPotatoApi cpApi) : base("admin") + ICouchPotatoApi cpApi, + IRepository logsRepo) : base("admin") { RpService = rpService; CpService = cpService; @@ -90,6 +95,7 @@ namespace PlexRequests.UI.Modules PushbulletApi = pbApi; CpApi = cpApi; SickRageService = sickrage; + LogsRepo = logsRepo; #if !DEBUG this.RequiresAuthentication(); @@ -126,6 +132,10 @@ namespace PlexRequests.UI.Modules Get["/pushbulletnotification"] = _ => PushbulletNotifications(); Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); + + Get["/logs"] = _ => Logs(); + Get["/loglevel"] = _ => GetLogLevels(); + Post["/loglevel"] = _ => UpdateLogLevels(Request.Form.level); } private Negotiator Authentication() @@ -246,8 +256,8 @@ namespace PlexRequests.UI.Modules } var result = CpService.SaveSettings(couchPotatoSettings); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } @@ -422,5 +432,24 @@ namespace PlexRequests.UI.Modules return Response.AsJson(profiles); } + + private Negotiator Logs() + { + var allLogs = LogsRepo.GetAll().OrderByDescending(x => x.Id).Take(20); + return View["Logs", allLogs]; + } + + private Response GetLogLevels() + { + var levels = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database")); + return Response.AsJson(levels.Levels); + } + + private Response UpdateLogLevels(int level) + { + var newLevel = LogLevel.FromOrdinal(level); + LoggingHelper.ReconfigureLogLevel(newLevel); + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}"}); + } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/ApprovalModule.cs b/PlexRequests.UI/Modules/ApprovalModule.cs index 10795ceaa..b2c6217bb 100644 --- a/PlexRequests.UI/Modules/ApprovalModule.cs +++ b/PlexRequests.UI/Modules/ApprovalModule.cs @@ -169,18 +169,14 @@ namespace PlexRequests.UI.Modules private Response RequestMovieAndUpdateStatus(RequestedModel request) { - if (!Context.CurrentUser.IsAuthenticated()) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." }); - } - var cpSettings = CpService.GetSettings(); var cp = new CouchPotatoApi(); - Log.Info("Adding movie to CP : {0}", request.Title); + Log.Info("Adding movie to CouchPotato : {0}", request.Title); if (!cpSettings.Enabled) { // Approve it request.Approved = true; + Log.Warn("We approved movie: {0} but could not add it to CouchPotato because it has not been setup", request.Title); // Update the record var inserted = Service.UpdateRequest(request); @@ -226,6 +222,11 @@ namespace PlexRequests.UI.Modules /// private Response ApproveAll() { + if (!Context.CurrentUser.IsAuthenticated()) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." }); + } + var requests = Service.GetAll().Where(x => x.Approved == false); var requestedModels = requests as RequestedModel[] ?? requests.ToArray(); if (!requestedModels.Any()) diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs index 1a10bcc77..36f0619c4 100644 --- a/PlexRequests.UI/Modules/LoginModule.cs +++ b/PlexRequests.UI/Modules/LoginModule.cs @@ -51,7 +51,7 @@ namespace PlexRequests.UI.Modules model.AdminExists = adminCreated; return View["Login/Index", model]; } - + }; Get["/logout"] = x => this.LogoutAndRedirect("~/"); @@ -76,7 +76,8 @@ namespace PlexRequests.UI.Modules return this.LoginAndRedirect(userId.Value, expiry); }; - Get["/register"] = x => { + Get["/register"] = x => + { { dynamic model = new ExpandoObject(); model.Errored = Request.Query.error.HasValue; @@ -87,13 +88,13 @@ namespace PlexRequests.UI.Modules Post["/register"] = x => { - var username = (string) Request.Form.Username; + var username = (string)Request.Form.Username; var exists = UserMapper.DoUsersExist(); if (exists) { - return Context.GetRedirect("~/register?error=true&username=" + username); + return Context.GetRedirect("~/register?error=true"); } - var userId = UserMapper.CreateUser(username, Request.Form.Password); + var userId = UserMapper.CreateUser(username, Request.Form.Password, new[] { "Admin" }); Session[SessionKeys.UsernameKey] = username; return this.LoginAndRedirect((Guid)userId); }; @@ -116,7 +117,7 @@ namespace PlexRequests.UI.Modules var newPasswordAgain = Request.Form.NewPasswordAgain; if (!newPassword.Equals(newPasswordAgain)) { - + } var result = UserMapper.UpdateUser(username, oldPass, newPassword); diff --git a/PlexRequests.UI/NLog.config b/PlexRequests.UI/NLog.config index 39605a1f7..9adcfc54b 100644 --- a/PlexRequests.UI/NLog.config +++ b/PlexRequests.UI/NLog.config @@ -13,7 +13,7 @@ layout="${date} ${logger} ${level}: ${message}" /> - - + --> - + \ No newline at end of file diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 44ec9805c..b88556373 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -97,6 +97,10 @@ False ..\Assemblies\Mono.Data.Sqlite.dll + + ..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll + True + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll True @@ -335,6 +339,9 @@ Always + + Always + web.config diff --git a/PlexRequests.UI/Program.cs b/PlexRequests.UI/Program.cs index 80f0adebb..f84972699 100644 --- a/PlexRequests.UI/Program.cs +++ b/PlexRequests.UI/Program.cs @@ -30,6 +30,8 @@ using System.Data; using Microsoft.Owin.Hosting; using Mono.Data.Sqlite; +using Mono.Unix; +using Mono.Unix.Native; using NLog; using NLog.Config; @@ -55,7 +57,7 @@ namespace PlexRequests.UI int portResult; if (!int.TryParse(args[0], out portResult)) { - Console.WriteLine("Incorrect Port format. Press Any Key to shut down."); + Console.WriteLine("Incorrect Port format. Press any key."); Console.ReadLine(); Environment.Exit(1); } @@ -65,7 +67,8 @@ namespace PlexRequests.UI WriteOutVersion(); var s = new Setup(); - s.SetupDb(); + var cn = s.SetupDb(); + ConfigureTargets(cn); if (port == -1) port = GetStartupPort(); @@ -76,18 +79,29 @@ namespace PlexRequests.UI }; try { - - using (WebApp.Start(options)) - { - Console.WriteLine($"Request Plex is running on the following port: {port}"); - Console.WriteLine("Press any key to exit"); - Console.ReadLine(); - } - + using (WebApp.Start(options)) + { + Console.WriteLine($"Request Plex is running on the following: http://+:{port}/"); + + if (Type.GetType("Mono.Runtime") != null) + { + Log.Info("We are on Mono!"); + // on mono, processes will usually run as daemons - this allows you to listen + // for termination signals (ctrl+c, shutdown, etc) and finalize correctly + UnixSignal.WaitAny( + new[] { new UnixSignal(Signum.SIGINT), new UnixSignal(Signum.SIGTERM), new UnixSignal(Signum.SIGQUIT), new UnixSignal(Signum.SIGHUP) }); + } + else + { + Log.Info("This is not Mono"); + Console.WriteLine("Press any key to exit"); + Console.ReadLine(); + } + } } catch (Exception e) { - var a = e.Message; + Log.Fatal(e); throw; } } @@ -116,59 +130,7 @@ namespace PlexRequests.UI private static void ConfigureTargets(string connectionString) { - LogManager.ThrowExceptions = true; - // Step 1. Create configuration object - var config = new LoggingConfiguration(); - - // Step 2. Create targets and add them to the configuration - var databaseTarget = new DatabaseTarget - { - CommandType = CommandType.Text, - ConnectionString = connectionString, - DBProvider = "Mono.Data.Sqlite.SqliteConnection, Mono.Data.Sqlite, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756", - Name = "database" - }; - - - var messageParam = new DatabaseParameterInfo { Name = "@Message", Layout = "${message}" }; - var callsiteParam = new DatabaseParameterInfo { Name = "@Callsite", Layout = "${callsite}" }; - var levelParam = new DatabaseParameterInfo { Name = "@Level", Layout = "${level}" }; - var usernameParam = new DatabaseParameterInfo { Name = "@Username", Layout = "${identity}" }; - var dateParam = new DatabaseParameterInfo { Name = "@Date", Layout = "${date}" }; - var loggerParam = new DatabaseParameterInfo { Name = "@Logger", Layout = "${logger}" }; - var exceptionParam = new DatabaseParameterInfo { Name = "@Exception", Layout = "${exception:tostring}" }; - - databaseTarget.Parameters.Add(messageParam); - databaseTarget.Parameters.Add(callsiteParam); - databaseTarget.Parameters.Add(levelParam); - databaseTarget.Parameters.Add(usernameParam); - databaseTarget.Parameters.Add(dateParam); - databaseTarget.Parameters.Add(loggerParam); - databaseTarget.Parameters.Add(exceptionParam); - - databaseTarget.CommandText = "INSERT INTO Log (Username,Date,Level,Logger, Message, Callsite, Exception) VALUES(@Username,@Date,@Level,@Logger, @Message, @Callsite, @Exception);"; - config.AddTarget("database", databaseTarget); - - // Step 4. Define rules - var rule1 = new LoggingRule("*", LogLevel.Error, databaseTarget); - config.LoggingRules.Add(rule1); - - try - { - - // Step 5. Activate the configuration - LogManager.Configuration = config; - } - catch (Exception) - { - - throw; - } - - // Example usage - Logger logger = LogManager.GetLogger("Example"); - - logger.Error("error log message"); + LoggingHelper.ConfigureLogging(connectionString); } } } diff --git a/PlexRequests.UI/Views/Admin/Logs.cshtml b/PlexRequests.UI/Views/Admin/Logs.cshtml new file mode 100644 index 000000000..c11e9bcbe --- /dev/null +++ b/PlexRequests.UI/Views/Admin/Logs.cshtml @@ -0,0 +1,118 @@ +@Html.Partial("_Sidebar") + +
+
+ Logs + +
+
+ +
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + + @foreach (var m in Model) + { + + + + + + + + + } +
MessageLoggerExceptionCallsiteLog LevelDate
+ @m.Message + + @m.Logger + + @m.Exception + + @m.Callsite + + @m.Level + + @m.Date +
+ + +
+
+ + + + \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/_Sidebar.cshtml b/PlexRequests.UI/Views/Admin/_Sidebar.cshtml index 2877729c7..641a857da 100644 --- a/PlexRequests.UI/Views/Admin/_Sidebar.cshtml +++ b/PlexRequests.UI/Views/Admin/_Sidebar.cshtml @@ -70,7 +70,14 @@ { Pushbullet Notifications } - + @if (Context.Request.Path == "/admin/logs") + { + Logs + } + else + { + Logs + } @if (Context.Request.Path == "/admin/status") { Status diff --git a/PlexRequests.UI/Views/Shared/_Layout.cshtml b/PlexRequests.UI/Views/Shared/_Layout.cshtml index d4c94f6b9..b96e89b20 100644 --- a/PlexRequests.UI/Views/Shared/_Layout.cshtml +++ b/PlexRequests.UI/Views/Shared/_Layout.cshtml @@ -57,26 +57,26 @@ {
  • Requests
  • } - @if (Context.CurrentUser.IsAuthenticated()) - { - if (Context.Request.Path == "/admin") - { -
  • Admin
  • - } - else - { -
  • Admin
  • - } - }
    @@ -34,6 +37,10 @@ { } + @if (Model.SearchForMusic) + { + + } }
    @@ -90,6 +97,19 @@
    } + + @if (Model.SearchForMusic) + { + +
    + +
    +
    + +
    +
    +
    + } diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index 5006a58c2..641e0d2a6 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -12,6 +12,10 @@ {
  • TV Shows
  • } + @if (Model.SearchForMusic) + { +
  • Music
  • + } @@ -53,11 +57,30 @@ } + + @if (Model.SearchForMusic) + { + +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + } + + + + + From 64125cc44c692dbc7a220e3928359f2fc469e65d Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 31 Mar 2016 10:23:17 +0100 Subject: [PATCH 73/94] First attempt at #123 Added a bunch of tracing and also some more error handling --- .../AvailabilityUpdateService.cs | 7 ++- .../PlexAvailabilityChecker.cs | 57 ++++++++++++++++--- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/PlexRequests.Services/AvailabilityUpdateService.cs b/PlexRequests.Services/AvailabilityUpdateService.cs index 0774307e6..19b23bbe0 100644 --- a/PlexRequests.Services/AvailabilityUpdateService.cs +++ b/PlexRequests.Services/AvailabilityUpdateService.cs @@ -48,9 +48,12 @@ namespace PlexRequests.Services { public AvailabilityUpdateService() { + var memCache = new MemoryCacheProvider(); + var dbConfig = new DbConfiguration(new SqliteFactory()); + var repo = new SettingsJsonRepository(dbConfig, memCache); + ConfigurationReader = new ConfigurationReader(); - var repo = new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()); - Checker = new PlexAvailabilityChecker(new SettingsServiceV2(repo), new SettingsServiceV2(repo), new JsonRequestService(new RequestJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())), new PlexApi()); + Checker = new PlexAvailabilityChecker(new SettingsServiceV2(repo), new SettingsServiceV2(repo), new JsonRequestService(new RequestJsonRepository(dbConfig, memCache)), new PlexApi()); HostingEnvironment.RegisterObject(this); } diff --git a/PlexRequests.Services/PlexAvailabilityChecker.cs b/PlexRequests.Services/PlexAvailabilityChecker.cs index 915cbda28..e7cf9812e 100644 --- a/PlexRequests.Services/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/PlexAvailabilityChecker.cs @@ -31,8 +31,10 @@ using System.Linq; using NLog; using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; using PlexRequests.Helpers.Exceptions; using PlexRequests.Services.Interfaces; using PlexRequests.Store; @@ -58,27 +60,66 @@ namespace PlexRequests.Services public void CheckAndUpdateAll(long check) { + Log.Trace("Getting the settings"); var plexSettings = Plex.GetSettings(); var authSettings = Auth.GetSettings(); + Log.Trace("Getting all the requests"); var requests = RequestService.GetAll(); var requestedModels = requests as RequestedModel[] ?? requests.ToArray(); + Log.Trace("Requests Count {0}", requestedModels.Length); + if (!ValidateSettings(plexSettings, authSettings) || !requestedModels.Any()) { + Log.Info("Validation of the settings failed or there is no requests."); return; } var modifiedModel = new List(); foreach (var r in requestedModels) { - var results = PlexApi.SearchContent(authSettings.PlexAuthToken, r.Title, plexSettings.FullUri); - var result = results.Video.FirstOrDefault(x => x.Title == r.Title); - var originalRequest = RequestService.Get(r.Id); + Log.Trace("We are going to see if Plex has the following title: {0}", r.Title); + PlexSearch results; + try + { + results = PlexApi.SearchContent(authSettings.PlexAuthToken, r.Title, plexSettings.FullUri); + } + catch (Exception e) + { + Log.Error("We failed to search Plex for the following request:"); + Log.Error(r.DumpJson()); + Log.Error(e); + break; // Let's finish processing and not crash the process, there is a reason why we cannot connect. + } + + if (results == null) + { + Log.Trace("Could not find any matching result for this title."); + continue; + } + + Log.Trace("Search results from Plex for the following request: {0}", r.Title); + Log.Trace(results.DumpJson()); + + var videoResult = results.Video.FirstOrDefault(x => x.Title == r.Title); + var directoryResult = results.Directory?.Title.Equals(r.Title, StringComparison.CurrentCultureIgnoreCase); + + Log.Trace("The result from Plex where the title matches for the video : {0}", videoResult != null); + Log.Trace("The result from Plex where the title matches for the directory : {0}", directoryResult != null); - originalRequest.Available = result != null; - modifiedModel.Add(originalRequest); + if (videoResult != null || directoryResult != null) + { + r.Available = true; + modifiedModel.Add(r); + continue; + } + + Log.Trace("The result from Plex where the title's match was null, so that means the content is not yet in Plex."); } + Log.Trace("Updating the requests now"); + Log.Trace("Requests that will be updates:"); + Log.Trace(modifiedModel.SelectMany(x => x.Title).DumpJson()); RequestService.BatchUpdate(modifiedModel); } @@ -91,24 +132,26 @@ namespace PlexRequests.Services /// The settings are not configured for Plex or Authentication public bool IsAvailable(string title, string year) { + Log.Trace("Checking if the following {0} {1} is available in Plex", title, year); var plexSettings = Plex.GetSettings(); var authSettings = Auth.GetSettings(); if (!ValidateSettings(plexSettings, authSettings)) { + Log.Warn("The settings are not configured"); throw new ApplicationSettingsException("The settings are not configured for Plex or Authentication"); } var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri); if (!string.IsNullOrEmpty(year)) { var result = results.Video?.FirstOrDefault(x => x.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase) && x.Year == year); - var directoryTitle = results.Directory?.Title == title && results.Directory?.Year == year; + var directoryTitle = string.Equals(results.Directory?.Title, title, StringComparison.CurrentCultureIgnoreCase) && results.Directory?.Year == year; return result?.Title != null || directoryTitle; } else { var result = results.Video?.FirstOrDefault(x => x.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase)); - var directoryTitle = results.Directory?.Title == title; + var directoryTitle = string.Equals(results.Directory?.Title, title, StringComparison.CurrentCultureIgnoreCase); return result?.Title != null || directoryTitle; } From d6763bf43503dfd40e635ec30bfe0510ee68576c Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 31 Mar 2016 10:40:54 +0100 Subject: [PATCH 74/94] Added happy path tests for the Checker --- .../PlexAvailabilityCheckerTests.cs | 102 +++++++++++++++++- .../PlexAvailabilityChecker.cs | 10 +- 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs index d9c3f1881..2fd0f4803 100644 --- a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs +++ b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs @@ -284,8 +284,7 @@ namespace PlexRequests.Services.Tests [Test] - [Ignore("Need to work out Plex Directory vs Video objects")] - public void CheckAndUpdateRequestsTest() + public void CheckAndUpdateRequestsThatDoNotExistInPlexTest() { var requests = new List { @@ -315,7 +314,26 @@ namespace PlexRequests.Services.Tests } }; - var search = new PlexSearch { }; + var search = new PlexSearch + { + Video = new List