From a9e24aaeca7ca0f5fc1bd4e9489715289b0bbb11 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 28 Oct 2016 22:35:02 +0100 Subject: [PATCH 01/68] Fixed an issue to stop blatting over the base url --- PlexRequests.UI/Modules/UserWizardModule.cs | 14 ++++++++++--- PlexRequests.sln | 22 --------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/PlexRequests.UI/Modules/UserWizardModule.cs b/PlexRequests.UI/Modules/UserWizardModule.cs index a0a94bb4f..e693f4a9c 100644 --- a/PlexRequests.UI/Modules/UserWizardModule.cs +++ b/PlexRequests.UI/Modules/UserWizardModule.cs @@ -153,8 +153,15 @@ namespace PlexRequests.UI.Modules { return Response.AsJson(valid.SendJsonError()); } - - var result = await PlexRequestSettings.SaveSettingsAsync(form); + var currentSettings = await PlexRequestSettings.GetSettingsAsync(); + currentSettings.SearchForMovies = form.SearchForMovies; + currentSettings.SearchForTvShows = form.SearchForTvShows; + currentSettings.SearchForMusic = form.SearchForMusic; + currentSettings.RequireMovieApproval = form.RequireMovieApproval; + currentSettings.RequireTvShowApproval = form.RequireTvShowApproval; + currentSettings.RequireMusicApproval = form.RequireMusicApproval; + + var result = await PlexRequestSettings.SaveSettingsAsync(currentSettings); if (result) { return Response.AsJson(new { Result = true }); @@ -190,8 +197,9 @@ namespace PlexRequests.UI.Modules settings.Wizard = true; await PlexRequestSettings.SaveSettingsAsync(settings); + var baseUrl = string.IsNullOrEmpty(settings.BaseUrl) ? string.Empty : $"/{settings.BaseUrl}"; - return this.LoginAndRedirect((Guid)userId, fallbackRedirectUrl: "/search"); + return this.LoginAndRedirect((Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search"); } } } \ No newline at end of file diff --git a/PlexRequests.sln b/PlexRequests.sln index 91f74cf72..6aedc31cd 100644 --- a/PlexRequests.sln +++ b/PlexRequests.sln @@ -37,14 +37,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Updater", "Ple EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers.Tests", "PlexRequests.Helpers.Tests\PlexRequests.Helpers.Tests.csproj", "{0E6395D3-B074-49E8-898D-0EB99E507E0E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Automation", "PlexRequests.Automation\PlexRequests.Automation.csproj", "{40DC5C6C-2860-44D0-9F91-DEB84C22D103}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Automation", "Automation", "{F9F1B821-AF59-447A-993B-2B328F7274D4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Automation.Pages", "PlexRequests.Automation.Pages\PlexRequests.Automation.Pages.csproj", "{F8D4A7A7-F0FB-4D04-81DB-637C953E0707}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequestes.Automation.Helpers", "PlexRequestes.Automation.Helpers\PlexRequestes.Automation.Helpers.csproj", "{DC8BACEF-C284-4A8F-A9AA-7F49EFABA288}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core.Migration", "PlexRequests.Core.Migration\PlexRequests.Core.Migration.csproj", "{8406EE57-D533-47C0-9302-C6B5F8C31E55}" EndProject Global @@ -101,15 +93,6 @@ Global {0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {40DC5C6C-2860-44D0-9F91-DEB84C22D103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {40DC5C6C-2860-44D0-9F91-DEB84C22D103}.Debug|Any CPU.Build.0 = Debug|Any CPU - {40DC5C6C-2860-44D0-9F91-DEB84C22D103}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8D4A7A7-F0FB-4D04-81DB-637C953E0707}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8D4A7A7-F0FB-4D04-81DB-637C953E0707}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8D4A7A7-F0FB-4D04-81DB-637C953E0707}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC8BACEF-C284-4A8F-A9AA-7F49EFABA288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC8BACEF-C284-4A8F-A9AA-7F49EFABA288}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC8BACEF-C284-4A8F-A9AA-7F49EFABA288}.Release|Any CPU.ActiveCfg = Release|Any CPU {8406EE57-D533-47C0-9302-C6B5F8C31E55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8406EE57-D533-47C0-9302-C6B5F8C31E55}.Debug|Any CPU.Build.0 = Debug|Any CPU {8406EE57-D533-47C0-9302-C6B5F8C31E55}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -118,11 +101,6 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {40DC5C6C-2860-44D0-9F91-DEB84C22D103} = {F9F1B821-AF59-447A-993B-2B328F7274D4} - {F8D4A7A7-F0FB-4D04-81DB-637C953E0707} = {F9F1B821-AF59-447A-993B-2B328F7274D4} - {DC8BACEF-C284-4A8F-A9AA-7F49EFABA288} = {F9F1B821-AF59-447A-993B-2B328F7274D4} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution RESX_PrefixTranslations = False EndGlobalSection From 683e4831c42042ba68a4d976d56e7f18d12f8348 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 2 Nov 2016 21:34:33 +0000 Subject: [PATCH 02/68] fixed #622 --- .github/ISSUE_TEMPLATE.md | 42 +- .travis.yml | 12 +- PlexRequests.Api.Interfaces/IApiRequest.cs | 80 ++-- PlexRequests.Api.Interfaces/IHeadphonesApi.cs | 86 ++-- PlexRequests.Api.Interfaces/IPushbulletApi.cs | 88 ++-- PlexRequests.Api.Interfaces/IPushoverApi.cs | 72 ++-- PlexRequests.Api.Interfaces/ISickRageApi.cs | 88 ++-- .../PlexRequests.Api.Interfaces.csproj | 154 +++---- .../Properties/AssemblyInfo.cs | 76 ++-- .../Movie/CouchPotatoMovies.cs | 24 +- .../Movie/CouchPotatoProfiles.cs | 114 ++--- .../Movie/CouchPotatoStatus.cs | 26 +- .../Music/HeadphonesAlbumSearchResult.cs | 88 ++-- .../Music/HeadphonesArtistSearchResult.cs | 72 ++-- .../Music/HeadphonesVersion.cs | 72 ++-- .../Music/MusicBrainzReleaseInfo.cs | 134 +++--- .../Notifications/PushbulletPush.cs | 74 ++-- .../Notifications/PushbulletResponse.cs | 94 ++-- .../Notifications/PushoverResponse.cs | 66 +-- PlexRequests.Api.Models/Plex/PlexError.cs | 78 ++-- PlexRequests.Api.Models/Plex/PlexLibraries.cs | 44 +- PlexRequests.Api.Models/Plex/PlexStatus.cs | 182 ++++---- .../Plex/PlexUserRequest.cs | 80 ++-- .../Properties/AssemblyInfo.cs | 76 ++-- .../SickRage/SickRagePing.cs | 72 ++-- .../SickRage/SickRageSeasonList.cs | 4 +- .../SickRage/SickRageShowInformation.cs | 162 +++---- .../SickRage/SickRageStatus.cs | 68 +-- .../SickRage/SickRageTvAdd.cs | 74 ++-- .../SickRage/SickrageShows.cs | 82 ++-- .../Sonarr/SonarrAllSeries.cs | 138 +++--- .../Sonarr/SonarrProfile.cs | 112 ++--- .../Sonarr/SystemStatus.cs | 100 ++--- PlexRequests.Api.Models/Tv/Authentication.cs | 64 +-- PlexRequests.Api.Models/Tv/TvSearchResult.cs | 136 +++--- PlexRequests.Api.Models/Tv/TvShow.cs | 122 +++--- PlexRequests.Api.Models/Tv/TvShowImages.cs | 106 ++--- PlexRequests.Api/HeadphonesApi.cs | 406 +++++++++--------- PlexRequests.Api/MovieBase.cs | 72 ++-- PlexRequests.Api/MusicBrainzApi.cs | 230 +++++----- PlexRequests.Api/Properties/AssemblyInfo.cs | 74 ++-- PlexRequests.Api/PushbulletApi.cs | 128 +++--- PlexRequests.Api/PushoverApi.cs | 114 ++--- PlexRequests.Api/TheTvDbApi.cs | 288 ++++++------- PlexRequests.Api/TvBase.cs | 78 ++-- PlexRequests.Api/TvMazeBase.cs | 64 +-- .../Properties/AssemblyInfo.cs | 74 ++-- PlexRequests.Core.Tests/StatusCheckerTests.cs | 92 ++-- PlexRequests.Core/IRequestService.cs | 112 ++--- PlexRequests.Core/ISettingsService.cs | 80 ++-- PlexRequests.Core/Models/StatusModel.cs | 74 ++-- PlexRequests.Core/SettingModels/Settings.cs | 64 +-- PlexRequests.Core/UserIdentity.cs | 76 ++-- .../AssemblyHelperTests.cs | 80 ++-- .../HtmlRemoverTests.cs | 110 ++--- .../PasswordHasherTests.cs | 96 ++--- .../Properties/AssemblyInfo.cs | 74 ++-- PlexRequests.Helpers.Tests/UriHelperTests.cs | 214 ++++----- PlexRequests.Helpers/AssemblyHelper.cs | 96 ++--- PlexRequests.Helpers/ByteConverterHelper.cs | 106 ++--- .../ApplicationSettingsException.cs | 94 ++-- PlexRequests.Helpers/HtmlRemover.cs | 88 ++-- PlexRequests.Helpers/ICacheProvider.cs | 134 +++--- PlexRequests.Helpers/JsonConvertHelper.cs | 100 ++--- PlexRequests.Helpers/ObjectCopier.cs | 112 ++--- PlexRequests.Helpers/PasswordHasher.cs | 138 +++--- PlexRequests.Helpers/SerializerSettings.cs | 88 ++-- PlexRequests.Helpers/StringCipher.cs | 288 ++++++------- PlexRequests.Helpers/UriHelper.cs | 230 +++++----- .../Properties/AssemblyInfo.cs | 74 ++-- .../Interfaces/ICouchPotatoCacher.cs | 16 +- .../Interfaces/IIntervals.cs | 68 +-- .../Interfaces/INotification.cs | 92 ++-- .../Interfaces/INotificationService.cs | 104 ++--- .../Notification/NotificationService.cs | 206 ++++----- .../Properties/AssemblyInfo.cs | 74 ++-- PlexRequests.Store/DbConfiguration.cs | 204 ++++----- PlexRequests.Store/Entity.cs | 72 ++-- PlexRequests.Store/ISqliteConfiguration.cs | 106 ++--- PlexRequests.Store/Models/GlobalSettings.cs | 76 ++-- PlexRequests.Store/Models/LogEntity.cs | 94 ++-- PlexRequests.Store/Models/RequestBlobs.cs | 76 ++-- PlexRequests.Store/Properties/AssemblyInfo.cs | 74 ++-- .../Repository/IRequestRepository.cs | 152 +++---- .../Repository/ISettingsRepository.cs | 148 +++---- PlexRequests.Store/Sql.Designer.cs | 182 ++++---- PlexRequests.Store/Sql.resx | 246 +++++------ PlexRequests.Store/UserEntity.cs | 76 ++-- .../BootstrapperExtensions.cs | 88 ++-- .../Properties/AssemblyInfo.cs | 72 ++-- PlexRequests.UI.Tests/TestRootPathProvider.cs | 126 +++--- PlexRequests.UI/Content/handlebars.min.js | 88 ++-- PlexRequests.UI/Content/moment.min.js | 18 +- PlexRequests.UI/Content/pace.min.js | 2 +- PlexRequests.UI/Content/pace.scss | 46 +- PlexRequests.UI/Helpers/HeadphonesSender.cs | 350 +++++++-------- PlexRequests.UI/Jobs/Scheduler.cs | 91 ++-- PlexRequests.UI/Models/DatatablesModel.cs | 70 +-- PlexRequests.UI/Models/JsonResponseModel.cs | 66 +-- PlexRequests.UI/Models/MovieSearchType.cs | 68 +-- PlexRequests.UI/Models/PlexAuth.cs | 68 +-- PlexRequests.UI/Models/QualityModel.cs | 16 +- .../Models/SearchMusicViewModel.cs | 80 ++-- .../Validators/CouchPotatoValidator.cs | 82 ++-- .../Validators/HeadphonesValidator.cs | 82 ++-- PlexRequests.UI/Validators/PlexValidator.cs | 80 ++-- .../Validators/PushbulletSettingsValidator.cs | 78 ++-- .../Validators/PushoverSettingsValidator.cs | 80 ++-- .../Validators/SickRageValidator.cs | 84 ++-- PlexRequests.UI/Validators/SonarrValidator.cs | 84 ++-- .../Admin/PushbulletNotifications.cshtml | 218 +++++----- .../Views/Admin/PushoverNotifications.cshtml | 218 +++++----- PlexRequests.UI/compilerconfig.json | 74 ++-- PlexRequests.UI/compilerconfig.json.defaults | 96 ++--- 114 files changed, 5916 insertions(+), 5885 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index aa693030a..a25cfb512 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,22 +1,22 @@ -If this is a bug report please make sure you have filled the following in: -(If it's not a bug and a feature request then just remove the below) - -#### Plex Requests.Net Version: - - -#### Operating System: - - -#### Mono Version: - - -#### Applicable Logs (from `/logs/` directory or the Admin page): - -``` -Logs go here (Please make sure you remove any personal information from the logs) -``` - - -#### Reproduction Steps: - +If this is a bug report please make sure you have filled the following in: +(If it's not a bug and a feature request then just remove the below) + +#### Plex Requests.Net Version: + + +#### Operating System: + + +#### Mono Version: + + +#### Applicable Logs (from `/logs/` directory or the Admin page): + +``` +Logs go here (Please make sure you remove any personal information from the logs) +``` + + +#### Reproduction Steps: + Please include any steps to reproduce the issue, this the request that is causing the problem etc. \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 255ac37d6..9657145fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ -language: csharp -solution: PlexRequests.sln -install: - - mono Tools/nuget.exe restore PlexRequests.sln - - nuget install NUnit.Runners -OutputDirectory testrunner -script: +language: csharp +solution: PlexRequests.sln +install: + - mono Tools/nuget.exe restore PlexRequests.sln + - nuget install NUnit.Runners -OutputDirectory testrunner +script: - xbuild /p:Configuration=Release PlexRequests.sln /p:TargetFrameworkVersion="v4.5" \ No newline at end of file diff --git a/PlexRequests.Api.Interfaces/IApiRequest.cs b/PlexRequests.Api.Interfaces/IApiRequest.cs index f2fa72f44..396551dba 100644 --- a/PlexRequests.Api.Interfaces/IApiRequest.cs +++ b/PlexRequests.Api.Interfaces/IApiRequest.cs @@ -1,40 +1,40 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: IApiRequest.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 RestSharp; - -namespace PlexRequests.Api.Interfaces -{ - public interface IApiRequest - { - T Execute(IRestRequest request, Uri baseUri) where T : new(); - IRestResponse Execute(IRestRequest request, Uri baseUri); - T ExecuteXml(IRestRequest request, Uri baseUri) where T : class; - T ExecuteJson(IRestRequest request, Uri baseUri) where T : new(); - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IApiRequest.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 RestSharp; + +namespace PlexRequests.Api.Interfaces +{ + public interface IApiRequest + { + T Execute(IRestRequest request, Uri baseUri) where T : new(); + IRestResponse Execute(IRestRequest request, Uri baseUri); + T ExecuteXml(IRestRequest request, Uri baseUri) where T : class; + T ExecuteJson(IRestRequest request, Uri baseUri) where T : new(); + } +} diff --git a/PlexRequests.Api.Interfaces/IHeadphonesApi.cs b/PlexRequests.Api.Interfaces/IHeadphonesApi.cs index a895f1689..edd041fd4 100644 --- a/PlexRequests.Api.Interfaces/IHeadphonesApi.cs +++ b/PlexRequests.Api.Interfaces/IHeadphonesApi.cs @@ -1,44 +1,44 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: IHeadphonesApi.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -using PlexRequests.Api.Models.Music; - -namespace PlexRequests.Api.Interfaces -{ - public interface IHeadphonesApi - { - Task AddAlbum(string apiKey, Uri baseUrl, string albumId); - HeadphonesVersion GetVersion(string apiKey, Uri baseUrl); - Task AddArtist(string apiKey, Uri baseUrl, string artistId); - Task QueueAlbum(string apiKey, Uri baseUrl, string albumId); - Task> GetIndex(string apiKey, Uri baseUrl); - Task RefreshArtist(string apiKey, Uri baseUrl, string artistId); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IHeadphonesApi.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using PlexRequests.Api.Models.Music; + +namespace PlexRequests.Api.Interfaces +{ + public interface IHeadphonesApi + { + Task AddAlbum(string apiKey, Uri baseUrl, string albumId); + HeadphonesVersion GetVersion(string apiKey, Uri baseUrl); + Task AddArtist(string apiKey, Uri baseUrl, string artistId); + Task QueueAlbum(string apiKey, Uri baseUrl, string albumId); + Task> GetIndex(string apiKey, Uri baseUrl); + Task RefreshArtist(string apiKey, Uri baseUrl, string artistId); + } } \ No newline at end of file diff --git a/PlexRequests.Api.Interfaces/IPushbulletApi.cs b/PlexRequests.Api.Interfaces/IPushbulletApi.cs index 5df902d4c..7d9b486b1 100644 --- a/PlexRequests.Api.Interfaces/IPushbulletApi.cs +++ b/PlexRequests.Api.Interfaces/IPushbulletApi.cs @@ -1,45 +1,45 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: IPushbulletApi.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.Threading.Tasks; - -using PlexRequests.Api.Models.Notifications; - -namespace PlexRequests.Api.Interfaces -{ - public interface IPushbulletApi - { - /// - /// Pushes the specified message. - /// - /// The access token. - /// The title. - /// The message. - /// The device identifier. - /// - Task PushAsync(string accessToken, string title, string message, string deviceIdentifier = default(string)); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IPushbulletApi.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.Threading.Tasks; + +using PlexRequests.Api.Models.Notifications; + +namespace PlexRequests.Api.Interfaces +{ + public interface IPushbulletApi + { + /// + /// Pushes the specified message. + /// + /// The access token. + /// The title. + /// The message. + /// The device identifier. + /// + Task PushAsync(string accessToken, string title, string message, string deviceIdentifier = default(string)); + } } \ No newline at end of file diff --git a/PlexRequests.Api.Interfaces/IPushoverApi.cs b/PlexRequests.Api.Interfaces/IPushoverApi.cs index 15f93f596..fb1179c28 100644 --- a/PlexRequests.Api.Interfaces/IPushoverApi.cs +++ b/PlexRequests.Api.Interfaces/IPushoverApi.cs @@ -1,37 +1,37 @@ -#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 System.Threading.Tasks; - -using PlexRequests.Api.Models.Notifications; - -namespace PlexRequests.Api.Interfaces -{ - public interface IPushoverApi - { - Task PushAsync(string accessToken, string message, string userToken); - } +#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 System.Threading.Tasks; + +using PlexRequests.Api.Models.Notifications; + +namespace PlexRequests.Api.Interfaces +{ + public interface IPushoverApi + { + Task PushAsync(string accessToken, string message, string userToken); + } } \ No newline at end of file diff --git a/PlexRequests.Api.Interfaces/ISickRageApi.cs b/PlexRequests.Api.Interfaces/ISickRageApi.cs index 13cfacd84..7363d676f 100644 --- a/PlexRequests.Api.Interfaces/ISickRageApi.cs +++ b/PlexRequests.Api.Interfaces/ISickRageApi.cs @@ -1,45 +1,45 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ISickRageApi.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using System; -using System.Threading.Tasks; -using PlexRequests.Api.Models.SickRage; - -namespace PlexRequests.Api.Interfaces -{ - public interface ISickRageApi - { - Task AddSeries(int tvdbId, int seasoncount, int[] seasons, string quality, string apiKey, - Uri baseUrl); - - SickRagePing Ping(string apiKey, Uri baseUrl); - - Task AddSeason(int tvdbId, int season, string apiKey, Uri baseUrl); - - Task GetShows(string apiKey, Uri baseUrl); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ISickRageApi.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Threading.Tasks; +using PlexRequests.Api.Models.SickRage; + +namespace PlexRequests.Api.Interfaces +{ + public interface ISickRageApi + { + Task AddSeries(int tvdbId, int seasoncount, int[] seasons, string quality, string apiKey, + Uri baseUrl); + + SickRagePing Ping(string apiKey, Uri baseUrl); + + Task AddSeason(int tvdbId, int season, string apiKey, Uri baseUrl); + + Task GetShows(string apiKey, Uri baseUrl); + } } \ 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 cf321d344..749739acc 100644 --- a/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj +++ b/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj @@ -1,78 +1,78 @@ - - - - - Debug - AnyCPU - {95834072-A675-415D-AA8F-877C91623810} - Library - Properties - PlexRequests.Api.Interfaces - PlexRequests.Api.Interfaces - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - {CB37A5F8-6DFC-4554-99D3-A42B502E4591} - PlexRequests.Api.Models - - - - - - - - + + + + + Debug + AnyCPU + {95834072-A675-415D-AA8F-877C91623810} + Library + Properties + PlexRequests.Api.Interfaces + PlexRequests.Api.Interfaces + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + {CB37A5F8-6DFC-4554-99D3-A42B502E4591} + PlexRequests.Api.Models + + + + + + + + \ No newline at end of file diff --git a/PlexRequests.Api.Interfaces/Properties/AssemblyInfo.cs b/PlexRequests.Api.Interfaces/Properties/AssemblyInfo.cs index e8fb7c60d..0ad652e33 100644 --- a/PlexRequests.Api.Interfaces/Properties/AssemblyInfo.cs +++ b/PlexRequests.Api.Interfaces/Properties/AssemblyInfo.cs @@ -1,38 +1,38 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Api.Interfaces")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Api.Interfaces")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("95834072-a675-415d-aa8f-877c91623810")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Api.Interfaces")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Api.Interfaces")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("95834072-a675-415d-aa8f-877c91623810")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Api.Models/Movie/CouchPotatoMovies.cs b/PlexRequests.Api.Models/Movie/CouchPotatoMovies.cs index 33399179c..672297588 100644 --- a/PlexRequests.Api.Models/Movie/CouchPotatoMovies.cs +++ b/PlexRequests.Api.Models/Movie/CouchPotatoMovies.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; - -namespace PlexRequests.Api.Models.Movie -{ - public class CouchPotatoMovies - { - public List movies { get; set; } - public int total { get; set; } - public bool success { get; set; } - public bool empty { get; set; } - } -} +using System.Collections.Generic; + +namespace PlexRequests.Api.Models.Movie +{ + public class CouchPotatoMovies + { + public List movies { get; set; } + public int total { get; set; } + public bool success { get; set; } + public bool empty { get; set; } + } +} diff --git a/PlexRequests.Api.Models/Movie/CouchPotatoProfiles.cs b/PlexRequests.Api.Models/Movie/CouchPotatoProfiles.cs index 2da2cd221..41fc02379 100644 --- a/PlexRequests.Api.Models/Movie/CouchPotatoProfiles.cs +++ b/PlexRequests.Api.Models/Movie/CouchPotatoProfiles.cs @@ -1,58 +1,58 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: CouchPotatoProfiles.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 Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace PlexRequests.Api.Models.Movie -{ - public class ProfileList - { - public bool core { get; set; } - public bool hide { get; set; } - public string _rev { get; set; } - public List finish { get; set; } - public List qualities { get; set; } - public string _id { get; set; } - public string _t { get; set; } - public string label { get; set; } - public int minimum_score { get; set; } - public List stop_after { get; set; } - public List wait_for { get; set; } - public int order { get; set; } - [JsonProperty(PropertyName = "3d")] - public List threeD { get; set; } - } - - public class CouchPotatoProfiles - { - public List list { get; set; } - public bool success { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CouchPotatoProfiles.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 Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace PlexRequests.Api.Models.Movie +{ + public class ProfileList + { + public bool core { get; set; } + public bool hide { get; set; } + public string _rev { get; set; } + public List finish { get; set; } + public List qualities { get; set; } + public string _id { get; set; } + public string _t { get; set; } + public string label { get; set; } + public int minimum_score { get; set; } + public List stop_after { get; set; } + public List wait_for { get; set; } + public int order { get; set; } + [JsonProperty(PropertyName = "3d")] + public List threeD { get; set; } + } + + public class CouchPotatoProfiles + { + public List list { get; set; } + public bool success { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Movie/CouchPotatoStatus.cs b/PlexRequests.Api.Models/Movie/CouchPotatoStatus.cs index 7f20a7596..fc527fd63 100644 --- a/PlexRequests.Api.Models/Movie/CouchPotatoStatus.cs +++ b/PlexRequests.Api.Models/Movie/CouchPotatoStatus.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PlexRequests.Api.Models.Movie -{ - public class CouchPotatoStatus - { - public bool success { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PlexRequests.Api.Models.Movie +{ + public class CouchPotatoStatus + { + public bool success { get; set; } + } +} diff --git a/PlexRequests.Api.Models/Music/HeadphonesAlbumSearchResult.cs b/PlexRequests.Api.Models/Music/HeadphonesAlbumSearchResult.cs index 8aa4684c6..bcbd8e4e6 100644 --- a/PlexRequests.Api.Models/Music/HeadphonesAlbumSearchResult.cs +++ b/PlexRequests.Api.Models/Music/HeadphonesAlbumSearchResult.cs @@ -1,45 +1,45 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: HeadphonesAlbumSearchResult.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.Music -{ - public class HeadphonesAlbumSearchResult - { - public string rgid { get; set; } - public string albumurl { get; set; } - public string tracks { get; set; } - public string date { get; set; } - public string id { get; set; } // Artist ID - public string rgtype { get; set; } - public string title { get; set; } - public string url { get; set; } - public string country { get; set; } - public string albumid { get; set; } // AlbumId - public int score { get; set; } - public string uniquename { get; set; } - public string formats { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: HeadphonesAlbumSearchResult.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.Music +{ + public class HeadphonesAlbumSearchResult + { + public string rgid { get; set; } + public string albumurl { get; set; } + public string tracks { get; set; } + public string date { get; set; } + public string id { get; set; } // Artist ID + public string rgtype { get; set; } + public string title { get; set; } + public string url { get; set; } + public string country { get; set; } + public string albumid { get; set; } // AlbumId + public int score { get; set; } + public string uniquename { get; set; } + public string formats { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Music/HeadphonesArtistSearchResult.cs b/PlexRequests.Api.Models/Music/HeadphonesArtistSearchResult.cs index 15c574277..0d822fe59 100644 --- a/PlexRequests.Api.Models/Music/HeadphonesArtistSearchResult.cs +++ b/PlexRequests.Api.Models/Music/HeadphonesArtistSearchResult.cs @@ -1,37 +1,37 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: HeadphonesSearchResult.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.Music -{ - public class HeadphonesArtistSearchResult - { - public string url { get; set; } // MusicBrainz url - public int score { get; set; } // Search Match score? - public string name { get; set; } // Artist Name - public string uniquename { get; set; } // Artist Unique Name - public string id { get; set; } // Artist Unique ID for MusicBrainz - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: HeadphonesSearchResult.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.Music +{ + public class HeadphonesArtistSearchResult + { + public string url { get; set; } // MusicBrainz url + public int score { get; set; } // Search Match score? + public string name { get; set; } // Artist Name + public string uniquename { get; set; } // Artist Unique Name + public string id { get; set; } // Artist Unique ID for MusicBrainz + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Music/HeadphonesVersion.cs b/PlexRequests.Api.Models/Music/HeadphonesVersion.cs index 1ee6e16e8..b700d7214 100644 --- a/PlexRequests.Api.Models/Music/HeadphonesVersion.cs +++ b/PlexRequests.Api.Models/Music/HeadphonesVersion.cs @@ -1,37 +1,37 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: HeadphonesVersion.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.Music -{ - public class HeadphonesVersion - { - public string install_type { get; set; } - public object current_version { get; set; } - public string git_path { get; set; } - public string latest_version { get; set; } - public int commits_behind { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: HeadphonesVersion.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.Music +{ + public class HeadphonesVersion + { + public string install_type { get; set; } + public object current_version { get; set; } + public string git_path { get; set; } + public string latest_version { get; set; } + public int commits_behind { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Music/MusicBrainzReleaseInfo.cs b/PlexRequests.Api.Models/Music/MusicBrainzReleaseInfo.cs index b3ca2ee62..683a36a2c 100644 --- a/PlexRequests.Api.Models/Music/MusicBrainzReleaseInfo.cs +++ b/PlexRequests.Api.Models/Music/MusicBrainzReleaseInfo.cs @@ -1,68 +1,68 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: MusicBrainzReleaseInfo.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 Newtonsoft.Json; - -namespace PlexRequests.Api.Models.Music -{ - public class CoverArtArchive - { - public int count { get; set; } - public bool back { get; set; } - public bool artwork { get; set; } - public bool front { get; set; } - public bool darkened { get; set; } - } - - - public class MusicBrainzReleaseInfo - { - [JsonProperty(PropertyName = "artist-credit")] - public List ArtistCredits { get; set; } - public string date { get; set; } - public string status { get; set; } - public string asin { get; set; } - public string title { get; set; } - public string quality { get; set; } - public string country { get; set; } - public string packaging { get; set; } - - [JsonProperty(PropertyName = "text-representation")] - public TextRepresentation TextRepresentation { get; set; } - - [JsonProperty(PropertyName = "cover-art-archive")] - public CoverArtArchive CoverArtArchive { get; set; } - public string barcode { get; set; } - public string disambiguation { get; set; } - - [JsonProperty(PropertyName = "release-events")] - public List ReleaseEvents { get; set; } - public string id { get; set; } - } - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: MusicBrainzReleaseInfo.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 Newtonsoft.Json; + +namespace PlexRequests.Api.Models.Music +{ + public class CoverArtArchive + { + public int count { get; set; } + public bool back { get; set; } + public bool artwork { get; set; } + public bool front { get; set; } + public bool darkened { get; set; } + } + + + public class MusicBrainzReleaseInfo + { + [JsonProperty(PropertyName = "artist-credit")] + public List ArtistCredits { get; set; } + public string date { get; set; } + public string status { get; set; } + public string asin { get; set; } + public string title { get; set; } + public string quality { get; set; } + public string country { get; set; } + public string packaging { get; set; } + + [JsonProperty(PropertyName = "text-representation")] + public TextRepresentation TextRepresentation { get; set; } + + [JsonProperty(PropertyName = "cover-art-archive")] + public CoverArtArchive CoverArtArchive { get; set; } + public string barcode { get; set; } + public string disambiguation { get; set; } + + [JsonProperty(PropertyName = "release-events")] + public List ReleaseEvents { get; set; } + public string id { get; set; } + } + } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Notifications/PushbulletPush.cs b/PlexRequests.Api.Models/Notifications/PushbulletPush.cs index d2f63efd1..755dc240d 100644 --- a/PlexRequests.Api.Models/Notifications/PushbulletPush.cs +++ b/PlexRequests.Api.Models/Notifications/PushbulletPush.cs @@ -1,38 +1,38 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PushbulletPush.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 Newtonsoft.Json; - -namespace PlexRequests.Api.Models.Notifications -{ - public class PushbulletPush - { - public string body { get; set; } - public string title { get; set; } - public string type { get; set; } - public string device_iden { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushbulletPush.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 Newtonsoft.Json; + +namespace PlexRequests.Api.Models.Notifications +{ + public class PushbulletPush + { + public string body { get; set; } + public string title { get; set; } + public string type { get; set; } + public string device_iden { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Notifications/PushbulletResponse.cs b/PlexRequests.Api.Models/Notifications/PushbulletResponse.cs index eda3c41c7..4c4daede9 100644 --- a/PlexRequests.Api.Models/Notifications/PushbulletResponse.cs +++ b/PlexRequests.Api.Models/Notifications/PushbulletResponse.cs @@ -1,48 +1,48 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PushbulletResponse.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 PushbulletResponse - { - public bool active { get; set; } - public string iden { get; set; } - public double created { get; set; } - public double modified { get; set; } - public string type { get; set; } - public bool dismissed { get; set; } - public string direction { get; set; } - public string sender_iden { get; set; } - public string sender_email { get; set; } - public string sender_email_normalized { get; set; } - public string sender_name { get; set; } - public string receiver_iden { get; set; } - public string receiver_email { get; set; } - public string receiver_email_normalized { get; set; } - public string title { get; set; } - public string body { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushbulletResponse.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 PushbulletResponse + { + public bool active { get; set; } + public string iden { get; set; } + public double created { get; set; } + public double modified { get; set; } + public string type { get; set; } + public bool dismissed { get; set; } + public string direction { get; set; } + public string sender_iden { get; set; } + public string sender_email { get; set; } + public string sender_email_normalized { get; set; } + public string sender_name { get; set; } + public string receiver_iden { get; set; } + public string receiver_email { get; set; } + public string receiver_email_normalized { get; set; } + public string title { get; set; } + public string body { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Notifications/PushoverResponse.cs b/PlexRequests.Api.Models/Notifications/PushoverResponse.cs index 94849fba1..1b31f80f1 100644 --- a/PlexRequests.Api.Models/Notifications/PushoverResponse.cs +++ b/PlexRequests.Api.Models/Notifications/PushoverResponse.cs @@ -1,34 +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; } - } +#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/Plex/PlexError.cs b/PlexRequests.Api.Models/Plex/PlexError.cs index f62a0a7f0..d5be3a374 100644 --- a/PlexRequests.Api.Models/Plex/PlexError.cs +++ b/PlexRequests.Api.Models/Plex/PlexError.cs @@ -1,39 +1,39 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexError.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.Xml.Serialization; - -namespace PlexRequests.Api.Models.Plex -{ - [XmlRoot(ElementName = "errors")] - public class PlexError - { - [XmlElement(ElementName = "error")] - public string Error { get; set; } - - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexError.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.Xml.Serialization; + +namespace PlexRequests.Api.Models.Plex +{ + [XmlRoot(ElementName = "errors")] + public class PlexError + { + [XmlElement(ElementName = "error")] + public string Error { get; set; } + + } +} diff --git a/PlexRequests.Api.Models/Plex/PlexLibraries.cs b/PlexRequests.Api.Models/Plex/PlexLibraries.cs index 0be7185a7..d9ae732d8 100644 --- a/PlexRequests.Api.Models/Plex/PlexLibraries.cs +++ b/PlexRequests.Api.Models/Plex/PlexLibraries.cs @@ -1,22 +1,22 @@ -using System.Collections.Generic; -using System.Xml.Serialization; - -namespace PlexRequests.Api.Models.Plex -{ - [XmlRoot(ElementName = "MediaContainer")] - public class PlexLibraries - { - [XmlElement(ElementName = "Directory")] - public List Directories { get; set; } - } - - [XmlRoot(ElementName = "Location")] - public partial class Location - { - [XmlElement(ElementName = "id")] - public int id { get; set; } - [XmlElement(ElementName = "path")] - public string path { get; set; } - } - -} +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace PlexRequests.Api.Models.Plex +{ + [XmlRoot(ElementName = "MediaContainer")] + public class PlexLibraries + { + [XmlElement(ElementName = "Directory")] + public List Directories { get; set; } + } + + [XmlRoot(ElementName = "Location")] + public partial class Location + { + [XmlElement(ElementName = "id")] + public int id { get; set; } + [XmlElement(ElementName = "path")] + public string path { get; set; } + } + +} diff --git a/PlexRequests.Api.Models/Plex/PlexStatus.cs b/PlexRequests.Api.Models/Plex/PlexStatus.cs index 7775be50b..db20e0f44 100644 --- a/PlexRequests.Api.Models/Plex/PlexStatus.cs +++ b/PlexRequests.Api.Models/Plex/PlexStatus.cs @@ -1,91 +1,91 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Serialization; - -namespace PlexRequests.Api.Models.Plex -{ - [XmlRoot(ElementName = "Directory")] - public class Directory - { - [XmlAttribute(AttributeName = "count")] - public string Count { get; set; } - [XmlAttribute(AttributeName = "key")] - public string Key { get; set; } - [XmlAttribute(AttributeName = "title")] - public string Title { get; set; } - [XmlAttribute(AttributeName = "type")] - public string type { get; set; } - } - - [XmlRoot(ElementName = "MediaContainer")] - public class PlexStatus - { - [XmlElement(ElementName = "Directory")] - public List Directory { get; set; } - [XmlAttribute(AttributeName = "size")] - public string Size { get; set; } - [XmlAttribute(AttributeName = "allowCameraUpload")] - public string AllowCameraUpload { get; set; } - [XmlAttribute(AttributeName = "allowChannelAccess")] - public string AllowChannelAccess { get; set; } - [XmlAttribute(AttributeName = "allowMediaDeletion")] - public string AllowMediaDeletion { get; set; } - [XmlAttribute(AttributeName = "allowSync")] - public string AllowSync { get; set; } - [XmlAttribute(AttributeName = "backgroundProcessing")] - public string BackgroundProcessing { get; set; } - [XmlAttribute(AttributeName = "certificate")] - public string Certificate { get; set; } - [XmlAttribute(AttributeName = "companionProxy")] - public string CompanionProxy { get; set; } - [XmlAttribute(AttributeName = "friendlyName")] - public string FriendlyName { get; set; } - [XmlAttribute(AttributeName = "machineIdentifier")] - public string MachineIdentifier { get; set; } - [XmlAttribute(AttributeName = "multiuser")] - public string Multiuser { get; set; } - [XmlAttribute(AttributeName = "myPlex")] - public string MyPlex { get; set; } - [XmlAttribute(AttributeName = "myPlexMappingState")] - public string MyPlexMappingState { get; set; } - [XmlAttribute(AttributeName = "myPlexSigninState")] - public string MyPlexSigninState { get; set; } - [XmlAttribute(AttributeName = "myPlexSubscription")] - public string MyPlexSubscription { get; set; } - [XmlAttribute(AttributeName = "myPlexUsername")] - public string MyPlexUsername { get; set; } - [XmlAttribute(AttributeName = "platform")] - public string Platform { get; set; } - [XmlAttribute(AttributeName = "platformVersion")] - public string PlatformVersion { get; set; } - [XmlAttribute(AttributeName = "requestParametersInCookie")] - public string RequestParametersInCookie { get; set; } - [XmlAttribute(AttributeName = "sync")] - public string Sync { get; set; } - [XmlAttribute(AttributeName = "transcoderActiveVideoSessions")] - public string TranscoderActiveVideoSessions { get; set; } - [XmlAttribute(AttributeName = "transcoderAudio")] - public string TranscoderAudio { get; set; } - [XmlAttribute(AttributeName = "transcoderLyrics")] - public string TranscoderLyrics { get; set; } - [XmlAttribute(AttributeName = "transcoderPhoto")] - public string TranscoderPhoto { get; set; } - [XmlAttribute(AttributeName = "transcoderSubtitles")] - public string TranscoderSubtitles { get; set; } - [XmlAttribute(AttributeName = "transcoderVideo")] - public string TranscoderVideo { get; set; } - [XmlAttribute(AttributeName = "transcoderVideoBitrates")] - public string TranscoderVideoBitrates { get; set; } - [XmlAttribute(AttributeName = "transcoderVideoQualities")] - public string TranscoderVideoQualities { get; set; } - [XmlAttribute(AttributeName = "transcoderVideoResolutions")] - public string TranscoderVideoResolutions { get; set; } - [XmlAttribute(AttributeName = "updatedAt")] - public string UpdatedAt { get; set; } - [XmlAttribute(AttributeName = "version")] - public string Version { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace PlexRequests.Api.Models.Plex +{ + [XmlRoot(ElementName = "Directory")] + public class Directory + { + [XmlAttribute(AttributeName = "count")] + public string Count { get; set; } + [XmlAttribute(AttributeName = "key")] + public string Key { get; set; } + [XmlAttribute(AttributeName = "title")] + public string Title { get; set; } + [XmlAttribute(AttributeName = "type")] + public string type { get; set; } + } + + [XmlRoot(ElementName = "MediaContainer")] + public class PlexStatus + { + [XmlElement(ElementName = "Directory")] + public List Directory { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + [XmlAttribute(AttributeName = "allowCameraUpload")] + public string AllowCameraUpload { get; set; } + [XmlAttribute(AttributeName = "allowChannelAccess")] + public string AllowChannelAccess { get; set; } + [XmlAttribute(AttributeName = "allowMediaDeletion")] + public string AllowMediaDeletion { get; set; } + [XmlAttribute(AttributeName = "allowSync")] + public string AllowSync { get; set; } + [XmlAttribute(AttributeName = "backgroundProcessing")] + public string BackgroundProcessing { get; set; } + [XmlAttribute(AttributeName = "certificate")] + public string Certificate { get; set; } + [XmlAttribute(AttributeName = "companionProxy")] + public string CompanionProxy { get; set; } + [XmlAttribute(AttributeName = "friendlyName")] + public string FriendlyName { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "multiuser")] + public string Multiuser { get; set; } + [XmlAttribute(AttributeName = "myPlex")] + public string MyPlex { get; set; } + [XmlAttribute(AttributeName = "myPlexMappingState")] + public string MyPlexMappingState { get; set; } + [XmlAttribute(AttributeName = "myPlexSigninState")] + public string MyPlexSigninState { get; set; } + [XmlAttribute(AttributeName = "myPlexSubscription")] + public string MyPlexSubscription { get; set; } + [XmlAttribute(AttributeName = "myPlexUsername")] + public string MyPlexUsername { get; set; } + [XmlAttribute(AttributeName = "platform")] + public string Platform { get; set; } + [XmlAttribute(AttributeName = "platformVersion")] + public string PlatformVersion { get; set; } + [XmlAttribute(AttributeName = "requestParametersInCookie")] + public string RequestParametersInCookie { get; set; } + [XmlAttribute(AttributeName = "sync")] + public string Sync { get; set; } + [XmlAttribute(AttributeName = "transcoderActiveVideoSessions")] + public string TranscoderActiveVideoSessions { get; set; } + [XmlAttribute(AttributeName = "transcoderAudio")] + public string TranscoderAudio { get; set; } + [XmlAttribute(AttributeName = "transcoderLyrics")] + public string TranscoderLyrics { get; set; } + [XmlAttribute(AttributeName = "transcoderPhoto")] + public string TranscoderPhoto { get; set; } + [XmlAttribute(AttributeName = "transcoderSubtitles")] + public string TranscoderSubtitles { get; set; } + [XmlAttribute(AttributeName = "transcoderVideo")] + public string TranscoderVideo { get; set; } + [XmlAttribute(AttributeName = "transcoderVideoBitrates")] + public string TranscoderVideoBitrates { get; set; } + [XmlAttribute(AttributeName = "transcoderVideoQualities")] + public string TranscoderVideoQualities { get; set; } + [XmlAttribute(AttributeName = "transcoderVideoResolutions")] + public string TranscoderVideoResolutions { get; set; } + [XmlAttribute(AttributeName = "updatedAt")] + public string UpdatedAt { get; set; } + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + } +} diff --git a/PlexRequests.Api.Models/Plex/PlexUserRequest.cs b/PlexRequests.Api.Models/Plex/PlexUserRequest.cs index a466740e4..ea45297ba 100644 --- a/PlexRequests.Api.Models/Plex/PlexUserRequest.cs +++ b/PlexRequests.Api.Models/Plex/PlexUserRequest.cs @@ -1,40 +1,40 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexUserRequest.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.Plex -{ - public class PlexUserRequest - { - public UserRequest user { get; set; } - } - - public class UserRequest - { - public string login { get; set; } - public string password { get; set; } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexUserRequest.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.Plex +{ + public class PlexUserRequest + { + public UserRequest user { get; set; } + } + + public class UserRequest + { + public string login { get; set; } + public string password { get; set; } + } +} diff --git a/PlexRequests.Api.Models/Properties/AssemblyInfo.cs b/PlexRequests.Api.Models/Properties/AssemblyInfo.cs index 8115a2543..f226d6680 100644 --- a/PlexRequests.Api.Models/Properties/AssemblyInfo.cs +++ b/PlexRequests.Api.Models/Properties/AssemblyInfo.cs @@ -1,38 +1,38 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Api.Models")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Api.Models")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("cb37a5f8-6dfc-4554-99d3-a42b502e4591")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] - -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Api.Models")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Api.Models")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cb37a5f8-6dfc-4554-99d3-a42b502e4591")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Api.Models/SickRage/SickRagePing.cs b/PlexRequests.Api.Models/SickRage/SickRagePing.cs index 46c8c94e7..1385c1ee8 100644 --- a/PlexRequests.Api.Models/SickRage/SickRagePing.cs +++ b/PlexRequests.Api.Models/SickRage/SickRagePing.cs @@ -1,37 +1,37 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SickRagePing.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.SickRage -{ - public class SickRagePingData - { - public int pid { get; set; } - } - - public class SickRagePing : SickRageBase - { - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SickRagePing.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.SickRage +{ + public class SickRagePingData + { + public int pid { get; set; } + } + + public class SickRagePing : SickRageBase + { + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/SickRage/SickRageSeasonList.cs b/PlexRequests.Api.Models/SickRage/SickRageSeasonList.cs index 7716204dc..80e561ca7 100644 --- a/PlexRequests.Api.Models/SickRage/SickRageSeasonList.cs +++ b/PlexRequests.Api.Models/SickRage/SickRageSeasonList.cs @@ -5,7 +5,7 @@ namespace PlexRequests.Api.Models.SickRage { public class SickRageSeasonList : SickRageBase { - [JsonIgnore] - public int[] Data => JsonConvertHelper.ParseObjectToArray(data); + [JsonIgnore] + public int[] Data => JsonConvertHelper.ParseObjectToArray(data); } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/SickRage/SickRageShowInformation.cs b/PlexRequests.Api.Models/SickRage/SickRageShowInformation.cs index 511f692ab..295be41c3 100644 --- a/PlexRequests.Api.Models/SickRage/SickRageShowInformation.cs +++ b/PlexRequests.Api.Models/SickRage/SickRageShowInformation.cs @@ -1,82 +1,82 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SickRageShowInformation.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; - -namespace PlexRequests.Api.Models.SickRage -{ - public class Cache - { - public int banner { get; set; } - public int poster { get; set; } - } - - public class QualityDetails - { - public List archive { get; set; } - public List initial { get; set; } - } - - public class SeasonList - { - } - - public class Data - { - public int air_by_date { get; set; } - public string airs { get; set; } - public int anime { get; set; } - public int archive_firstmatch { get; set; } - public Cache cache { get; set; } - public int dvdorder { get; set; } - public int flatten_folders { get; set; } - public List genre { get; set; } - public string imdbid { get; set; } - public int indexerid { get; set; } - public string language { get; set; } - public string location { get; set; } - public string network { get; set; } - public string next_ep_airdate { get; set; } - public int paused { get; set; } - public string quality { get; set; } - public QualityDetails quality_details { get; set; } - public List rls_ignore_words { get; set; } - public List rls_require_words { get; set; } - public int scene { get; set; } - public SeasonList season_list { get; set; } - public string show_name { get; set; } - public int sports { get; set; } - public string status { get; set; } - public int subtitles { get; set; } - public int tvdbid { get; set; } - } - - public class SickRageShowInformation : SickRageBase - { - } - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SickRageShowInformation.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; + +namespace PlexRequests.Api.Models.SickRage +{ + public class Cache + { + public int banner { get; set; } + public int poster { get; set; } + } + + public class QualityDetails + { + public List archive { get; set; } + public List initial { get; set; } + } + + public class SeasonList + { + } + + public class Data + { + public int air_by_date { get; set; } + public string airs { get; set; } + public int anime { get; set; } + public int archive_firstmatch { get; set; } + public Cache cache { get; set; } + public int dvdorder { get; set; } + public int flatten_folders { get; set; } + public List genre { get; set; } + public string imdbid { get; set; } + public int indexerid { get; set; } + public string language { get; set; } + public string location { get; set; } + public string network { get; set; } + public string next_ep_airdate { get; set; } + public int paused { get; set; } + public string quality { get; set; } + public QualityDetails quality_details { get; set; } + public List rls_ignore_words { get; set; } + public List rls_require_words { get; set; } + public int scene { get; set; } + public SeasonList season_list { get; set; } + public string show_name { get; set; } + public int sports { get; set; } + public string status { get; set; } + public int subtitles { get; set; } + public int tvdbid { get; set; } + } + + public class SickRageShowInformation : SickRageBase + { + } + } \ No newline at end of file diff --git a/PlexRequests.Api.Models/SickRage/SickRageStatus.cs b/PlexRequests.Api.Models/SickRage/SickRageStatus.cs index 2ea03632d..edeb03a21 100644 --- a/PlexRequests.Api.Models/SickRage/SickRageStatus.cs +++ b/PlexRequests.Api.Models/SickRage/SickRageStatus.cs @@ -1,35 +1,35 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SickRageStatus.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.SickRage -{ - public static class SickRageStatus - { - public const string Wanted = "wanted"; - public const string Skipped = "skipped"; - public const string Ignored = "Ignored"; - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SickRageStatus.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.SickRage +{ + public static class SickRageStatus + { + public const string Wanted = "wanted"; + public const string Skipped = "skipped"; + public const string Ignored = "Ignored"; + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/SickRage/SickRageTvAdd.cs b/PlexRequests.Api.Models/SickRage/SickRageTvAdd.cs index 15e2e3eae..e668b841c 100644 --- a/PlexRequests.Api.Models/SickRage/SickRageTvAdd.cs +++ b/PlexRequests.Api.Models/SickRage/SickRageTvAdd.cs @@ -1,38 +1,38 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SickRageTvAdd.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.SickRage -{ - public class SickRageTvAddData - { - public string name { get; set; } - } - - public class SickRageTvAdd : SickRageBase - { - } - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SickRageTvAdd.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.SickRage +{ + public class SickRageTvAddData + { + public string name { get; set; } + } + + public class SickRageTvAdd : SickRageBase + { + } + } \ No newline at end of file diff --git a/PlexRequests.Api.Models/SickRage/SickrageShows.cs b/PlexRequests.Api.Models/SickRage/SickrageShows.cs index b33aeaf58..53131e56e 100644 --- a/PlexRequests.Api.Models/SickRage/SickrageShows.cs +++ b/PlexRequests.Api.Models/SickRage/SickrageShows.cs @@ -1,42 +1,42 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SickrageShows.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using System; -using System.Collections.Generic; - -namespace PlexRequests.Api.Models.SickRage -{ - public class SickrageShows : SickRageBase> - { - - } - - public class Item - { - public int tvdbid { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SickrageShows.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; + +namespace PlexRequests.Api.Models.SickRage +{ + public class SickrageShows : SickRageBase> + { + + } + + public class Item + { + public int tvdbid { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SonarrAllSeries.cs b/PlexRequests.Api.Models/Sonarr/SonarrAllSeries.cs index f788d18b7..608bb4f4a 100644 --- a/PlexRequests.Api.Models/Sonarr/SonarrAllSeries.cs +++ b/PlexRequests.Api.Models/Sonarr/SonarrAllSeries.cs @@ -1,69 +1,69 @@ -using System; -using System.Collections.Generic; - -namespace PlexRequests.Api.Models.Sonarr -{ - public class SonarrAllSeries - { - public List list { get; set; } - } - - public class Series - { - public string title { get; set; } - public List alternateTitles { get; set; } - public string sortTitle { get; set; } - public int seasonCount { get; set; } - public int totalEpisodeCount { get; set; } - public int episodeCount { get; set; } - public int episodeFileCount { get; set; } - public long sizeOnDisk { get; set; } - public string status { get; set; } - public string overview { get; set; } - public DateTime previousAiring { get; set; } - public string network { get; set; } - public List images { get; set; } - public List seasons { get; set; } - public int year { get; set; } - public string path { get; set; } - public int profileId { get; set; } - public bool seasonFolder { get; set; } - public bool monitored { get; set; } - public bool useSceneNumbering { get; set; } - public int runtime { get; set; } - public int tvdbId { get; set; } - public int tvRageId { get; set; } - public int tvMazeId { get; set; } - public DateTime firstAired { get; set; } - public DateTime lastInfoSync { get; set; } - public string seriesType { get; set; } - public string cleanTitle { get; set; } - public string imdbId { get; set; } - public string titleSlug { get; set; } - public string certification { get; set; } - public List genres { get; set; } - public List tags { get; set; } - public DateTime added { get; set; } - public Ratings ratings { get; set; } - public int qualityProfileId { get; set; } - public int id { get; set; } - } - - public class Ratings - { - public int votes { get; set; } - public float value { get; set; } - } - - public class Alternatetitle - { - public string title { get; set; } - public int seasonNumber { get; set; } - } - - public class Image - { - public string coverType { get; set; } - public string url { get; set; } - } -} +using System; +using System.Collections.Generic; + +namespace PlexRequests.Api.Models.Sonarr +{ + public class SonarrAllSeries + { + public List list { get; set; } + } + + public class Series + { + public string title { get; set; } + public List alternateTitles { get; set; } + public string sortTitle { get; set; } + public int seasonCount { get; set; } + public int totalEpisodeCount { get; set; } + public int episodeCount { get; set; } + public int episodeFileCount { get; set; } + public long sizeOnDisk { get; set; } + public string status { get; set; } + public string overview { get; set; } + public DateTime previousAiring { get; set; } + public string network { get; set; } + public List images { get; set; } + public List seasons { get; set; } + public int year { get; set; } + public string path { get; set; } + public int profileId { get; set; } + public bool seasonFolder { get; set; } + public bool monitored { get; set; } + public bool useSceneNumbering { get; set; } + public int runtime { get; set; } + public int tvdbId { get; set; } + public int tvRageId { get; set; } + public int tvMazeId { get; set; } + public DateTime firstAired { get; set; } + public DateTime lastInfoSync { get; set; } + public string seriesType { get; set; } + public string cleanTitle { get; set; } + public string imdbId { get; set; } + public string titleSlug { get; set; } + public string certification { get; set; } + public List genres { get; set; } + public List tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public int qualityProfileId { get; set; } + public int id { get; set; } + } + + public class Ratings + { + public int votes { get; set; } + public float value { get; set; } + } + + public class Alternatetitle + { + public string title { get; set; } + public int seasonNumber { get; set; } + } + + public class Image + { + public string coverType { get; set; } + public string url { get; set; } + } +} diff --git a/PlexRequests.Api.Models/Sonarr/SonarrProfile.cs b/PlexRequests.Api.Models/Sonarr/SonarrProfile.cs index 2aee359bb..b95fe32fe 100644 --- a/PlexRequests.Api.Models/Sonarr/SonarrProfile.cs +++ b/PlexRequests.Api.Models/Sonarr/SonarrProfile.cs @@ -1,57 +1,57 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SonarrProfile.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; - -namespace PlexRequests.Api.Models.Sonarr -{ - public class Cutoff - { - public int id { get; set; } - public string name { get; set; } - } - - public class Quality - { - public int id { get; set; } - public string name { get; set; } - } - - public class Item - { - public Quality quality { get; set; } - public bool allowed { get; set; } - } - - public class SonarrProfile - { - public string name { get; set; } - public Cutoff cutoff { get; set; } - public List items { get; set; } - public int id { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrProfile.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; + +namespace PlexRequests.Api.Models.Sonarr +{ + public class Cutoff + { + public int id { get; set; } + public string name { get; set; } + } + + public class Quality + { + public int id { get; set; } + public string name { get; set; } + } + + public class Item + { + public Quality quality { get; set; } + public bool allowed { get; set; } + } + + public class SonarrProfile + { + public string name { get; set; } + public Cutoff cutoff { get; set; } + public List items { get; set; } + public int id { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Sonarr/SystemStatus.cs b/PlexRequests.Api.Models/Sonarr/SystemStatus.cs index 19d822805..8dc3c0f57 100644 --- a/PlexRequests.Api.Models/Sonarr/SystemStatus.cs +++ b/PlexRequests.Api.Models/Sonarr/SystemStatus.cs @@ -1,51 +1,51 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SystemStatus.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.Sonarr -{ - public class SystemStatus - { - public string version { get; set; } - public string buildTime { get; set; } - public bool isDebug { get; set; } - public bool isProduction { get; set; } - public bool isAdmin { get; set; } - public bool isUserInteractive { get; set; } - public string startupPath { get; set; } - public string appData { get; set; } - public string osVersion { get; set; } - public bool isMonoRuntime { get; set; } - public bool isMono { get; set; } - public bool isLinux { get; set; } - public bool isOsx { get; set; } - public bool isWindows { get; set; } - public string branch { get; set; } - public string authentication { get; set; } - public string sqliteVersion { get; set; } - public string urlBase { get; set; } - public string runtimeVersion { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SystemStatus.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.Sonarr +{ + public class SystemStatus + { + public string version { get; set; } + public string buildTime { get; set; } + public bool isDebug { get; set; } + public bool isProduction { get; set; } + public bool isAdmin { get; set; } + public bool isUserInteractive { get; set; } + public string startupPath { get; set; } + public string appData { get; set; } + public string osVersion { get; set; } + public bool isMonoRuntime { get; set; } + public bool isMono { get; set; } + public bool isLinux { get; set; } + public bool isOsx { get; set; } + public bool isWindows { get; set; } + public string branch { get; set; } + public string authentication { get; set; } + public string sqliteVersion { get; set; } + public string urlBase { get; set; } + public string runtimeVersion { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Tv/Authentication.cs b/PlexRequests.Api.Models/Tv/Authentication.cs index ca2cb1d62..ae427ff45 100644 --- a/PlexRequests.Api.Models/Tv/Authentication.cs +++ b/PlexRequests.Api.Models/Tv/Authentication.cs @@ -1,33 +1,33 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: Authentication.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.Tv -{ - public class Authentication - { - public string token { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Authentication.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.Tv +{ + public class Authentication + { + public string token { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Api.Models/Tv/TvSearchResult.cs b/PlexRequests.Api.Models/Tv/TvSearchResult.cs index a8bd9d35f..a97b6834b 100644 --- a/PlexRequests.Api.Models/Tv/TvSearchResult.cs +++ b/PlexRequests.Api.Models/Tv/TvSearchResult.cs @@ -1,68 +1,68 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TvSearchResult.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; - -namespace PlexRequests.Api.Models.Tv -{ - public class TvShowSearchResult - { - public int id { get; set; } - public int airedSeason { get; set; } - public int airedEpisodeNumber { get; set; } - public string episodeName { get; set; } - public string firstAired { get; set; } - public string guestStars { get; set; } - public string director { get; set; } - public List writers { get; set; } - public string overview { get; set; } - public string productionCode { get; set; } - public string showUrl { get; set; } - public int lastUpdated { get; set; } - public string dvdDiscid { get; set; } - public int dvdSeason { get; set; } - public int dvdEpisodeNumber { get; set; } - public int dvdChapter { get; set; } - public int absoluteNumber { get; set; } - public string filename { get; set; } - public string seriesId { get; set; } - public string lastUpdatedBy { get; set; } - public int airsAfterSeason { get; set; } - public int airsBeforeSeason { get; set; } - public int airsBeforeEpisode { get; set; } - public string thumbAuthor { get; set; } - public string thumbAdded { get; set; } - public string thumbWidth { get; set; } - public string thumbHeight { get; set; } - public string imdbId { get; set; } - public int siteRating { get; set; } - } - - public class TvSearchResult - { - public List data { get; set; } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TvSearchResult.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; + +namespace PlexRequests.Api.Models.Tv +{ + public class TvShowSearchResult + { + public int id { get; set; } + public int airedSeason { get; set; } + public int airedEpisodeNumber { get; set; } + public string episodeName { get; set; } + public string firstAired { get; set; } + public string guestStars { get; set; } + public string director { get; set; } + public List writers { get; set; } + public string overview { get; set; } + public string productionCode { get; set; } + public string showUrl { get; set; } + public int lastUpdated { get; set; } + public string dvdDiscid { get; set; } + public int dvdSeason { get; set; } + public int dvdEpisodeNumber { get; set; } + public int dvdChapter { get; set; } + public int absoluteNumber { get; set; } + public string filename { get; set; } + public string seriesId { get; set; } + public string lastUpdatedBy { get; set; } + public int airsAfterSeason { get; set; } + public int airsBeforeSeason { get; set; } + public int airsBeforeEpisode { get; set; } + public string thumbAuthor { get; set; } + public string thumbAdded { get; set; } + public string thumbWidth { get; set; } + public string thumbHeight { get; set; } + public string imdbId { get; set; } + public int siteRating { get; set; } + } + + public class TvSearchResult + { + public List data { get; set; } + } +} diff --git a/PlexRequests.Api.Models/Tv/TvShow.cs b/PlexRequests.Api.Models/Tv/TvShow.cs index 84cb69fb9..d11024a31 100644 --- a/PlexRequests.Api.Models/Tv/TvShow.cs +++ b/PlexRequests.Api.Models/Tv/TvShow.cs @@ -1,61 +1,61 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TvShow.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; - -namespace PlexRequests.Api.Models.Tv -{ - public class TvShow - { - public int id { get; set; } - public string seriesName { get; set; } - public List aliases { get; set; } - public string banner { get; set; } - public string seriesId { get; set; } - public string status { get; set; } - public string firstAired { get; set; } - public string network { get; set; } - public string networkId { get; set; } - public string runtime { get; set; } - public List genre { get; set; } - public string overview { get; set; } - public int lastUpdated { get; set; } - public string airsDayOfWeek { get; set; } - public string airsTime { get; set; } - public string rating { get; set; } - public string imdbId { get; set; } - public string zap2itId { get; set; } - public string added { get; set; } - public int addedBy { get; set; } - public int siteRating { get; set; } - - } - - public class TvShowInformation - { - public TvShow data { get; set; } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TvShow.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; + +namespace PlexRequests.Api.Models.Tv +{ + public class TvShow + { + public int id { get; set; } + public string seriesName { get; set; } + public List aliases { get; set; } + public string banner { get; set; } + public string seriesId { get; set; } + public string status { get; set; } + public string firstAired { get; set; } + public string network { get; set; } + public string networkId { get; set; } + public string runtime { get; set; } + public List genre { get; set; } + public string overview { get; set; } + public int lastUpdated { get; set; } + public string airsDayOfWeek { get; set; } + public string airsTime { get; set; } + public string rating { get; set; } + public string imdbId { get; set; } + public string zap2itId { get; set; } + public string added { get; set; } + public int addedBy { get; set; } + public int siteRating { get; set; } + + } + + public class TvShowInformation + { + public TvShow data { get; set; } + } +} diff --git a/PlexRequests.Api.Models/Tv/TvShowImages.cs b/PlexRequests.Api.Models/Tv/TvShowImages.cs index 6d9d4387b..7100657c3 100644 --- a/PlexRequests.Api.Models/Tv/TvShowImages.cs +++ b/PlexRequests.Api.Models/Tv/TvShowImages.cs @@ -1,54 +1,54 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TvShowImages.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; - -namespace PlexRequests.Api.Models.Tv -{ - public class RatingsInfo - { - public double average { get; set; } - } - - public class Datum - { - public int id { get; set; } - public string keyType { get; set; } - public string subKey { get; set; } - public string fileName { get; set; } - public string resolution { get; set; } - public RatingsInfo ratingsInfo { get; set; } - public string thumbnail { get; set; } - } - - public class TvShowImages - { - public List data { get; set; } - public object errors { get; set; } - } - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TvShowImages.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; + +namespace PlexRequests.Api.Models.Tv +{ + public class RatingsInfo + { + public double average { get; set; } + } + + public class Datum + { + public int id { get; set; } + public string keyType { get; set; } + public string subKey { get; set; } + public string fileName { get; set; } + public string resolution { get; set; } + public RatingsInfo ratingsInfo { get; set; } + public string thumbnail { get; set; } + } + + public class TvShowImages + { + public List data { get; set; } + public object errors { get; set; } + } + } \ No newline at end of file diff --git a/PlexRequests.Api/HeadphonesApi.cs b/PlexRequests.Api/HeadphonesApi.cs index 94c29f126..4b6c08a80 100644 --- a/PlexRequests.Api/HeadphonesApi.cs +++ b/PlexRequests.Api/HeadphonesApi.cs @@ -1,204 +1,204 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: HeadphonesApi.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -using Newtonsoft.Json; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Music; -using PlexRequests.Helpers; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class HeadphonesApi : IHeadphonesApi - { - public HeadphonesApi() - { - Api = new ApiRequest(); - } - private ApiRequest Api { get; } - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - - public async Task AddAlbum(string apiKey, Uri baseUrl, string albumId) - { - Log.Trace("Adding album: {0}", albumId); - var request = new RestRequest - { - Resource = "/api?cmd=addAlbum&id={albumId}", - Method = Method.GET - }; - - request.AddQueryParameter("apikey", apiKey); - request.AddUrlSegment("albumId", albumId); - - try - { - var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); - Log.Trace("Add Album Result: {0}", result.DumpJson()); - - var albumResult = result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); - Log.Info("Album add result {0}", albumResult); - - return albumResult; - } - catch (Exception jse) - { - Log.Error(jse); - return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - public async Task> GetIndex(string apiKey, Uri baseUrl) - { - var request = new RestRequest - { - Resource = "/api", - Method = Method.GET - }; - - request.AddQueryParameter("apikey", apiKey); - request.AddQueryParameter("cmd", "getIndex"); - - try - { - var result = await Task.Run(() => Api.ExecuteJson>(request, baseUrl)).ConfigureAwait(false); - - return result; - } - catch (Exception jse) - { - Log.Error(jse); - return new List(); - } - } - public async Task AddArtist(string apiKey, Uri baseUrl, string artistId) - { - Log.Trace("Adding Artist: {0}", artistId); - var request = new RestRequest - { - Resource = "/api", - Method = Method.GET - }; - - request.AddQueryParameter("apikey", apiKey); - request.AddQueryParameter("cmd", "addArtist"); - request.AddQueryParameter("id", artistId); - - try - { - var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); - Log.Info("Add Artist Result: {0}", result.Content); - Log.Trace("Add Artist Result: {0}", result.DumpJson()); - return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); - } - catch (JsonSerializationException jse) - { - Log.Error(jse); - return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - public async Task QueueAlbum(string apiKey, Uri baseUrl, string albumId) - { - Log.Trace("Queing album: {0}", albumId); - var request = new RestRequest - { - Resource = "/api?cmd=queueAlbum&id={albumId}", - Method = Method.GET - }; - - request.AddQueryParameter("apikey", apiKey); - request.AddUrlSegment("albumId", albumId); - - try - { - var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); - Log.Info("Queue Result: {0}", result.Content); - Log.Trace("Queue Result: {0}", result.DumpJson()); - return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); - } - catch (JsonSerializationException jse) - { - Log.Error(jse); - return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - - public async Task RefreshArtist(string apiKey, Uri baseUrl, string artistId) - { - Log.Trace("Refreshing artist: {0}", artistId); - var request = new RestRequest - { - Resource = "/api", - Method = Method.GET - }; - - request.AddQueryParameter("apikey", apiKey); - request.AddQueryParameter("cmd", "queueAlbum"); - request.AddQueryParameter("id", artistId); - - try - { - var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); - Log.Info("Artist refresh Result: {0}", result.Content); - Log.Trace("Artist refresh Result: {0}", result.DumpJson()); - return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); - } - catch (JsonSerializationException jse) - { - Log.Error(jse); - return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - - public HeadphonesVersion GetVersion(string apiKey, Uri baseUrl) - { - var request = new RestRequest - { - Resource = "/api", - Method = Method.GET - }; - - request.AddQueryParameter("apikey", apiKey); - request.AddQueryParameter("cmd", "getVersion"); - - try - { - return Api.ExecuteJson(request, baseUrl); - } - catch (JsonSerializationException jse) - { - Log.Error(jse); - return new HeadphonesVersion(); // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: HeadphonesApi.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Newtonsoft.Json; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Music; +using PlexRequests.Helpers; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class HeadphonesApi : IHeadphonesApi + { + public HeadphonesApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; } + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public async Task AddAlbum(string apiKey, Uri baseUrl, string albumId) + { + Log.Trace("Adding album: {0}", albumId); + var request = new RestRequest + { + Resource = "/api?cmd=addAlbum&id={albumId}", + Method = Method.GET + }; + + request.AddQueryParameter("apikey", apiKey); + request.AddUrlSegment("albumId", albumId); + + try + { + var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); + Log.Trace("Add Album Result: {0}", result.DumpJson()); + + var albumResult = result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); + Log.Info("Album add result {0}", albumResult); + + return albumResult; + } + catch (Exception jse) + { + Log.Error(jse); + return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + public async Task> GetIndex(string apiKey, Uri baseUrl) + { + var request = new RestRequest + { + Resource = "/api", + Method = Method.GET + }; + + request.AddQueryParameter("apikey", apiKey); + request.AddQueryParameter("cmd", "getIndex"); + + try + { + var result = await Task.Run(() => Api.ExecuteJson>(request, baseUrl)).ConfigureAwait(false); + + return result; + } + catch (Exception jse) + { + Log.Error(jse); + return new List(); + } + } + public async Task AddArtist(string apiKey, Uri baseUrl, string artistId) + { + Log.Trace("Adding Artist: {0}", artistId); + var request = new RestRequest + { + Resource = "/api", + Method = Method.GET + }; + + request.AddQueryParameter("apikey", apiKey); + request.AddQueryParameter("cmd", "addArtist"); + request.AddQueryParameter("id", artistId); + + try + { + var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); + Log.Info("Add Artist Result: {0}", result.Content); + Log.Trace("Add Artist Result: {0}", result.DumpJson()); + return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); + } + catch (JsonSerializationException jse) + { + Log.Error(jse); + return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + public async Task QueueAlbum(string apiKey, Uri baseUrl, string albumId) + { + Log.Trace("Queing album: {0}", albumId); + var request = new RestRequest + { + Resource = "/api?cmd=queueAlbum&id={albumId}", + Method = Method.GET + }; + + request.AddQueryParameter("apikey", apiKey); + request.AddUrlSegment("albumId", albumId); + + try + { + var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); + Log.Info("Queue Result: {0}", result.Content); + Log.Trace("Queue Result: {0}", result.DumpJson()); + return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); + } + catch (JsonSerializationException jse) + { + Log.Error(jse); + return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + + public async Task RefreshArtist(string apiKey, Uri baseUrl, string artistId) + { + Log.Trace("Refreshing artist: {0}", artistId); + var request = new RestRequest + { + Resource = "/api", + Method = Method.GET + }; + + request.AddQueryParameter("apikey", apiKey); + request.AddQueryParameter("cmd", "queueAlbum"); + request.AddQueryParameter("id", artistId); + + try + { + var result = await Task.Run(() => Api.Execute(request, baseUrl)).ConfigureAwait(false); + Log.Info("Artist refresh Result: {0}", result.Content); + Log.Trace("Artist refresh Result: {0}", result.DumpJson()); + return result.Content.Equals("OK", StringComparison.CurrentCultureIgnoreCase); + } + catch (JsonSerializationException jse) + { + Log.Error(jse); + return false; // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + + public HeadphonesVersion GetVersion(string apiKey, Uri baseUrl) + { + var request = new RestRequest + { + Resource = "/api", + Method = Method.GET + }; + + request.AddQueryParameter("apikey", apiKey); + request.AddQueryParameter("cmd", "getVersion"); + + try + { + return Api.ExecuteJson(request, baseUrl); + } + catch (JsonSerializationException jse) + { + Log.Error(jse); + return new HeadphonesVersion(); // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Api/MovieBase.cs b/PlexRequests.Api/MovieBase.cs index d3d69fbd1..8a62e0209 100644 --- a/PlexRequests.Api/MovieBase.cs +++ b/PlexRequests.Api/MovieBase.cs @@ -1,36 +1,36 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TvBase.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.Helpers; - -namespace PlexRequests.Api -{ - public abstract class MovieBase - { - private static readonly string Encrypted = "0T3QNSseexLO7n7UPiJvl70Y+KKnvbeTlsusl7Kwq0hPH0BHOuFNGwksNCjkwqWedyDdI/MJeUR4wtL4bIl0Z+//uHXEaYM/4H2pjeLbH5EWdUe5TTj1AhaIR5PQweamvcienRyFD/3YPCC/+qL5mHkKXBkPumMod3Zb/4yN0Ik="; - protected string ApiKey = StringCipher.Decrypt(Encrypted, "ApiKey"); - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TvBase.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.Helpers; + +namespace PlexRequests.Api +{ + public abstract class MovieBase + { + private static readonly string Encrypted = "0T3QNSseexLO7n7UPiJvl70Y+KKnvbeTlsusl7Kwq0hPH0BHOuFNGwksNCjkwqWedyDdI/MJeUR4wtL4bIl0Z+//uHXEaYM/4H2pjeLbH5EWdUe5TTj1AhaIR5PQweamvcienRyFD/3YPCC/+qL5mHkKXBkPumMod3Zb/4yN0Ik="; + protected string ApiKey = StringCipher.Decrypt(Encrypted, "ApiKey"); + } +} diff --git a/PlexRequests.Api/MusicBrainzApi.cs b/PlexRequests.Api/MusicBrainzApi.cs index c810df6c3..b9c581986 100644 --- a/PlexRequests.Api/MusicBrainzApi.cs +++ b/PlexRequests.Api/MusicBrainzApi.cs @@ -1,116 +1,116 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: MusicBrainzApi.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 Newtonsoft.Json; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Music; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class MusicBrainzApi : IMusicBrainzApi - { - public MusicBrainzApi() - { - Api = new ApiRequest(); - } - private ApiRequest Api { get; } - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private readonly Uri BaseUri = new Uri("http://musicbrainz.org/ws/2/"); - - public MusicBrainzSearchResults SearchAlbum(string searchTerm) - { - Log.Trace("Searching for album: {0}", searchTerm); - var request = new RestRequest - { - Resource = "release/?query={searchTerm}&fmt=json", - Method = Method.GET - }; - request.AddUrlSegment("searchTerm", searchTerm); - - try - { - return Api.ExecuteJson(request, BaseUri); - } - catch (JsonSerializationException jse) - { - Log.Warn(jse); - return new MusicBrainzSearchResults(); // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - - public MusicBrainzReleaseInfo GetAlbum(string releaseId) - { - Log.Trace("Getting album: {0}", releaseId); - var request = new RestRequest - { - Resource = "release/{albumId}", - Method = Method.GET - }; - request.AddUrlSegment("albumId", releaseId); - request.AddQueryParameter("fmt", "json"); - request.AddQueryParameter("inc", "artists"); - - try - { - return Api.ExecuteJson(request, BaseUri); - } - catch (JsonSerializationException jse) - { - Log.Warn(jse); - return new MusicBrainzReleaseInfo(); // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - - public MusicBrainzCoverArt GetCoverArt(string releaseId) - { - Log.Trace("Getting cover art for release: {0}", releaseId); - var request = new RestRequest - { - Resource = "release/{releaseId}", - Method = Method.GET - }; - request.AddUrlSegment("releaseId", releaseId); - - try - { - return Api.Execute(request, new Uri("http://coverartarchive.org/")); - } - catch (Exception e) - { - Log.Warn(e); - return new MusicBrainzCoverArt(); // If there is no matching result we do not get returned a JSON string, it just returns "false". - } - } - - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: MusicBrainzApi.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 Newtonsoft.Json; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Music; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class MusicBrainzApi : IMusicBrainzApi + { + public MusicBrainzApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; } + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private readonly Uri BaseUri = new Uri("http://musicbrainz.org/ws/2/"); + + public MusicBrainzSearchResults SearchAlbum(string searchTerm) + { + Log.Trace("Searching for album: {0}", searchTerm); + var request = new RestRequest + { + Resource = "release/?query={searchTerm}&fmt=json", + Method = Method.GET + }; + request.AddUrlSegment("searchTerm", searchTerm); + + try + { + return Api.ExecuteJson(request, BaseUri); + } + catch (JsonSerializationException jse) + { + Log.Warn(jse); + return new MusicBrainzSearchResults(); // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + + public MusicBrainzReleaseInfo GetAlbum(string releaseId) + { + Log.Trace("Getting album: {0}", releaseId); + var request = new RestRequest + { + Resource = "release/{albumId}", + Method = Method.GET + }; + request.AddUrlSegment("albumId", releaseId); + request.AddQueryParameter("fmt", "json"); + request.AddQueryParameter("inc", "artists"); + + try + { + return Api.ExecuteJson(request, BaseUri); + } + catch (JsonSerializationException jse) + { + Log.Warn(jse); + return new MusicBrainzReleaseInfo(); // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + + public MusicBrainzCoverArt GetCoverArt(string releaseId) + { + Log.Trace("Getting cover art for release: {0}", releaseId); + var request = new RestRequest + { + Resource = "release/{releaseId}", + Method = Method.GET + }; + request.AddUrlSegment("releaseId", releaseId); + + try + { + return Api.Execute(request, new Uri("http://coverartarchive.org/")); + } + catch (Exception e) + { + Log.Warn(e); + return new MusicBrainzCoverArt(); // If there is no matching result we do not get returned a JSON string, it just returns "false". + } + } + + } } \ No newline at end of file diff --git a/PlexRequests.Api/Properties/AssemblyInfo.cs b/PlexRequests.Api/Properties/AssemblyInfo.cs index e09af95eb..9b93a138e 100644 --- a/PlexRequests.Api/Properties/AssemblyInfo.cs +++ b/PlexRequests.Api/Properties/AssemblyInfo.cs @@ -1,37 +1,37 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Api")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Api")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8cb8d235-2674-442d-9c6a-35fcaeeb160d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Api")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Api")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8cb8d235-2674-442d-9c6a-35fcaeeb160d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Api/PushbulletApi.cs b/PlexRequests.Api/PushbulletApi.cs index 1e399e048..fe9dbf46e 100644 --- a/PlexRequests.Api/PushbulletApi.cs +++ b/PlexRequests.Api/PushbulletApi.cs @@ -1,64 +1,64 @@ -#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 System.Threading.Tasks; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Notifications; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class PushbulletApi : IPushbulletApi - { - public async Task PushAsync(string accessToken, string title, string message, string deviceIdentifier = default(string)) - { - var request = new RestRequest - { - Method = Method.POST, - - }; - - request.AddHeader("Access-Token", accessToken); - request.AddHeader("Content-Type", "application/json"); - - var push = new PushbulletPush { title = title, body = message, type = "note"}; - - if (!string.IsNullOrEmpty(deviceIdentifier)) - { - push.device_iden = deviceIdentifier; - } - - request.AddJsonBody(push); - - var api = new ApiRequest(); - return await Task.Run(() => api.ExecuteJson(request, new Uri("https://api.pushbullet.com/v2/pushes"))); - } - } -} - +#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 System.Threading.Tasks; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Notifications; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class PushbulletApi : IPushbulletApi + { + public async Task PushAsync(string accessToken, string title, string message, string deviceIdentifier = default(string)) + { + var request = new RestRequest + { + Method = Method.POST, + + }; + + request.AddHeader("Access-Token", accessToken); + request.AddHeader("Content-Type", "application/json"); + + var push = new PushbulletPush { title = title, body = message, type = "note"}; + + if (!string.IsNullOrEmpty(deviceIdentifier)) + { + push.device_iden = deviceIdentifier; + } + + request.AddJsonBody(push); + + var api = new ApiRequest(); + return await Task.Run(() => api.ExecuteJson(request, new Uri("https://api.pushbullet.com/v2/pushes"))); + } + } +} + diff --git a/PlexRequests.Api/PushoverApi.cs b/PlexRequests.Api/PushoverApi.cs index 6d109ca9b..068e5b800 100644 --- a/PlexRequests.Api/PushoverApi.cs +++ b/PlexRequests.Api/PushoverApi.cs @@ -1,57 +1,57 @@ -#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 System.Threading.Tasks; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Notifications; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class PushoverApi : IPushoverApi - { - public async Task PushAsync(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 await Task.Run(() => api.ExecuteJson(request, new Uri("https://api.pushover.net/1"))); - } - } -} - +#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 System.Threading.Tasks; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Notifications; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class PushoverApi : IPushoverApi + { + public async Task PushAsync(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 await Task.Run(() => api.ExecuteJson(request, new Uri("https://api.pushover.net/1"))); + } + } +} + diff --git a/PlexRequests.Api/TheTvDbApi.cs b/PlexRequests.Api/TheTvDbApi.cs index 2ac7734f7..057558eef 100644 --- a/PlexRequests.Api/TheTvDbApi.cs +++ b/PlexRequests.Api/TheTvDbApi.cs @@ -1,144 +1,144 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TheTvDbApi.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 PlexRequests.Api.Models.Tv; - -using RestSharp; - -namespace PlexRequests.Api -{ - [Obsolete("Use TVMazeAPP")] - public class TheTvDbApi : TvBase - { - public TheTvDbApi() - { - Api = new ApiRequest(); - } - private ApiRequest Api { get; } - - /// - /// Authenticates against TheTVDB. - /// - /// - public string Authenticate() - { - var request = new RestRequest - { - Method = Method.POST, - Resource = "login", - RequestFormat = DataFormat.Json, - - }; - var apiKey = new { apikey = ApiKey }; - - request.AddBody(apiKey); - request.AddHeader("Content-Type", "application/json"); - - return Api.Execute(request, Url).token; - } - - /// - /// Refreshes the token. - /// - /// The old token. - /// - public Authentication RefreshToken(string oldToken) - { - var request = new RestRequest - { - Method = Method.POST, - Resource = "refresh_token" - }; - request.AddHeader("Authorization", $"Bearer {oldToken}"); - request.AddHeader("Content-Type", "application/json"); - - return Api.Execute(request, Url); - } - - /// - /// Searches for a tv series. - /// - /// The search term. - /// The token. - /// - public TvSearchResult SearchTv(string searchTerm, string token) - { - var request = new RestRequest - { - Method = Method.GET, - Resource = "search/series?name={searchTerm}" - }; - request.AddUrlSegment("searchTerm", searchTerm); - request.AddHeader("Authorization", $"Bearer {token}"); - request.AddHeader("Content-Type", "application/json"); - - return Api.Execute(request, Url); - } - - /// - /// Gets the tv images. - /// - /// The series identifier. - /// The token. - /// - public TvShowImages GetTvImages(int seriesId, string token) - { - var request = new RestRequest - { - Method = Method.GET, - Resource = "/series/{id}/images/query?keyType=poster" - }; - request.AddUrlSegment("id", seriesId.ToString()); - request.AddHeader("Authorization", $"Bearer {token}"); - request.AddHeader("Content-Type", "application/json"); - - return Api.Execute(request, Url); - } - - - /// - /// Gets the information for a TV Series. - /// - /// The TVDB identifier. - /// The token. - /// - public TvShowInformation GetInformation(int tvdbId, string token) - { - var request = new RestRequest - { - Method = Method.GET, - Resource = "series/{id}" - }; - request.AddUrlSegment("id", tvdbId.ToString()); - request.AddHeader("Authorization", $"Bearer {token}"); - request.AddHeader("Content-Type", "application/json"); - - return Api.Execute(request, Url); - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TheTvDbApi.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 PlexRequests.Api.Models.Tv; + +using RestSharp; + +namespace PlexRequests.Api +{ + [Obsolete("Use TVMazeAPP")] + public class TheTvDbApi : TvBase + { + public TheTvDbApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; } + + /// + /// Authenticates against TheTVDB. + /// + /// + public string Authenticate() + { + var request = new RestRequest + { + Method = Method.POST, + Resource = "login", + RequestFormat = DataFormat.Json, + + }; + var apiKey = new { apikey = ApiKey }; + + request.AddBody(apiKey); + request.AddHeader("Content-Type", "application/json"); + + return Api.Execute(request, Url).token; + } + + /// + /// Refreshes the token. + /// + /// The old token. + /// + public Authentication RefreshToken(string oldToken) + { + var request = new RestRequest + { + Method = Method.POST, + Resource = "refresh_token" + }; + request.AddHeader("Authorization", $"Bearer {oldToken}"); + request.AddHeader("Content-Type", "application/json"); + + return Api.Execute(request, Url); + } + + /// + /// Searches for a tv series. + /// + /// The search term. + /// The token. + /// + public TvSearchResult SearchTv(string searchTerm, string token) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "search/series?name={searchTerm}" + }; + request.AddUrlSegment("searchTerm", searchTerm); + request.AddHeader("Authorization", $"Bearer {token}"); + request.AddHeader("Content-Type", "application/json"); + + return Api.Execute(request, Url); + } + + /// + /// Gets the tv images. + /// + /// The series identifier. + /// The token. + /// + public TvShowImages GetTvImages(int seriesId, string token) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "/series/{id}/images/query?keyType=poster" + }; + request.AddUrlSegment("id", seriesId.ToString()); + request.AddHeader("Authorization", $"Bearer {token}"); + request.AddHeader("Content-Type", "application/json"); + + return Api.Execute(request, Url); + } + + + /// + /// Gets the information for a TV Series. + /// + /// The TVDB identifier. + /// The token. + /// + public TvShowInformation GetInformation(int tvdbId, string token) + { + var request = new RestRequest + { + Method = Method.GET, + Resource = "series/{id}" + }; + request.AddUrlSegment("id", tvdbId.ToString()); + request.AddHeader("Authorization", $"Bearer {token}"); + request.AddHeader("Content-Type", "application/json"); + + return Api.Execute(request, Url); + } + } +} diff --git a/PlexRequests.Api/TvBase.cs b/PlexRequests.Api/TvBase.cs index 43555fede..3c20b6274 100644 --- a/PlexRequests.Api/TvBase.cs +++ b/PlexRequests.Api/TvBase.cs @@ -1,39 +1,39 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: MovieBase.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 PlexRequests.Helpers; - -namespace PlexRequests.Api -{ - public abstract class TvBase - { - private static readonly string Encrypted = "AVdhrVK6XX8anvrQgEyN/qNr9rk8ZPwy7/r1t5t5cKyUEzxcyk0L1v6dSxgE7hTCxvITUX2cWa6VlFMlTMgJWyuPZml7fN3csCHntgd/VGYro6VfNf24snZ/rQ3mf005"; - protected string ApiKey = StringCipher.Decrypt(Encrypted, "ApiKey"); - protected Uri Url = new Uri("https://api-beta.thetvdb.com/"); - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: MovieBase.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 PlexRequests.Helpers; + +namespace PlexRequests.Api +{ + public abstract class TvBase + { + private static readonly string Encrypted = "AVdhrVK6XX8anvrQgEyN/qNr9rk8ZPwy7/r1t5t5cKyUEzxcyk0L1v6dSxgE7hTCxvITUX2cWa6VlFMlTMgJWyuPZml7fN3csCHntgd/VGYro6VfNf24snZ/rQ3mf005"; + protected string ApiKey = StringCipher.Decrypt(Encrypted, "ApiKey"); + protected Uri Url = new Uri("https://api-beta.thetvdb.com/"); + } +} diff --git a/PlexRequests.Api/TvMazeBase.cs b/PlexRequests.Api/TvMazeBase.cs index d4eddefee..e4e723c95 100644 --- a/PlexRequests.Api/TvMazeBase.cs +++ b/PlexRequests.Api/TvMazeBase.cs @@ -1,33 +1,33 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TvMazeBase.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 -{ - public class TvMazeBase - { - protected string Uri = "http://api.tvmaze.com"; - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TvMazeBase.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 +{ + public class TvMazeBase + { + protected string Uri = "http://api.tvmaze.com"; + } } \ No newline at end of file diff --git a/PlexRequests.Core.Tests/Properties/AssemblyInfo.cs b/PlexRequests.Core.Tests/Properties/AssemblyInfo.cs index 1cc04fc46..ce0830f7e 100644 --- a/PlexRequests.Core.Tests/Properties/AssemblyInfo.cs +++ b/PlexRequests.Core.Tests/Properties/AssemblyInfo.cs @@ -1,37 +1,37 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Core.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Core.Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fcfecd5d-47f6-454d-8692-e27a921be655")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Core.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Core.Tests")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fcfecd5d-47f6-454d-8692-e27a921be655")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Core.Tests/StatusCheckerTests.cs b/PlexRequests.Core.Tests/StatusCheckerTests.cs index da0dd44d7..38b9b4674 100644 --- a/PlexRequests.Core.Tests/StatusCheckerTests.cs +++ b/PlexRequests.Core.Tests/StatusCheckerTests.cs @@ -1,46 +1,46 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AuthenticationSettingsTests.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 NUnit.Framework; - -namespace PlexRequests.Core.Tests -{ - [TestFixture] - public class StatusCheckerTests - { - [Test] - [Ignore("API Limit")] - public void CheckStatusTest() - { - var checker = new StatusChecker(); - var status = checker.GetStatus(); - - Assert.That(status, Is.Not.Null); - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AuthenticationSettingsTests.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 NUnit.Framework; + +namespace PlexRequests.Core.Tests +{ + [TestFixture] + public class StatusCheckerTests + { + [Test] + [Ignore("API Limit")] + public void CheckStatusTest() + { + var checker = new StatusChecker(); + var status = checker.GetStatus(); + + Assert.That(status, Is.Not.Null); + } + } +} diff --git a/PlexRequests.Core/IRequestService.cs b/PlexRequests.Core/IRequestService.cs index 740216bec..7dae2f6a7 100644 --- a/PlexRequests.Core/IRequestService.cs +++ b/PlexRequests.Core/IRequestService.cs @@ -1,57 +1,57 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: IRequestService.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.Threading.Tasks; - -using PlexRequests.Store; - -namespace PlexRequests.Core -{ - public interface IRequestService - { - long AddRequest(RequestedModel model); - Task AddRequestAsync(RequestedModel model); - RequestedModel CheckRequest(int providerId); - Task CheckRequestAsync(int providerId); - RequestedModel CheckRequest(string musicId); - Task CheckRequestAsync(string musicId); - - void DeleteRequest(RequestedModel request); - Task DeleteRequestAsync(RequestedModel request); - bool UpdateRequest(RequestedModel model); - Task UpdateRequestAsync(RequestedModel model); - RequestedModel Get(int id); - Task GetAsync(int id); - IEnumerable GetAll(); - Task> GetAllAsync(); - bool BatchUpdate(IEnumerable model); - Task BatchUpdateAsync(IEnumerable model); - bool BatchDelete(IEnumerable model); - Task BatchDeleteAsync(IEnumerable model); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IRequestService.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.Threading.Tasks; + +using PlexRequests.Store; + +namespace PlexRequests.Core +{ + public interface IRequestService + { + long AddRequest(RequestedModel model); + Task AddRequestAsync(RequestedModel model); + RequestedModel CheckRequest(int providerId); + Task CheckRequestAsync(int providerId); + RequestedModel CheckRequest(string musicId); + Task CheckRequestAsync(string musicId); + + void DeleteRequest(RequestedModel request); + Task DeleteRequestAsync(RequestedModel request); + bool UpdateRequest(RequestedModel model); + Task UpdateRequestAsync(RequestedModel model); + RequestedModel Get(int id); + Task GetAsync(int id); + IEnumerable GetAll(); + Task> GetAllAsync(); + bool BatchUpdate(IEnumerable model); + Task BatchUpdateAsync(IEnumerable model); + bool BatchDelete(IEnumerable model); + Task BatchDeleteAsync(IEnumerable model); + } } \ No newline at end of file diff --git a/PlexRequests.Core/ISettingsService.cs b/PlexRequests.Core/ISettingsService.cs index a1ef1341e..2b3098811 100644 --- a/PlexRequests.Core/ISettingsService.cs +++ b/PlexRequests.Core/ISettingsService.cs @@ -1,41 +1,41 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ISettingsService.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.Threading.Tasks; - -namespace PlexRequests.Core -{ - public interface ISettingsService - { - T GetSettings(); - Task GetSettingsAsync(); - bool SaveSettings(T model); - Task SaveSettingsAsync(T model); - bool Delete(T model); - Task DeleteAsync(T model); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ISettingsService.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.Threading.Tasks; + +namespace PlexRequests.Core +{ + public interface ISettingsService + { + T GetSettings(); + Task GetSettingsAsync(); + bool SaveSettings(T model); + Task SaveSettingsAsync(T model); + bool Delete(T model); + Task DeleteAsync(T model); + } } \ No newline at end of file diff --git a/PlexRequests.Core/Models/StatusModel.cs b/PlexRequests.Core/Models/StatusModel.cs index 62047126e..9cbcf644d 100644 --- a/PlexRequests.Core/Models/StatusModel.cs +++ b/PlexRequests.Core/Models/StatusModel.cs @@ -1,38 +1,38 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: StatusModel.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.Core.Models -{ - public class StatusModel - { - public string Version { get; set; } - public bool UpdateAvailable { get; set; } - public string UpdateUri { get; set; } - public string DownloadUri { get; set; } - public string ReleaseNotes { get; set; } - public string ReleaseTitle { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: StatusModel.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.Core.Models +{ + public class StatusModel + { + public string Version { get; set; } + public bool UpdateAvailable { get; set; } + public string UpdateUri { get; set; } + public string DownloadUri { get; set; } + public string ReleaseNotes { get; set; } + public string ReleaseTitle { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/Settings.cs b/PlexRequests.Core/SettingModels/Settings.cs index 4eb005707..dfdb4cbde 100644 --- a/PlexRequests.Core/SettingModels/Settings.cs +++ b/PlexRequests.Core/SettingModels/Settings.cs @@ -1,33 +1,33 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: Settings.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.Core.SettingModels -{ - public class Settings - { - public int Id { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Settings.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.Core.SettingModels +{ + public class Settings + { + public int Id { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Core/UserIdentity.cs b/PlexRequests.Core/UserIdentity.cs index 5c5f7302c..3a01146e5 100644 --- a/PlexRequests.Core/UserIdentity.cs +++ b/PlexRequests.Core/UserIdentity.cs @@ -1,38 +1,38 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: UserIdentity.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 Nancy.Security; - -namespace PlexRequests.Core -{ - public class UserIdentity : IUserIdentity - { - public string UserName { get; set; } - public IEnumerable Claims { get; set; } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserIdentity.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 Nancy.Security; + +namespace PlexRequests.Core +{ + public class UserIdentity : IUserIdentity + { + public string UserName { get; set; } + public IEnumerable Claims { get; set; } + } +} diff --git a/PlexRequests.Helpers.Tests/AssemblyHelperTests.cs b/PlexRequests.Helpers.Tests/AssemblyHelperTests.cs index b55443e61..0f4e6b9c5 100644 --- a/PlexRequests.Helpers.Tests/AssemblyHelperTests.cs +++ b/PlexRequests.Helpers.Tests/AssemblyHelperTests.cs @@ -1,41 +1,41 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AssemblyHelperTests.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 NUnit.Framework; - -namespace PlexRequests.Helpers.Tests -{ - [TestFixture] - public class AssemblyHelperTests - { - [Test] - public void GetReleaseVersionTest() - { - var result = AssemblyHelper.GetProductVersion(); - Assert.That(result, Is.Not.Null); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AssemblyHelperTests.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 NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class AssemblyHelperTests + { + [Test] + public void GetReleaseVersionTest() + { + var result = AssemblyHelper.GetProductVersion(); + Assert.That(result, Is.Not.Null); + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/HtmlRemoverTests.cs b/PlexRequests.Helpers.Tests/HtmlRemoverTests.cs index 6a56a20ae..85d6cc192 100644 --- a/PlexRequests.Helpers.Tests/HtmlRemoverTests.cs +++ b/PlexRequests.Helpers.Tests/HtmlRemoverTests.cs @@ -1,56 +1,56 @@ -#region Copyright - -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: HtmlRemoverTests.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 NUnit.Framework; - -namespace PlexRequests.Helpers.Tests -{ - [TestFixture] - public class HtmlRemoverTests - { - [Test] - public void RemoveHtmlBasic() - { - var html = "this is bold

para

OK!"; - var result = html.RemoveHtml(); - Assert.That(result, Is.Not.Null); - Assert.That(result, Is.EqualTo("this is bold para OK!")); - } - - [Test] - public void RemoveHtmlMoreTags() - { - // Good 'ol Ali G ;) - var html = "

\"Ali G: Rezurection\" includes every episode of Da Ali G Show with new, original introductions by star, creator/writer Sacha Baron Cohen, along with the BAFTA(R) Award-winning English episodes of Da Ali G Show which have never aired on American television and The Best of Ali G.

"; - var result = html.RemoveHtml(); - Assert.That(result, Is.Not.Null); - Assert.That(result, Is.EqualTo("\"Ali G: Rezurection\" includes every episode of Da Ali G Show with new, original introductions by star, creator/writer Sacha Baron Cohen, along with the BAFTA(R) Award-winning English episodes of Da Ali G Show which have never aired on American television and The Best of Ali G.")); - } - } +#region Copyright + +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: HtmlRemoverTests.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 NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class HtmlRemoverTests + { + [Test] + public void RemoveHtmlBasic() + { + var html = "this is bold

para

OK!"; + var result = html.RemoveHtml(); + Assert.That(result, Is.Not.Null); + Assert.That(result, Is.EqualTo("this is bold para OK!")); + } + + [Test] + public void RemoveHtmlMoreTags() + { + // Good 'ol Ali G ;) + var html = "

\"Ali G: Rezurection\" includes every episode of Da Ali G Show with new, original introductions by star, creator/writer Sacha Baron Cohen, along with the BAFTA(R) Award-winning English episodes of Da Ali G Show which have never aired on American television and The Best of Ali G.

"; + var result = html.RemoveHtml(); + Assert.That(result, Is.Not.Null); + Assert.That(result, Is.EqualTo("\"Ali G: Rezurection\" includes every episode of Da Ali G Show with new, original introductions by star, creator/writer Sacha Baron Cohen, along with the BAFTA(R) Award-winning English episodes of Da Ali G Show which have never aired on American television and The Best of Ali G.")); + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/PasswordHasherTests.cs b/PlexRequests.Helpers.Tests/PasswordHasherTests.cs index 2ac54a547..dc34e528b 100644 --- a/PlexRequests.Helpers.Tests/PasswordHasherTests.cs +++ b/PlexRequests.Helpers.Tests/PasswordHasherTests.cs @@ -1,49 +1,49 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AssemblyHelperTests.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 NUnit.Framework; - -namespace PlexRequests.Helpers.Tests -{ - [TestFixture] - public class PasswordHasherTests - { - [Test] - public void TestHash() - { - var password = "abcdef"; - var salt = PasswordHasher.GenerateSalt(); - var hash = PasswordHasher.ComputeHash(password, salt); - - Assert.That(hash, Is.Not.EqualTo(password)); - - var match = PasswordHasher.VerifyPassword(password, salt, hash); - - Assert.That(match, Is.True); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AssemblyHelperTests.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 NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class PasswordHasherTests + { + [Test] + public void TestHash() + { + var password = "abcdef"; + var salt = PasswordHasher.GenerateSalt(); + var hash = PasswordHasher.ComputeHash(password, salt); + + Assert.That(hash, Is.Not.EqualTo(password)); + + var match = PasswordHasher.VerifyPassword(password, salt, hash); + + Assert.That(match, Is.True); + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/Properties/AssemblyInfo.cs b/PlexRequests.Helpers.Tests/Properties/AssemblyInfo.cs index 1bebc4548..3a3229bd4 100644 --- a/PlexRequests.Helpers.Tests/Properties/AssemblyInfo.cs +++ b/PlexRequests.Helpers.Tests/Properties/AssemblyInfo.cs @@ -1,37 +1,37 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Helpers.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Helpers.Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("0e6395d3-b074-49e8-898d-0eb99e507e0e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Helpers.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Helpers.Tests")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0e6395d3-b074-49e8-898d-0eb99e507e0e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Helpers.Tests/UriHelperTests.cs b/PlexRequests.Helpers.Tests/UriHelperTests.cs index cc748f95c..be69d7932 100644 --- a/PlexRequests.Helpers.Tests/UriHelperTests.cs +++ b/PlexRequests.Helpers.Tests/UriHelperTests.cs @@ -1,108 +1,108 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: UriHelperTests.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using System; -using System.Collections.Generic; - -using NUnit.Framework; - -namespace PlexRequests.Helpers.Tests -{ - [TestFixture] - public class UriHelperTests - { - [TestCaseSource(nameof(UriData))] - public Uri CreateUri1(string uri) - { - return uri.ReturnUri(); - } - - [Test] - public void CreateUriWithSsl() - { - var uri = "192.168.1.69"; - var result = uri.ReturnUri(8080, true); - - Assert.That(result, Is.EqualTo(new Uri("https://192.168.1.69:8080"))); - } - - [TestCaseSource(nameof(UriDataWithPort))] - public Uri CreateUri2(string uri, int port) - { - return uri.ReturnUri(port); - } - - [TestCaseSource(nameof(UriDataWithSubDir))] - public Uri CreateUriWithSubDir(string uri, int port, bool ssl, string subDir) - { - return uri.ReturnUriWithSubDir(port, ssl, subDir); - } - - private static IEnumerable UriData - { - get - { - yield return new TestCaseData("google.com").Returns(new Uri("http://google.com/")); - yield return new TestCaseData("http://google.com").Returns(new Uri("http://google.com/")); - yield return new TestCaseData("https://google.com").Returns(new Uri("https://google.com/")); - yield return new TestCaseData("192.168.1.1").Returns(new Uri("http://192.168.1.1")); - yield return new TestCaseData("0.0.0.0:5533").Returns(new Uri("http://0.0.0.0:5533")); - yield return new TestCaseData("www.google.com").Returns(new Uri("http://www.google.com/")); - yield return new TestCaseData("http://www.google.com/").Returns(new Uri("http://www.google.com/")); - yield return new TestCaseData("https://www.google.com").Returns(new Uri("https://www.google.com/")); - yield return new TestCaseData("www.google.com:443").Returns(new Uri("http://www.google.com:443/")); - yield return new TestCaseData("https://www.google.com:443").Returns(new Uri("https://www.google.com/")); - yield return new TestCaseData("http://www.google.com:443/id=2").Returns(new Uri("http://www.google.com:443/id=2")); - yield return new TestCaseData("www.google.com:4438/id=22").Returns(new Uri("http://www.google.com:4438/id=22")); - } - } - - private static IEnumerable UriDataWithPort - { - get - { - yield return new TestCaseData("www.google.com", 80).Returns(new Uri("http://www.google.com:80/")); - yield return new TestCaseData("www.google.com", 443).Returns(new Uri("http://www.google.com:443/")); - yield return new TestCaseData("http://www.google.com", 443).Returns(new Uri("http://www.google.com:443/")); - yield return new TestCaseData("https://www.google.com", 443).Returns(new Uri("https://www.google.com:443/")); - yield return new TestCaseData("http://www.google.com/id=2", 443).Returns(new Uri("http://www.google.com:443/id=2")); - yield return new TestCaseData("https://www.google.com/id=2", 443).Returns(new Uri("https://www.google.com:443/id=2")); - } - } - - private static IEnumerable UriDataWithSubDir - { - get - { - yield return new TestCaseData("www.google.com", 80, false, "test").Returns(new Uri("http://www.google.com:80/test")); - yield return new TestCaseData("www.google.com", 443, false, "test").Returns(new Uri("http://www.google.com:443/test")); - yield return new TestCaseData("http://www.google.com", 443, true, "test").Returns(new Uri("https://www.google.com:443/test")); - yield return new TestCaseData("https://www.google.com", 443, true, "test").Returns(new Uri("https://www.google.com:443/test")); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UriHelperTests.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class UriHelperTests + { + [TestCaseSource(nameof(UriData))] + public Uri CreateUri1(string uri) + { + return uri.ReturnUri(); + } + + [Test] + public void CreateUriWithSsl() + { + var uri = "192.168.1.69"; + var result = uri.ReturnUri(8080, true); + + Assert.That(result, Is.EqualTo(new Uri("https://192.168.1.69:8080"))); + } + + [TestCaseSource(nameof(UriDataWithPort))] + public Uri CreateUri2(string uri, int port) + { + return uri.ReturnUri(port); + } + + [TestCaseSource(nameof(UriDataWithSubDir))] + public Uri CreateUriWithSubDir(string uri, int port, bool ssl, string subDir) + { + return uri.ReturnUriWithSubDir(port, ssl, subDir); + } + + private static IEnumerable UriData + { + get + { + yield return new TestCaseData("google.com").Returns(new Uri("http://google.com/")); + yield return new TestCaseData("http://google.com").Returns(new Uri("http://google.com/")); + yield return new TestCaseData("https://google.com").Returns(new Uri("https://google.com/")); + yield return new TestCaseData("192.168.1.1").Returns(new Uri("http://192.168.1.1")); + yield return new TestCaseData("0.0.0.0:5533").Returns(new Uri("http://0.0.0.0:5533")); + yield return new TestCaseData("www.google.com").Returns(new Uri("http://www.google.com/")); + yield return new TestCaseData("http://www.google.com/").Returns(new Uri("http://www.google.com/")); + yield return new TestCaseData("https://www.google.com").Returns(new Uri("https://www.google.com/")); + yield return new TestCaseData("www.google.com:443").Returns(new Uri("http://www.google.com:443/")); + yield return new TestCaseData("https://www.google.com:443").Returns(new Uri("https://www.google.com/")); + yield return new TestCaseData("http://www.google.com:443/id=2").Returns(new Uri("http://www.google.com:443/id=2")); + yield return new TestCaseData("www.google.com:4438/id=22").Returns(new Uri("http://www.google.com:4438/id=22")); + } + } + + private static IEnumerable UriDataWithPort + { + get + { + yield return new TestCaseData("www.google.com", 80).Returns(new Uri("http://www.google.com:80/")); + yield return new TestCaseData("www.google.com", 443).Returns(new Uri("http://www.google.com:443/")); + yield return new TestCaseData("http://www.google.com", 443).Returns(new Uri("http://www.google.com:443/")); + yield return new TestCaseData("https://www.google.com", 443).Returns(new Uri("https://www.google.com:443/")); + yield return new TestCaseData("http://www.google.com/id=2", 443).Returns(new Uri("http://www.google.com:443/id=2")); + yield return new TestCaseData("https://www.google.com/id=2", 443).Returns(new Uri("https://www.google.com:443/id=2")); + } + } + + private static IEnumerable UriDataWithSubDir + { + get + { + yield return new TestCaseData("www.google.com", 80, false, "test").Returns(new Uri("http://www.google.com:80/test")); + yield return new TestCaseData("www.google.com", 443, false, "test").Returns(new Uri("http://www.google.com:443/test")); + yield return new TestCaseData("http://www.google.com", 443, true, "test").Returns(new Uri("https://www.google.com:443/test")); + yield return new TestCaseData("https://www.google.com", 443, true, "test").Returns(new Uri("https://www.google.com:443/test")); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/AssemblyHelper.cs b/PlexRequests.Helpers/AssemblyHelper.cs index 6c450bda8..ef342cc41 100644 --- a/PlexRequests.Helpers/AssemblyHelper.cs +++ b/PlexRequests.Helpers/AssemblyHelper.cs @@ -1,49 +1,49 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AssemblyHelper.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.Diagnostics; -using System.Reflection; - -namespace PlexRequests.Helpers -{ - public class AssemblyHelper - { - public static string GetAssemblyVersion() - { - var assembly = Assembly.GetExecutingAssembly(); - var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); - return fvi.FileVersion; - } - - public static string GetProductVersion() - { - var assembly = Assembly.GetExecutingAssembly(); - var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); - var retVersion = fvi.ProductVersion; - return retVersion; - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AssemblyHelper.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.Diagnostics; +using System.Reflection; + +namespace PlexRequests.Helpers +{ + public class AssemblyHelper + { + public static string GetAssemblyVersion() + { + var assembly = Assembly.GetExecutingAssembly(); + var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); + return fvi.FileVersion; + } + + public static string GetProductVersion() + { + var assembly = Assembly.GetExecutingAssembly(); + var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); + var retVersion = fvi.ProductVersion; + return retVersion; + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/ByteConverterHelper.cs b/PlexRequests.Helpers/ByteConverterHelper.cs index 87d569592..a18ecfce8 100644 --- a/PlexRequests.Helpers/ByteConverterHelper.cs +++ b/PlexRequests.Helpers/ByteConverterHelper.cs @@ -1,54 +1,54 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ByteConverterHelper.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.Text; - -using Newtonsoft.Json; - -namespace PlexRequests.Helpers -{ - public class ByteConverterHelper - { - public static byte[] ReturnBytes(object obj) - { - var json = JsonConvert.SerializeObject(obj); - var bytes = Encoding.UTF8.GetBytes(json); - - return bytes; - } - - public static T ReturnObject(byte[] bytes) - { - var json = Encoding.UTF8.GetString(bytes); - var model = JsonConvert.DeserializeObject(json); - return model; - } - public static string ReturnFromBytes(byte[] bytes) - { - return Encoding.UTF8.GetString(bytes); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ByteConverterHelper.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.Text; + +using Newtonsoft.Json; + +namespace PlexRequests.Helpers +{ + public class ByteConverterHelper + { + public static byte[] ReturnBytes(object obj) + { + var json = JsonConvert.SerializeObject(obj); + var bytes = Encoding.UTF8.GetBytes(json); + + return bytes; + } + + public static T ReturnObject(byte[] bytes) + { + var json = Encoding.UTF8.GetString(bytes); + var model = JsonConvert.DeserializeObject(json); + return model; + } + public static string ReturnFromBytes(byte[] bytes) + { + return Encoding.UTF8.GetString(bytes); + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/Exceptions/ApplicationSettingsException.cs b/PlexRequests.Helpers/Exceptions/ApplicationSettingsException.cs index 6539fc4cb..a923651d1 100644 --- a/PlexRequests.Helpers/Exceptions/ApplicationSettingsException.cs +++ b/PlexRequests.Helpers/Exceptions/ApplicationSettingsException.cs @@ -1,48 +1,48 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ApplicationSettingsException.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.Helpers.Exceptions -{ - public class ApplicationSettingsException : Exception - { - - public ApplicationSettingsException(string message) : base(message) - { - - } - public ApplicationSettingsException(string message, Exception innerException) : base(message, innerException) - { - - } - - public ApplicationSettingsException() - { - - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ApplicationSettingsException.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.Helpers.Exceptions +{ + public class ApplicationSettingsException : Exception + { + + public ApplicationSettingsException(string message) : base(message) + { + + } + public ApplicationSettingsException(string message, Exception innerException) : base(message, innerException) + { + + } + + public ApplicationSettingsException() + { + + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/HtmlRemover.cs b/PlexRequests.Helpers/HtmlRemover.cs index 43d484255..812f845a7 100644 --- a/PlexRequests.Helpers/HtmlRemover.cs +++ b/PlexRequests.Helpers/HtmlRemover.cs @@ -1,45 +1,45 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: HtmlRemover.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.Text.RegularExpressions; - -namespace PlexRequests.Helpers -{ - public static class HtmlRemover - { - public static string RemoveHtml(this string value) - { - if (string.IsNullOrEmpty(value)) - { - return string.Empty; - } - var step1 = Regex.Replace(value, @"<[^>]+>| ", "").Trim(); - var step2 = Regex.Replace(step1, @"\s{2,}", " "); - return step2; - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: HtmlRemover.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.Text.RegularExpressions; + +namespace PlexRequests.Helpers +{ + public static class HtmlRemover + { + public static string RemoveHtml(this string value) + { + if (string.IsNullOrEmpty(value)) + { + return string.Empty; + } + var step1 = Regex.Replace(value, @"<[^>]+>| ", "").Trim(); + var step2 = Regex.Replace(step1, @"\s{2,}", " "); + return step2; + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/ICacheProvider.cs b/PlexRequests.Helpers/ICacheProvider.cs index 00fde411b..18b051ca0 100644 --- a/PlexRequests.Helpers/ICacheProvider.cs +++ b/PlexRequests.Helpers/ICacheProvider.cs @@ -1,68 +1,68 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ICacheProvider.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Threading.Tasks; - -namespace PlexRequests.Helpers -{ - public interface ICacheProvider - { - /// - /// Gets the item from the cache, if the item is not present - /// then we will get that item and store it in the cache. - /// - /// Type to store in the cache - /// The key - /// The item callback. This will be called if the item is not present in the cache. - /// The amount of time we want to cache the object - /// - T GetOrSet(string key, Func itemCallback, int cacheTime = 20) where T : class; - Task GetOrSetAsync(string key, Func> itemCallback, int cacheTime = 20) where T : class; - - /// - /// Gets the specified item from the cache. - /// - /// Type to get from the cache - /// The key. - /// - T Get(string key) where T : class; - - /// - /// Set/Store the specified object in the cache - /// - /// The key. - /// The object we want to store. - /// The amount of time we want to cache the object. - void Set(string key, object data, int cacheTime = 20); - - /// - /// Removes the specified object from the cache. - /// - /// The key. - void Remove(string key); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ICacheProvider.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Threading.Tasks; + +namespace PlexRequests.Helpers +{ + public interface ICacheProvider + { + /// + /// Gets the item from the cache, if the item is not present + /// then we will get that item and store it in the cache. + /// + /// Type to store in the cache + /// The key + /// The item callback. This will be called if the item is not present in the cache. + /// The amount of time we want to cache the object + /// + T GetOrSet(string key, Func itemCallback, int cacheTime = 20) where T : class; + Task GetOrSetAsync(string key, Func> itemCallback, int cacheTime = 20) where T : class; + + /// + /// Gets the specified item from the cache. + /// + /// Type to get from the cache + /// The key. + /// + T Get(string key) where T : class; + + /// + /// Set/Store the specified object in the cache + /// + /// The key. + /// The object we want to store. + /// The amount of time we want to cache the object. + void Set(string key, object data, int cacheTime = 20); + + /// + /// Removes the specified object from the cache. + /// + /// The key. + void Remove(string key); + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/JsonConvertHelper.cs b/PlexRequests.Helpers/JsonConvertHelper.cs index 9762e1363..9eda9652f 100644 --- a/PlexRequests.Helpers/JsonConvertHelper.cs +++ b/PlexRequests.Helpers/JsonConvertHelper.cs @@ -1,54 +1,54 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: JsonConvertHelper.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 Newtonsoft.Json; - -namespace PlexRequests.Helpers -{ - public static class JsonConvertHelper - { - public static T[] ParseObjectToArray(object ambiguousObject) - { - var json = ambiguousObject.ToString(); - if (string.IsNullOrWhiteSpace(json)) - { - return new T[0]; // Could return null here instead. - } - if (json.TrimStart().StartsWith("[")) - { - return JsonConvert.DeserializeObject(json); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: JsonConvertHelper.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 Newtonsoft.Json; + +namespace PlexRequests.Helpers +{ + public static class JsonConvertHelper + { + public static T[] ParseObjectToArray(object ambiguousObject) + { + var json = ambiguousObject.ToString(); + if (string.IsNullOrWhiteSpace(json)) + { + return new T[0]; // Could return null here instead. + } + if (json.TrimStart().StartsWith("[")) + { + return JsonConvert.DeserializeObject(json); + } if (json.TrimStart().Equals("{}")) { return new T[0]; - } - - return new T[1] { JsonConvert.DeserializeObject(json) }; - - } - } + } + + return new T[1] { JsonConvert.DeserializeObject(json) }; + + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/ObjectCopier.cs b/PlexRequests.Helpers/ObjectCopier.cs index 2c4ffc2b4..2cd24808c 100644 --- a/PlexRequests.Helpers/ObjectCopier.cs +++ b/PlexRequests.Helpers/ObjectCopier.cs @@ -1,57 +1,57 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ObjectCopier.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 Newtonsoft.Json; - -namespace PlexRequests.Helpers -{ - /// - /// Provides a method for performing a deep copy of an object. - /// Binary Serialization is used to perform the copy. - /// - public static class ObjectCopier - { - /// - /// Initialize inner objects individually - /// For example in default constructor some list property initialized with some values, - /// but in 'source' these items are cleaned - - /// without ObjectCreationHandling.Replace default constructor values will be added to result - /// - private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; - - /// - /// Perform a deep Copy of the object, using Json as a serialisation method. - /// - /// The type of object being copied. - /// The object instance to copy. - /// The copied object. - public static T CloneJson(this T source) - { - // Don't serialize a null object, simply return the default for that object - return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), Settings); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ObjectCopier.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 Newtonsoft.Json; + +namespace PlexRequests.Helpers +{ + /// + /// Provides a method for performing a deep copy of an object. + /// Binary Serialization is used to perform the copy. + /// + public static class ObjectCopier + { + /// + /// Initialize inner objects individually + /// For example in default constructor some list property initialized with some values, + /// but in 'source' these items are cleaned - + /// without ObjectCreationHandling.Replace default constructor values will be added to result + /// + private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; + + /// + /// Perform a deep Copy of the object, using Json as a serialisation method. + /// + /// The type of object being copied. + /// The object instance to copy. + /// The copied object. + public static T CloneJson(this T source) + { + // Don't serialize a null object, simply return the default for that object + return ReferenceEquals(source, null) ? default(T) : JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), Settings); + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/PasswordHasher.cs b/PlexRequests.Helpers/PasswordHasher.cs index 68d797ae8..b3b4543cc 100644 --- a/PlexRequests.Helpers/PasswordHasher.cs +++ b/PlexRequests.Helpers/PasswordHasher.cs @@ -1,69 +1,69 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PasswordHasher.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.Security.Cryptography; -namespace PlexRequests.Helpers -{ - - public static class PasswordHasher - { - // 24 = 192 bits - private const int SaltByteSize = 24; - private const int HashByteSize = 24; - private const int HasingIterationsCount = 10101; - - public static byte[] ComputeHash(string password, byte[] salt, int iterations = HasingIterationsCount, int hashByteSize = HashByteSize) - { - var hashGenerator = new Rfc2898DeriveBytes(password, salt) { IterationCount = iterations }; - return hashGenerator.GetBytes(hashByteSize); - } - - public static byte[] GenerateSalt(int saltByteSize = SaltByteSize) - { - var saltGenerator = new RNGCryptoServiceProvider(); - var salt = new byte[saltByteSize]; - saltGenerator.GetBytes(salt); - return salt; - } - - public static bool VerifyPassword(string password, byte[] passwordSalt, byte[] passwordHash) - { - var computedHash = ComputeHash(password, passwordSalt); - return AreHashesEqual(computedHash, passwordHash); - } - - //Length constant verification - prevents timing attack - private static bool AreHashesEqual(IReadOnlyList firstHash, IReadOnlyList secondHash) - { - var minHashLength = firstHash.Count <= secondHash.Count ? firstHash.Count : secondHash.Count; - var xor = firstHash.Count ^ secondHash.Count; - for (var i = 0; i < minHashLength; i++) - xor |= firstHash[i] ^ secondHash[i]; - return 0 == xor; - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PasswordHasher.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.Security.Cryptography; +namespace PlexRequests.Helpers +{ + + public static class PasswordHasher + { + // 24 = 192 bits + private const int SaltByteSize = 24; + private const int HashByteSize = 24; + private const int HasingIterationsCount = 10101; + + public static byte[] ComputeHash(string password, byte[] salt, int iterations = HasingIterationsCount, int hashByteSize = HashByteSize) + { + var hashGenerator = new Rfc2898DeriveBytes(password, salt) { IterationCount = iterations }; + return hashGenerator.GetBytes(hashByteSize); + } + + public static byte[] GenerateSalt(int saltByteSize = SaltByteSize) + { + var saltGenerator = new RNGCryptoServiceProvider(); + var salt = new byte[saltByteSize]; + saltGenerator.GetBytes(salt); + return salt; + } + + public static bool VerifyPassword(string password, byte[] passwordSalt, byte[] passwordHash) + { + var computedHash = ComputeHash(password, passwordSalt); + return AreHashesEqual(computedHash, passwordHash); + } + + //Length constant verification - prevents timing attack + private static bool AreHashesEqual(IReadOnlyList firstHash, IReadOnlyList secondHash) + { + var minHashLength = firstHash.Count <= secondHash.Count ? firstHash.Count : secondHash.Count; + var xor = firstHash.Count ^ secondHash.Count; + for (var i = 0; i < minHashLength; i++) + xor |= firstHash[i] ^ secondHash[i]; + return 0 == xor; + } + } +} diff --git a/PlexRequests.Helpers/SerializerSettings.cs b/PlexRequests.Helpers/SerializerSettings.cs index 71cbdc0e3..00a995189 100644 --- a/PlexRequests.Helpers/SerializerSettings.cs +++ b/PlexRequests.Helpers/SerializerSettings.cs @@ -1,45 +1,45 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SerializerSettings.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.Runtime.Serialization.Formatters; - -using Newtonsoft.Json; - -namespace PlexRequests.Helpers -{ - public static class SerializerSettings - { - - public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings - { - Formatting = Formatting.None, - TypeNameHandling = TypeNameHandling.Objects, - TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, - NullValueHandling = NullValueHandling.Ignore - }; - - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SerializerSettings.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.Runtime.Serialization.Formatters; + +using Newtonsoft.Json; + +namespace PlexRequests.Helpers +{ + public static class SerializerSettings + { + + public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + { + Formatting = Formatting.None, + TypeNameHandling = TypeNameHandling.Objects, + TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple, + NullValueHandling = NullValueHandling.Ignore + }; + + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/StringCipher.cs b/PlexRequests.Helpers/StringCipher.cs index 8fb48e589..55455a8ae 100644 --- a/PlexRequests.Helpers/StringCipher.cs +++ b/PlexRequests.Helpers/StringCipher.cs @@ -1,145 +1,145 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: StringCipher.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; - -namespace PlexRequests.Helpers -{ - public class StringCipher - { - // This constant determines the number of iterations for the password bytes generation function. - private const int DerivationIterations = 1000; - // This constant is used to determine the keysize of the encryption algorithm in bits. - // We divide this by 8 within the code below to get the equivalent number of bytes. - private const int Keysize = 256; - - /// - /// Decrypts the specified cipher text. - /// - /// The cipher text. - /// The pass phrase. - /// - public static string Decrypt(string cipherText, string passPhrase) - { - // Get the complete stream of bytes that represent: - // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] - var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); - // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. - var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); - // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. - var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); - // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. - var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); - - using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) - { - var keyBytes = password.GetBytes(Keysize / 8); - using (var symmetricKey = new RijndaelManaged()) - { - symmetricKey.BlockSize = 256; - symmetricKey.Mode = CipherMode.CBC; - symmetricKey.Padding = PaddingMode.PKCS7; - using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) - { - using (var memoryStream = new MemoryStream(cipherTextBytes)) - { - using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) - { - var plainTextBytes = new byte[cipherTextBytes.Length]; - var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); - memoryStream.Close(); - cryptoStream.Close(); - return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); - } - } - } - } - } - } - - /// - /// Encrypts the specified plain text. - /// - /// The plain text. - /// The pass phrase. - /// - public static string Encrypt(string plainText, string passPhrase) - { - // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text - // so that the same Salt and IV values can be used when decrypting. - var saltStringBytes = Generate256BitsOfRandomEntropy(); - var ivStringBytes = Generate256BitsOfRandomEntropy(); - var plainTextBytes = Encoding.UTF8.GetBytes(plainText); - using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) - { - var keyBytes = password.GetBytes(Keysize / 8); - using (var symmetricKey = new RijndaelManaged()) - { - symmetricKey.BlockSize = 256; - symmetricKey.Mode = CipherMode.CBC; - symmetricKey.Padding = PaddingMode.PKCS7; - using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) - { - using (var memoryStream = new MemoryStream()) - { - using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) - { - cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); - cryptoStream.FlushFinalBlock(); - // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. - var cipherTextBytes = saltStringBytes; - cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); - cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); - memoryStream.Close(); - cryptoStream.Close(); - return Convert.ToBase64String(cipherTextBytes); - } - } - } - } - } - } - - /// - /// Generate256s the bits of random entropy. - /// - /// - private static byte[] Generate256BitsOfRandomEntropy() - { - var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. - using (var rngCsp = new RNGCryptoServiceProvider()) - { - // Fill the array with cryptographically secure random bytes. - rngCsp.GetBytes(randomBytes); - } - return randomBytes; - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: StringCipher.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace PlexRequests.Helpers +{ + public class StringCipher + { + // This constant determines the number of iterations for the password bytes generation function. + private const int DerivationIterations = 1000; + // This constant is used to determine the keysize of the encryption algorithm in bits. + // We divide this by 8 within the code below to get the equivalent number of bytes. + private const int Keysize = 256; + + /// + /// Decrypts the specified cipher text. + /// + /// The cipher text. + /// The pass phrase. + /// + public static string Decrypt(string cipherText, string passPhrase) + { + // Get the complete stream of bytes that represent: + // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] + var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); + // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. + var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); + // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. + var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); + // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. + var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); + + using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + { + var keyBytes = password.GetBytes(Keysize / 8); + using (var symmetricKey = new RijndaelManaged()) + { + symmetricKey.BlockSize = 256; + symmetricKey.Mode = CipherMode.CBC; + symmetricKey.Padding = PaddingMode.PKCS7; + using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) + { + using (var memoryStream = new MemoryStream(cipherTextBytes)) + { + using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + { + var plainTextBytes = new byte[cipherTextBytes.Length]; + var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); + memoryStream.Close(); + cryptoStream.Close(); + return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); + } + } + } + } + } + } + + /// + /// Encrypts the specified plain text. + /// + /// The plain text. + /// The pass phrase. + /// + public static string Encrypt(string plainText, string passPhrase) + { + // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text + // so that the same Salt and IV values can be used when decrypting. + var saltStringBytes = Generate256BitsOfRandomEntropy(); + var ivStringBytes = Generate256BitsOfRandomEntropy(); + var plainTextBytes = Encoding.UTF8.GetBytes(plainText); + using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) + { + var keyBytes = password.GetBytes(Keysize / 8); + using (var symmetricKey = new RijndaelManaged()) + { + symmetricKey.BlockSize = 256; + symmetricKey.Mode = CipherMode.CBC; + symmetricKey.Padding = PaddingMode.PKCS7; + using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) + { + using (var memoryStream = new MemoryStream()) + { + using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); + cryptoStream.FlushFinalBlock(); + // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. + var cipherTextBytes = saltStringBytes; + cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); + cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); + memoryStream.Close(); + cryptoStream.Close(); + return Convert.ToBase64String(cipherTextBytes); + } + } + } + } + } + } + + /// + /// Generate256s the bits of random entropy. + /// + /// + private static byte[] Generate256BitsOfRandomEntropy() + { + var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits. + using (var rngCsp = new RNGCryptoServiceProvider()) + { + // Fill the array with cryptographically secure random bytes. + rngCsp.GetBytes(randomBytes); + } + return randomBytes; + } + } } \ No newline at end of file diff --git a/PlexRequests.Helpers/UriHelper.cs b/PlexRequests.Helpers/UriHelper.cs index 707f70436..11a4863e5 100644 --- a/PlexRequests.Helpers/UriHelper.cs +++ b/PlexRequests.Helpers/UriHelper.cs @@ -1,116 +1,116 @@ -using System; - -using PlexRequests.Helpers.Exceptions; - -namespace PlexRequests.Helpers -{ - public static class UriHelper - { - - public static Uri ReturnUri(this string val) - { - if (val == null) - { - throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly."); - } - try - { - var uri = new UriBuilder(); - - if (val.StartsWith("http://", StringComparison.Ordinal)) - { - uri = new UriBuilder(val); - } - else if (val.StartsWith("https://", StringComparison.Ordinal)) - { - uri = new UriBuilder(val); - } - else if (val.Contains(":")) - { - var split = val.Split(':', '/'); - int port; - int.TryParse(split[1], out port); - - uri = split.Length == 3 - ? new UriBuilder(Uri.UriSchemeHttp, split[0], port, "/" + split[2]) - : new UriBuilder(Uri.UriSchemeHttp, split[0], port); - } - else - { - uri = new UriBuilder(Uri.UriSchemeHttp, val); - } - - return uri.Uri; - } - catch (Exception exception) - { - throw new Exception(exception.Message, exception); - } - } - - /// - /// Returns the URI. - /// - /// The value. - /// The port. - /// if set to true [SSL]. - /// The subdir. - /// - /// The URI is null, please check your settings to make sure you have configured the applications correctly. - /// - public static Uri ReturnUri(this string val, int port, bool ssl = default(bool)) - { - if (val == null) - { - throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly."); - } - try - { - var uri = new UriBuilder(); - - if (val.StartsWith("http://", StringComparison.Ordinal)) - { - var split = val.Split('/'); - uri = split.Length >= 4 ? new UriBuilder(Uri.UriSchemeHttp, split[2], port, "/" + split[3]) : new UriBuilder(new Uri($"{val}:{port}")); - } - else if (val.StartsWith("https://", StringComparison.Ordinal)) - { - var split = val.Split('/'); - uri = split.Length >= 4 - ? new UriBuilder(Uri.UriSchemeHttps, split[2], port, "/" + split[3]) - : new UriBuilder(Uri.UriSchemeHttps, split[2], port); - } - else if(ssl) - { - uri = new UriBuilder(Uri.UriSchemeHttps, val, port); - } - else - { - uri = new UriBuilder(Uri.UriSchemeHttp, val, port); - } - - return uri.Uri; - } - catch (Exception exception) - { - throw new Exception(exception.Message, exception); - } - } - - public static Uri ReturnUriWithSubDir(this string val, int port, bool ssl, string subDir) - { - var uriBuilder = new UriBuilder(val); - if (ssl) - { - uriBuilder.Scheme = Uri.UriSchemeHttps; - } - if (!string.IsNullOrEmpty(subDir)) - { - uriBuilder.Path = subDir; - } - uriBuilder.Port = port; - - return uriBuilder.Uri; - } - } +using System; + +using PlexRequests.Helpers.Exceptions; + +namespace PlexRequests.Helpers +{ + public static class UriHelper + { + + public static Uri ReturnUri(this string val) + { + if (val == null) + { + throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly."); + } + try + { + var uri = new UriBuilder(); + + if (val.StartsWith("http://", StringComparison.Ordinal)) + { + uri = new UriBuilder(val); + } + else if (val.StartsWith("https://", StringComparison.Ordinal)) + { + uri = new UriBuilder(val); + } + else if (val.Contains(":")) + { + var split = val.Split(':', '/'); + int port; + int.TryParse(split[1], out port); + + uri = split.Length == 3 + ? new UriBuilder(Uri.UriSchemeHttp, split[0], port, "/" + split[2]) + : new UriBuilder(Uri.UriSchemeHttp, split[0], port); + } + else + { + uri = new UriBuilder(Uri.UriSchemeHttp, val); + } + + return uri.Uri; + } + catch (Exception exception) + { + throw new Exception(exception.Message, exception); + } + } + + /// + /// Returns the URI. + /// + /// The value. + /// The port. + /// if set to true [SSL]. + /// The subdir. + /// + /// The URI is null, please check your settings to make sure you have configured the applications correctly. + /// + public static Uri ReturnUri(this string val, int port, bool ssl = default(bool)) + { + if (val == null) + { + throw new ApplicationSettingsException("The URI is null, please check your settings to make sure you have configured the applications correctly."); + } + try + { + var uri = new UriBuilder(); + + if (val.StartsWith("http://", StringComparison.Ordinal)) + { + var split = val.Split('/'); + uri = split.Length >= 4 ? new UriBuilder(Uri.UriSchemeHttp, split[2], port, "/" + split[3]) : new UriBuilder(new Uri($"{val}:{port}")); + } + else if (val.StartsWith("https://", StringComparison.Ordinal)) + { + var split = val.Split('/'); + uri = split.Length >= 4 + ? new UriBuilder(Uri.UriSchemeHttps, split[2], port, "/" + split[3]) + : new UriBuilder(Uri.UriSchemeHttps, split[2], port); + } + else if(ssl) + { + uri = new UriBuilder(Uri.UriSchemeHttps, val, port); + } + else + { + uri = new UriBuilder(Uri.UriSchemeHttp, val, port); + } + + return uri.Uri; + } + catch (Exception exception) + { + throw new Exception(exception.Message, exception); + } + } + + public static Uri ReturnUriWithSubDir(this string val, int port, bool ssl, string subDir) + { + var uriBuilder = new UriBuilder(val); + if (ssl) + { + uriBuilder.Scheme = Uri.UriSchemeHttps; + } + if (!string.IsNullOrEmpty(subDir)) + { + uriBuilder.Path = subDir; + } + uriBuilder.Port = port; + + return uriBuilder.Uri; + } + } } \ No newline at end of file diff --git a/PlexRequests.Services.Tests/Properties/AssemblyInfo.cs b/PlexRequests.Services.Tests/Properties/AssemblyInfo.cs index 40fbae3f4..acc2a63f4 100644 --- a/PlexRequests.Services.Tests/Properties/AssemblyInfo.cs +++ b/PlexRequests.Services.Tests/Properties/AssemblyInfo.cs @@ -1,37 +1,37 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Services.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Services.Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("eaadb4ac-064f-4d3a-aff9-64a33131a9a7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Services.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Services.Tests")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("eaadb4ac-064f-4d3a-aff9-64a33131a9a7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Services/Interfaces/ICouchPotatoCacher.cs b/PlexRequests.Services/Interfaces/ICouchPotatoCacher.cs index 05fa8da75..826c8129e 100644 --- a/PlexRequests.Services/Interfaces/ICouchPotatoCacher.cs +++ b/PlexRequests.Services/Interfaces/ICouchPotatoCacher.cs @@ -1,8 +1,8 @@ -namespace PlexRequests.Services.Interfaces -{ - public interface ICouchPotatoCacher - { - void Queued(); - int[] QueuedIds(); - } -} +namespace PlexRequests.Services.Interfaces +{ + public interface ICouchPotatoCacher + { + void Queued(); + int[] QueuedIds(); + } +} diff --git a/PlexRequests.Services/Interfaces/IIntervals.cs b/PlexRequests.Services/Interfaces/IIntervals.cs index 09e13df08..0d946302e 100644 --- a/PlexRequests.Services/Interfaces/IIntervals.cs +++ b/PlexRequests.Services/Interfaces/IIntervals.cs @@ -1,35 +1,35 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: IIntervals.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.Interfaces -{ - public interface IIntervals - { - TimeSpan Notification { get; } // notification interval for high load - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: IIntervals.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.Interfaces +{ + public interface IIntervals + { + TimeSpan Notification { get; } // notification interval for high load + } } \ No newline at end of file diff --git a/PlexRequests.Services/Interfaces/INotification.cs b/PlexRequests.Services/Interfaces/INotification.cs index ea3488cfd..1ca2ed5fe 100644 --- a/PlexRequests.Services/Interfaces/INotification.cs +++ b/PlexRequests.Services/Interfaces/INotification.cs @@ -1,47 +1,47 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: INotification.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.Threading.Tasks; - -using PlexRequests.Services.Notification; -using PlexRequests.Core.SettingModels; - -namespace PlexRequests.Services.Interfaces -{ - public interface INotification - { - string NotificationName { get; } - - Task NotifyAsync(NotificationModel model); - /// - /// Sends a notification to the user, this is usually for testing the settings. - /// - /// The model. - /// The settings. - /// - Task NotifyAsync(NotificationModel model, Settings settings); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: INotification.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.Threading.Tasks; + +using PlexRequests.Services.Notification; +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Services.Interfaces +{ + public interface INotification + { + string NotificationName { get; } + + Task NotifyAsync(NotificationModel model); + /// + /// Sends a notification to the user, this is usually for testing the settings. + /// + /// The model. + /// The settings. + /// + Task NotifyAsync(NotificationModel model, Settings settings); + } } \ No newline at end of file diff --git a/PlexRequests.Services/Interfaces/INotificationService.cs b/PlexRequests.Services/Interfaces/INotificationService.cs index 66f0853cd..c97556f99 100644 --- a/PlexRequests.Services/Interfaces/INotificationService.cs +++ b/PlexRequests.Services/Interfaces/INotificationService.cs @@ -1,53 +1,53 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: INotificationService.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.Threading.Tasks; - -using PlexRequests.Services.Notification; -using PlexRequests.Core.SettingModels; - -namespace PlexRequests.Services.Interfaces -{ - public interface INotificationService - { - /// - /// Sends a notification to the user. This one is used in normal notification scenarios - /// - /// The model. - /// - Task Publish(NotificationModel model); - /// - /// Sends a notification to the user, this is usually for testing the settings. - /// - /// The model. - /// The settings. - /// - Task Publish(NotificationModel model, Settings settings); - void Subscribe(INotification notification); - void UnSubscribe(INotification notification); - - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: INotificationService.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.Threading.Tasks; + +using PlexRequests.Services.Notification; +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Services.Interfaces +{ + public interface INotificationService + { + /// + /// Sends a notification to the user. This one is used in normal notification scenarios + /// + /// The model. + /// + Task Publish(NotificationModel model); + /// + /// Sends a notification to the user, this is usually for testing the settings. + /// + /// The model. + /// The settings. + /// + Task Publish(NotificationModel model, Settings settings); + void Subscribe(INotification notification); + void UnSubscribe(INotification notification); + + } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/NotificationService.cs b/PlexRequests.Services/Notification/NotificationService.cs index 79e5dcb51..1d87c2fdf 100644 --- a/PlexRequests.Services/Notification/NotificationService.cs +++ b/PlexRequests.Services/Notification/NotificationService.cs @@ -1,104 +1,104 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: NotificationService.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; - -using NLog; - -using PlexRequests.Services.Interfaces; -using PlexRequests.Core.SettingModels; - -namespace PlexRequests.Services.Notification -{ - public class NotificationService : INotificationService - { - private static Logger Log = LogManager.GetCurrentClassLogger(); - public ConcurrentDictionary Observers { get; } = new ConcurrentDictionary(); - - /// - /// Sends a notification to the user. This one is used in normal notification scenarios - /// - /// The model. - /// - public async Task Publish(NotificationModel model) - { - var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model)); - - await Task.WhenAll(notificationTasks).ConfigureAwait(false); - } - - /// - /// Sends a notification to the user, this is usually for testing the settings. - /// - /// The model. - /// The settings. - /// - public async Task Publish(NotificationModel model, Settings settings) - { - var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model, settings)); - - await Task.WhenAll(notificationTasks).ConfigureAwait(false); - } - - public void Subscribe(INotification notification) - { - Observers.TryAdd(notification.NotificationName, notification); - } - - public void UnSubscribe(INotification notification) - { - Observers.TryRemove(notification.NotificationName, out notification); - } - - private async Task NotifyAsync(INotification notification, NotificationModel model) - { - try - { - await notification.NotifyAsync(model).ConfigureAwait(false); - } - catch (Exception ex) - { - Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception"); - } - - } - - private async Task NotifyAsync(INotification notification, NotificationModel model, Settings settings) - { - try - { - await notification.NotifyAsync(model, settings).ConfigureAwait(false); - } - catch (Exception ex) - { - Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception"); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: NotificationService.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +using NLog; + +using PlexRequests.Services.Interfaces; +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Services.Notification +{ + public class NotificationService : INotificationService + { + private static Logger Log = LogManager.GetCurrentClassLogger(); + public ConcurrentDictionary Observers { get; } = new ConcurrentDictionary(); + + /// + /// Sends a notification to the user. This one is used in normal notification scenarios + /// + /// The model. + /// + public async Task Publish(NotificationModel model) + { + var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model)); + + await Task.WhenAll(notificationTasks).ConfigureAwait(false); + } + + /// + /// Sends a notification to the user, this is usually for testing the settings. + /// + /// The model. + /// The settings. + /// + public async Task Publish(NotificationModel model, Settings settings) + { + var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model, settings)); + + await Task.WhenAll(notificationTasks).ConfigureAwait(false); + } + + public void Subscribe(INotification notification) + { + Observers.TryAdd(notification.NotificationName, notification); + } + + public void UnSubscribe(INotification notification) + { + Observers.TryRemove(notification.NotificationName, out notification); + } + + private async Task NotifyAsync(INotification notification, NotificationModel model) + { + try + { + await notification.NotifyAsync(model).ConfigureAwait(false); + } + catch (Exception ex) + { + Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception"); + } + + } + + private async Task NotifyAsync(INotification notification, NotificationModel model, Settings settings) + { + try + { + await notification.NotifyAsync(model, settings).ConfigureAwait(false); + } + catch (Exception ex) + { + Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception"); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Services/Properties/AssemblyInfo.cs b/PlexRequests.Services/Properties/AssemblyInfo.cs index 1e66f1698..6341e31b2 100644 --- a/PlexRequests.Services/Properties/AssemblyInfo.cs +++ b/PlexRequests.Services/Properties/AssemblyInfo.cs @@ -1,37 +1,37 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Services")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Services")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("566efa49-68f8-4716-9693-a6b3f2624dea")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Services")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Services")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("566efa49-68f8-4716-9693-a6b3f2624dea")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Store/DbConfiguration.cs b/PlexRequests.Store/DbConfiguration.cs index eb8458d02..14d2670a6 100644 --- a/PlexRequests.Store/DbConfiguration.cs +++ b/PlexRequests.Store/DbConfiguration.cs @@ -1,102 +1,102 @@ -#region Copyright -// *********************************************************************** -// Copyright (c) 2016 Jamie Rees -// File: DbConfiguration.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. -// *********************************************************************** -using System.Globalization; - - -#endregion -using System; -using System.Data; -using System.IO; -using System.Windows.Forms; - -using Mono.Data.Sqlite; - -using NLog; - -namespace PlexRequests.Store -{ - public class DbConfiguration : ISqliteConfiguration - { - private static Logger Log = LogManager.GetCurrentClassLogger(); - public DbConfiguration(SqliteFactory provider) - { - Factory = provider; - } - - private SqliteFactory Factory { get; } - public string CurrentPath =>Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, DbFile); - - public virtual bool CheckDb() - { - Log.Trace("Checking DB"); - Console.WriteLine("Location of the database: {0}",CurrentPath); - if (!File.Exists(CurrentPath)) - { - Log.Trace("DB doesn't exist, creating a new one"); - CreateDatabase(); - return true; - } - return false; - } - - public const string DbFile = "PlexRequests.sqlite"; - - /// - /// Gets the database connection. - /// - /// - /// Factory returned null - public virtual IDbConnection DbConnection() - { - var fact = Factory.CreateConnection(); - if (fact == null) - { - throw new SqliteException("Factory returned null"); - } - fact.ConnectionString = "Data Source=" + CurrentPath; - return fact; - } - - /// - /// Create the database file. - /// - public virtual void CreateDatabase() - { - try - { - using (File.Create(CurrentPath)) - { - } - } - catch (Exception e) - { - Log.Error(e); - } - } - - - } -} +#region Copyright +// *********************************************************************** +// Copyright (c) 2016 Jamie Rees +// File: DbConfiguration.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. +// *********************************************************************** +using System.Globalization; + + +#endregion +using System; +using System.Data; +using System.IO; +using System.Windows.Forms; + +using Mono.Data.Sqlite; + +using NLog; + +namespace PlexRequests.Store +{ + public class DbConfiguration : ISqliteConfiguration + { + private static Logger Log = LogManager.GetCurrentClassLogger(); + public DbConfiguration(SqliteFactory provider) + { + Factory = provider; + } + + private SqliteFactory Factory { get; } + public string CurrentPath =>Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, DbFile); + + public virtual bool CheckDb() + { + Log.Trace("Checking DB"); + Console.WriteLine("Location of the database: {0}",CurrentPath); + if (!File.Exists(CurrentPath)) + { + Log.Trace("DB doesn't exist, creating a new one"); + CreateDatabase(); + return true; + } + return false; + } + + public const string DbFile = "PlexRequests.sqlite"; + + /// + /// Gets the database connection. + /// + /// + /// Factory returned null + public virtual IDbConnection DbConnection() + { + var fact = Factory.CreateConnection(); + if (fact == null) + { + throw new SqliteException("Factory returned null"); + } + fact.ConnectionString = "Data Source=" + CurrentPath; + return fact; + } + + /// + /// Create the database file. + /// + public virtual void CreateDatabase() + { + try + { + using (File.Create(CurrentPath)) + { + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + + } +} diff --git a/PlexRequests.Store/Entity.cs b/PlexRequests.Store/Entity.cs index 3730d4b5a..318eab3f6 100644 --- a/PlexRequests.Store/Entity.cs +++ b/PlexRequests.Store/Entity.cs @@ -1,36 +1,36 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: Entity.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 Dapper.Contrib.Extensions; - -namespace PlexRequests.Store -{ - public class Entity - { - [Key] - public int Id { get; set; } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Entity.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 Dapper.Contrib.Extensions; + +namespace PlexRequests.Store +{ + public class Entity + { + [Key] + public int Id { get; set; } + } +} diff --git a/PlexRequests.Store/ISqliteConfiguration.cs b/PlexRequests.Store/ISqliteConfiguration.cs index 528944e95..963c2c498 100644 --- a/PlexRequests.Store/ISqliteConfiguration.cs +++ b/PlexRequests.Store/ISqliteConfiguration.cs @@ -1,53 +1,53 @@ -#region Copyright -// *********************************************************************** -// Copyright (c) 2016 Jamie Rees -// File: ISqliteConfiguration.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.Data; - -namespace PlexRequests.Store -{ - public interface ISqliteConfiguration - { - /// - /// Checks the database. - /// - bool CheckDb(); - - /// - /// Returns the database connection. - /// - /// - IDbConnection DbConnection(); - - /// - /// Creates the database. - /// - void CreateDatabase(); - - string CurrentPath { get; } - - } -} - +#region Copyright +// *********************************************************************** +// Copyright (c) 2016 Jamie Rees +// File: ISqliteConfiguration.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.Data; + +namespace PlexRequests.Store +{ + public interface ISqliteConfiguration + { + /// + /// Checks the database. + /// + bool CheckDb(); + + /// + /// Returns the database connection. + /// + /// + IDbConnection DbConnection(); + + /// + /// Creates the database. + /// + void CreateDatabase(); + + string CurrentPath { get; } + + } +} + diff --git a/PlexRequests.Store/Models/GlobalSettings.cs b/PlexRequests.Store/Models/GlobalSettings.cs index a98d0e614..ece36bbe7 100644 --- a/PlexRequests.Store/Models/GlobalSettings.cs +++ b/PlexRequests.Store/Models/GlobalSettings.cs @@ -1,39 +1,39 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: GlobalSettings.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 Dapper.Contrib.Extensions; - -namespace PlexRequests.Store.Models -{ - - [Table("GlobalSettings")] - public class GlobalSettings : Entity - { - public string Content { get; set; } - public string SettingsName { get; set; } - } - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: GlobalSettings.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 Dapper.Contrib.Extensions; + +namespace PlexRequests.Store.Models +{ + + [Table("GlobalSettings")] + public class GlobalSettings : Entity + { + public string Content { get; set; } + public string SettingsName { get; set; } + } + } \ No newline at end of file diff --git a/PlexRequests.Store/Models/LogEntity.cs b/PlexRequests.Store/Models/LogEntity.cs index d1d45cfc1..860fc482a 100644 --- a/PlexRequests.Store/Models/LogEntity.cs +++ b/PlexRequests.Store/Models/LogEntity.cs @@ -1,47 +1,47 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: LogEntity.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 Dapper.Contrib.Extensions; -using Newtonsoft.Json; - -namespace PlexRequests.Store.Models -{ - [Table("Logs")] - public class LogEntity : Entity - { - public DateTime Date { get; set; } - public string Level { get; set; } - public string Logger { get; set; } - public string Message { get; set; } - public string Callsite { get; set; } - public string Exception { get; set; } - - [JsonIgnore] - public string DateString { get; set; } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: LogEntity.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 Dapper.Contrib.Extensions; +using Newtonsoft.Json; + +namespace PlexRequests.Store.Models +{ + [Table("Logs")] + public class LogEntity : Entity + { + public DateTime Date { get; set; } + public string Level { get; set; } + public string Logger { get; set; } + public string Message { get; set; } + public string Callsite { get; set; } + public string Exception { get; set; } + + [JsonIgnore] + public string DateString { get; set; } + } +} diff --git a/PlexRequests.Store/Models/RequestBlobs.cs b/PlexRequests.Store/Models/RequestBlobs.cs index f9af75a25..9e1bcf0fd 100644 --- a/PlexRequests.Store/Models/RequestBlobs.cs +++ b/PlexRequests.Store/Models/RequestBlobs.cs @@ -1,39 +1,39 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: RequestBlobs.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 Dapper.Contrib.Extensions; - -namespace PlexRequests.Store.Models -{ - [Table("RequestBlobs")] - public class RequestBlobs : Entity - { - public int ProviderId { get; set; } - public byte[] Content { get; set; } - public RequestType Type { get; set; } - public string MusicId { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RequestBlobs.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 Dapper.Contrib.Extensions; + +namespace PlexRequests.Store.Models +{ + [Table("RequestBlobs")] + public class RequestBlobs : Entity + { + public int ProviderId { get; set; } + public byte[] Content { get; set; } + public RequestType Type { get; set; } + public string MusicId { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Store/Properties/AssemblyInfo.cs b/PlexRequests.Store/Properties/AssemblyInfo.cs index 0995b2f1f..96d3e6e03 100644 --- a/PlexRequests.Store/Properties/AssemblyInfo.cs +++ b/PlexRequests.Store/Properties/AssemblyInfo.cs @@ -1,37 +1,37 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.Store")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.Store")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("92433867-2b7b-477b-a566-96c382427525")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.Store")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Store")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("92433867-2b7b-477b-a566-96c382427525")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")] diff --git a/PlexRequests.Store/Repository/IRequestRepository.cs b/PlexRequests.Store/Repository/IRequestRepository.cs index 440fe715e..ae0e1f78d 100644 --- a/PlexRequests.Store/Repository/IRequestRepository.cs +++ b/PlexRequests.Store/Repository/IRequestRepository.cs @@ -1,77 +1,77 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ISettingsRepository.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.Threading.Tasks; - -using PlexRequests.Store.Models; - -namespace PlexRequests.Store.Repository -{ - public interface IRequestRepository - { - /// - /// Inserts the specified entity. - /// - /// The entity. - long Insert(RequestBlobs entity); - - Task InsertAsync(RequestBlobs entity); - - /// - /// Gets all. - /// - /// - IEnumerable GetAll(); - - Task> GetAllAsync(); - - RequestBlobs Get(int id); - Task GetAsync(int id); - - /// - /// Deletes the specified entity. - /// - /// The entity. - /// - bool Delete(RequestBlobs entity); - Task DeleteAsync(RequestBlobs entity); - - bool DeleteAll(IEnumerable entity); - Task DeleteAllAsync(IEnumerable entity); - - /// - /// Updates the specified entity. - /// - /// The entity. - /// - bool Update(RequestBlobs entity); - Task UpdateAsync(RequestBlobs entity); - - bool UpdateAll(IEnumerable entity); - Task UpdateAllAsync(IEnumerable entity); - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ISettingsRepository.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.Threading.Tasks; + +using PlexRequests.Store.Models; + +namespace PlexRequests.Store.Repository +{ + public interface IRequestRepository + { + /// + /// Inserts the specified entity. + /// + /// The entity. + long Insert(RequestBlobs entity); + + Task InsertAsync(RequestBlobs entity); + + /// + /// Gets all. + /// + /// + IEnumerable GetAll(); + + Task> GetAllAsync(); + + RequestBlobs Get(int id); + Task GetAsync(int id); + + /// + /// Deletes the specified entity. + /// + /// The entity. + /// + bool Delete(RequestBlobs entity); + Task DeleteAsync(RequestBlobs entity); + + bool DeleteAll(IEnumerable entity); + Task DeleteAllAsync(IEnumerable entity); + + /// + /// Updates the specified entity. + /// + /// The entity. + /// + bool Update(RequestBlobs entity); + Task UpdateAsync(RequestBlobs entity); + + bool UpdateAll(IEnumerable entity); + Task UpdateAllAsync(IEnumerable entity); + } } \ No newline at end of file diff --git a/PlexRequests.Store/Repository/ISettingsRepository.cs b/PlexRequests.Store/Repository/ISettingsRepository.cs index 3f6761c66..65161950a 100644 --- a/PlexRequests.Store/Repository/ISettingsRepository.cs +++ b/PlexRequests.Store/Repository/ISettingsRepository.cs @@ -1,75 +1,75 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: ISettingsRepository.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.Threading.Tasks; -using PlexRequests.Store.Models; - -namespace PlexRequests.Store.Repository -{ - public interface ISettingsRepository - { - /// - /// Inserts the specified entity. - /// - /// The entity. - long Insert(GlobalSettings entity); - Task InsertAsync(GlobalSettings entity); - - /// - /// Gets all. - /// - /// - IEnumerable GetAll(); - Task> GetAllAsync(); - - /// - /// Gets the specified identifier. - /// - /// Name of the settings. - /// - GlobalSettings Get(string settingsName); - Task GetAsync(string settingsName); - - /// - /// Deletes the specified entity. - /// - /// The entity. - /// - Task DeleteAsync(GlobalSettings entity); - bool Delete(GlobalSettings entity); - - /// - /// Updates the specified entity. - /// - /// The entity. - /// - Task UpdateAsync(GlobalSettings entity); - bool Update(GlobalSettings entity); - - - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ISettingsRepository.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.Threading.Tasks; +using PlexRequests.Store.Models; + +namespace PlexRequests.Store.Repository +{ + public interface ISettingsRepository + { + /// + /// Inserts the specified entity. + /// + /// The entity. + long Insert(GlobalSettings entity); + Task InsertAsync(GlobalSettings entity); + + /// + /// Gets all. + /// + /// + IEnumerable GetAll(); + Task> GetAllAsync(); + + /// + /// Gets the specified identifier. + /// + /// Name of the settings. + /// + GlobalSettings Get(string settingsName); + Task GetAsync(string settingsName); + + /// + /// Deletes the specified entity. + /// + /// The entity. + /// + Task DeleteAsync(GlobalSettings entity); + bool Delete(GlobalSettings entity); + + /// + /// Updates the specified entity. + /// + /// The entity. + /// + Task UpdateAsync(GlobalSettings entity); + bool Update(GlobalSettings entity); + + + } } \ No newline at end of file diff --git a/PlexRequests.Store/Sql.Designer.cs b/PlexRequests.Store/Sql.Designer.cs index 21da4fd5f..8cd4f8666 100644 --- a/PlexRequests.Store/Sql.Designer.cs +++ b/PlexRequests.Store/Sql.Designer.cs @@ -1,91 +1,91 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace PlexRequests.Store { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Sql { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Sql() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PlexRequests.Store.Sql", typeof(Sql).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to --Any DB changes need to be made in this file. - /// - ///CREATE TABLE IF NOT EXISTS User - ///( - /// Id INTEGER PRIMARY KEY AUTOINCREMENT, - /// User varchar(50) NOT NULL , - /// UserName varchar(50) NOT NULL, - /// Password varchar(100) NOT NULL - ///); - /// - ///CREATE TABLE IF NOT EXISTS Settings - ///( - /// Id INTEGER PRIMARY KEY AUTOINCREMENT, - /// Port INTEGER NOT NULL, - /// UserAuthentication INTEGER NOT NULL, - /// PlexAuthToken varchar(50) - ///); - /// - ///CREATE TABLE IF NOT EXISTS Requested - ///( [rest of string was truncated]";. - /// - internal static string SqlTables { - get { - return ResourceManager.GetString("SqlTables", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PlexRequests.Store { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Sql { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Sql() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PlexRequests.Store.Sql", typeof(Sql).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to --Any DB changes need to be made in this file. + /// + ///CREATE TABLE IF NOT EXISTS User + ///( + /// Id INTEGER PRIMARY KEY AUTOINCREMENT, + /// User varchar(50) NOT NULL , + /// UserName varchar(50) NOT NULL, + /// Password varchar(100) NOT NULL + ///); + /// + ///CREATE TABLE IF NOT EXISTS Settings + ///( + /// Id INTEGER PRIMARY KEY AUTOINCREMENT, + /// Port INTEGER NOT NULL, + /// UserAuthentication INTEGER NOT NULL, + /// PlexAuthToken varchar(50) + ///); + /// + ///CREATE TABLE IF NOT EXISTS Requested + ///( [rest of string was truncated]";. + /// + internal static string SqlTables { + get { + return ResourceManager.GetString("SqlTables", resourceCulture); + } + } + } +} diff --git a/PlexRequests.Store/Sql.resx b/PlexRequests.Store/Sql.resx index c6f4f4870..08548bd87 100644 --- a/PlexRequests.Store/Sql.resx +++ b/PlexRequests.Store/Sql.resx @@ -1,124 +1,124 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - SqlTables.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + SqlTables.sql;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + \ No newline at end of file diff --git a/PlexRequests.Store/UserEntity.cs b/PlexRequests.Store/UserEntity.cs index b3d53f8cc..3f9311fec 100644 --- a/PlexRequests.Store/UserEntity.cs +++ b/PlexRequests.Store/UserEntity.cs @@ -1,39 +1,39 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: UserEntity.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 Dapper.Contrib.Extensions; - -namespace PlexRequests.Store -{ - public class UserEntity - { - [Key] - public int Id { get; set; } - - public string UserName { get; set; } - public string UserGuid { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserEntity.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 Dapper.Contrib.Extensions; + +namespace PlexRequests.Store +{ + public class UserEntity + { + [Key] + public int Id { get; set; } + + public string UserName { get; set; } + public string UserGuid { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.UI.Tests/BootstrapperExtensions.cs b/PlexRequests.UI.Tests/BootstrapperExtensions.cs index 52e656311..c8f0eab3d 100644 --- a/PlexRequests.UI.Tests/BootstrapperExtensions.cs +++ b/PlexRequests.UI.Tests/BootstrapperExtensions.cs @@ -1,45 +1,45 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: BootstrapperExtensions.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 Nancy.Bootstrapper; -using Nancy.Session; - -namespace PlexRequests.UI.Tests -{ - public static class BootstrapperExtensions - { - public static void WithSession(this IPipelines pipeline, IDictionary session) - { - pipeline.BeforeRequest.AddItemToEndOfPipeline(ctx => - { - ctx.Request.Session = new Session(session); - return null; - }); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: BootstrapperExtensions.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 Nancy.Bootstrapper; +using Nancy.Session; + +namespace PlexRequests.UI.Tests +{ + public static class BootstrapperExtensions + { + public static void WithSession(this IPipelines pipeline, IDictionary session) + { + pipeline.BeforeRequest.AddItemToEndOfPipeline(ctx => + { + ctx.Request.Session = new Session(session); + return null; + }); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI.Tests/Properties/AssemblyInfo.cs b/PlexRequests.UI.Tests/Properties/AssemblyInfo.cs index a6da2256b..054088d5a 100644 --- a/PlexRequests.UI.Tests/Properties/AssemblyInfo.cs +++ b/PlexRequests.UI.Tests/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("PlexRequests.UI.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("PlexRequests.UI.Tests")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a930e2cf-79e2-45f9-b06a-9a719a254ce4")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlexRequests.UI.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.UI.Tests")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a930e2cf-79e2-45f9-b06a-9a719a254ce4")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/PlexRequests.UI.Tests/TestRootPathProvider.cs b/PlexRequests.UI.Tests/TestRootPathProvider.cs index 544eb88d6..b1682ef6f 100644 --- a/PlexRequests.UI.Tests/TestRootPathProvider.cs +++ b/PlexRequests.UI.Tests/TestRootPathProvider.cs @@ -1,64 +1,64 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TestRootPathProvider.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.IO; -using System.Linq; - -using Nancy; - -namespace PlexRequests.UI.Tests -{ - public class TestRootPathProvider : IRootPathProvider - { - private static string _CachedRootPath; - - public string GetRootPath() - { - if (!string.IsNullOrEmpty(_CachedRootPath)) - return _CachedRootPath; - - var currentDirectory = new DirectoryInfo(Environment.CurrentDirectory); - - bool rootPathFound = false; - while (!rootPathFound) - { - var directoriesContainingViewFolder = currentDirectory.GetDirectories( - "Views", SearchOption.AllDirectories); - if (directoriesContainingViewFolder.Any()) - { - _CachedRootPath = directoriesContainingViewFolder.First().FullName; - rootPathFound = true; - } - - currentDirectory = currentDirectory.Parent; - } - - return _CachedRootPath; - } - } - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TestRootPathProvider.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.IO; +using System.Linq; + +using Nancy; + +namespace PlexRequests.UI.Tests +{ + public class TestRootPathProvider : IRootPathProvider + { + private static string _CachedRootPath; + + public string GetRootPath() + { + if (!string.IsNullOrEmpty(_CachedRootPath)) + return _CachedRootPath; + + var currentDirectory = new DirectoryInfo(Environment.CurrentDirectory); + + bool rootPathFound = false; + while (!rootPathFound) + { + var directoriesContainingViewFolder = currentDirectory.GetDirectories( + "Views", SearchOption.AllDirectories); + if (directoriesContainingViewFolder.Any()) + { + _CachedRootPath = directoriesContainingViewFolder.First().FullName; + rootPathFound = true; + } + + currentDirectory = currentDirectory.Parent; + } + + return _CachedRootPath; + } + } + } \ No newline at end of file diff --git a/PlexRequests.UI/Content/handlebars.min.js b/PlexRequests.UI/Content/handlebars.min.js index 8e74cabb0..40a2dffc1 100644 --- a/PlexRequests.UI/Content/handlebars.min.js +++ b/PlexRequests.UI/Content/handlebars.min.js @@ -1,45 +1,45 @@ -/*! - - handlebars v4.0.5 - -Copyright (C) 2011-2015 by Yehuda Katz - -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. - -@license -*/ -!function (a, b) { "object" == typeof exports && "object" == typeof module ? module.exports = b() : "function" == typeof define && define.amd ? define([], b) : "object" == typeof exports ? exports.Handlebars = b() : a.Handlebars = b() }(this, function () { - return function (a) { function b(d) { if (c[d]) return c[d].exports; var e = c[d] = { exports: {}, id: d, loaded: !1 }; return a[d].call(e.exports, e, e.exports, b), e.loaded = !0, e.exports } var c = {}; return b.m = a, b.c = c, b.p = "", b(0) }([function (a, b, c) { "use strict"; function d() { var a = r(); return a.compile = function (b, c) { return k.compile(b, c, a) }, a.precompile = function (b, c) { return k.precompile(b, c, a) }, a.AST = i["default"], a.Compiler = k.Compiler, a.JavaScriptCompiler = m["default"], a.Parser = j.parser, a.parse = j.parse, a } var e = c(1)["default"]; b.__esModule = !0; var f = c(2), g = e(f), h = c(21), i = e(h), j = c(22), k = c(27), l = c(28), m = e(l), n = c(25), o = e(n), p = c(20), q = e(p), r = g["default"].create, s = d(); s.create = d, q["default"](s), s.Visitor = o["default"], s["default"] = s, b["default"] = s, a.exports = b["default"] }, function (a, b) { "use strict"; b["default"] = function (a) { return a && a.__esModule ? a : { "default": a } }, b.__esModule = !0 }, function (a, b, c) { "use strict"; function d() { var a = new h.HandlebarsEnvironment; return n.extend(a, h), a.SafeString = j["default"], a.Exception = l["default"], a.Utils = n, a.escapeExpression = n.escapeExpression, a.VM = p, a.template = function (b) { return p.template(b, a) }, a } var e = c(3)["default"], f = c(1)["default"]; b.__esModule = !0; var g = c(4), h = e(g), i = c(18), j = f(i), k = c(6), l = f(k), m = c(5), n = e(m), o = c(19), p = e(o), q = c(20), r = f(q), s = d(); s.create = d, r["default"](s), s["default"] = s, b["default"] = s, a.exports = b["default"] }, function (a, b) { "use strict"; b["default"] = function (a) { if (a && a.__esModule) return a; var b = {}; if (null != a) for (var c in a) Object.prototype.hasOwnProperty.call(a, c) && (b[c] = a[c]); return b["default"] = a, b }, b.__esModule = !0 }, function (a, b, c) { "use strict"; function d(a, b, c) { this.helpers = a || {}, this.partials = b || {}, this.decorators = c || {}, i.registerDefaultHelpers(this), j.registerDefaultDecorators(this) } var e = c(1)["default"]; b.__esModule = !0, b.HandlebarsEnvironment = d; var f = c(5), g = c(6), h = e(g), i = c(7), j = c(15), k = c(17), l = e(k), m = "4.0.5"; b.VERSION = m; var n = 7; b.COMPILER_REVISION = n; var o = { 1: "<= 1.0.rc.2", 2: "== 1.0.0-rc.3", 3: "== 1.0.0-rc.4", 4: "== 1.x.x", 5: "== 2.0.0-alpha.x", 6: ">= 2.0.0-beta.1", 7: ">= 4.0.0" }; b.REVISION_CHANGES = o; var p = "[object Object]"; d.prototype = { constructor: d, logger: l["default"], log: l["default"].log, registerHelper: function (a, b) { if (f.toString.call(a) === p) { if (b) throw new h["default"]("Arg not supported with multiple helpers"); f.extend(this.helpers, a) } else this.helpers[a] = b }, unregisterHelper: function (a) { delete this.helpers[a] }, registerPartial: function (a, b) { if (f.toString.call(a) === p) f.extend(this.partials, a); else { if ("undefined" == typeof b) throw new h["default"]('Attempting to register a partial called "' + a + '" as undefined'); this.partials[a] = b } }, unregisterPartial: function (a) { delete this.partials[a] }, registerDecorator: function (a, b) { if (f.toString.call(a) === p) { if (b) throw new h["default"]("Arg not supported with multiple decorators"); f.extend(this.decorators, a) } else this.decorators[a] = b }, unregisterDecorator: function (a) { delete this.decorators[a] } }; var q = l["default"].log; b.log = q, b.createFrame = f.createFrame, b.logger = l["default"] }, function (a, b) { "use strict"; function c(a) { return k[a] } function d(a) { for (var b = 1; b < arguments.length; b++) for (var c in arguments[b]) Object.prototype.hasOwnProperty.call(arguments[b], c) && (a[c] = arguments[b][c]); return a } function e(a, b) { for (var c = 0, d = a.length; d > c; c++) if (a[c] === b) return c; return -1 } function f(a) { if ("string" != typeof a) { if (a && a.toHTML) return a.toHTML(); if (null == a) return ""; if (!a) return a + ""; a = "" + a } return m.test(a) ? a.replace(l, c) : a } function g(a) { return a || 0 === a ? p(a) && 0 === a.length ? !0 : !1 : !0 } function h(a) { var b = d({}, a); return b._parent = a, b } function i(a, b) { return a.path = b, a } function j(a, b) { return (a ? a + "." : "") + b } b.__esModule = !0, b.extend = d, b.indexOf = e, b.escapeExpression = f, b.isEmpty = g, b.createFrame = h, b.blockParams = i, b.appendContextPath = j; var k = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "`": "`", "=": "=" }, l = /[&<>"'`=]/g, m = /[&<>"'`=]/, n = Object.prototype.toString; b.toString = n; var o = function (a) { return "function" == typeof a }; o(/x/) && (b.isFunction = o = function (a) { return "function" == typeof a && "[object Function]" === n.call(a) }), b.isFunction = o; var p = Array.isArray || function (a) { return a && "object" == typeof a ? "[object Array]" === n.call(a) : !1 }; b.isArray = p }, function (a, b) { "use strict"; function c(a, b) { var e = b && b.loc, f = void 0, g = void 0; e && (f = e.start.line, g = e.start.column, a += " - " + f + ":" + g); for (var h = Error.prototype.constructor.call(this, a), i = 0; i < d.length; i++) this[d[i]] = h[d[i]]; Error.captureStackTrace && Error.captureStackTrace(this, c), e && (this.lineNumber = f, this.column = g) } b.__esModule = !0; var d = ["description", "fileName", "lineNumber", "message", "name", "number", "stack"]; c.prototype = new Error, b["default"] = c, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a) { g["default"](a), i["default"](a), k["default"](a), m["default"](a), o["default"](a), q["default"](a), s["default"](a) } var e = c(1)["default"]; b.__esModule = !0, b.registerDefaultHelpers = d; var f = c(8), g = e(f), h = c(9), i = e(h), j = c(10), k = e(j), l = c(11), m = e(l), n = c(12), o = e(n), p = c(13), q = e(p), r = c(14), s = e(r) }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerHelper("blockHelperMissing", function (b, c) { var e = c.inverse, f = c.fn; if (b === !0) return f(this); if (b === !1 || null == b) return e(this); if (d.isArray(b)) return b.length > 0 ? (c.ids && (c.ids = [c.name]), a.helpers.each(b, c)) : e(this); if (c.data && c.ids) { var g = d.createFrame(c.data); g.contextPath = d.appendContextPath(c.data.contextPath, c.name), c = { data: g } } return f(b, c) }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; var d = c(1)["default"]; b.__esModule = !0; var e = c(5), f = c(6), g = d(f); b["default"] = function (a) { a.registerHelper("each", function (a, b) { function c(b, c, f) { j && (j.key = b, j.index = c, j.first = 0 === c, j.last = !!f, k && (j.contextPath = k + b)), i += d(a[b], { data: j, blockParams: e.blockParams([a[b], b], [k + b, null]) }) } if (!b) throw new g["default"]("Must pass iterator to #each"); var d = b.fn, f = b.inverse, h = 0, i = "", j = void 0, k = void 0; if (b.data && b.ids && (k = e.appendContextPath(b.data.contextPath, b.ids[0]) + "."), e.isFunction(a) && (a = a.call(this)), b.data && (j = e.createFrame(b.data)), a && "object" == typeof a) if (e.isArray(a)) for (var l = a.length; l > h; h++) h in a && c(h, h, h === a.length - 1); else { var m = void 0; for (var n in a) a.hasOwnProperty(n) && (void 0 !== m && c(m, h - 1), m = n, h++); void 0 !== m && c(m, h - 1, !0) } return 0 === h && (i = f(this)), i }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; var d = c(1)["default"]; b.__esModule = !0; var e = c(6), f = d(e); b["default"] = function (a) { a.registerHelper("helperMissing", function () { if (1 !== arguments.length) throw new f["default"]('Missing helper: "' + arguments[arguments.length - 1].name + '"') }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerHelper("if", function (a, b) { return d.isFunction(a) && (a = a.call(this)), !b.hash.includeZero && !a || d.isEmpty(a) ? b.inverse(this) : b.fn(this) }), a.registerHelper("unless", function (b, c) { return a.helpers["if"].call(this, b, { fn: c.inverse, inverse: c.fn, hash: c.hash }) }) }, a.exports = b["default"] }, function (a, b) { "use strict"; b.__esModule = !0, b["default"] = function (a) { a.registerHelper("log", function () { for (var b = [void 0], c = arguments[arguments.length - 1], d = 0; d < arguments.length - 1; d++) b.push(arguments[d]); var e = 1; null != c.hash.level ? e = c.hash.level : c.data && null != c.data.level && (e = c.data.level), b[0] = e, a.log.apply(a, b) }) }, a.exports = b["default"] }, function (a, b) { "use strict"; b.__esModule = !0, b["default"] = function (a) { a.registerHelper("lookup", function (a, b) { return a && a[b] }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerHelper("with", function (a, b) { d.isFunction(a) && (a = a.call(this)); var c = b.fn; if (d.isEmpty(a)) return b.inverse(this); var e = b.data; return b.data && b.ids && (e = d.createFrame(b.data), e.contextPath = d.appendContextPath(b.data.contextPath, b.ids[0])), c(a, { data: e, blockParams: d.blockParams([a], [e && e.contextPath]) }) }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a) { g["default"](a) } var e = c(1)["default"]; b.__esModule = !0, b.registerDefaultDecorators = d; var f = c(16), g = e(f) }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerDecorator("inline", function (a, b, c, e) { var f = a; return b.partials || (b.partials = {}, f = function (e, f) { var g = c.partials; c.partials = d.extend({}, g, b.partials); var h = a(e, f); return c.partials = g, h }), b.partials[e.args[0]] = e.fn, f }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5), e = { methodMap: ["debug", "info", "warn", "error"], level: "info", lookupLevel: function (a) { if ("string" == typeof a) { var b = d.indexOf(e.methodMap, a.toLowerCase()); a = b >= 0 ? b : parseInt(a, 10) } return a }, log: function (a) { if (a = e.lookupLevel(a), "undefined" != typeof console && e.lookupLevel(e.level) <= a) { var b = e.methodMap[a]; console[b] || (b = "log"); for (var c = arguments.length, d = Array(c > 1 ? c - 1 : 0), f = 1; c > f; f++) d[f - 1] = arguments[f]; console[b].apply(console, d) } } }; b["default"] = e, a.exports = b["default"] }, function (a, b) { "use strict"; function c(a) { this.string = a } b.__esModule = !0, c.prototype.toString = c.prototype.toHTML = function () { return "" + this.string }, b["default"] = c, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a) { var b = a && a[0] || 1, c = r.COMPILER_REVISION; if (b !== c) { if (c > b) { var d = r.REVISION_CHANGES[c], e = r.REVISION_CHANGES[b]; throw new q["default"]("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version (" + d + ") or downgrade your runtime to an older version (" + e + ").") } throw new q["default"]("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version (" + a[1] + ").") } } function e(a, b) { function c(c, d, e) { e.hash && (d = o.extend({}, d, e.hash), e.ids && (e.ids[0] = !0)), c = b.VM.resolvePartial.call(this, c, d, e); var f = b.VM.invokePartial.call(this, c, d, e); if (null == f && b.compile && (e.partials[e.name] = b.compile(c, a.compilerOptions, b), f = e.partials[e.name](d, e)), null != f) { if (e.indent) { for (var g = f.split("\n"), h = 0, i = g.length; i > h && (g[h] || h + 1 !== i) ; h++) g[h] = e.indent + g[h]; f = g.join("\n") } return f } throw new q["default"]("The partial " + e.name + " could not be compiled when running in runtime-only mode") } function d(b) { function c(b) { return "" + a.main(e, b, e.helpers, e.partials, g, i, h) } var f = arguments.length <= 1 || void 0 === arguments[1] ? {} : arguments[1], g = f.data; d._setup(f), !f.partial && a.useData && (g = j(b, g)); var h = void 0, i = a.useBlockParams ? [] : void 0; return a.useDepths && (h = f.depths ? b !== f.depths[0] ? [b].concat(f.depths) : f.depths : [b]), (c = k(a.main, c, e, f.depths || [], g, i))(b, f) } if (!b) throw new q["default"]("No environment passed to template"); if (!a || !a.main) throw new q["default"]("Unknown template object: " + typeof a); a.main.decorator = a.main_d, b.VM.checkRevision(a.compiler); var e = { strict: function (a, b) { if (!(b in a)) throw new q["default"]('"' + b + '" not defined in ' + a); return a[b] }, lookup: function (a, b) { for (var c = a.length, d = 0; c > d; d++) if (a[d] && null != a[d][b]) return a[d][b] }, lambda: function (a, b) { return "function" == typeof a ? a.call(b) : a }, escapeExpression: o.escapeExpression, invokePartial: c, fn: function (b) { var c = a[b]; return c.decorator = a[b + "_d"], c }, programs: [], program: function (a, b, c, d, e) { var g = this.programs[a], h = this.fn(a); return b || e || d || c ? g = f(this, a, h, b, c, d, e) : g || (g = this.programs[a] = f(this, a, h)), g }, data: function (a, b) { for (; a && b--;) a = a._parent; return a }, merge: function (a, b) { var c = a || b; return a && b && a !== b && (c = o.extend({}, b, a)), c }, noop: b.VM.noop, compilerInfo: a.compiler }; return d.isTop = !0, d._setup = function (c) { c.partial ? (e.helpers = c.helpers, e.partials = c.partials, e.decorators = c.decorators) : (e.helpers = e.merge(c.helpers, b.helpers), a.usePartial && (e.partials = e.merge(c.partials, b.partials)), (a.usePartial || a.useDecorators) && (e.decorators = e.merge(c.decorators, b.decorators))) }, d._child = function (b, c, d, g) { if (a.useBlockParams && !d) throw new q["default"]("must pass block params"); if (a.useDepths && !g) throw new q["default"]("must pass parent depths"); return f(e, b, a[b], c, 0, d, g) }, d } function f(a, b, c, d, e, f, g) { function h(b) { var e = arguments.length <= 1 || void 0 === arguments[1] ? {} : arguments[1], h = g; return g && b !== g[0] && (h = [b].concat(g)), c(a, b, a.helpers, a.partials, e.data || d, f && [e.blockParams].concat(f), h) } return h = k(c, h, a, g, d, f), h.program = b, h.depth = g ? g.length : 0, h.blockParams = e || 0, h } function g(a, b, c) { return a ? a.call || c.name || (c.name = a, a = c.partials[a]) : a = "@partial-block" === c.name ? c.data["partial-block"] : c.partials[c.name], a } function h(a, b, c) { c.partial = !0, c.ids && (c.data.contextPath = c.ids[0] || c.data.contextPath); var d = void 0; if (c.fn && c.fn !== i && (c.data = r.createFrame(c.data), d = c.data["partial-block"] = c.fn, d.partials && (c.partials = o.extend({}, c.partials, d.partials))), void 0 === a && d && (a = d), void 0 === a) throw new q["default"]("The partial " + c.name + " could not be found"); return a instanceof Function ? a(b, c) : void 0 } function i() { return "" } function j(a, b) { return b && "root" in b || (b = b ? r.createFrame(b) : {}, b.root = a), b } function k(a, b, c, d, e, f) { if (a.decorator) { var g = {}; b = a.decorator(b, g, c, d && d[0], e, f, d), o.extend(b, g) } return b } var l = c(3)["default"], m = c(1)["default"]; b.__esModule = !0, b.checkRevision = d, b.template = e, b.wrapProgram = f, b.resolvePartial = g, b.invokePartial = h, b.noop = i; var n = c(5), o = l(n), p = c(6), q = m(p), r = c(4) }, function (a, b) { (function (c) { "use strict"; b.__esModule = !0, b["default"] = function (a) { var b = "undefined" != typeof c ? c : window, d = b.Handlebars; a.noConflict = function () { return b.Handlebars === a && (b.Handlebars = d), a } }, a.exports = b["default"] }).call(b, function () { return this }()) }, function (a, b) { "use strict"; b.__esModule = !0; var c = { helpers: { helperExpression: function (a) { return "SubExpression" === a.type || ("MustacheStatement" === a.type || "BlockStatement" === a.type) && !!(a.params && a.params.length || a.hash) }, scopedId: function (a) { return /^\.|this\b/.test(a.original) }, simpleId: function (a) { return 1 === a.parts.length && !c.helpers.scopedId(a) && !a.depth } } }; b["default"] = c, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a, b) { if ("Program" === a.type) return a; h["default"].yy = n, n.locInfo = function (a) { return new n.SourceLocation(b && b.srcName, a) }; var c = new j["default"](b); return c.accept(h["default"].parse(a)) } var e = c(1)["default"], f = c(3)["default"]; b.__esModule = !0, b.parse = d; var g = c(23), h = e(g), i = c(24), j = e(i), k = c(26), l = f(k), m = c(5); b.parser = h["default"]; var n = {}; m.extend(n, l) }, function (a, b) { - "use strict"; var c = function () { - function a() { this.yy = {} } var b = { - trace: function () { }, yy: {}, symbols_: { error: 2, root: 3, program: 4, EOF: 5, program_repetition0: 6, statement: 7, mustache: 8, block: 9, rawBlock: 10, partial: 11, partialBlock: 12, content: 13, COMMENT: 14, CONTENT: 15, openRawBlock: 16, rawBlock_repetition_plus0: 17, END_RAW_BLOCK: 18, OPEN_RAW_BLOCK: 19, helperName: 20, openRawBlock_repetition0: 21, openRawBlock_option0: 22, CLOSE_RAW_BLOCK: 23, openBlock: 24, block_option0: 25, closeBlock: 26, openInverse: 27, block_option1: 28, OPEN_BLOCK: 29, openBlock_repetition0: 30, openBlock_option0: 31, openBlock_option1: 32, CLOSE: 33, OPEN_INVERSE: 34, openInverse_repetition0: 35, openInverse_option0: 36, openInverse_option1: 37, openInverseChain: 38, OPEN_INVERSE_CHAIN: 39, openInverseChain_repetition0: 40, openInverseChain_option0: 41, openInverseChain_option1: 42, inverseAndProgram: 43, INVERSE: 44, inverseChain: 45, inverseChain_option0: 46, OPEN_ENDBLOCK: 47, OPEN: 48, mustache_repetition0: 49, mustache_option0: 50, OPEN_UNESCAPED: 51, mustache_repetition1: 52, mustache_option1: 53, CLOSE_UNESCAPED: 54, OPEN_PARTIAL: 55, partialName: 56, partial_repetition0: 57, partial_option0: 58, openPartialBlock: 59, OPEN_PARTIAL_BLOCK: 60, openPartialBlock_repetition0: 61, openPartialBlock_option0: 62, param: 63, sexpr: 64, OPEN_SEXPR: 65, sexpr_repetition0: 66, sexpr_option0: 67, CLOSE_SEXPR: 68, hash: 69, hash_repetition_plus0: 70, hashSegment: 71, ID: 72, EQUALS: 73, blockParams: 74, OPEN_BLOCK_PARAMS: 75, blockParams_repetition_plus0: 76, CLOSE_BLOCK_PARAMS: 77, path: 78, dataName: 79, STRING: 80, NUMBER: 81, BOOLEAN: 82, UNDEFINED: 83, NULL: 84, DATA: 85, pathSegments: 86, SEP: 87, $accept: 0, $end: 1 }, terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" }, productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]], performAction: function (a, b, c, d, e, f, g) { var h = f.length - 1; switch (e) { case 1: return f[h - 1]; case 2: this.$ = d.prepareProgram(f[h]); break; case 3: this.$ = f[h]; break; case 4: this.$ = f[h]; break; case 5: this.$ = f[h]; break; case 6: this.$ = f[h]; break; case 7: this.$ = f[h]; break; case 8: this.$ = f[h]; break; case 9: this.$ = { type: "CommentStatement", value: d.stripComment(f[h]), strip: d.stripFlags(f[h], f[h]), loc: d.locInfo(this._$) }; break; case 10: this.$ = { type: "ContentStatement", original: f[h], value: f[h], loc: d.locInfo(this._$) }; break; case 11: this.$ = d.prepareRawBlock(f[h - 2], f[h - 1], f[h], this._$); break; case 12: this.$ = { path: f[h - 3], params: f[h - 2], hash: f[h - 1] }; break; case 13: this.$ = d.prepareBlock(f[h - 3], f[h - 2], f[h - 1], f[h], !1, this._$); break; case 14: this.$ = d.prepareBlock(f[h - 3], f[h - 2], f[h - 1], f[h], !0, this._$); break; case 15: this.$ = { open: f[h - 5], path: f[h - 4], params: f[h - 3], hash: f[h - 2], blockParams: f[h - 1], strip: d.stripFlags(f[h - 5], f[h]) }; break; case 16: this.$ = { path: f[h - 4], params: f[h - 3], hash: f[h - 2], blockParams: f[h - 1], strip: d.stripFlags(f[h - 5], f[h]) }; break; case 17: this.$ = { path: f[h - 4], params: f[h - 3], hash: f[h - 2], blockParams: f[h - 1], strip: d.stripFlags(f[h - 5], f[h]) }; break; case 18: this.$ = { strip: d.stripFlags(f[h - 1], f[h - 1]), program: f[h] }; break; case 19: var i = d.prepareBlock(f[h - 2], f[h - 1], f[h], f[h], !1, this._$), j = d.prepareProgram([i], f[h - 1].loc); j.chained = !0, this.$ = { strip: f[h - 2].strip, program: j, chain: !0 }; break; case 20: this.$ = f[h]; break; case 21: this.$ = { path: f[h - 1], strip: d.stripFlags(f[h - 2], f[h]) }; break; case 22: this.$ = d.prepareMustache(f[h - 3], f[h - 2], f[h - 1], f[h - 4], d.stripFlags(f[h - 4], f[h]), this._$); break; case 23: this.$ = d.prepareMustache(f[h - 3], f[h - 2], f[h - 1], f[h - 4], d.stripFlags(f[h - 4], f[h]), this._$); break; case 24: this.$ = { type: "PartialStatement", name: f[h - 3], params: f[h - 2], hash: f[h - 1], indent: "", strip: d.stripFlags(f[h - 4], f[h]), loc: d.locInfo(this._$) }; break; case 25: this.$ = d.preparePartialBlock(f[h - 2], f[h - 1], f[h], this._$); break; case 26: this.$ = { path: f[h - 3], params: f[h - 2], hash: f[h - 1], strip: d.stripFlags(f[h - 4], f[h]) }; break; case 27: this.$ = f[h]; break; case 28: this.$ = f[h]; break; case 29: this.$ = { type: "SubExpression", path: f[h - 3], params: f[h - 2], hash: f[h - 1], loc: d.locInfo(this._$) }; break; case 30: this.$ = { type: "Hash", pairs: f[h], loc: d.locInfo(this._$) }; break; case 31: this.$ = { type: "HashPair", key: d.id(f[h - 2]), value: f[h], loc: d.locInfo(this._$) }; break; case 32: this.$ = d.id(f[h - 1]); break; case 33: this.$ = f[h]; break; case 34: this.$ = f[h]; break; case 35: this.$ = { type: "StringLiteral", value: f[h], original: f[h], loc: d.locInfo(this._$) }; break; case 36: this.$ = { type: "NumberLiteral", value: Number(f[h]), original: Number(f[h]), loc: d.locInfo(this._$) }; break; case 37: this.$ = { type: "BooleanLiteral", value: "true" === f[h], original: "true" === f[h], loc: d.locInfo(this._$) }; break; case 38: this.$ = { type: "UndefinedLiteral", original: void 0, value: void 0, loc: d.locInfo(this._$) }; break; case 39: this.$ = { type: "NullLiteral", original: null, value: null, loc: d.locInfo(this._$) }; break; case 40: this.$ = f[h]; break; case 41: this.$ = f[h]; break; case 42: this.$ = d.preparePath(!0, f[h], this._$); break; case 43: this.$ = d.preparePath(!1, f[h], this._$); break; case 44: f[h - 2].push({ part: d.id(f[h]), original: f[h], separator: f[h - 1] }), this.$ = f[h - 2]; break; case 45: this.$ = [{ part: d.id(f[h]), original: f[h] }]; break; case 46: this.$ = []; break; case 47: f[h - 1].push(f[h]); break; case 48: this.$ = [f[h]]; break; case 49: f[h - 1].push(f[h]); break; case 50: this.$ = []; break; case 51: f[h - 1].push(f[h]); break; case 58: this.$ = []; break; case 59: f[h - 1].push(f[h]); break; case 64: this.$ = []; break; case 65: f[h - 1].push(f[h]); break; case 70: this.$ = []; break; case 71: f[h - 1].push(f[h]); break; case 78: this.$ = []; break; case 79: f[h - 1].push(f[h]); break; case 82: this.$ = []; break; case 83: f[h - 1].push(f[h]); break; case 86: this.$ = []; break; case 87: f[h - 1].push(f[h]); break; case 90: this.$ = []; break; case 91: f[h - 1].push(f[h]); break; case 94: this.$ = []; break; case 95: f[h - 1].push(f[h]); break; case 98: this.$ = [f[h]]; break; case 99: f[h - 1].push(f[h]); break; case 100: this.$ = [f[h]]; break; case 101: f[h - 1].push(f[h]) } }, table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 13: 40, 15: [1, 20], 17: 39 }, { 20: 42, 56: 41, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 45, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 48, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 42, 56: 49, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 50, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 51] }, { 72: [1, 35], 86: 52 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 53, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 54, 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 55, 47: [2, 54] }, { 28: 60, 43: 61, 44: [1, 59], 47: [2, 56] }, { 13: 63, 15: [1, 20], 18: [1, 62] }, { 15: [2, 48], 18: [2, 48] }, { 33: [2, 86], 57: 64, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 65, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 66, 47: [1, 67] }, { 30: 68, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 69, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 70, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 71, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 75, 33: [2, 80], 50: 72, 63: 73, 64: 76, 65: [1, 44], 69: 74, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 80] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 51] }, { 20: 75, 53: 81, 54: [2, 84], 63: 82, 64: 76, 65: [1, 44], 69: 83, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 84, 47: [1, 67] }, { 47: [2, 55] }, { 4: 85, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 86, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 87, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 88, 47: [1, 67] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 75, 33: [2, 88], 58: 89, 63: 90, 64: 76, 65: [1, 44], 69: 91, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 92, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 93, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 31: 94, 33: [2, 60], 63: 95, 64: 76, 65: [1, 44], 69: 96, 70: 77, 71: 78, 72: [1, 79], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 66], 36: 97, 63: 98, 64: 76, 65: [1, 44], 69: 99, 70: 77, 71: 78, 72: [1, 79], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 22: 100, 23: [2, 52], 63: 101, 64: 76, 65: [1, 44], 69: 102, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 92], 62: 103, 63: 104, 64: 76, 65: [1, 44], 69: 105, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 106] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 107, 72: [1, 108], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 109], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 110] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 112, 46: 111, 47: [2, 76] }, { 33: [2, 70], 40: 113, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 114] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 75, 63: 116, 64: 76, 65: [1, 44], 67: 115, 68: [2, 96], 69: 117, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 118] }, { 32: 119, 33: [2, 62], 74: 120, 75: [1, 121] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 122, 74: 123, 75: [1, 121] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 124] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 125] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 109] }, { 20: 75, 63: 126, 64: 76, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 75, 33: [2, 72], 41: 127, 63: 128, 64: 76, 65: [1, 44], 69: 129, 70: 77, 71: 78, 72: [1, 79], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 130] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 131] }, { 33: [2, 63] }, { 72: [1, 133], 76: 132 }, { 33: [1, 134] }, { 33: [2, 69] }, { 15: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 135, 74: 136, 75: [1, 121] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 138], 77: [1, 137] }, { 72: [2, 100], 77: [2, 100] }, { - 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], - 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] - }, { 33: [1, 139] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }], defaultActions: { 4: [2, 1], 55: [2, 55], 57: [2, 20], 61: [2, 57], 74: [2, 81], 83: [2, 85], 87: [2, 18], 91: [2, 89], 102: [2, 53], 105: [2, 93], 111: [2, 19], 112: [2, 77], 117: [2, 97], 120: [2, 63], 123: [2, 69], 124: [2, 12], 136: [2, 75], 137: [2, 32] }, parseError: function (a, b) { throw new Error(a) }, parse: function (a) { function b() { var a; return a = c.lexer.lex() || 1, "number" != typeof a && (a = c.symbols_[a] || a), a } var c = this, d = [0], e = [null], f = [], g = this.table, h = "", i = 0, j = 0, k = 0; this.lexer.setInput(a), this.lexer.yy = this.yy, this.yy.lexer = this.lexer, this.yy.parser = this, "undefined" == typeof this.lexer.yylloc && (this.lexer.yylloc = {}); var l = this.lexer.yylloc; f.push(l); var m = this.lexer.options && this.lexer.options.ranges; "function" == typeof this.yy.parseError && (this.parseError = this.yy.parseError); for (var n, o, p, q, r, s, t, u, v, w = {}; ;) { if (p = d[d.length - 1], this.defaultActions[p] ? q = this.defaultActions[p] : ((null === n || "undefined" == typeof n) && (n = b()), q = g[p] && g[p][n]), "undefined" == typeof q || !q.length || !q[0]) { var x = ""; if (!k) { v = []; for (s in g[p]) this.terminals_[s] && s > 2 && v.push("'" + this.terminals_[s] + "'"); x = this.lexer.showPosition ? "Parse error on line " + (i + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + v.join(", ") + ", got '" + (this.terminals_[n] || n) + "'" : "Parse error on line " + (i + 1) + ": Unexpected " + (1 == n ? "end of input" : "'" + (this.terminals_[n] || n) + "'"), this.parseError(x, { text: this.lexer.match, token: this.terminals_[n] || n, line: this.lexer.yylineno, loc: l, expected: v }) } } if (q[0] instanceof Array && q.length > 1) throw new Error("Parse Error: multiple actions possible at state: " + p + ", token: " + n); switch (q[0]) { case 1: d.push(n), e.push(this.lexer.yytext), f.push(this.lexer.yylloc), d.push(q[1]), n = null, o ? (n = o, o = null) : (j = this.lexer.yyleng, h = this.lexer.yytext, i = this.lexer.yylineno, l = this.lexer.yylloc, k > 0 && k--); break; case 2: if (t = this.productions_[q[1]][1], w.$ = e[e.length - t], w._$ = { first_line: f[f.length - (t || 1)].first_line, last_line: f[f.length - 1].last_line, first_column: f[f.length - (t || 1)].first_column, last_column: f[f.length - 1].last_column }, m && (w._$.range = [f[f.length - (t || 1)].range[0], f[f.length - 1].range[1]]), r = this.performAction.call(w, h, j, i, this.yy, q[1], e, f), "undefined" != typeof r) return r; t && (d = d.slice(0, -1 * t * 2), e = e.slice(0, -1 * t), f = f.slice(0, -1 * t)), d.push(this.productions_[q[1]][0]), e.push(w.$), f.push(w._$), u = g[d[d.length - 2]][d[d.length - 1]], d.push(u); break; case 3: return !0 } } return !0 } - }, c = function () { var a = { EOF: 1, parseError: function (a, b) { if (!this.yy.parser) throw new Error(a); this.yy.parser.parseError(a, b) }, setInput: function (a) { return this._input = a, this._more = this._less = this.done = !1, this.yylineno = this.yyleng = 0, this.yytext = this.matched = this.match = "", this.conditionStack = ["INITIAL"], this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 }, this.options.ranges && (this.yylloc.range = [0, 0]), this.offset = 0, this }, input: function () { var a = this._input[0]; this.yytext += a, this.yyleng++, this.offset++, this.match += a, this.matched += a; var b = a.match(/(?:\r\n?|\n).*/g); return b ? (this.yylineno++, this.yylloc.last_line++) : this.yylloc.last_column++, this.options.ranges && this.yylloc.range[1]++, this._input = this._input.slice(1), a }, unput: function (a) { var b = a.length, c = a.split(/(?:\r\n?|\n)/g); this._input = a + this._input, this.yytext = this.yytext.substr(0, this.yytext.length - b - 1), this.offset -= b; var d = this.match.split(/(?:\r\n?|\n)/g); this.match = this.match.substr(0, this.match.length - 1), this.matched = this.matched.substr(0, this.matched.length - 1), c.length - 1 && (this.yylineno -= c.length - 1); var e = this.yylloc.range; return this.yylloc = { first_line: this.yylloc.first_line, last_line: this.yylineno + 1, first_column: this.yylloc.first_column, last_column: c ? (c.length === d.length ? this.yylloc.first_column : 0) + d[d.length - c.length].length - c[0].length : this.yylloc.first_column - b }, this.options.ranges && (this.yylloc.range = [e[0], e[0] + this.yyleng - b]), this }, more: function () { return this._more = !0, this }, less: function (a) { this.unput(this.match.slice(a)) }, pastInput: function () { var a = this.matched.substr(0, this.matched.length - this.match.length); return (a.length > 20 ? "..." : "") + a.substr(-20).replace(/\n/g, "") }, upcomingInput: function () { var a = this.match; return a.length < 20 && (a += this._input.substr(0, 20 - a.length)), (a.substr(0, 20) + (a.length > 20 ? "..." : "")).replace(/\n/g, "") }, showPosition: function () { var a = this.pastInput(), b = new Array(a.length + 1).join("-"); return a + this.upcomingInput() + "\n" + b + "^" }, next: function () { if (this.done) return this.EOF; this._input || (this.done = !0); var a, b, c, d, e; this._more || (this.yytext = "", this.match = ""); for (var f = this._currentRules(), g = 0; g < f.length && (c = this._input.match(this.rules[f[g]]), !c || b && !(c[0].length > b[0].length) || (b = c, d = g, this.options.flex)) ; g++); return b ? (e = b[0].match(/(?:\r\n?|\n).*/g), e && (this.yylineno += e.length), this.yylloc = { first_line: this.yylloc.last_line, last_line: this.yylineno + 1, first_column: this.yylloc.last_column, last_column: e ? e[e.length - 1].length - e[e.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + b[0].length }, this.yytext += b[0], this.match += b[0], this.matches = b, this.yyleng = this.yytext.length, this.options.ranges && (this.yylloc.range = [this.offset, this.offset += this.yyleng]), this._more = !1, this._input = this._input.slice(b[0].length), this.matched += b[0], a = this.performAction.call(this, this.yy, this, f[d], this.conditionStack[this.conditionStack.length - 1]), this.done && this._input && (this.done = !1), a ? a : void 0) : "" === this._input ? this.EOF : this.parseError("Lexical error on line " + (this.yylineno + 1) + ". Unrecognized text.\n" + this.showPosition(), { text: "", token: null, line: this.yylineno }) }, lex: function () { var a = this.next(); return "undefined" != typeof a ? a : this.lex() }, begin: function (a) { this.conditionStack.push(a) }, popState: function () { return this.conditionStack.pop() }, _currentRules: function () { return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules }, topState: function () { return this.conditionStack[this.conditionStack.length - 2] }, pushState: function (a) { this.begin(a) } }; return a.options = {}, a.performAction = function (a, b, c, d) { function e(a, c) { return b.yytext = b.yytext.substr(a, b.yyleng - c) } switch (c) { case 0: if ("\\\\" === b.yytext.slice(-2) ? (e(0, 1), this.begin("mu")) : "\\" === b.yytext.slice(-1) ? (e(0, 1), this.begin("emu")) : this.begin("mu"), b.yytext) return 15; break; case 1: return 15; case 2: return this.popState(), 15; case 3: return this.begin("raw"), 15; case 4: return this.popState(), "raw" === this.conditionStack[this.conditionStack.length - 1] ? 15 : (b.yytext = b.yytext.substr(5, b.yyleng - 9), "END_RAW_BLOCK"); case 5: return 15; case 6: return this.popState(), 14; case 7: return 65; case 8: return 68; case 9: return 19; case 10: return this.popState(), this.begin("raw"), 23; case 11: return 55; case 12: return 60; case 13: return 29; case 14: return 47; case 15: return this.popState(), 44; case 16: return this.popState(), 44; case 17: return 34; case 18: return 39; case 19: return 51; case 20: return 48; case 21: this.unput(b.yytext), this.popState(), this.begin("com"); break; case 22: return this.popState(), 14; case 23: return 48; case 24: return 73; case 25: return 72; case 26: return 72; case 27: return 87; case 28: break; case 29: return this.popState(), 54; case 30: return this.popState(), 33; case 31: return b.yytext = e(1, 2).replace(/\\"/g, '"'), 80; case 32: return b.yytext = e(1, 2).replace(/\\'/g, "'"), 80; case 33: return 85; case 34: return 82; case 35: return 82; case 36: return 83; case 37: return 84; case 38: return 81; case 39: return 75; case 40: return 77; case 41: return 72; case 42: return b.yytext = b.yytext.replace(/\\([\\\]])/g, "$1"), 72; case 43: return "INVALID"; case 44: return 5 } }, a.rules = [/^(?:[^\x00]*?(?=(\{\{)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/, /^(?:\{\{\{\{(?=[^\/]))/, /^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/, /^(?:[^\x00]*?(?=(\{\{\{\{)))/, /^(?:[\s\S]*?--(~)?\}\})/, /^(?:\()/, /^(?:\))/, /^(?:\{\{\{\{)/, /^(?:\}\}\}\})/, /^(?:\{\{(~)?>)/, /^(?:\{\{(~)?#>)/, /^(?:\{\{(~)?#\*?)/, /^(?:\{\{(~)?\/)/, /^(?:\{\{(~)?\^\s*(~)?\}\})/, /^(?:\{\{(~)?\s*else\s*(~)?\}\})/, /^(?:\{\{(~)?\^)/, /^(?:\{\{(~)?\s*else\b)/, /^(?:\{\{(~)?\{)/, /^(?:\{\{(~)?&)/, /^(?:\{\{(~)?!--)/, /^(?:\{\{(~)?![\s\S]*?\}\})/, /^(?:\{\{(~)?\*?)/, /^(?:=)/, /^(?:\.\.)/, /^(?:\.(?=([=~}\s\/.)|])))/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:\}(~)?\}\})/, /^(?:(~)?\}\})/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\s)])))/, /^(?:false(?=([~}\s)])))/, /^(?:undefined(?=([~}\s)])))/, /^(?:null(?=([~}\s)])))/, /^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/, /^(?:as\s+\|)/, /^(?:\|)/, /^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/, /^(?:\[(\\\]|[^\]])*\])/, /^(?:.)/, /^(?:$)/], a.conditions = { mu: { rules: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], inclusive: !1 }, emu: { rules: [2], inclusive: !1 }, com: { rules: [6], inclusive: !1 }, raw: { rules: [3, 4, 5], inclusive: !1 }, INITIAL: { rules: [0, 1, 44], inclusive: !0 } }, a }(); return b.lexer = c, a.prototype = b, b.Parser = a, new a - }(); b.__esModule = !0, b["default"] = c - }, function (a, b, c) { "use strict"; function d() { var a = arguments.length <= 0 || void 0 === arguments[0] ? {} : arguments[0]; this.options = a } function e(a, b, c) { void 0 === b && (b = a.length); var d = a[b - 1], e = a[b - 2]; return d ? "ContentStatement" === d.type ? (e || !c ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(d.original) : void 0 : c } function f(a, b, c) { void 0 === b && (b = -1); var d = a[b + 1], e = a[b + 2]; return d ? "ContentStatement" === d.type ? (e || !c ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(d.original) : void 0 : c } function g(a, b, c) { var d = a[null == b ? 0 : b + 1]; if (d && "ContentStatement" === d.type && (c || !d.rightStripped)) { var e = d.value; d.value = d.value.replace(c ? /^\s+/ : /^[ \t]*\r?\n?/, ""), d.rightStripped = d.value !== e } } function h(a, b, c) { var d = a[null == b ? a.length - 1 : b - 1]; if (d && "ContentStatement" === d.type && (c || !d.leftStripped)) { var e = d.value; return d.value = d.value.replace(c ? /\s+$/ : /[ \t]+$/, ""), d.leftStripped = d.value !== e, d.leftStripped } } var i = c(1)["default"]; b.__esModule = !0; var j = c(25), k = i(j); d.prototype = new k["default"], d.prototype.Program = function (a) { var b = !this.options.ignoreStandalone, c = !this.isRootSeen; this.isRootSeen = !0; for (var d = a.body, i = 0, j = d.length; j > i; i++) { var k = d[i], l = this.accept(k); if (l) { var m = e(d, i, c), n = f(d, i, c), o = l.openStandalone && m, p = l.closeStandalone && n, q = l.inlineStandalone && m && n; l.close && g(d, i, !0), l.open && h(d, i, !0), b && q && (g(d, i), h(d, i) && "PartialStatement" === k.type && (k.indent = /([ \t]+$)/.exec(d[i - 1].original)[1])), b && o && (g((k.program || k.inverse).body), h(d, i)), b && p && (g(d, i), h((k.inverse || k.program).body)) } } return a }, d.prototype.BlockStatement = d.prototype.DecoratorBlock = d.prototype.PartialBlockStatement = function (a) { this.accept(a.program), this.accept(a.inverse); var b = a.program || a.inverse, c = a.program && a.inverse, d = c, i = c; if (c && c.chained) for (d = c.body[0].program; i.chained;) i = i.body[i.body.length - 1].program; var j = { open: a.openStrip.open, close: a.closeStrip.close, openStandalone: f(b.body), closeStandalone: e((d || b).body) }; if (a.openStrip.close && g(b.body, null, !0), c) { var k = a.inverseStrip; k.open && h(b.body, null, !0), k.close && g(d.body, null, !0), a.closeStrip.open && h(i.body, null, !0), !this.options.ignoreStandalone && e(b.body) && f(d.body) && (h(b.body), g(d.body)) } else a.closeStrip.open && h(b.body, null, !0); return j }, d.prototype.Decorator = d.prototype.MustacheStatement = function (a) { return a.strip }, d.prototype.PartialStatement = d.prototype.CommentStatement = function (a) { var b = a.strip || {}; return { inlineStandalone: !0, open: b.open, close: b.close } }, b["default"] = d, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d() { this.parents = [] } function e(a) { this.acceptRequired(a, "path"), this.acceptArray(a.params), this.acceptKey(a, "hash") } function f(a) { e.call(this, a), this.acceptKey(a, "program"), this.acceptKey(a, "inverse") } function g(a) { this.acceptRequired(a, "name"), this.acceptArray(a.params), this.acceptKey(a, "hash") } var h = c(1)["default"]; b.__esModule = !0; var i = c(6), j = h(i); d.prototype = { constructor: d, mutating: !1, acceptKey: function (a, b) { var c = this.accept(a[b]); if (this.mutating) { if (c && !d.prototype[c.type]) throw new j["default"]('Unexpected node type "' + c.type + '" found when accepting ' + b + " on " + a.type); a[b] = c } }, acceptRequired: function (a, b) { if (this.acceptKey(a, b), !a[b]) throw new j["default"](a.type + " requires " + b) }, acceptArray: function (a) { for (var b = 0, c = a.length; c > b; b++) this.acceptKey(a, b), a[b] || (a.splice(b, 1), b--, c--) }, accept: function (a) { if (a) { if (!this[a.type]) throw new j["default"]("Unknown type: " + a.type, a); this.current && this.parents.unshift(this.current), this.current = a; var b = this[a.type](a); return this.current = this.parents.shift(), !this.mutating || b ? b : b !== !1 ? a : void 0 } }, Program: function (a) { this.acceptArray(a.body) }, MustacheStatement: e, Decorator: e, BlockStatement: f, DecoratorBlock: f, PartialStatement: g, PartialBlockStatement: function (a) { g.call(this, a), this.acceptKey(a, "program") }, ContentStatement: function () { }, CommentStatement: function () { }, SubExpression: e, PathExpression: function () { }, StringLiteral: function () { }, NumberLiteral: function () { }, BooleanLiteral: function () { }, UndefinedLiteral: function () { }, NullLiteral: function () { }, Hash: function (a) { this.acceptArray(a.pairs) }, HashPair: function (a) { this.acceptRequired(a, "value") } }, b["default"] = d, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a, b) { if (b = b.path ? b.path.original : b, a.path.original !== b) { var c = { loc: a.path.loc }; throw new q["default"](a.path.original + " doesn't match " + b, c) } } function e(a, b) { this.source = a, this.start = { line: b.first_line, column: b.first_column }, this.end = { line: b.last_line, column: b.last_column } } function f(a) { return /^\[.*\]$/.test(a) ? a.substr(1, a.length - 2) : a } function g(a, b) { return { open: "~" === a.charAt(2), close: "~" === b.charAt(b.length - 3) } } function h(a) { return a.replace(/^\{\{~?\!-?-?/, "").replace(/-?-?~?\}\}$/, "") } function i(a, b, c) { c = this.locInfo(c); for (var d = a ? "@" : "", e = [], f = 0, g = "", h = 0, i = b.length; i > h; h++) { var j = b[h].part, k = b[h].original !== j; if (d += (b[h].separator || "") + j, k || ".." !== j && "." !== j && "this" !== j) e.push(j); else { if (e.length > 0) throw new q["default"]("Invalid path: " + d, { loc: c }); ".." === j && (f++, g += "../") } } return { type: "PathExpression", data: a, depth: f, parts: e, original: d, loc: c } } function j(a, b, c, d, e, f) { var g = d.charAt(3) || d.charAt(2), h = "{" !== g && "&" !== g, i = /\*/.test(d); return { type: i ? "Decorator" : "MustacheStatement", path: a, params: b, hash: c, escaped: h, strip: e, loc: this.locInfo(f) } } function k(a, b, c, e) { d(a, c), e = this.locInfo(e); var f = { type: "Program", body: b, strip: {}, loc: e }; return { type: "BlockStatement", path: a.path, params: a.params, hash: a.hash, program: f, openStrip: {}, inverseStrip: {}, closeStrip: {}, loc: e } } function l(a, b, c, e, f, g) { e && e.path && d(a, e); var h = /\*/.test(a.open); b.blockParams = a.blockParams; var i = void 0, j = void 0; if (c) { if (h) throw new q["default"]("Unexpected inverse block on decorator", c); c.chain && (c.program.body[0].closeStrip = e.strip), j = c.strip, i = c.program } return f && (f = i, i = b, b = f), { type: h ? "DecoratorBlock" : "BlockStatement", path: a.path, params: a.params, hash: a.hash, program: b, inverse: i, openStrip: a.strip, inverseStrip: j, closeStrip: e && e.strip, loc: this.locInfo(g) } } function m(a, b) { if (!b && a.length) { var c = a[0].loc, d = a[a.length - 1].loc; c && d && (b = { source: c.source, start: { line: c.start.line, column: c.start.column }, end: { line: d.end.line, column: d.end.column } }) } return { type: "Program", body: a, strip: {}, loc: b } } function n(a, b, c, e) { return d(a, c), { type: "PartialBlockStatement", name: a.path, params: a.params, hash: a.hash, program: b, openStrip: a.strip, closeStrip: c && c.strip, loc: this.locInfo(e) } } var o = c(1)["default"]; b.__esModule = !0, b.SourceLocation = e, b.id = f, b.stripFlags = g, b.stripComment = h, b.preparePath = i, b.prepareMustache = j, b.prepareRawBlock = k, b.prepareBlock = l, b.prepareProgram = m, b.preparePartialBlock = n; var p = c(6), q = o(p) }, function (a, b, c) { "use strict"; function d() { } function e(a, b, c) { if (null == a || "string" != typeof a && "Program" !== a.type) throw new k["default"]("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + a); b = b || {}, "data" in b || (b.data = !0), b.compat && (b.useDepths = !0); var d = c.parse(a, b), e = (new c.Compiler).compile(d, b); return (new c.JavaScriptCompiler).compile(e, b) } function f(a, b, c) { function d() { var d = c.parse(a, b), e = (new c.Compiler).compile(d, b), f = (new c.JavaScriptCompiler).compile(e, b, void 0, !0); return c.template(f) } function e(a, b) { return f || (f = d()), f.call(this, a, b) } if (void 0 === b && (b = {}), null == a || "string" != typeof a && "Program" !== a.type) throw new k["default"]("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + a); "data" in b || (b.data = !0), b.compat && (b.useDepths = !0); var f = void 0; return e._setup = function (a) { return f || (f = d()), f._setup(a) }, e._child = function (a, b, c, e) { return f || (f = d()), f._child(a, b, c, e) }, e } function g(a, b) { if (a === b) return !0; if (l.isArray(a) && l.isArray(b) && a.length === b.length) { for (var c = 0; c < a.length; c++) if (!g(a[c], b[c])) return !1; return !0 } } function h(a) { if (!a.path.parts) { var b = a.path; a.path = { type: "PathExpression", data: !1, depth: 0, parts: [b.original + ""], original: b.original + "", loc: b.loc } } } var i = c(1)["default"]; b.__esModule = !0, b.Compiler = d, b.precompile = e, b.compile = f; var j = c(6), k = i(j), l = c(5), m = c(21), n = i(m), o = [].slice; d.prototype = { compiler: d, equals: function (a) { var b = this.opcodes.length; if (a.opcodes.length !== b) return !1; for (var c = 0; b > c; c++) { var d = this.opcodes[c], e = a.opcodes[c]; if (d.opcode !== e.opcode || !g(d.args, e.args)) return !1 } b = this.children.length; for (var c = 0; b > c; c++) if (!this.children[c].equals(a.children[c])) return !1; return !0 }, guid: 0, compile: function (a, b) { this.sourceNode = [], this.opcodes = [], this.children = [], this.options = b, this.stringParams = b.stringParams, this.trackIds = b.trackIds, b.blockParams = b.blockParams || []; var c = b.knownHelpers; if (b.knownHelpers = { helperMissing: !0, blockHelperMissing: !0, each: !0, "if": !0, unless: !0, "with": !0, log: !0, lookup: !0 }, c) for (var d in c) d in c && (b.knownHelpers[d] = c[d]); return this.accept(a) }, compileProgram: function (a) { var b = new this.compiler, c = b.compile(a, this.options), d = this.guid++; return this.usePartial = this.usePartial || c.usePartial, this.children[d] = c, this.useDepths = this.useDepths || c.useDepths, d }, accept: function (a) { if (!this[a.type]) throw new k["default"]("Unknown type: " + a.type, a); this.sourceNode.unshift(a); var b = this[a.type](a); return this.sourceNode.shift(), b }, Program: function (a) { this.options.blockParams.unshift(a.blockParams); for (var b = a.body, c = b.length, d = 0; c > d; d++) this.accept(b[d]); return this.options.blockParams.shift(), this.isSimple = 1 === c, this.blockParams = a.blockParams ? a.blockParams.length : 0, this }, BlockStatement: function (a) { h(a); var b = a.program, c = a.inverse; b = b && this.compileProgram(b), c = c && this.compileProgram(c); var d = this.classifySexpr(a); "helper" === d ? this.helperSexpr(a, b, c) : "simple" === d ? (this.simpleSexpr(a), this.opcode("pushProgram", b), this.opcode("pushProgram", c), this.opcode("emptyHash"), this.opcode("blockValue", a.path.original)) : (this.ambiguousSexpr(a, b, c), this.opcode("pushProgram", b), this.opcode("pushProgram", c), this.opcode("emptyHash"), this.opcode("ambiguousBlockValue")), this.opcode("append") }, DecoratorBlock: function (a) { var b = a.program && this.compileProgram(a.program), c = this.setupFullMustacheParams(a, b, void 0), d = a.path; this.useDecorators = !0, this.opcode("registerDecorator", c.length, d.original) }, PartialStatement: function (a) { this.usePartial = !0; var b = a.program; b && (b = this.compileProgram(a.program)); var c = a.params; if (c.length > 1) throw new k["default"]("Unsupported number of partial arguments: " + c.length, a); c.length || (this.options.explicitPartialContext ? this.opcode("pushLiteral", "undefined") : c.push({ type: "PathExpression", parts: [], depth: 0 })); var d = a.name.original, e = "SubExpression" === a.name.type; e && this.accept(a.name), this.setupFullMustacheParams(a, b, void 0, !0); var f = a.indent || ""; this.options.preventIndent && f && (this.opcode("appendContent", f), f = ""), this.opcode("invokePartial", e, d, f), this.opcode("append") }, PartialBlockStatement: function (a) { this.PartialStatement(a) }, MustacheStatement: function (a) { this.SubExpression(a), a.escaped && !this.options.noEscape ? this.opcode("appendEscaped") : this.opcode("append") }, Decorator: function (a) { this.DecoratorBlock(a) }, ContentStatement: function (a) { a.value && this.opcode("appendContent", a.value) }, CommentStatement: function () { }, SubExpression: function (a) { h(a); var b = this.classifySexpr(a); "simple" === b ? this.simpleSexpr(a) : "helper" === b ? this.helperSexpr(a) : this.ambiguousSexpr(a) }, ambiguousSexpr: function (a, b, c) { var d = a.path, e = d.parts[0], f = null != b || null != c; this.opcode("getContext", d.depth), this.opcode("pushProgram", b), this.opcode("pushProgram", c), d.strict = !0, this.accept(d), this.opcode("invokeAmbiguous", e, f) }, simpleSexpr: function (a) { var b = a.path; b.strict = !0, this.accept(b), this.opcode("resolvePossibleLambda") }, helperSexpr: function (a, b, c) { var d = this.setupFullMustacheParams(a, b, c), e = a.path, f = e.parts[0]; if (this.options.knownHelpers[f]) this.opcode("invokeKnownHelper", d.length, f); else { if (this.options.knownHelpersOnly) throw new k["default"]("You specified knownHelpersOnly, but used the unknown helper " + f, a); e.strict = !0, e.falsy = !0, this.accept(e), this.opcode("invokeHelper", d.length, e.original, n["default"].helpers.simpleId(e)) } }, PathExpression: function (a) { this.addDepth(a.depth), this.opcode("getContext", a.depth); var b = a.parts[0], c = n["default"].helpers.scopedId(a), d = !a.depth && !c && this.blockParamIndex(b); d ? this.opcode("lookupBlockParam", d, a.parts) : b ? a.data ? (this.options.data = !0, this.opcode("lookupData", a.depth, a.parts, a.strict)) : this.opcode("lookupOnContext", a.parts, a.falsy, a.strict, c) : this.opcode("pushContext") }, StringLiteral: function (a) { this.opcode("pushString", a.value) }, NumberLiteral: function (a) { this.opcode("pushLiteral", a.value) }, BooleanLiteral: function (a) { this.opcode("pushLiteral", a.value) }, UndefinedLiteral: function () { this.opcode("pushLiteral", "undefined") }, NullLiteral: function () { this.opcode("pushLiteral", "null") }, Hash: function (a) { var b = a.pairs, c = 0, d = b.length; for (this.opcode("pushHash") ; d > c; c++) this.pushParam(b[c].value); for (; c--;) this.opcode("assignToHash", b[c].key); this.opcode("popHash") }, opcode: function (a) { this.opcodes.push({ opcode: a, args: o.call(arguments, 1), loc: this.sourceNode[0].loc }) }, addDepth: function (a) { a && (this.useDepths = !0) }, classifySexpr: function (a) { var b = n["default"].helpers.simpleId(a.path), c = b && !!this.blockParamIndex(a.path.parts[0]), d = !c && n["default"].helpers.helperExpression(a), e = !c && (d || b); if (e && !d) { var f = a.path.parts[0], g = this.options; g.knownHelpers[f] ? d = !0 : g.knownHelpersOnly && (e = !1) } return d ? "helper" : e ? "ambiguous" : "simple" }, pushParams: function (a) { for (var b = 0, c = a.length; c > b; b++) this.pushParam(a[b]) }, pushParam: function (a) { var b = null != a.value ? a.value : a.original || ""; if (this.stringParams) b.replace && (b = b.replace(/^(\.?\.\/)*/g, "").replace(/\//g, ".")), a.depth && this.addDepth(a.depth), this.opcode("getContext", a.depth || 0), this.opcode("pushStringParam", b, a.type), "SubExpression" === a.type && this.accept(a); else { if (this.trackIds) { var c = void 0; if (!a.parts || n["default"].helpers.scopedId(a) || a.depth || (c = this.blockParamIndex(a.parts[0])), c) { var d = a.parts.slice(1).join("."); this.opcode("pushId", "BlockParam", c, d) } else b = a.original || b, b.replace && (b = b.replace(/^this(?:\.|$)/, "").replace(/^\.\//, "").replace(/^\.$/, "")), this.opcode("pushId", a.type, b) } this.accept(a) } }, setupFullMustacheParams: function (a, b, c, d) { var e = a.params; return this.pushParams(e), this.opcode("pushProgram", b), this.opcode("pushProgram", c), a.hash ? this.accept(a.hash) : this.opcode("emptyHash", d), e }, blockParamIndex: function (a) { for (var b = 0, c = this.options.blockParams.length; c > b; b++) { var d = this.options.blockParams[b], e = d && l.indexOf(d, a); if (d && e >= 0) return [b, e] } } } }, function (a, b, c) { - "use strict"; function d(a) { this.value = a } function e() { } function f(a, b, c, d) { var e = b.popStack(), f = 0, g = c.length; for (a && g--; g > f; f++) e = b.nameLookup(e, c[f], d); return a ? [b.aliasable("container.strict"), "(", e, ", ", b.quotedString(c[f]), ")"] : e } var g = c(1)["default"]; b.__esModule = !0; var h = c(4), i = c(6), j = g(i), k = c(5), l = c(29), m = g(l); e.prototype = { - nameLookup: function (a, b) { return e.isValidJavaScriptVariableName(b) ? [a, ".", b] : [a, "[", JSON.stringify(b), "]"] }, depthedLookup: function (a) { return [this.aliasable("container.lookup"), '(depths, "', a, '")'] }, compilerInfo: function () { var a = h.COMPILER_REVISION, b = h.REVISION_CHANGES[a]; return [a, b] }, appendToBuffer: function (a, b, c) { return k.isArray(a) || (a = [a]), a = this.source.wrap(a, b), this.environment.isSimple ? ["return ", a, ";"] : c ? ["buffer += ", a, ";"] : (a.appendToBuffer = !0, a) }, initializeBuffer: function () { return this.quotedString("") }, compile: function (a, b, c, d) { this.environment = a, this.options = b, this.stringParams = this.options.stringParams, this.trackIds = this.options.trackIds, this.precompile = !d, this.name = this.environment.name, this.isChild = !!c, this.context = c || { decorators: [], programs: [], environments: [] }, this.preamble(), this.stackSlot = 0, this.stackVars = [], this.aliases = {}, this.registers = { list: [] }, this.hashes = [], this.compileStack = [], this.inlineStack = [], this.blockParams = [], this.compileChildren(a, b), this.useDepths = this.useDepths || a.useDepths || a.useDecorators || this.options.compat, this.useBlockParams = this.useBlockParams || a.useBlockParams; var e = a.opcodes, f = void 0, g = void 0, h = void 0, i = void 0; for (h = 0, i = e.length; i > h; h++) f = e[h], this.source.currentLocation = f.loc, g = g || f.loc, this[f.opcode].apply(this, f.args); if (this.source.currentLocation = g, this.pushSource(""), this.stackSlot || this.inlineStack.length || this.compileStack.length) throw new j["default"]("Compile completed with content left on stack"); this.decorators.isEmpty() ? this.decorators = void 0 : (this.useDecorators = !0, this.decorators.prepend("var decorators = container.decorators;\n"), this.decorators.push("return fn;"), d ? this.decorators = Function.apply(this, ["fn", "props", "container", "depth0", "data", "blockParams", "depths", this.decorators.merge()]) : (this.decorators.prepend("function(fn, props, container, depth0, data, blockParams, depths) {\n"), this.decorators.push("}\n"), this.decorators = this.decorators.merge())); var k = this.createFunctionContext(d); if (this.isChild) return k; var l = { compiler: this.compilerInfo(), main: k }; this.decorators && (l.main_d = this.decorators, l.useDecorators = !0); var m = this.context, n = m.programs, o = m.decorators; for (h = 0, i = n.length; i > h; h++) n[h] && (l[h] = n[h], o[h] && (l[h + "_d"] = o[h], l.useDecorators = !0)); return this.environment.usePartial && (l.usePartial = !0), this.options.data && (l.useData = !0), this.useDepths && (l.useDepths = !0), this.useBlockParams && (l.useBlockParams = !0), this.options.compat && (l.compat = !0), d ? l.compilerOptions = this.options : (l.compiler = JSON.stringify(l.compiler), this.source.currentLocation = { start: { line: 1, column: 0 } }, l = this.objectLiteral(l), b.srcName ? (l = l.toStringWithSourceMap({ file: b.destName }), l.map = l.map && l.map.toString()) : l = l.toString()), l }, preamble: function () { this.lastContext = 0, this.source = new m["default"](this.options.srcName), this.decorators = new m["default"](this.options.srcName) }, createFunctionContext: function (a) { var b = "", c = this.stackVars.concat(this.registers.list); c.length > 0 && (b += ", " + c.join(", ")); var d = 0; for (var e in this.aliases) { var f = this.aliases[e]; this.aliases.hasOwnProperty(e) && f.children && f.referenceCount > 1 && (b += ", alias" + ++d + "=" + e, f.children[0] = "alias" + d) } var g = ["container", "depth0", "helpers", "partials", "data"]; (this.useBlockParams || this.useDepths) && g.push("blockParams"), this.useDepths && g.push("depths"); var h = this.mergeSource(b); return a ? (g.push(h), Function.apply(this, g)) : this.source.wrap(["function(", g.join(","), ") {\n ", h, "}"]) }, mergeSource: function (a) { var b = this.environment.isSimple, c = !this.forceBuffer, d = void 0, e = void 0, f = void 0, g = void 0; return this.source.each(function (a) { a.appendToBuffer ? (f ? a.prepend(" + ") : f = a, g = a) : (f && (e ? f.prepend("buffer += ") : d = !0, g.add(";"), f = g = void 0), e = !0, b || (c = !1)) }), c ? f ? (f.prepend("return "), g.add(";")) : e || this.source.push('return "";') : (a += ", buffer = " + (d ? "" : this.initializeBuffer()), f ? (f.prepend("return buffer + "), g.add(";")) : this.source.push("return buffer;")), a && this.source.prepend("var " + a.substring(2) + (d ? "" : ";\n")), this.source.merge() }, blockValue: function (a) { var b = this.aliasable("helpers.blockHelperMissing"), c = [this.contextName(0)]; this.setupHelperArgs(a, 0, c); var d = this.popStack(); c.splice(1, 0, d), this.push(this.source.functionCall(b, "call", c)) }, ambiguousBlockValue: function () { var a = this.aliasable("helpers.blockHelperMissing"), b = [this.contextName(0)]; this.setupHelperArgs("", 0, b, !0), this.flushInline(); var c = this.topStack(); b.splice(1, 0, c), this.pushSource(["if (!", this.lastHelper, ") { ", c, " = ", this.source.functionCall(a, "call", b), "}"]) }, appendContent: function (a) { this.pendingContent ? a = this.pendingContent + a : this.pendingLocation = this.source.currentLocation, this.pendingContent = a }, append: function () { if (this.isInline()) this.replaceStack(function (a) { return [" != null ? ", a, ' : ""'] }), this.pushSource(this.appendToBuffer(this.popStack())); else { var a = this.popStack(); this.pushSource(["if (", a, " != null) { ", this.appendToBuffer(a, void 0, !0), " }"]), this.environment.isSimple && this.pushSource(["else { ", this.appendToBuffer("''", void 0, !0), " }"]) } }, appendEscaped: function () { this.pushSource(this.appendToBuffer([this.aliasable("container.escapeExpression"), "(", this.popStack(), ")"])) }, getContext: function (a) { this.lastContext = a }, pushContext: function () { this.pushStackLiteral(this.contextName(this.lastContext)) }, lookupOnContext: function (a, b, c, d) { var e = 0; d || !this.options.compat || this.lastContext ? this.pushContext() : this.push(this.depthedLookup(a[e++])), this.resolvePath("context", a, e, b, c) }, lookupBlockParam: function (a, b) { this.useBlockParams = !0, this.push(["blockParams[", a[0], "][", a[1], "]"]), this.resolvePath("context", b, 1) }, lookupData: function (a, b, c) { a ? this.pushStackLiteral("container.data(data, " + a + ")") : this.pushStackLiteral("data"), this.resolvePath("data", b, 0, !0, c) }, resolvePath: function (a, b, c, d, e) { var g = this; if (this.options.strict || this.options.assumeObjects) return void this.push(f(this.options.strict && e, this, b, a)); for (var h = b.length; h > c; c++) this.replaceStack(function (e) { var f = g.nameLookup(e, b[c], a); return d ? [" && ", f] : [" != null ? ", f, " : ", e] }) }, resolvePossibleLambda: function () { this.push([this.aliasable("container.lambda"), "(", this.popStack(), ", ", this.contextName(0), ")"]) }, pushStringParam: function (a, b) { this.pushContext(), this.pushString(b), "SubExpression" !== b && ("string" == typeof a ? this.pushString(a) : this.pushStackLiteral(a)) }, emptyHash: function (a) { this.trackIds && this.push("{}"), this.stringParams && (this.push("{}"), this.push("{}")), this.pushStackLiteral(a ? "undefined" : "{}") }, pushHash: function () { this.hash && this.hashes.push(this.hash), this.hash = { values: [], types: [], contexts: [], ids: [] } }, popHash: function () { var a = this.hash; this.hash = this.hashes.pop(), this.trackIds && this.push(this.objectLiteral(a.ids)), this.stringParams && (this.push(this.objectLiteral(a.contexts)), this.push(this.objectLiteral(a.types))), this.push(this.objectLiteral(a.values)) }, pushString: function (a) { this.pushStackLiteral(this.quotedString(a)) }, pushLiteral: function (a) { this.pushStackLiteral(a) }, pushProgram: function (a) { null != a ? this.pushStackLiteral(this.programExpression(a)) : this.pushStackLiteral(null) }, registerDecorator: function (a, b) { var c = this.nameLookup("decorators", b, "decorator"), d = this.setupHelperArgs(b, a); this.decorators.push(["fn = ", this.decorators.functionCall(c, "", ["fn", "props", "container", d]), " || fn;"]) }, invokeHelper: function (a, b, c) { var d = this.popStack(), e = this.setupHelper(a, b), f = c ? [e.name, " || "] : "", g = ["("].concat(f, d); this.options.strict || g.push(" || ", this.aliasable("helpers.helperMissing")), g.push(")"), this.push(this.source.functionCall(g, "call", e.callParams)) }, invokeKnownHelper: function (a, b) { var c = this.setupHelper(a, b); this.push(this.source.functionCall(c.name, "call", c.callParams)) }, invokeAmbiguous: function (a, b) { this.useRegister("helper"); var c = this.popStack(); this.emptyHash(); var d = this.setupHelper(0, a, b), e = this.lastHelper = this.nameLookup("helpers", a, "helper"), f = ["(", "(helper = ", e, " || ", c, ")"]; this.options.strict || (f[0] = "(helper = ", f.push(" != null ? helper : ", this.aliasable("helpers.helperMissing"))), this.push(["(", f, d.paramsInit ? ["),(", d.paramsInit] : [], "),", "(typeof helper === ", this.aliasable('"function"'), " ? ", this.source.functionCall("helper", "call", d.callParams), " : helper))"]) }, invokePartial: function (a, b, c) { - var d = [], e = this.setupParams(b, 1, d); a && (b = this.popStack(), delete e.name), c && (e.indent = JSON.stringify(c)), e.helpers = "helpers", e.partials = "partials", e.decorators = "container.decorators", a ? d.unshift(b) : d.unshift(this.nameLookup("partials", b, "partial")), this.options.compat && (e.depths = "depths"), e = this.objectLiteral(e), - d.push(e), this.push(this.source.functionCall("container.invokePartial", "", d)) - }, assignToHash: function (a) { var b = this.popStack(), c = void 0, d = void 0, e = void 0; this.trackIds && (e = this.popStack()), this.stringParams && (d = this.popStack(), c = this.popStack()); var f = this.hash; c && (f.contexts[a] = c), d && (f.types[a] = d), e && (f.ids[a] = e), f.values[a] = b }, pushId: function (a, b, c) { "BlockParam" === a ? this.pushStackLiteral("blockParams[" + b[0] + "].path[" + b[1] + "]" + (c ? " + " + JSON.stringify("." + c) : "")) : "PathExpression" === a ? this.pushString(b) : "SubExpression" === a ? this.pushStackLiteral("true") : this.pushStackLiteral("null") }, compiler: e, compileChildren: function (a, b) { for (var c = a.children, d = void 0, e = void 0, f = 0, g = c.length; g > f; f++) { d = c[f], e = new this.compiler; var h = this.matchExistingProgram(d); null == h ? (this.context.programs.push(""), h = this.context.programs.length, d.index = h, d.name = "program" + h, this.context.programs[h] = e.compile(d, b, this.context, !this.precompile), this.context.decorators[h] = e.decorators, this.context.environments[h] = d, this.useDepths = this.useDepths || e.useDepths, this.useBlockParams = this.useBlockParams || e.useBlockParams) : (d.index = h, d.name = "program" + h, this.useDepths = this.useDepths || d.useDepths, this.useBlockParams = this.useBlockParams || d.useBlockParams) } }, matchExistingProgram: function (a) { for (var b = 0, c = this.context.environments.length; c > b; b++) { var d = this.context.environments[b]; if (d && d.equals(a)) return b } }, programExpression: function (a) { var b = this.environment.children[a], c = [b.index, "data", b.blockParams]; return (this.useBlockParams || this.useDepths) && c.push("blockParams"), this.useDepths && c.push("depths"), "container.program(" + c.join(", ") + ")" }, useRegister: function (a) { this.registers[a] || (this.registers[a] = !0, this.registers.list.push(a)) }, push: function (a) { return a instanceof d || (a = this.source.wrap(a)), this.inlineStack.push(a), a }, pushStackLiteral: function (a) { this.push(new d(a)) }, pushSource: function (a) { this.pendingContent && (this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation)), this.pendingContent = void 0), a && this.source.push(a) }, replaceStack: function (a) { var b = ["("], c = void 0, e = void 0, f = void 0; if (!this.isInline()) throw new j["default"]("replaceStack on non-inline"); var g = this.popStack(!0); if (g instanceof d) c = [g.value], b = ["(", c], f = !0; else { e = !0; var h = this.incrStack(); b = ["((", this.push(h), " = ", g, ")"], c = this.topStack() } var i = a.call(this, c); f || this.popStack(), e && this.stackSlot--, this.push(b.concat(i, ")")) }, incrStack: function () { return this.stackSlot++, this.stackSlot > this.stackVars.length && this.stackVars.push("stack" + this.stackSlot), this.topStackName() }, topStackName: function () { return "stack" + this.stackSlot }, flushInline: function () { var a = this.inlineStack; this.inlineStack = []; for (var b = 0, c = a.length; c > b; b++) { var e = a[b]; if (e instanceof d) this.compileStack.push(e); else { var f = this.incrStack(); this.pushSource([f, " = ", e, ";"]), this.compileStack.push(f) } } }, isInline: function () { return this.inlineStack.length }, popStack: function (a) { var b = this.isInline(), c = (b ? this.inlineStack : this.compileStack).pop(); if (!a && c instanceof d) return c.value; if (!b) { if (!this.stackSlot) throw new j["default"]("Invalid stack pop"); this.stackSlot-- } return c }, topStack: function () { var a = this.isInline() ? this.inlineStack : this.compileStack, b = a[a.length - 1]; return b instanceof d ? b.value : b }, contextName: function (a) { return this.useDepths && a ? "depths[" + a + "]" : "depth" + a }, quotedString: function (a) { return this.source.quotedString(a) }, objectLiteral: function (a) { return this.source.objectLiteral(a) }, aliasable: function (a) { var b = this.aliases[a]; return b ? (b.referenceCount++, b) : (b = this.aliases[a] = this.source.wrap(a), b.aliasable = !0, b.referenceCount = 1, b) }, setupHelper: function (a, b, c) { var d = [], e = this.setupHelperArgs(b, a, d, c), f = this.nameLookup("helpers", b, "helper"), g = this.aliasable(this.contextName(0) + " != null ? " + this.contextName(0) + " : {}"); return { params: d, paramsInit: e, name: f, callParams: [g].concat(d) } }, setupParams: function (a, b, c) { var d = {}, e = [], f = [], g = [], h = !c, i = void 0; h && (c = []), d.name = this.quotedString(a), d.hash = this.popStack(), this.trackIds && (d.hashIds = this.popStack()), this.stringParams && (d.hashTypes = this.popStack(), d.hashContexts = this.popStack()); var j = this.popStack(), k = this.popStack(); (k || j) && (d.fn = k || "container.noop", d.inverse = j || "container.noop"); for (var l = b; l--;) i = this.popStack(), c[l] = i, this.trackIds && (g[l] = this.popStack()), this.stringParams && (f[l] = this.popStack(), e[l] = this.popStack()); return h && (d.args = this.source.generateArray(c)), this.trackIds && (d.ids = this.source.generateArray(g)), this.stringParams && (d.types = this.source.generateArray(f), d.contexts = this.source.generateArray(e)), this.options.data && (d.data = "data"), this.useBlockParams && (d.blockParams = "blockParams"), d }, setupHelperArgs: function (a, b, c, d) { var e = this.setupParams(a, b, c); return e = this.objectLiteral(e), d ? (this.useRegister("options"), c.push("options"), ["options=", e]) : c ? (c.push(e), "") : e } - }, function () { for (var a = "break else new var case finally return void catch for switch while continue function this with default if throw delete in try do instanceof typeof abstract enum int short boolean export interface static byte extends long super char final native synchronized class float package throws const goto private transient debugger implements protected volatile double import public let yield await null true false".split(" "), b = e.RESERVED_WORDS = {}, c = 0, d = a.length; d > c; c++) b[a[c]] = !0 }(), e.isValidJavaScriptVariableName = function (a) { return !e.RESERVED_WORDS[a] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(a) }, b["default"] = e, a.exports = b["default"] - }, function (a, b, c) { "use strict"; function d(a, b, c) { if (f.isArray(a)) { for (var d = [], e = 0, g = a.length; g > e; e++) d.push(b.wrap(a[e], c)); return d } return "boolean" == typeof a || "number" == typeof a ? a + "" : a } function e(a) { this.srcFile = a, this.source = [] } b.__esModule = !0; var f = c(5), g = void 0; try { } catch (h) { } g || (g = function (a, b, c, d) { this.src = "", d && this.add(d) }, g.prototype = { add: function (a) { f.isArray(a) && (a = a.join("")), this.src += a }, prepend: function (a) { f.isArray(a) && (a = a.join("")), this.src = a + this.src }, toStringWithSourceMap: function () { return { code: this.toString() } }, toString: function () { return this.src } }), e.prototype = { isEmpty: function () { return !this.source.length }, prepend: function (a, b) { this.source.unshift(this.wrap(a, b)) }, push: function (a, b) { this.source.push(this.wrap(a, b)) }, merge: function () { var a = this.empty(); return this.each(function (b) { a.add([" ", b, "\n"]) }), a }, each: function (a) { for (var b = 0, c = this.source.length; c > b; b++) a(this.source[b]) }, empty: function () { var a = this.currentLocation || { start: {} }; return new g(a.start.line, a.start.column, this.srcFile) }, wrap: function (a) { var b = arguments.length <= 1 || void 0 === arguments[1] ? this.currentLocation || { start: {} } : arguments[1]; return a instanceof g ? a : (a = d(a, this, b), new g(b.start.line, b.start.column, this.srcFile, a)) }, functionCall: function (a, b, c) { return c = this.generateList(c), this.wrap([a, b ? "." + b + "(" : "(", c, ")"]) }, quotedString: function (a) { return '"' + (a + "").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029") + '"' }, objectLiteral: function (a) { var b = []; for (var c in a) if (a.hasOwnProperty(c)) { var e = d(a[c], this); "undefined" !== e && b.push([this.quotedString(c), ":", e]) } var f = this.generateList(b); return f.prepend("{"), f.add("}"), f }, generateList: function (a) { for (var b = this.empty(), c = 0, e = a.length; e > c; c++) c && b.add(","), b.add(d(a[c], this)); return b }, generateArray: function (a) { var b = this.generateList(a); return b.prepend("["), b.add("]"), b } }, b["default"] = e, a.exports = b["default"] }]) +/*! + + handlebars v4.0.5 + +Copyright (C) 2011-2015 by Yehuda Katz + +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. + +@license +*/ +!function (a, b) { "object" == typeof exports && "object" == typeof module ? module.exports = b() : "function" == typeof define && define.amd ? define([], b) : "object" == typeof exports ? exports.Handlebars = b() : a.Handlebars = b() }(this, function () { + return function (a) { function b(d) { if (c[d]) return c[d].exports; var e = c[d] = { exports: {}, id: d, loaded: !1 }; return a[d].call(e.exports, e, e.exports, b), e.loaded = !0, e.exports } var c = {}; return b.m = a, b.c = c, b.p = "", b(0) }([function (a, b, c) { "use strict"; function d() { var a = r(); return a.compile = function (b, c) { return k.compile(b, c, a) }, a.precompile = function (b, c) { return k.precompile(b, c, a) }, a.AST = i["default"], a.Compiler = k.Compiler, a.JavaScriptCompiler = m["default"], a.Parser = j.parser, a.parse = j.parse, a } var e = c(1)["default"]; b.__esModule = !0; var f = c(2), g = e(f), h = c(21), i = e(h), j = c(22), k = c(27), l = c(28), m = e(l), n = c(25), o = e(n), p = c(20), q = e(p), r = g["default"].create, s = d(); s.create = d, q["default"](s), s.Visitor = o["default"], s["default"] = s, b["default"] = s, a.exports = b["default"] }, function (a, b) { "use strict"; b["default"] = function (a) { return a && a.__esModule ? a : { "default": a } }, b.__esModule = !0 }, function (a, b, c) { "use strict"; function d() { var a = new h.HandlebarsEnvironment; return n.extend(a, h), a.SafeString = j["default"], a.Exception = l["default"], a.Utils = n, a.escapeExpression = n.escapeExpression, a.VM = p, a.template = function (b) { return p.template(b, a) }, a } var e = c(3)["default"], f = c(1)["default"]; b.__esModule = !0; var g = c(4), h = e(g), i = c(18), j = f(i), k = c(6), l = f(k), m = c(5), n = e(m), o = c(19), p = e(o), q = c(20), r = f(q), s = d(); s.create = d, r["default"](s), s["default"] = s, b["default"] = s, a.exports = b["default"] }, function (a, b) { "use strict"; b["default"] = function (a) { if (a && a.__esModule) return a; var b = {}; if (null != a) for (var c in a) Object.prototype.hasOwnProperty.call(a, c) && (b[c] = a[c]); return b["default"] = a, b }, b.__esModule = !0 }, function (a, b, c) { "use strict"; function d(a, b, c) { this.helpers = a || {}, this.partials = b || {}, this.decorators = c || {}, i.registerDefaultHelpers(this), j.registerDefaultDecorators(this) } var e = c(1)["default"]; b.__esModule = !0, b.HandlebarsEnvironment = d; var f = c(5), g = c(6), h = e(g), i = c(7), j = c(15), k = c(17), l = e(k), m = "4.0.5"; b.VERSION = m; var n = 7; b.COMPILER_REVISION = n; var o = { 1: "<= 1.0.rc.2", 2: "== 1.0.0-rc.3", 3: "== 1.0.0-rc.4", 4: "== 1.x.x", 5: "== 2.0.0-alpha.x", 6: ">= 2.0.0-beta.1", 7: ">= 4.0.0" }; b.REVISION_CHANGES = o; var p = "[object Object]"; d.prototype = { constructor: d, logger: l["default"], log: l["default"].log, registerHelper: function (a, b) { if (f.toString.call(a) === p) { if (b) throw new h["default"]("Arg not supported with multiple helpers"); f.extend(this.helpers, a) } else this.helpers[a] = b }, unregisterHelper: function (a) { delete this.helpers[a] }, registerPartial: function (a, b) { if (f.toString.call(a) === p) f.extend(this.partials, a); else { if ("undefined" == typeof b) throw new h["default"]('Attempting to register a partial called "' + a + '" as undefined'); this.partials[a] = b } }, unregisterPartial: function (a) { delete this.partials[a] }, registerDecorator: function (a, b) { if (f.toString.call(a) === p) { if (b) throw new h["default"]("Arg not supported with multiple decorators"); f.extend(this.decorators, a) } else this.decorators[a] = b }, unregisterDecorator: function (a) { delete this.decorators[a] } }; var q = l["default"].log; b.log = q, b.createFrame = f.createFrame, b.logger = l["default"] }, function (a, b) { "use strict"; function c(a) { return k[a] } function d(a) { for (var b = 1; b < arguments.length; b++) for (var c in arguments[b]) Object.prototype.hasOwnProperty.call(arguments[b], c) && (a[c] = arguments[b][c]); return a } function e(a, b) { for (var c = 0, d = a.length; d > c; c++) if (a[c] === b) return c; return -1 } function f(a) { if ("string" != typeof a) { if (a && a.toHTML) return a.toHTML(); if (null == a) return ""; if (!a) return a + ""; a = "" + a } return m.test(a) ? a.replace(l, c) : a } function g(a) { return a || 0 === a ? p(a) && 0 === a.length ? !0 : !1 : !0 } function h(a) { var b = d({}, a); return b._parent = a, b } function i(a, b) { return a.path = b, a } function j(a, b) { return (a ? a + "." : "") + b } b.__esModule = !0, b.extend = d, b.indexOf = e, b.escapeExpression = f, b.isEmpty = g, b.createFrame = h, b.blockParams = i, b.appendContextPath = j; var k = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "`": "`", "=": "=" }, l = /[&<>"'`=]/g, m = /[&<>"'`=]/, n = Object.prototype.toString; b.toString = n; var o = function (a) { return "function" == typeof a }; o(/x/) && (b.isFunction = o = function (a) { return "function" == typeof a && "[object Function]" === n.call(a) }), b.isFunction = o; var p = Array.isArray || function (a) { return a && "object" == typeof a ? "[object Array]" === n.call(a) : !1 }; b.isArray = p }, function (a, b) { "use strict"; function c(a, b) { var e = b && b.loc, f = void 0, g = void 0; e && (f = e.start.line, g = e.start.column, a += " - " + f + ":" + g); for (var h = Error.prototype.constructor.call(this, a), i = 0; i < d.length; i++) this[d[i]] = h[d[i]]; Error.captureStackTrace && Error.captureStackTrace(this, c), e && (this.lineNumber = f, this.column = g) } b.__esModule = !0; var d = ["description", "fileName", "lineNumber", "message", "name", "number", "stack"]; c.prototype = new Error, b["default"] = c, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a) { g["default"](a), i["default"](a), k["default"](a), m["default"](a), o["default"](a), q["default"](a), s["default"](a) } var e = c(1)["default"]; b.__esModule = !0, b.registerDefaultHelpers = d; var f = c(8), g = e(f), h = c(9), i = e(h), j = c(10), k = e(j), l = c(11), m = e(l), n = c(12), o = e(n), p = c(13), q = e(p), r = c(14), s = e(r) }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerHelper("blockHelperMissing", function (b, c) { var e = c.inverse, f = c.fn; if (b === !0) return f(this); if (b === !1 || null == b) return e(this); if (d.isArray(b)) return b.length > 0 ? (c.ids && (c.ids = [c.name]), a.helpers.each(b, c)) : e(this); if (c.data && c.ids) { var g = d.createFrame(c.data); g.contextPath = d.appendContextPath(c.data.contextPath, c.name), c = { data: g } } return f(b, c) }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; var d = c(1)["default"]; b.__esModule = !0; var e = c(5), f = c(6), g = d(f); b["default"] = function (a) { a.registerHelper("each", function (a, b) { function c(b, c, f) { j && (j.key = b, j.index = c, j.first = 0 === c, j.last = !!f, k && (j.contextPath = k + b)), i += d(a[b], { data: j, blockParams: e.blockParams([a[b], b], [k + b, null]) }) } if (!b) throw new g["default"]("Must pass iterator to #each"); var d = b.fn, f = b.inverse, h = 0, i = "", j = void 0, k = void 0; if (b.data && b.ids && (k = e.appendContextPath(b.data.contextPath, b.ids[0]) + "."), e.isFunction(a) && (a = a.call(this)), b.data && (j = e.createFrame(b.data)), a && "object" == typeof a) if (e.isArray(a)) for (var l = a.length; l > h; h++) h in a && c(h, h, h === a.length - 1); else { var m = void 0; for (var n in a) a.hasOwnProperty(n) && (void 0 !== m && c(m, h - 1), m = n, h++); void 0 !== m && c(m, h - 1, !0) } return 0 === h && (i = f(this)), i }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; var d = c(1)["default"]; b.__esModule = !0; var e = c(6), f = d(e); b["default"] = function (a) { a.registerHelper("helperMissing", function () { if (1 !== arguments.length) throw new f["default"]('Missing helper: "' + arguments[arguments.length - 1].name + '"') }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerHelper("if", function (a, b) { return d.isFunction(a) && (a = a.call(this)), !b.hash.includeZero && !a || d.isEmpty(a) ? b.inverse(this) : b.fn(this) }), a.registerHelper("unless", function (b, c) { return a.helpers["if"].call(this, b, { fn: c.inverse, inverse: c.fn, hash: c.hash }) }) }, a.exports = b["default"] }, function (a, b) { "use strict"; b.__esModule = !0, b["default"] = function (a) { a.registerHelper("log", function () { for (var b = [void 0], c = arguments[arguments.length - 1], d = 0; d < arguments.length - 1; d++) b.push(arguments[d]); var e = 1; null != c.hash.level ? e = c.hash.level : c.data && null != c.data.level && (e = c.data.level), b[0] = e, a.log.apply(a, b) }) }, a.exports = b["default"] }, function (a, b) { "use strict"; b.__esModule = !0, b["default"] = function (a) { a.registerHelper("lookup", function (a, b) { return a && a[b] }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerHelper("with", function (a, b) { d.isFunction(a) && (a = a.call(this)); var c = b.fn; if (d.isEmpty(a)) return b.inverse(this); var e = b.data; return b.data && b.ids && (e = d.createFrame(b.data), e.contextPath = d.appendContextPath(b.data.contextPath, b.ids[0])), c(a, { data: e, blockParams: d.blockParams([a], [e && e.contextPath]) }) }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a) { g["default"](a) } var e = c(1)["default"]; b.__esModule = !0, b.registerDefaultDecorators = d; var f = c(16), g = e(f) }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5); b["default"] = function (a) { a.registerDecorator("inline", function (a, b, c, e) { var f = a; return b.partials || (b.partials = {}, f = function (e, f) { var g = c.partials; c.partials = d.extend({}, g, b.partials); var h = a(e, f); return c.partials = g, h }), b.partials[e.args[0]] = e.fn, f }) }, a.exports = b["default"] }, function (a, b, c) { "use strict"; b.__esModule = !0; var d = c(5), e = { methodMap: ["debug", "info", "warn", "error"], level: "info", lookupLevel: function (a) { if ("string" == typeof a) { var b = d.indexOf(e.methodMap, a.toLowerCase()); a = b >= 0 ? b : parseInt(a, 10) } return a }, log: function (a) { if (a = e.lookupLevel(a), "undefined" != typeof console && e.lookupLevel(e.level) <= a) { var b = e.methodMap[a]; console[b] || (b = "log"); for (var c = arguments.length, d = Array(c > 1 ? c - 1 : 0), f = 1; c > f; f++) d[f - 1] = arguments[f]; console[b].apply(console, d) } } }; b["default"] = e, a.exports = b["default"] }, function (a, b) { "use strict"; function c(a) { this.string = a } b.__esModule = !0, c.prototype.toString = c.prototype.toHTML = function () { return "" + this.string }, b["default"] = c, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a) { var b = a && a[0] || 1, c = r.COMPILER_REVISION; if (b !== c) { if (c > b) { var d = r.REVISION_CHANGES[c], e = r.REVISION_CHANGES[b]; throw new q["default"]("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version (" + d + ") or downgrade your runtime to an older version (" + e + ").") } throw new q["default"]("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version (" + a[1] + ").") } } function e(a, b) { function c(c, d, e) { e.hash && (d = o.extend({}, d, e.hash), e.ids && (e.ids[0] = !0)), c = b.VM.resolvePartial.call(this, c, d, e); var f = b.VM.invokePartial.call(this, c, d, e); if (null == f && b.compile && (e.partials[e.name] = b.compile(c, a.compilerOptions, b), f = e.partials[e.name](d, e)), null != f) { if (e.indent) { for (var g = f.split("\n"), h = 0, i = g.length; i > h && (g[h] || h + 1 !== i) ; h++) g[h] = e.indent + g[h]; f = g.join("\n") } return f } throw new q["default"]("The partial " + e.name + " could not be compiled when running in runtime-only mode") } function d(b) { function c(b) { return "" + a.main(e, b, e.helpers, e.partials, g, i, h) } var f = arguments.length <= 1 || void 0 === arguments[1] ? {} : arguments[1], g = f.data; d._setup(f), !f.partial && a.useData && (g = j(b, g)); var h = void 0, i = a.useBlockParams ? [] : void 0; return a.useDepths && (h = f.depths ? b !== f.depths[0] ? [b].concat(f.depths) : f.depths : [b]), (c = k(a.main, c, e, f.depths || [], g, i))(b, f) } if (!b) throw new q["default"]("No environment passed to template"); if (!a || !a.main) throw new q["default"]("Unknown template object: " + typeof a); a.main.decorator = a.main_d, b.VM.checkRevision(a.compiler); var e = { strict: function (a, b) { if (!(b in a)) throw new q["default"]('"' + b + '" not defined in ' + a); return a[b] }, lookup: function (a, b) { for (var c = a.length, d = 0; c > d; d++) if (a[d] && null != a[d][b]) return a[d][b] }, lambda: function (a, b) { return "function" == typeof a ? a.call(b) : a }, escapeExpression: o.escapeExpression, invokePartial: c, fn: function (b) { var c = a[b]; return c.decorator = a[b + "_d"], c }, programs: [], program: function (a, b, c, d, e) { var g = this.programs[a], h = this.fn(a); return b || e || d || c ? g = f(this, a, h, b, c, d, e) : g || (g = this.programs[a] = f(this, a, h)), g }, data: function (a, b) { for (; a && b--;) a = a._parent; return a }, merge: function (a, b) { var c = a || b; return a && b && a !== b && (c = o.extend({}, b, a)), c }, noop: b.VM.noop, compilerInfo: a.compiler }; return d.isTop = !0, d._setup = function (c) { c.partial ? (e.helpers = c.helpers, e.partials = c.partials, e.decorators = c.decorators) : (e.helpers = e.merge(c.helpers, b.helpers), a.usePartial && (e.partials = e.merge(c.partials, b.partials)), (a.usePartial || a.useDecorators) && (e.decorators = e.merge(c.decorators, b.decorators))) }, d._child = function (b, c, d, g) { if (a.useBlockParams && !d) throw new q["default"]("must pass block params"); if (a.useDepths && !g) throw new q["default"]("must pass parent depths"); return f(e, b, a[b], c, 0, d, g) }, d } function f(a, b, c, d, e, f, g) { function h(b) { var e = arguments.length <= 1 || void 0 === arguments[1] ? {} : arguments[1], h = g; return g && b !== g[0] && (h = [b].concat(g)), c(a, b, a.helpers, a.partials, e.data || d, f && [e.blockParams].concat(f), h) } return h = k(c, h, a, g, d, f), h.program = b, h.depth = g ? g.length : 0, h.blockParams = e || 0, h } function g(a, b, c) { return a ? a.call || c.name || (c.name = a, a = c.partials[a]) : a = "@partial-block" === c.name ? c.data["partial-block"] : c.partials[c.name], a } function h(a, b, c) { c.partial = !0, c.ids && (c.data.contextPath = c.ids[0] || c.data.contextPath); var d = void 0; if (c.fn && c.fn !== i && (c.data = r.createFrame(c.data), d = c.data["partial-block"] = c.fn, d.partials && (c.partials = o.extend({}, c.partials, d.partials))), void 0 === a && d && (a = d), void 0 === a) throw new q["default"]("The partial " + c.name + " could not be found"); return a instanceof Function ? a(b, c) : void 0 } function i() { return "" } function j(a, b) { return b && "root" in b || (b = b ? r.createFrame(b) : {}, b.root = a), b } function k(a, b, c, d, e, f) { if (a.decorator) { var g = {}; b = a.decorator(b, g, c, d && d[0], e, f, d), o.extend(b, g) } return b } var l = c(3)["default"], m = c(1)["default"]; b.__esModule = !0, b.checkRevision = d, b.template = e, b.wrapProgram = f, b.resolvePartial = g, b.invokePartial = h, b.noop = i; var n = c(5), o = l(n), p = c(6), q = m(p), r = c(4) }, function (a, b) { (function (c) { "use strict"; b.__esModule = !0, b["default"] = function (a) { var b = "undefined" != typeof c ? c : window, d = b.Handlebars; a.noConflict = function () { return b.Handlebars === a && (b.Handlebars = d), a } }, a.exports = b["default"] }).call(b, function () { return this }()) }, function (a, b) { "use strict"; b.__esModule = !0; var c = { helpers: { helperExpression: function (a) { return "SubExpression" === a.type || ("MustacheStatement" === a.type || "BlockStatement" === a.type) && !!(a.params && a.params.length || a.hash) }, scopedId: function (a) { return /^\.|this\b/.test(a.original) }, simpleId: function (a) { return 1 === a.parts.length && !c.helpers.scopedId(a) && !a.depth } } }; b["default"] = c, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a, b) { if ("Program" === a.type) return a; h["default"].yy = n, n.locInfo = function (a) { return new n.SourceLocation(b && b.srcName, a) }; var c = new j["default"](b); return c.accept(h["default"].parse(a)) } var e = c(1)["default"], f = c(3)["default"]; b.__esModule = !0, b.parse = d; var g = c(23), h = e(g), i = c(24), j = e(i), k = c(26), l = f(k), m = c(5); b.parser = h["default"]; var n = {}; m.extend(n, l) }, function (a, b) { + "use strict"; var c = function () { + function a() { this.yy = {} } var b = { + trace: function () { }, yy: {}, symbols_: { error: 2, root: 3, program: 4, EOF: 5, program_repetition0: 6, statement: 7, mustache: 8, block: 9, rawBlock: 10, partial: 11, partialBlock: 12, content: 13, COMMENT: 14, CONTENT: 15, openRawBlock: 16, rawBlock_repetition_plus0: 17, END_RAW_BLOCK: 18, OPEN_RAW_BLOCK: 19, helperName: 20, openRawBlock_repetition0: 21, openRawBlock_option0: 22, CLOSE_RAW_BLOCK: 23, openBlock: 24, block_option0: 25, closeBlock: 26, openInverse: 27, block_option1: 28, OPEN_BLOCK: 29, openBlock_repetition0: 30, openBlock_option0: 31, openBlock_option1: 32, CLOSE: 33, OPEN_INVERSE: 34, openInverse_repetition0: 35, openInverse_option0: 36, openInverse_option1: 37, openInverseChain: 38, OPEN_INVERSE_CHAIN: 39, openInverseChain_repetition0: 40, openInverseChain_option0: 41, openInverseChain_option1: 42, inverseAndProgram: 43, INVERSE: 44, inverseChain: 45, inverseChain_option0: 46, OPEN_ENDBLOCK: 47, OPEN: 48, mustache_repetition0: 49, mustache_option0: 50, OPEN_UNESCAPED: 51, mustache_repetition1: 52, mustache_option1: 53, CLOSE_UNESCAPED: 54, OPEN_PARTIAL: 55, partialName: 56, partial_repetition0: 57, partial_option0: 58, openPartialBlock: 59, OPEN_PARTIAL_BLOCK: 60, openPartialBlock_repetition0: 61, openPartialBlock_option0: 62, param: 63, sexpr: 64, OPEN_SEXPR: 65, sexpr_repetition0: 66, sexpr_option0: 67, CLOSE_SEXPR: 68, hash: 69, hash_repetition_plus0: 70, hashSegment: 71, ID: 72, EQUALS: 73, blockParams: 74, OPEN_BLOCK_PARAMS: 75, blockParams_repetition_plus0: 76, CLOSE_BLOCK_PARAMS: 77, path: 78, dataName: 79, STRING: 80, NUMBER: 81, BOOLEAN: 82, UNDEFINED: 83, NULL: 84, DATA: 85, pathSegments: 86, SEP: 87, $accept: 0, $end: 1 }, terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" }, productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]], performAction: function (a, b, c, d, e, f, g) { var h = f.length - 1; switch (e) { case 1: return f[h - 1]; case 2: this.$ = d.prepareProgram(f[h]); break; case 3: this.$ = f[h]; break; case 4: this.$ = f[h]; break; case 5: this.$ = f[h]; break; case 6: this.$ = f[h]; break; case 7: this.$ = f[h]; break; case 8: this.$ = f[h]; break; case 9: this.$ = { type: "CommentStatement", value: d.stripComment(f[h]), strip: d.stripFlags(f[h], f[h]), loc: d.locInfo(this._$) }; break; case 10: this.$ = { type: "ContentStatement", original: f[h], value: f[h], loc: d.locInfo(this._$) }; break; case 11: this.$ = d.prepareRawBlock(f[h - 2], f[h - 1], f[h], this._$); break; case 12: this.$ = { path: f[h - 3], params: f[h - 2], hash: f[h - 1] }; break; case 13: this.$ = d.prepareBlock(f[h - 3], f[h - 2], f[h - 1], f[h], !1, this._$); break; case 14: this.$ = d.prepareBlock(f[h - 3], f[h - 2], f[h - 1], f[h], !0, this._$); break; case 15: this.$ = { open: f[h - 5], path: f[h - 4], params: f[h - 3], hash: f[h - 2], blockParams: f[h - 1], strip: d.stripFlags(f[h - 5], f[h]) }; break; case 16: this.$ = { path: f[h - 4], params: f[h - 3], hash: f[h - 2], blockParams: f[h - 1], strip: d.stripFlags(f[h - 5], f[h]) }; break; case 17: this.$ = { path: f[h - 4], params: f[h - 3], hash: f[h - 2], blockParams: f[h - 1], strip: d.stripFlags(f[h - 5], f[h]) }; break; case 18: this.$ = { strip: d.stripFlags(f[h - 1], f[h - 1]), program: f[h] }; break; case 19: var i = d.prepareBlock(f[h - 2], f[h - 1], f[h], f[h], !1, this._$), j = d.prepareProgram([i], f[h - 1].loc); j.chained = !0, this.$ = { strip: f[h - 2].strip, program: j, chain: !0 }; break; case 20: this.$ = f[h]; break; case 21: this.$ = { path: f[h - 1], strip: d.stripFlags(f[h - 2], f[h]) }; break; case 22: this.$ = d.prepareMustache(f[h - 3], f[h - 2], f[h - 1], f[h - 4], d.stripFlags(f[h - 4], f[h]), this._$); break; case 23: this.$ = d.prepareMustache(f[h - 3], f[h - 2], f[h - 1], f[h - 4], d.stripFlags(f[h - 4], f[h]), this._$); break; case 24: this.$ = { type: "PartialStatement", name: f[h - 3], params: f[h - 2], hash: f[h - 1], indent: "", strip: d.stripFlags(f[h - 4], f[h]), loc: d.locInfo(this._$) }; break; case 25: this.$ = d.preparePartialBlock(f[h - 2], f[h - 1], f[h], this._$); break; case 26: this.$ = { path: f[h - 3], params: f[h - 2], hash: f[h - 1], strip: d.stripFlags(f[h - 4], f[h]) }; break; case 27: this.$ = f[h]; break; case 28: this.$ = f[h]; break; case 29: this.$ = { type: "SubExpression", path: f[h - 3], params: f[h - 2], hash: f[h - 1], loc: d.locInfo(this._$) }; break; case 30: this.$ = { type: "Hash", pairs: f[h], loc: d.locInfo(this._$) }; break; case 31: this.$ = { type: "HashPair", key: d.id(f[h - 2]), value: f[h], loc: d.locInfo(this._$) }; break; case 32: this.$ = d.id(f[h - 1]); break; case 33: this.$ = f[h]; break; case 34: this.$ = f[h]; break; case 35: this.$ = { type: "StringLiteral", value: f[h], original: f[h], loc: d.locInfo(this._$) }; break; case 36: this.$ = { type: "NumberLiteral", value: Number(f[h]), original: Number(f[h]), loc: d.locInfo(this._$) }; break; case 37: this.$ = { type: "BooleanLiteral", value: "true" === f[h], original: "true" === f[h], loc: d.locInfo(this._$) }; break; case 38: this.$ = { type: "UndefinedLiteral", original: void 0, value: void 0, loc: d.locInfo(this._$) }; break; case 39: this.$ = { type: "NullLiteral", original: null, value: null, loc: d.locInfo(this._$) }; break; case 40: this.$ = f[h]; break; case 41: this.$ = f[h]; break; case 42: this.$ = d.preparePath(!0, f[h], this._$); break; case 43: this.$ = d.preparePath(!1, f[h], this._$); break; case 44: f[h - 2].push({ part: d.id(f[h]), original: f[h], separator: f[h - 1] }), this.$ = f[h - 2]; break; case 45: this.$ = [{ part: d.id(f[h]), original: f[h] }]; break; case 46: this.$ = []; break; case 47: f[h - 1].push(f[h]); break; case 48: this.$ = [f[h]]; break; case 49: f[h - 1].push(f[h]); break; case 50: this.$ = []; break; case 51: f[h - 1].push(f[h]); break; case 58: this.$ = []; break; case 59: f[h - 1].push(f[h]); break; case 64: this.$ = []; break; case 65: f[h - 1].push(f[h]); break; case 70: this.$ = []; break; case 71: f[h - 1].push(f[h]); break; case 78: this.$ = []; break; case 79: f[h - 1].push(f[h]); break; case 82: this.$ = []; break; case 83: f[h - 1].push(f[h]); break; case 86: this.$ = []; break; case 87: f[h - 1].push(f[h]); break; case 90: this.$ = []; break; case 91: f[h - 1].push(f[h]); break; case 94: this.$ = []; break; case 95: f[h - 1].push(f[h]); break; case 98: this.$ = [f[h]]; break; case 99: f[h - 1].push(f[h]); break; case 100: this.$ = [f[h]]; break; case 101: f[h - 1].push(f[h]) } }, table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 13: 40, 15: [1, 20], 17: 39 }, { 20: 42, 56: 41, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 45, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 48, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 42, 56: 49, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 50, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 51] }, { 72: [1, 35], 86: 52 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 53, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 54, 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 55, 47: [2, 54] }, { 28: 60, 43: 61, 44: [1, 59], 47: [2, 56] }, { 13: 63, 15: [1, 20], 18: [1, 62] }, { 15: [2, 48], 18: [2, 48] }, { 33: [2, 86], 57: 64, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 65, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 66, 47: [1, 67] }, { 30: 68, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 69, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 70, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 71, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 75, 33: [2, 80], 50: 72, 63: 73, 64: 76, 65: [1, 44], 69: 74, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 80] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 51] }, { 20: 75, 53: 81, 54: [2, 84], 63: 82, 64: 76, 65: [1, 44], 69: 83, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 84, 47: [1, 67] }, { 47: [2, 55] }, { 4: 85, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 86, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 87, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 88, 47: [1, 67] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 75, 33: [2, 88], 58: 89, 63: 90, 64: 76, 65: [1, 44], 69: 91, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 92, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 93, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 31: 94, 33: [2, 60], 63: 95, 64: 76, 65: [1, 44], 69: 96, 70: 77, 71: 78, 72: [1, 79], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 66], 36: 97, 63: 98, 64: 76, 65: [1, 44], 69: 99, 70: 77, 71: 78, 72: [1, 79], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 22: 100, 23: [2, 52], 63: 101, 64: 76, 65: [1, 44], 69: 102, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 92], 62: 103, 63: 104, 64: 76, 65: [1, 44], 69: 105, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 106] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 107, 72: [1, 108], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 109], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 110] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 112, 46: 111, 47: [2, 76] }, { 33: [2, 70], 40: 113, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 114] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 75, 63: 116, 64: 76, 65: [1, 44], 67: 115, 68: [2, 96], 69: 117, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 118] }, { 32: 119, 33: [2, 62], 74: 120, 75: [1, 121] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 122, 74: 123, 75: [1, 121] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 124] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 125] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 109] }, { 20: 75, 63: 126, 64: 76, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 75, 33: [2, 72], 41: 127, 63: 128, 64: 76, 65: [1, 44], 69: 129, 70: 77, 71: 78, 72: [1, 79], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 130] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 131] }, { 33: [2, 63] }, { 72: [1, 133], 76: 132 }, { 33: [1, 134] }, { 33: [2, 69] }, { 15: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 135, 74: 136, 75: [1, 121] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 138], 77: [1, 137] }, { 72: [2, 100], 77: [2, 100] }, { + 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], + 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] + }, { 33: [1, 139] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }], defaultActions: { 4: [2, 1], 55: [2, 55], 57: [2, 20], 61: [2, 57], 74: [2, 81], 83: [2, 85], 87: [2, 18], 91: [2, 89], 102: [2, 53], 105: [2, 93], 111: [2, 19], 112: [2, 77], 117: [2, 97], 120: [2, 63], 123: [2, 69], 124: [2, 12], 136: [2, 75], 137: [2, 32] }, parseError: function (a, b) { throw new Error(a) }, parse: function (a) { function b() { var a; return a = c.lexer.lex() || 1, "number" != typeof a && (a = c.symbols_[a] || a), a } var c = this, d = [0], e = [null], f = [], g = this.table, h = "", i = 0, j = 0, k = 0; this.lexer.setInput(a), this.lexer.yy = this.yy, this.yy.lexer = this.lexer, this.yy.parser = this, "undefined" == typeof this.lexer.yylloc && (this.lexer.yylloc = {}); var l = this.lexer.yylloc; f.push(l); var m = this.lexer.options && this.lexer.options.ranges; "function" == typeof this.yy.parseError && (this.parseError = this.yy.parseError); for (var n, o, p, q, r, s, t, u, v, w = {}; ;) { if (p = d[d.length - 1], this.defaultActions[p] ? q = this.defaultActions[p] : ((null === n || "undefined" == typeof n) && (n = b()), q = g[p] && g[p][n]), "undefined" == typeof q || !q.length || !q[0]) { var x = ""; if (!k) { v = []; for (s in g[p]) this.terminals_[s] && s > 2 && v.push("'" + this.terminals_[s] + "'"); x = this.lexer.showPosition ? "Parse error on line " + (i + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + v.join(", ") + ", got '" + (this.terminals_[n] || n) + "'" : "Parse error on line " + (i + 1) + ": Unexpected " + (1 == n ? "end of input" : "'" + (this.terminals_[n] || n) + "'"), this.parseError(x, { text: this.lexer.match, token: this.terminals_[n] || n, line: this.lexer.yylineno, loc: l, expected: v }) } } if (q[0] instanceof Array && q.length > 1) throw new Error("Parse Error: multiple actions possible at state: " + p + ", token: " + n); switch (q[0]) { case 1: d.push(n), e.push(this.lexer.yytext), f.push(this.lexer.yylloc), d.push(q[1]), n = null, o ? (n = o, o = null) : (j = this.lexer.yyleng, h = this.lexer.yytext, i = this.lexer.yylineno, l = this.lexer.yylloc, k > 0 && k--); break; case 2: if (t = this.productions_[q[1]][1], w.$ = e[e.length - t], w._$ = { first_line: f[f.length - (t || 1)].first_line, last_line: f[f.length - 1].last_line, first_column: f[f.length - (t || 1)].first_column, last_column: f[f.length - 1].last_column }, m && (w._$.range = [f[f.length - (t || 1)].range[0], f[f.length - 1].range[1]]), r = this.performAction.call(w, h, j, i, this.yy, q[1], e, f), "undefined" != typeof r) return r; t && (d = d.slice(0, -1 * t * 2), e = e.slice(0, -1 * t), f = f.slice(0, -1 * t)), d.push(this.productions_[q[1]][0]), e.push(w.$), f.push(w._$), u = g[d[d.length - 2]][d[d.length - 1]], d.push(u); break; case 3: return !0 } } return !0 } + }, c = function () { var a = { EOF: 1, parseError: function (a, b) { if (!this.yy.parser) throw new Error(a); this.yy.parser.parseError(a, b) }, setInput: function (a) { return this._input = a, this._more = this._less = this.done = !1, this.yylineno = this.yyleng = 0, this.yytext = this.matched = this.match = "", this.conditionStack = ["INITIAL"], this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 }, this.options.ranges && (this.yylloc.range = [0, 0]), this.offset = 0, this }, input: function () { var a = this._input[0]; this.yytext += a, this.yyleng++, this.offset++, this.match += a, this.matched += a; var b = a.match(/(?:\r\n?|\n).*/g); return b ? (this.yylineno++, this.yylloc.last_line++) : this.yylloc.last_column++, this.options.ranges && this.yylloc.range[1]++, this._input = this._input.slice(1), a }, unput: function (a) { var b = a.length, c = a.split(/(?:\r\n?|\n)/g); this._input = a + this._input, this.yytext = this.yytext.substr(0, this.yytext.length - b - 1), this.offset -= b; var d = this.match.split(/(?:\r\n?|\n)/g); this.match = this.match.substr(0, this.match.length - 1), this.matched = this.matched.substr(0, this.matched.length - 1), c.length - 1 && (this.yylineno -= c.length - 1); var e = this.yylloc.range; return this.yylloc = { first_line: this.yylloc.first_line, last_line: this.yylineno + 1, first_column: this.yylloc.first_column, last_column: c ? (c.length === d.length ? this.yylloc.first_column : 0) + d[d.length - c.length].length - c[0].length : this.yylloc.first_column - b }, this.options.ranges && (this.yylloc.range = [e[0], e[0] + this.yyleng - b]), this }, more: function () { return this._more = !0, this }, less: function (a) { this.unput(this.match.slice(a)) }, pastInput: function () { var a = this.matched.substr(0, this.matched.length - this.match.length); return (a.length > 20 ? "..." : "") + a.substr(-20).replace(/\n/g, "") }, upcomingInput: function () { var a = this.match; return a.length < 20 && (a += this._input.substr(0, 20 - a.length)), (a.substr(0, 20) + (a.length > 20 ? "..." : "")).replace(/\n/g, "") }, showPosition: function () { var a = this.pastInput(), b = new Array(a.length + 1).join("-"); return a + this.upcomingInput() + "\n" + b + "^" }, next: function () { if (this.done) return this.EOF; this._input || (this.done = !0); var a, b, c, d, e; this._more || (this.yytext = "", this.match = ""); for (var f = this._currentRules(), g = 0; g < f.length && (c = this._input.match(this.rules[f[g]]), !c || b && !(c[0].length > b[0].length) || (b = c, d = g, this.options.flex)) ; g++); return b ? (e = b[0].match(/(?:\r\n?|\n).*/g), e && (this.yylineno += e.length), this.yylloc = { first_line: this.yylloc.last_line, last_line: this.yylineno + 1, first_column: this.yylloc.last_column, last_column: e ? e[e.length - 1].length - e[e.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + b[0].length }, this.yytext += b[0], this.match += b[0], this.matches = b, this.yyleng = this.yytext.length, this.options.ranges && (this.yylloc.range = [this.offset, this.offset += this.yyleng]), this._more = !1, this._input = this._input.slice(b[0].length), this.matched += b[0], a = this.performAction.call(this, this.yy, this, f[d], this.conditionStack[this.conditionStack.length - 1]), this.done && this._input && (this.done = !1), a ? a : void 0) : "" === this._input ? this.EOF : this.parseError("Lexical error on line " + (this.yylineno + 1) + ". Unrecognized text.\n" + this.showPosition(), { text: "", token: null, line: this.yylineno }) }, lex: function () { var a = this.next(); return "undefined" != typeof a ? a : this.lex() }, begin: function (a) { this.conditionStack.push(a) }, popState: function () { return this.conditionStack.pop() }, _currentRules: function () { return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules }, topState: function () { return this.conditionStack[this.conditionStack.length - 2] }, pushState: function (a) { this.begin(a) } }; return a.options = {}, a.performAction = function (a, b, c, d) { function e(a, c) { return b.yytext = b.yytext.substr(a, b.yyleng - c) } switch (c) { case 0: if ("\\\\" === b.yytext.slice(-2) ? (e(0, 1), this.begin("mu")) : "\\" === b.yytext.slice(-1) ? (e(0, 1), this.begin("emu")) : this.begin("mu"), b.yytext) return 15; break; case 1: return 15; case 2: return this.popState(), 15; case 3: return this.begin("raw"), 15; case 4: return this.popState(), "raw" === this.conditionStack[this.conditionStack.length - 1] ? 15 : (b.yytext = b.yytext.substr(5, b.yyleng - 9), "END_RAW_BLOCK"); case 5: return 15; case 6: return this.popState(), 14; case 7: return 65; case 8: return 68; case 9: return 19; case 10: return this.popState(), this.begin("raw"), 23; case 11: return 55; case 12: return 60; case 13: return 29; case 14: return 47; case 15: return this.popState(), 44; case 16: return this.popState(), 44; case 17: return 34; case 18: return 39; case 19: return 51; case 20: return 48; case 21: this.unput(b.yytext), this.popState(), this.begin("com"); break; case 22: return this.popState(), 14; case 23: return 48; case 24: return 73; case 25: return 72; case 26: return 72; case 27: return 87; case 28: break; case 29: return this.popState(), 54; case 30: return this.popState(), 33; case 31: return b.yytext = e(1, 2).replace(/\\"/g, '"'), 80; case 32: return b.yytext = e(1, 2).replace(/\\'/g, "'"), 80; case 33: return 85; case 34: return 82; case 35: return 82; case 36: return 83; case 37: return 84; case 38: return 81; case 39: return 75; case 40: return 77; case 41: return 72; case 42: return b.yytext = b.yytext.replace(/\\([\\\]])/g, "$1"), 72; case 43: return "INVALID"; case 44: return 5 } }, a.rules = [/^(?:[^\x00]*?(?=(\{\{)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/, /^(?:\{\{\{\{(?=[^\/]))/, /^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/, /^(?:[^\x00]*?(?=(\{\{\{\{)))/, /^(?:[\s\S]*?--(~)?\}\})/, /^(?:\()/, /^(?:\))/, /^(?:\{\{\{\{)/, /^(?:\}\}\}\})/, /^(?:\{\{(~)?>)/, /^(?:\{\{(~)?#>)/, /^(?:\{\{(~)?#\*?)/, /^(?:\{\{(~)?\/)/, /^(?:\{\{(~)?\^\s*(~)?\}\})/, /^(?:\{\{(~)?\s*else\s*(~)?\}\})/, /^(?:\{\{(~)?\^)/, /^(?:\{\{(~)?\s*else\b)/, /^(?:\{\{(~)?\{)/, /^(?:\{\{(~)?&)/, /^(?:\{\{(~)?!--)/, /^(?:\{\{(~)?![\s\S]*?\}\})/, /^(?:\{\{(~)?\*?)/, /^(?:=)/, /^(?:\.\.)/, /^(?:\.(?=([=~}\s\/.)|])))/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:\}(~)?\}\})/, /^(?:(~)?\}\})/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\s)])))/, /^(?:false(?=([~}\s)])))/, /^(?:undefined(?=([~}\s)])))/, /^(?:null(?=([~}\s)])))/, /^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/, /^(?:as\s+\|)/, /^(?:\|)/, /^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/, /^(?:\[(\\\]|[^\]])*\])/, /^(?:.)/, /^(?:$)/], a.conditions = { mu: { rules: [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], inclusive: !1 }, emu: { rules: [2], inclusive: !1 }, com: { rules: [6], inclusive: !1 }, raw: { rules: [3, 4, 5], inclusive: !1 }, INITIAL: { rules: [0, 1, 44], inclusive: !0 } }, a }(); return b.lexer = c, a.prototype = b, b.Parser = a, new a + }(); b.__esModule = !0, b["default"] = c + }, function (a, b, c) { "use strict"; function d() { var a = arguments.length <= 0 || void 0 === arguments[0] ? {} : arguments[0]; this.options = a } function e(a, b, c) { void 0 === b && (b = a.length); var d = a[b - 1], e = a[b - 2]; return d ? "ContentStatement" === d.type ? (e || !c ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(d.original) : void 0 : c } function f(a, b, c) { void 0 === b && (b = -1); var d = a[b + 1], e = a[b + 2]; return d ? "ContentStatement" === d.type ? (e || !c ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(d.original) : void 0 : c } function g(a, b, c) { var d = a[null == b ? 0 : b + 1]; if (d && "ContentStatement" === d.type && (c || !d.rightStripped)) { var e = d.value; d.value = d.value.replace(c ? /^\s+/ : /^[ \t]*\r?\n?/, ""), d.rightStripped = d.value !== e } } function h(a, b, c) { var d = a[null == b ? a.length - 1 : b - 1]; if (d && "ContentStatement" === d.type && (c || !d.leftStripped)) { var e = d.value; return d.value = d.value.replace(c ? /\s+$/ : /[ \t]+$/, ""), d.leftStripped = d.value !== e, d.leftStripped } } var i = c(1)["default"]; b.__esModule = !0; var j = c(25), k = i(j); d.prototype = new k["default"], d.prototype.Program = function (a) { var b = !this.options.ignoreStandalone, c = !this.isRootSeen; this.isRootSeen = !0; for (var d = a.body, i = 0, j = d.length; j > i; i++) { var k = d[i], l = this.accept(k); if (l) { var m = e(d, i, c), n = f(d, i, c), o = l.openStandalone && m, p = l.closeStandalone && n, q = l.inlineStandalone && m && n; l.close && g(d, i, !0), l.open && h(d, i, !0), b && q && (g(d, i), h(d, i) && "PartialStatement" === k.type && (k.indent = /([ \t]+$)/.exec(d[i - 1].original)[1])), b && o && (g((k.program || k.inverse).body), h(d, i)), b && p && (g(d, i), h((k.inverse || k.program).body)) } } return a }, d.prototype.BlockStatement = d.prototype.DecoratorBlock = d.prototype.PartialBlockStatement = function (a) { this.accept(a.program), this.accept(a.inverse); var b = a.program || a.inverse, c = a.program && a.inverse, d = c, i = c; if (c && c.chained) for (d = c.body[0].program; i.chained;) i = i.body[i.body.length - 1].program; var j = { open: a.openStrip.open, close: a.closeStrip.close, openStandalone: f(b.body), closeStandalone: e((d || b).body) }; if (a.openStrip.close && g(b.body, null, !0), c) { var k = a.inverseStrip; k.open && h(b.body, null, !0), k.close && g(d.body, null, !0), a.closeStrip.open && h(i.body, null, !0), !this.options.ignoreStandalone && e(b.body) && f(d.body) && (h(b.body), g(d.body)) } else a.closeStrip.open && h(b.body, null, !0); return j }, d.prototype.Decorator = d.prototype.MustacheStatement = function (a) { return a.strip }, d.prototype.PartialStatement = d.prototype.CommentStatement = function (a) { var b = a.strip || {}; return { inlineStandalone: !0, open: b.open, close: b.close } }, b["default"] = d, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d() { this.parents = [] } function e(a) { this.acceptRequired(a, "path"), this.acceptArray(a.params), this.acceptKey(a, "hash") } function f(a) { e.call(this, a), this.acceptKey(a, "program"), this.acceptKey(a, "inverse") } function g(a) { this.acceptRequired(a, "name"), this.acceptArray(a.params), this.acceptKey(a, "hash") } var h = c(1)["default"]; b.__esModule = !0; var i = c(6), j = h(i); d.prototype = { constructor: d, mutating: !1, acceptKey: function (a, b) { var c = this.accept(a[b]); if (this.mutating) { if (c && !d.prototype[c.type]) throw new j["default"]('Unexpected node type "' + c.type + '" found when accepting ' + b + " on " + a.type); a[b] = c } }, acceptRequired: function (a, b) { if (this.acceptKey(a, b), !a[b]) throw new j["default"](a.type + " requires " + b) }, acceptArray: function (a) { for (var b = 0, c = a.length; c > b; b++) this.acceptKey(a, b), a[b] || (a.splice(b, 1), b--, c--) }, accept: function (a) { if (a) { if (!this[a.type]) throw new j["default"]("Unknown type: " + a.type, a); this.current && this.parents.unshift(this.current), this.current = a; var b = this[a.type](a); return this.current = this.parents.shift(), !this.mutating || b ? b : b !== !1 ? a : void 0 } }, Program: function (a) { this.acceptArray(a.body) }, MustacheStatement: e, Decorator: e, BlockStatement: f, DecoratorBlock: f, PartialStatement: g, PartialBlockStatement: function (a) { g.call(this, a), this.acceptKey(a, "program") }, ContentStatement: function () { }, CommentStatement: function () { }, SubExpression: e, PathExpression: function () { }, StringLiteral: function () { }, NumberLiteral: function () { }, BooleanLiteral: function () { }, UndefinedLiteral: function () { }, NullLiteral: function () { }, Hash: function (a) { this.acceptArray(a.pairs) }, HashPair: function (a) { this.acceptRequired(a, "value") } }, b["default"] = d, a.exports = b["default"] }, function (a, b, c) { "use strict"; function d(a, b) { if (b = b.path ? b.path.original : b, a.path.original !== b) { var c = { loc: a.path.loc }; throw new q["default"](a.path.original + " doesn't match " + b, c) } } function e(a, b) { this.source = a, this.start = { line: b.first_line, column: b.first_column }, this.end = { line: b.last_line, column: b.last_column } } function f(a) { return /^\[.*\]$/.test(a) ? a.substr(1, a.length - 2) : a } function g(a, b) { return { open: "~" === a.charAt(2), close: "~" === b.charAt(b.length - 3) } } function h(a) { return a.replace(/^\{\{~?\!-?-?/, "").replace(/-?-?~?\}\}$/, "") } function i(a, b, c) { c = this.locInfo(c); for (var d = a ? "@" : "", e = [], f = 0, g = "", h = 0, i = b.length; i > h; h++) { var j = b[h].part, k = b[h].original !== j; if (d += (b[h].separator || "") + j, k || ".." !== j && "." !== j && "this" !== j) e.push(j); else { if (e.length > 0) throw new q["default"]("Invalid path: " + d, { loc: c }); ".." === j && (f++, g += "../") } } return { type: "PathExpression", data: a, depth: f, parts: e, original: d, loc: c } } function j(a, b, c, d, e, f) { var g = d.charAt(3) || d.charAt(2), h = "{" !== g && "&" !== g, i = /\*/.test(d); return { type: i ? "Decorator" : "MustacheStatement", path: a, params: b, hash: c, escaped: h, strip: e, loc: this.locInfo(f) } } function k(a, b, c, e) { d(a, c), e = this.locInfo(e); var f = { type: "Program", body: b, strip: {}, loc: e }; return { type: "BlockStatement", path: a.path, params: a.params, hash: a.hash, program: f, openStrip: {}, inverseStrip: {}, closeStrip: {}, loc: e } } function l(a, b, c, e, f, g) { e && e.path && d(a, e); var h = /\*/.test(a.open); b.blockParams = a.blockParams; var i = void 0, j = void 0; if (c) { if (h) throw new q["default"]("Unexpected inverse block on decorator", c); c.chain && (c.program.body[0].closeStrip = e.strip), j = c.strip, i = c.program } return f && (f = i, i = b, b = f), { type: h ? "DecoratorBlock" : "BlockStatement", path: a.path, params: a.params, hash: a.hash, program: b, inverse: i, openStrip: a.strip, inverseStrip: j, closeStrip: e && e.strip, loc: this.locInfo(g) } } function m(a, b) { if (!b && a.length) { var c = a[0].loc, d = a[a.length - 1].loc; c && d && (b = { source: c.source, start: { line: c.start.line, column: c.start.column }, end: { line: d.end.line, column: d.end.column } }) } return { type: "Program", body: a, strip: {}, loc: b } } function n(a, b, c, e) { return d(a, c), { type: "PartialBlockStatement", name: a.path, params: a.params, hash: a.hash, program: b, openStrip: a.strip, closeStrip: c && c.strip, loc: this.locInfo(e) } } var o = c(1)["default"]; b.__esModule = !0, b.SourceLocation = e, b.id = f, b.stripFlags = g, b.stripComment = h, b.preparePath = i, b.prepareMustache = j, b.prepareRawBlock = k, b.prepareBlock = l, b.prepareProgram = m, b.preparePartialBlock = n; var p = c(6), q = o(p) }, function (a, b, c) { "use strict"; function d() { } function e(a, b, c) { if (null == a || "string" != typeof a && "Program" !== a.type) throw new k["default"]("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + a); b = b || {}, "data" in b || (b.data = !0), b.compat && (b.useDepths = !0); var d = c.parse(a, b), e = (new c.Compiler).compile(d, b); return (new c.JavaScriptCompiler).compile(e, b) } function f(a, b, c) { function d() { var d = c.parse(a, b), e = (new c.Compiler).compile(d, b), f = (new c.JavaScriptCompiler).compile(e, b, void 0, !0); return c.template(f) } function e(a, b) { return f || (f = d()), f.call(this, a, b) } if (void 0 === b && (b = {}), null == a || "string" != typeof a && "Program" !== a.type) throw new k["default"]("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + a); "data" in b || (b.data = !0), b.compat && (b.useDepths = !0); var f = void 0; return e._setup = function (a) { return f || (f = d()), f._setup(a) }, e._child = function (a, b, c, e) { return f || (f = d()), f._child(a, b, c, e) }, e } function g(a, b) { if (a === b) return !0; if (l.isArray(a) && l.isArray(b) && a.length === b.length) { for (var c = 0; c < a.length; c++) if (!g(a[c], b[c])) return !1; return !0 } } function h(a) { if (!a.path.parts) { var b = a.path; a.path = { type: "PathExpression", data: !1, depth: 0, parts: [b.original + ""], original: b.original + "", loc: b.loc } } } var i = c(1)["default"]; b.__esModule = !0, b.Compiler = d, b.precompile = e, b.compile = f; var j = c(6), k = i(j), l = c(5), m = c(21), n = i(m), o = [].slice; d.prototype = { compiler: d, equals: function (a) { var b = this.opcodes.length; if (a.opcodes.length !== b) return !1; for (var c = 0; b > c; c++) { var d = this.opcodes[c], e = a.opcodes[c]; if (d.opcode !== e.opcode || !g(d.args, e.args)) return !1 } b = this.children.length; for (var c = 0; b > c; c++) if (!this.children[c].equals(a.children[c])) return !1; return !0 }, guid: 0, compile: function (a, b) { this.sourceNode = [], this.opcodes = [], this.children = [], this.options = b, this.stringParams = b.stringParams, this.trackIds = b.trackIds, b.blockParams = b.blockParams || []; var c = b.knownHelpers; if (b.knownHelpers = { helperMissing: !0, blockHelperMissing: !0, each: !0, "if": !0, unless: !0, "with": !0, log: !0, lookup: !0 }, c) for (var d in c) d in c && (b.knownHelpers[d] = c[d]); return this.accept(a) }, compileProgram: function (a) { var b = new this.compiler, c = b.compile(a, this.options), d = this.guid++; return this.usePartial = this.usePartial || c.usePartial, this.children[d] = c, this.useDepths = this.useDepths || c.useDepths, d }, accept: function (a) { if (!this[a.type]) throw new k["default"]("Unknown type: " + a.type, a); this.sourceNode.unshift(a); var b = this[a.type](a); return this.sourceNode.shift(), b }, Program: function (a) { this.options.blockParams.unshift(a.blockParams); for (var b = a.body, c = b.length, d = 0; c > d; d++) this.accept(b[d]); return this.options.blockParams.shift(), this.isSimple = 1 === c, this.blockParams = a.blockParams ? a.blockParams.length : 0, this }, BlockStatement: function (a) { h(a); var b = a.program, c = a.inverse; b = b && this.compileProgram(b), c = c && this.compileProgram(c); var d = this.classifySexpr(a); "helper" === d ? this.helperSexpr(a, b, c) : "simple" === d ? (this.simpleSexpr(a), this.opcode("pushProgram", b), this.opcode("pushProgram", c), this.opcode("emptyHash"), this.opcode("blockValue", a.path.original)) : (this.ambiguousSexpr(a, b, c), this.opcode("pushProgram", b), this.opcode("pushProgram", c), this.opcode("emptyHash"), this.opcode("ambiguousBlockValue")), this.opcode("append") }, DecoratorBlock: function (a) { var b = a.program && this.compileProgram(a.program), c = this.setupFullMustacheParams(a, b, void 0), d = a.path; this.useDecorators = !0, this.opcode("registerDecorator", c.length, d.original) }, PartialStatement: function (a) { this.usePartial = !0; var b = a.program; b && (b = this.compileProgram(a.program)); var c = a.params; if (c.length > 1) throw new k["default"]("Unsupported number of partial arguments: " + c.length, a); c.length || (this.options.explicitPartialContext ? this.opcode("pushLiteral", "undefined") : c.push({ type: "PathExpression", parts: [], depth: 0 })); var d = a.name.original, e = "SubExpression" === a.name.type; e && this.accept(a.name), this.setupFullMustacheParams(a, b, void 0, !0); var f = a.indent || ""; this.options.preventIndent && f && (this.opcode("appendContent", f), f = ""), this.opcode("invokePartial", e, d, f), this.opcode("append") }, PartialBlockStatement: function (a) { this.PartialStatement(a) }, MustacheStatement: function (a) { this.SubExpression(a), a.escaped && !this.options.noEscape ? this.opcode("appendEscaped") : this.opcode("append") }, Decorator: function (a) { this.DecoratorBlock(a) }, ContentStatement: function (a) { a.value && this.opcode("appendContent", a.value) }, CommentStatement: function () { }, SubExpression: function (a) { h(a); var b = this.classifySexpr(a); "simple" === b ? this.simpleSexpr(a) : "helper" === b ? this.helperSexpr(a) : this.ambiguousSexpr(a) }, ambiguousSexpr: function (a, b, c) { var d = a.path, e = d.parts[0], f = null != b || null != c; this.opcode("getContext", d.depth), this.opcode("pushProgram", b), this.opcode("pushProgram", c), d.strict = !0, this.accept(d), this.opcode("invokeAmbiguous", e, f) }, simpleSexpr: function (a) { var b = a.path; b.strict = !0, this.accept(b), this.opcode("resolvePossibleLambda") }, helperSexpr: function (a, b, c) { var d = this.setupFullMustacheParams(a, b, c), e = a.path, f = e.parts[0]; if (this.options.knownHelpers[f]) this.opcode("invokeKnownHelper", d.length, f); else { if (this.options.knownHelpersOnly) throw new k["default"]("You specified knownHelpersOnly, but used the unknown helper " + f, a); e.strict = !0, e.falsy = !0, this.accept(e), this.opcode("invokeHelper", d.length, e.original, n["default"].helpers.simpleId(e)) } }, PathExpression: function (a) { this.addDepth(a.depth), this.opcode("getContext", a.depth); var b = a.parts[0], c = n["default"].helpers.scopedId(a), d = !a.depth && !c && this.blockParamIndex(b); d ? this.opcode("lookupBlockParam", d, a.parts) : b ? a.data ? (this.options.data = !0, this.opcode("lookupData", a.depth, a.parts, a.strict)) : this.opcode("lookupOnContext", a.parts, a.falsy, a.strict, c) : this.opcode("pushContext") }, StringLiteral: function (a) { this.opcode("pushString", a.value) }, NumberLiteral: function (a) { this.opcode("pushLiteral", a.value) }, BooleanLiteral: function (a) { this.opcode("pushLiteral", a.value) }, UndefinedLiteral: function () { this.opcode("pushLiteral", "undefined") }, NullLiteral: function () { this.opcode("pushLiteral", "null") }, Hash: function (a) { var b = a.pairs, c = 0, d = b.length; for (this.opcode("pushHash") ; d > c; c++) this.pushParam(b[c].value); for (; c--;) this.opcode("assignToHash", b[c].key); this.opcode("popHash") }, opcode: function (a) { this.opcodes.push({ opcode: a, args: o.call(arguments, 1), loc: this.sourceNode[0].loc }) }, addDepth: function (a) { a && (this.useDepths = !0) }, classifySexpr: function (a) { var b = n["default"].helpers.simpleId(a.path), c = b && !!this.blockParamIndex(a.path.parts[0]), d = !c && n["default"].helpers.helperExpression(a), e = !c && (d || b); if (e && !d) { var f = a.path.parts[0], g = this.options; g.knownHelpers[f] ? d = !0 : g.knownHelpersOnly && (e = !1) } return d ? "helper" : e ? "ambiguous" : "simple" }, pushParams: function (a) { for (var b = 0, c = a.length; c > b; b++) this.pushParam(a[b]) }, pushParam: function (a) { var b = null != a.value ? a.value : a.original || ""; if (this.stringParams) b.replace && (b = b.replace(/^(\.?\.\/)*/g, "").replace(/\//g, ".")), a.depth && this.addDepth(a.depth), this.opcode("getContext", a.depth || 0), this.opcode("pushStringParam", b, a.type), "SubExpression" === a.type && this.accept(a); else { if (this.trackIds) { var c = void 0; if (!a.parts || n["default"].helpers.scopedId(a) || a.depth || (c = this.blockParamIndex(a.parts[0])), c) { var d = a.parts.slice(1).join("."); this.opcode("pushId", "BlockParam", c, d) } else b = a.original || b, b.replace && (b = b.replace(/^this(?:\.|$)/, "").replace(/^\.\//, "").replace(/^\.$/, "")), this.opcode("pushId", a.type, b) } this.accept(a) } }, setupFullMustacheParams: function (a, b, c, d) { var e = a.params; return this.pushParams(e), this.opcode("pushProgram", b), this.opcode("pushProgram", c), a.hash ? this.accept(a.hash) : this.opcode("emptyHash", d), e }, blockParamIndex: function (a) { for (var b = 0, c = this.options.blockParams.length; c > b; b++) { var d = this.options.blockParams[b], e = d && l.indexOf(d, a); if (d && e >= 0) return [b, e] } } } }, function (a, b, c) { + "use strict"; function d(a) { this.value = a } function e() { } function f(a, b, c, d) { var e = b.popStack(), f = 0, g = c.length; for (a && g--; g > f; f++) e = b.nameLookup(e, c[f], d); return a ? [b.aliasable("container.strict"), "(", e, ", ", b.quotedString(c[f]), ")"] : e } var g = c(1)["default"]; b.__esModule = !0; var h = c(4), i = c(6), j = g(i), k = c(5), l = c(29), m = g(l); e.prototype = { + nameLookup: function (a, b) { return e.isValidJavaScriptVariableName(b) ? [a, ".", b] : [a, "[", JSON.stringify(b), "]"] }, depthedLookup: function (a) { return [this.aliasable("container.lookup"), '(depths, "', a, '")'] }, compilerInfo: function () { var a = h.COMPILER_REVISION, b = h.REVISION_CHANGES[a]; return [a, b] }, appendToBuffer: function (a, b, c) { return k.isArray(a) || (a = [a]), a = this.source.wrap(a, b), this.environment.isSimple ? ["return ", a, ";"] : c ? ["buffer += ", a, ";"] : (a.appendToBuffer = !0, a) }, initializeBuffer: function () { return this.quotedString("") }, compile: function (a, b, c, d) { this.environment = a, this.options = b, this.stringParams = this.options.stringParams, this.trackIds = this.options.trackIds, this.precompile = !d, this.name = this.environment.name, this.isChild = !!c, this.context = c || { decorators: [], programs: [], environments: [] }, this.preamble(), this.stackSlot = 0, this.stackVars = [], this.aliases = {}, this.registers = { list: [] }, this.hashes = [], this.compileStack = [], this.inlineStack = [], this.blockParams = [], this.compileChildren(a, b), this.useDepths = this.useDepths || a.useDepths || a.useDecorators || this.options.compat, this.useBlockParams = this.useBlockParams || a.useBlockParams; var e = a.opcodes, f = void 0, g = void 0, h = void 0, i = void 0; for (h = 0, i = e.length; i > h; h++) f = e[h], this.source.currentLocation = f.loc, g = g || f.loc, this[f.opcode].apply(this, f.args); if (this.source.currentLocation = g, this.pushSource(""), this.stackSlot || this.inlineStack.length || this.compileStack.length) throw new j["default"]("Compile completed with content left on stack"); this.decorators.isEmpty() ? this.decorators = void 0 : (this.useDecorators = !0, this.decorators.prepend("var decorators = container.decorators;\n"), this.decorators.push("return fn;"), d ? this.decorators = Function.apply(this, ["fn", "props", "container", "depth0", "data", "blockParams", "depths", this.decorators.merge()]) : (this.decorators.prepend("function(fn, props, container, depth0, data, blockParams, depths) {\n"), this.decorators.push("}\n"), this.decorators = this.decorators.merge())); var k = this.createFunctionContext(d); if (this.isChild) return k; var l = { compiler: this.compilerInfo(), main: k }; this.decorators && (l.main_d = this.decorators, l.useDecorators = !0); var m = this.context, n = m.programs, o = m.decorators; for (h = 0, i = n.length; i > h; h++) n[h] && (l[h] = n[h], o[h] && (l[h + "_d"] = o[h], l.useDecorators = !0)); return this.environment.usePartial && (l.usePartial = !0), this.options.data && (l.useData = !0), this.useDepths && (l.useDepths = !0), this.useBlockParams && (l.useBlockParams = !0), this.options.compat && (l.compat = !0), d ? l.compilerOptions = this.options : (l.compiler = JSON.stringify(l.compiler), this.source.currentLocation = { start: { line: 1, column: 0 } }, l = this.objectLiteral(l), b.srcName ? (l = l.toStringWithSourceMap({ file: b.destName }), l.map = l.map && l.map.toString()) : l = l.toString()), l }, preamble: function () { this.lastContext = 0, this.source = new m["default"](this.options.srcName), this.decorators = new m["default"](this.options.srcName) }, createFunctionContext: function (a) { var b = "", c = this.stackVars.concat(this.registers.list); c.length > 0 && (b += ", " + c.join(", ")); var d = 0; for (var e in this.aliases) { var f = this.aliases[e]; this.aliases.hasOwnProperty(e) && f.children && f.referenceCount > 1 && (b += ", alias" + ++d + "=" + e, f.children[0] = "alias" + d) } var g = ["container", "depth0", "helpers", "partials", "data"]; (this.useBlockParams || this.useDepths) && g.push("blockParams"), this.useDepths && g.push("depths"); var h = this.mergeSource(b); return a ? (g.push(h), Function.apply(this, g)) : this.source.wrap(["function(", g.join(","), ") {\n ", h, "}"]) }, mergeSource: function (a) { var b = this.environment.isSimple, c = !this.forceBuffer, d = void 0, e = void 0, f = void 0, g = void 0; return this.source.each(function (a) { a.appendToBuffer ? (f ? a.prepend(" + ") : f = a, g = a) : (f && (e ? f.prepend("buffer += ") : d = !0, g.add(";"), f = g = void 0), e = !0, b || (c = !1)) }), c ? f ? (f.prepend("return "), g.add(";")) : e || this.source.push('return "";') : (a += ", buffer = " + (d ? "" : this.initializeBuffer()), f ? (f.prepend("return buffer + "), g.add(";")) : this.source.push("return buffer;")), a && this.source.prepend("var " + a.substring(2) + (d ? "" : ";\n")), this.source.merge() }, blockValue: function (a) { var b = this.aliasable("helpers.blockHelperMissing"), c = [this.contextName(0)]; this.setupHelperArgs(a, 0, c); var d = this.popStack(); c.splice(1, 0, d), this.push(this.source.functionCall(b, "call", c)) }, ambiguousBlockValue: function () { var a = this.aliasable("helpers.blockHelperMissing"), b = [this.contextName(0)]; this.setupHelperArgs("", 0, b, !0), this.flushInline(); var c = this.topStack(); b.splice(1, 0, c), this.pushSource(["if (!", this.lastHelper, ") { ", c, " = ", this.source.functionCall(a, "call", b), "}"]) }, appendContent: function (a) { this.pendingContent ? a = this.pendingContent + a : this.pendingLocation = this.source.currentLocation, this.pendingContent = a }, append: function () { if (this.isInline()) this.replaceStack(function (a) { return [" != null ? ", a, ' : ""'] }), this.pushSource(this.appendToBuffer(this.popStack())); else { var a = this.popStack(); this.pushSource(["if (", a, " != null) { ", this.appendToBuffer(a, void 0, !0), " }"]), this.environment.isSimple && this.pushSource(["else { ", this.appendToBuffer("''", void 0, !0), " }"]) } }, appendEscaped: function () { this.pushSource(this.appendToBuffer([this.aliasable("container.escapeExpression"), "(", this.popStack(), ")"])) }, getContext: function (a) { this.lastContext = a }, pushContext: function () { this.pushStackLiteral(this.contextName(this.lastContext)) }, lookupOnContext: function (a, b, c, d) { var e = 0; d || !this.options.compat || this.lastContext ? this.pushContext() : this.push(this.depthedLookup(a[e++])), this.resolvePath("context", a, e, b, c) }, lookupBlockParam: function (a, b) { this.useBlockParams = !0, this.push(["blockParams[", a[0], "][", a[1], "]"]), this.resolvePath("context", b, 1) }, lookupData: function (a, b, c) { a ? this.pushStackLiteral("container.data(data, " + a + ")") : this.pushStackLiteral("data"), this.resolvePath("data", b, 0, !0, c) }, resolvePath: function (a, b, c, d, e) { var g = this; if (this.options.strict || this.options.assumeObjects) return void this.push(f(this.options.strict && e, this, b, a)); for (var h = b.length; h > c; c++) this.replaceStack(function (e) { var f = g.nameLookup(e, b[c], a); return d ? [" && ", f] : [" != null ? ", f, " : ", e] }) }, resolvePossibleLambda: function () { this.push([this.aliasable("container.lambda"), "(", this.popStack(), ", ", this.contextName(0), ")"]) }, pushStringParam: function (a, b) { this.pushContext(), this.pushString(b), "SubExpression" !== b && ("string" == typeof a ? this.pushString(a) : this.pushStackLiteral(a)) }, emptyHash: function (a) { this.trackIds && this.push("{}"), this.stringParams && (this.push("{}"), this.push("{}")), this.pushStackLiteral(a ? "undefined" : "{}") }, pushHash: function () { this.hash && this.hashes.push(this.hash), this.hash = { values: [], types: [], contexts: [], ids: [] } }, popHash: function () { var a = this.hash; this.hash = this.hashes.pop(), this.trackIds && this.push(this.objectLiteral(a.ids)), this.stringParams && (this.push(this.objectLiteral(a.contexts)), this.push(this.objectLiteral(a.types))), this.push(this.objectLiteral(a.values)) }, pushString: function (a) { this.pushStackLiteral(this.quotedString(a)) }, pushLiteral: function (a) { this.pushStackLiteral(a) }, pushProgram: function (a) { null != a ? this.pushStackLiteral(this.programExpression(a)) : this.pushStackLiteral(null) }, registerDecorator: function (a, b) { var c = this.nameLookup("decorators", b, "decorator"), d = this.setupHelperArgs(b, a); this.decorators.push(["fn = ", this.decorators.functionCall(c, "", ["fn", "props", "container", d]), " || fn;"]) }, invokeHelper: function (a, b, c) { var d = this.popStack(), e = this.setupHelper(a, b), f = c ? [e.name, " || "] : "", g = ["("].concat(f, d); this.options.strict || g.push(" || ", this.aliasable("helpers.helperMissing")), g.push(")"), this.push(this.source.functionCall(g, "call", e.callParams)) }, invokeKnownHelper: function (a, b) { var c = this.setupHelper(a, b); this.push(this.source.functionCall(c.name, "call", c.callParams)) }, invokeAmbiguous: function (a, b) { this.useRegister("helper"); var c = this.popStack(); this.emptyHash(); var d = this.setupHelper(0, a, b), e = this.lastHelper = this.nameLookup("helpers", a, "helper"), f = ["(", "(helper = ", e, " || ", c, ")"]; this.options.strict || (f[0] = "(helper = ", f.push(" != null ? helper : ", this.aliasable("helpers.helperMissing"))), this.push(["(", f, d.paramsInit ? ["),(", d.paramsInit] : [], "),", "(typeof helper === ", this.aliasable('"function"'), " ? ", this.source.functionCall("helper", "call", d.callParams), " : helper))"]) }, invokePartial: function (a, b, c) { + var d = [], e = this.setupParams(b, 1, d); a && (b = this.popStack(), delete e.name), c && (e.indent = JSON.stringify(c)), e.helpers = "helpers", e.partials = "partials", e.decorators = "container.decorators", a ? d.unshift(b) : d.unshift(this.nameLookup("partials", b, "partial")), this.options.compat && (e.depths = "depths"), e = this.objectLiteral(e), + d.push(e), this.push(this.source.functionCall("container.invokePartial", "", d)) + }, assignToHash: function (a) { var b = this.popStack(), c = void 0, d = void 0, e = void 0; this.trackIds && (e = this.popStack()), this.stringParams && (d = this.popStack(), c = this.popStack()); var f = this.hash; c && (f.contexts[a] = c), d && (f.types[a] = d), e && (f.ids[a] = e), f.values[a] = b }, pushId: function (a, b, c) { "BlockParam" === a ? this.pushStackLiteral("blockParams[" + b[0] + "].path[" + b[1] + "]" + (c ? " + " + JSON.stringify("." + c) : "")) : "PathExpression" === a ? this.pushString(b) : "SubExpression" === a ? this.pushStackLiteral("true") : this.pushStackLiteral("null") }, compiler: e, compileChildren: function (a, b) { for (var c = a.children, d = void 0, e = void 0, f = 0, g = c.length; g > f; f++) { d = c[f], e = new this.compiler; var h = this.matchExistingProgram(d); null == h ? (this.context.programs.push(""), h = this.context.programs.length, d.index = h, d.name = "program" + h, this.context.programs[h] = e.compile(d, b, this.context, !this.precompile), this.context.decorators[h] = e.decorators, this.context.environments[h] = d, this.useDepths = this.useDepths || e.useDepths, this.useBlockParams = this.useBlockParams || e.useBlockParams) : (d.index = h, d.name = "program" + h, this.useDepths = this.useDepths || d.useDepths, this.useBlockParams = this.useBlockParams || d.useBlockParams) } }, matchExistingProgram: function (a) { for (var b = 0, c = this.context.environments.length; c > b; b++) { var d = this.context.environments[b]; if (d && d.equals(a)) return b } }, programExpression: function (a) { var b = this.environment.children[a], c = [b.index, "data", b.blockParams]; return (this.useBlockParams || this.useDepths) && c.push("blockParams"), this.useDepths && c.push("depths"), "container.program(" + c.join(", ") + ")" }, useRegister: function (a) { this.registers[a] || (this.registers[a] = !0, this.registers.list.push(a)) }, push: function (a) { return a instanceof d || (a = this.source.wrap(a)), this.inlineStack.push(a), a }, pushStackLiteral: function (a) { this.push(new d(a)) }, pushSource: function (a) { this.pendingContent && (this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation)), this.pendingContent = void 0), a && this.source.push(a) }, replaceStack: function (a) { var b = ["("], c = void 0, e = void 0, f = void 0; if (!this.isInline()) throw new j["default"]("replaceStack on non-inline"); var g = this.popStack(!0); if (g instanceof d) c = [g.value], b = ["(", c], f = !0; else { e = !0; var h = this.incrStack(); b = ["((", this.push(h), " = ", g, ")"], c = this.topStack() } var i = a.call(this, c); f || this.popStack(), e && this.stackSlot--, this.push(b.concat(i, ")")) }, incrStack: function () { return this.stackSlot++, this.stackSlot > this.stackVars.length && this.stackVars.push("stack" + this.stackSlot), this.topStackName() }, topStackName: function () { return "stack" + this.stackSlot }, flushInline: function () { var a = this.inlineStack; this.inlineStack = []; for (var b = 0, c = a.length; c > b; b++) { var e = a[b]; if (e instanceof d) this.compileStack.push(e); else { var f = this.incrStack(); this.pushSource([f, " = ", e, ";"]), this.compileStack.push(f) } } }, isInline: function () { return this.inlineStack.length }, popStack: function (a) { var b = this.isInline(), c = (b ? this.inlineStack : this.compileStack).pop(); if (!a && c instanceof d) return c.value; if (!b) { if (!this.stackSlot) throw new j["default"]("Invalid stack pop"); this.stackSlot-- } return c }, topStack: function () { var a = this.isInline() ? this.inlineStack : this.compileStack, b = a[a.length - 1]; return b instanceof d ? b.value : b }, contextName: function (a) { return this.useDepths && a ? "depths[" + a + "]" : "depth" + a }, quotedString: function (a) { return this.source.quotedString(a) }, objectLiteral: function (a) { return this.source.objectLiteral(a) }, aliasable: function (a) { var b = this.aliases[a]; return b ? (b.referenceCount++, b) : (b = this.aliases[a] = this.source.wrap(a), b.aliasable = !0, b.referenceCount = 1, b) }, setupHelper: function (a, b, c) { var d = [], e = this.setupHelperArgs(b, a, d, c), f = this.nameLookup("helpers", b, "helper"), g = this.aliasable(this.contextName(0) + " != null ? " + this.contextName(0) + " : {}"); return { params: d, paramsInit: e, name: f, callParams: [g].concat(d) } }, setupParams: function (a, b, c) { var d = {}, e = [], f = [], g = [], h = !c, i = void 0; h && (c = []), d.name = this.quotedString(a), d.hash = this.popStack(), this.trackIds && (d.hashIds = this.popStack()), this.stringParams && (d.hashTypes = this.popStack(), d.hashContexts = this.popStack()); var j = this.popStack(), k = this.popStack(); (k || j) && (d.fn = k || "container.noop", d.inverse = j || "container.noop"); for (var l = b; l--;) i = this.popStack(), c[l] = i, this.trackIds && (g[l] = this.popStack()), this.stringParams && (f[l] = this.popStack(), e[l] = this.popStack()); return h && (d.args = this.source.generateArray(c)), this.trackIds && (d.ids = this.source.generateArray(g)), this.stringParams && (d.types = this.source.generateArray(f), d.contexts = this.source.generateArray(e)), this.options.data && (d.data = "data"), this.useBlockParams && (d.blockParams = "blockParams"), d }, setupHelperArgs: function (a, b, c, d) { var e = this.setupParams(a, b, c); return e = this.objectLiteral(e), d ? (this.useRegister("options"), c.push("options"), ["options=", e]) : c ? (c.push(e), "") : e } + }, function () { for (var a = "break else new var case finally return void catch for switch while continue function this with default if throw delete in try do instanceof typeof abstract enum int short boolean export interface static byte extends long super char final native synchronized class float package throws const goto private transient debugger implements protected volatile double import public let yield await null true false".split(" "), b = e.RESERVED_WORDS = {}, c = 0, d = a.length; d > c; c++) b[a[c]] = !0 }(), e.isValidJavaScriptVariableName = function (a) { return !e.RESERVED_WORDS[a] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(a) }, b["default"] = e, a.exports = b["default"] + }, function (a, b, c) { "use strict"; function d(a, b, c) { if (f.isArray(a)) { for (var d = [], e = 0, g = a.length; g > e; e++) d.push(b.wrap(a[e], c)); return d } return "boolean" == typeof a || "number" == typeof a ? a + "" : a } function e(a) { this.srcFile = a, this.source = [] } b.__esModule = !0; var f = c(5), g = void 0; try { } catch (h) { } g || (g = function (a, b, c, d) { this.src = "", d && this.add(d) }, g.prototype = { add: function (a) { f.isArray(a) && (a = a.join("")), this.src += a }, prepend: function (a) { f.isArray(a) && (a = a.join("")), this.src = a + this.src }, toStringWithSourceMap: function () { return { code: this.toString() } }, toString: function () { return this.src } }), e.prototype = { isEmpty: function () { return !this.source.length }, prepend: function (a, b) { this.source.unshift(this.wrap(a, b)) }, push: function (a, b) { this.source.push(this.wrap(a, b)) }, merge: function () { var a = this.empty(); return this.each(function (b) { a.add([" ", b, "\n"]) }), a }, each: function (a) { for (var b = 0, c = this.source.length; c > b; b++) a(this.source[b]) }, empty: function () { var a = this.currentLocation || { start: {} }; return new g(a.start.line, a.start.column, this.srcFile) }, wrap: function (a) { var b = arguments.length <= 1 || void 0 === arguments[1] ? this.currentLocation || { start: {} } : arguments[1]; return a instanceof g ? a : (a = d(a, this, b), new g(b.start.line, b.start.column, this.srcFile, a)) }, functionCall: function (a, b, c) { return c = this.generateList(c), this.wrap([a, b ? "." + b + "(" : "(", c, ")"]) }, quotedString: function (a) { return '"' + (a + "").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029") + '"' }, objectLiteral: function (a) { var b = []; for (var c in a) if (a.hasOwnProperty(c)) { var e = d(a[c], this); "undefined" !== e && b.push([this.quotedString(c), ":", e]) } var f = this.generateList(b); return f.prepend("{"), f.add("}"), f }, generateList: function (a) { for (var b = this.empty(), c = 0, e = a.length; e > c; c++) c && b.add(","), b.add(d(a[c], this)); return b }, generateArray: function (a) { var b = this.generateList(a); return b.prepend("["), b.add("]"), b } }, b["default"] = e, a.exports = b["default"] }]) }); \ No newline at end of file diff --git a/PlexRequests.UI/Content/moment.min.js b/PlexRequests.UI/Content/moment.min.js index 28e8f2b50..bb84f7a6c 100644 --- a/PlexRequests.UI/Content/moment.min.js +++ b/PlexRequests.UI/Content/moment.min.js @@ -1,10 +1,10 @@ -//! moment.js -//! version : 2.12.0 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com -!function (a, b) { "object" == typeof exports && "undefined" != typeof module ? module.exports = b() : "function" == typeof define && define.amd ? define(b) : a.moment = b() }(this, function () { - "use strict"; function a() { return Zc.apply(null, arguments) } function b(a) { Zc = a } function c(a) { return a instanceof Array || "[object Array]" === Object.prototype.toString.call(a) } function d(a) { return a instanceof Date || "[object Date]" === Object.prototype.toString.call(a) } function e(a, b) { var c, d = []; for (c = 0; c < a.length; ++c) d.push(b(a[c], c)); return d } function f(a, b) { return Object.prototype.hasOwnProperty.call(a, b) } function g(a, b) { for (var c in b) f(b, c) && (a[c] = b[c]); return f(b, "toString") && (a.toString = b.toString), f(b, "valueOf") && (a.valueOf = b.valueOf), a } function h(a, b, c, d) { return Ia(a, b, c, d, !0).utc() } function i() { return { empty: !1, unusedTokens: [], unusedInput: [], overflow: -2, charsLeftOver: 0, nullInput: !1, invalidMonth: null, invalidFormat: !1, userInvalidated: !1, iso: !1 } } function j(a) { return null == a._pf && (a._pf = i()), a._pf } function k(a) { if (null == a._isValid) { var b = j(a); a._isValid = !(isNaN(a._d.getTime()) || !(b.overflow < 0) || b.empty || b.invalidMonth || b.invalidWeekday || b.nullInput || b.invalidFormat || b.userInvalidated), a._strict && (a._isValid = a._isValid && 0 === b.charsLeftOver && 0 === b.unusedTokens.length && void 0 === b.bigHour) } return a._isValid } function l(a) { var b = h(NaN); return null != a ? g(j(b), a) : j(b).userInvalidated = !0, b } function m(a) { return void 0 === a } function n(a, b) { var c, d, e; if (m(b._isAMomentObject) || (a._isAMomentObject = b._isAMomentObject), m(b._i) || (a._i = b._i), m(b._f) || (a._f = b._f), m(b._l) || (a._l = b._l), m(b._strict) || (a._strict = b._strict), m(b._tzm) || (a._tzm = b._tzm), m(b._isUTC) || (a._isUTC = b._isUTC), m(b._offset) || (a._offset = b._offset), m(b._pf) || (a._pf = j(b)), m(b._locale) || (a._locale = b._locale), $c.length > 0) for (c in $c) d = $c[c], e = b[d], m(e) || (a[d] = e); return a } function o(b) { n(this, b), this._d = new Date(null != b._d ? b._d.getTime() : NaN), _c === !1 && (_c = !0, a.updateOffset(this), _c = !1) } function p(a) { return a instanceof o || null != a && null != a._isAMomentObject } function q(a) { return 0 > a ? Math.ceil(a) : Math.floor(a) } function r(a) { var b = +a, c = 0; return 0 !== b && isFinite(b) && (c = q(b)), c } function s(a, b, c) { var d, e = Math.min(a.length, b.length), f = Math.abs(a.length - b.length), g = 0; for (d = 0; e > d; d++) (c && a[d] !== b[d] || !c && r(a[d]) !== r(b[d])) && g++; return g + f } function t(b) { a.suppressDeprecationWarnings === !1 && "undefined" != typeof console && console.warn && console.warn("Deprecation warning: " + b) } function u(a, b) { var c = !0; return g(function () { return c && (t(a + "\nArguments: " + Array.prototype.slice.call(arguments).join(", ") + "\n" + (new Error).stack), c = !1), b.apply(this, arguments) }, b) } function v(a, b) { ad[a] || (t(b), ad[a] = !0) } function w(a) { return a instanceof Function || "[object Function]" === Object.prototype.toString.call(a) } function x(a) { return "[object Object]" === Object.prototype.toString.call(a) } function y(a) { var b, c; for (c in a) b = a[c], w(b) ? this[c] = b : this["_" + c] = b; this._config = a, this._ordinalParseLenient = new RegExp(this._ordinalParse.source + "|" + /\d{1,2}/.source) } function z(a, b) { var c, d = g({}, a); for (c in b) f(b, c) && (x(a[c]) && x(b[c]) ? (d[c] = {}, g(d[c], a[c]), g(d[c], b[c])) : null != b[c] ? d[c] = b[c] : delete d[c]); return d } function A(a) { null != a && this.set(a) } function B(a) { return a ? a.toLowerCase().replace("_", "-") : a } function C(a) { for (var b, c, d, e, f = 0; f < a.length;) { for (e = B(a[f]).split("-"), b = e.length, c = B(a[f + 1]), c = c ? c.split("-") : null; b > 0;) { if (d = D(e.slice(0, b).join("-"))) return d; if (c && c.length >= b && s(e, c, !0) >= b - 1) break; b-- } f++ } return null } function D(a) { var b = null; if (!cd[a] && "undefined" != typeof module && module && module.exports) try { b = bd._abbr, require("./locale/" + a), E(b) } catch (c) { } return cd[a] } function E(a, b) { var c; return a && (c = m(b) ? H(a) : F(a, b), c && (bd = c)), bd._abbr } function F(a, b) { return null !== b ? (b.abbr = a, null != cd[a] ? (v("defineLocaleOverride", "use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"), b = z(cd[a]._config, b)) : null != b.parentLocale && (null != cd[b.parentLocale] ? b = z(cd[b.parentLocale]._config, b) : v("parentLocaleUndefined", "specified parentLocale is not defined yet")), cd[a] = new A(b), E(a), cd[a]) : (delete cd[a], null) } function G(a, b) { if (null != b) { var c; null != cd[a] && (b = z(cd[a]._config, b)), c = new A(b), c.parentLocale = cd[a], cd[a] = c, E(a) } else null != cd[a] && (null != cd[a].parentLocale ? cd[a] = cd[a].parentLocale : null != cd[a] && delete cd[a]); return cd[a] } function H(a) { var b; if (a && a._locale && a._locale._abbr && (a = a._locale._abbr), !a) return bd; if (!c(a)) { if (b = D(a)) return b; a = [a] } return C(a) } function I() { return Object.keys(cd) } function J(a, b) { var c = a.toLowerCase(); dd[c] = dd[c + "s"] = dd[b] = a } function K(a) { return "string" == typeof a ? dd[a] || dd[a.toLowerCase()] : void 0 } function L(a) { var b, c, d = {}; for (c in a) f(a, c) && (b = K(c), b && (d[b] = a[c])); return d } function M(b, c) { return function (d) { return null != d ? (O(this, b, d), a.updateOffset(this, c), this) : N(this, b) } } function N(a, b) { return a.isValid() ? a._d["get" + (a._isUTC ? "UTC" : "") + b]() : NaN } function O(a, b, c) { a.isValid() && a._d["set" + (a._isUTC ? "UTC" : "") + b](c) } function P(a, b) { var c; if ("object" == typeof a) for (c in a) this.set(c, a[c]); else if (a = K(a), w(this[a])) return this[a](b); return this } function Q(a, b, c) { var d = "" + Math.abs(a), e = b - d.length, f = a >= 0; return (f ? c ? "+" : "" : "-") + Math.pow(10, Math.max(0, e)).toString().substr(1) + d } function R(a, b, c, d) { var e = d; "string" == typeof d && (e = function () { return this[d]() }), a && (hd[a] = e), b && (hd[b[0]] = function () { return Q(e.apply(this, arguments), b[1], b[2]) }), c && (hd[c] = function () { return this.localeData().ordinal(e.apply(this, arguments), a) }) } function S(a) { return a.match(/\[[\s\S]/) ? a.replace(/^\[|\]$/g, "") : a.replace(/\\/g, "") } function T(a) { var b, c, d = a.match(ed); for (b = 0, c = d.length; c > b; b++) hd[d[b]] ? d[b] = hd[d[b]] : d[b] = S(d[b]); return function (e) { var f = ""; for (b = 0; c > b; b++) f += d[b] instanceof Function ? d[b].call(e, a) : d[b]; return f } } function U(a, b) { return a.isValid() ? (b = V(b, a.localeData()), gd[b] = gd[b] || T(b), gd[b](a)) : a.localeData().invalidDate() } function V(a, b) { function c(a) { return b.longDateFormat(a) || a } var d = 5; for (fd.lastIndex = 0; d >= 0 && fd.test(a) ;) a = a.replace(fd, c), fd.lastIndex = 0, d -= 1; return a } function W(a, b, c) { zd[a] = w(b) ? b : function (a, d) { return a && c ? c : b } } function X(a, b) { return f(zd, a) ? zd[a](b._strict, b._locale) : new RegExp(Y(a)) } function Y(a) { return Z(a.replace("\\", "").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (a, b, c, d, e) { return b || c || d || e })) } function Z(a) { return a.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") } function $(a, b) { var c, d = b; for ("string" == typeof a && (a = [a]), "number" == typeof b && (d = function (a, c) { c[b] = r(a) }), c = 0; c < a.length; c++) Ad[a[c]] = d } function _(a, b) { $(a, function (a, c, d, e) { d._w = d._w || {}, b(a, d._w, d, e) }) } function aa(a, b, c) { null != b && f(Ad, a) && Ad[a](b, c._a, c, a) } function ba(a, b) { return new Date(Date.UTC(a, b + 1, 0)).getUTCDate() } function ca(a, b) { return c(this._months) ? this._months[a.month()] : this._months[Kd.test(b) ? "format" : "standalone"][a.month()] } function da(a, b) { return c(this._monthsShort) ? this._monthsShort[a.month()] : this._monthsShort[Kd.test(b) ? "format" : "standalone"][a.month()] } function ea(a, b, c) { var d, e, f; for (this._monthsParse || (this._monthsParse = [], this._longMonthsParse = [], this._shortMonthsParse = []), d = 0; 12 > d; d++) { if (e = h([2e3, d]), c && !this._longMonthsParse[d] && (this._longMonthsParse[d] = new RegExp("^" + this.months(e, "").replace(".", "") + "$", "i"), this._shortMonthsParse[d] = new RegExp("^" + this.monthsShort(e, "").replace(".", "") + "$", "i")), c || this._monthsParse[d] || (f = "^" + this.months(e, "") + "|^" + this.monthsShort(e, ""), this._monthsParse[d] = new RegExp(f.replace(".", ""), "i")), c && "MMMM" === b && this._longMonthsParse[d].test(a)) return d; if (c && "MMM" === b && this._shortMonthsParse[d].test(a)) return d; if (!c && this._monthsParse[d].test(a)) return d } } function fa(a, b) { var c; if (!a.isValid()) return a; if ("string" == typeof b) if (/^\d+$/.test(b)) b = r(b); else if (b = a.localeData().monthsParse(b), "number" != typeof b) return a; return c = Math.min(a.date(), ba(a.year(), b)), a._d["set" + (a._isUTC ? "UTC" : "") + "Month"](b, c), a } function ga(b) { return null != b ? (fa(this, b), a.updateOffset(this, !0), this) : N(this, "Month") } function ha() { return ba(this.year(), this.month()) } function ia(a) { return this._monthsParseExact ? (f(this, "_monthsRegex") || ka.call(this), a ? this._monthsShortStrictRegex : this._monthsShortRegex) : this._monthsShortStrictRegex && a ? this._monthsShortStrictRegex : this._monthsShortRegex } function ja(a) { return this._monthsParseExact ? (f(this, "_monthsRegex") || ka.call(this), a ? this._monthsStrictRegex : this._monthsRegex) : this._monthsStrictRegex && a ? this._monthsStrictRegex : this._monthsRegex } function ka() { function a(a, b) { return b.length - a.length } var b, c, d = [], e = [], f = []; for (b = 0; 12 > b; b++) c = h([2e3, b]), d.push(this.monthsShort(c, "")), e.push(this.months(c, "")), f.push(this.months(c, "")), f.push(this.monthsShort(c, "")); for (d.sort(a), e.sort(a), f.sort(a), b = 0; 12 > b; b++) d[b] = Z(d[b]), e[b] = Z(e[b]), f[b] = Z(f[b]); this._monthsRegex = new RegExp("^(" + f.join("|") + ")", "i"), this._monthsShortRegex = this._monthsRegex, this._monthsStrictRegex = new RegExp("^(" + e.join("|") + ")$", "i"), this._monthsShortStrictRegex = new RegExp("^(" + d.join("|") + ")$", "i") } function la(a) { var b, c = a._a; return c && -2 === j(a).overflow && (b = c[Cd] < 0 || c[Cd] > 11 ? Cd : c[Dd] < 1 || c[Dd] > ba(c[Bd], c[Cd]) ? Dd : c[Ed] < 0 || c[Ed] > 24 || 24 === c[Ed] && (0 !== c[Fd] || 0 !== c[Gd] || 0 !== c[Hd]) ? Ed : c[Fd] < 0 || c[Fd] > 59 ? Fd : c[Gd] < 0 || c[Gd] > 59 ? Gd : c[Hd] < 0 || c[Hd] > 999 ? Hd : -1, j(a)._overflowDayOfYear && (Bd > b || b > Dd) && (b = Dd), j(a)._overflowWeeks && -1 === b && (b = Id), j(a)._overflowWeekday && -1 === b && (b = Jd), j(a).overflow = b), a } function ma(a) { var b, c, d, e, f, g, h = a._i, i = Pd.exec(h) || Qd.exec(h); if (i) { for (j(a).iso = !0, b = 0, c = Sd.length; c > b; b++) if (Sd[b][1].exec(i[1])) { e = Sd[b][0], d = Sd[b][2] !== !1; break } if (null == e) return void (a._isValid = !1); if (i[3]) { for (b = 0, c = Td.length; c > b; b++) if (Td[b][1].exec(i[3])) { f = (i[2] || " ") + Td[b][0]; break } if (null == f) return void (a._isValid = !1) } if (!d && null != f) return void (a._isValid = !1); if (i[4]) { if (!Rd.exec(i[4])) return void (a._isValid = !1); g = "Z" } a._f = e + (f || "") + (g || ""), Ba(a) } else a._isValid = !1 } function na(b) { var c = Ud.exec(b._i); return null !== c ? void (b._d = new Date(+c[1])) : (ma(b), void (b._isValid === !1 && (delete b._isValid, a.createFromInputFallback(b)))) } function oa(a, b, c, d, e, f, g) { var h = new Date(a, b, c, d, e, f, g); return 100 > a && a >= 0 && isFinite(h.getFullYear()) && h.setFullYear(a), h } function pa(a) { var b = new Date(Date.UTC.apply(null, arguments)); return 100 > a && a >= 0 && isFinite(b.getUTCFullYear()) && b.setUTCFullYear(a), b } function qa(a) { return ra(a) ? 366 : 365 } function ra(a) { return a % 4 === 0 && a % 100 !== 0 || a % 400 === 0 } function sa() { return ra(this.year()) } function ta(a, b, c) { var d = 7 + b - c, e = (7 + pa(a, 0, d).getUTCDay() - b) % 7; return -e + d - 1 } function ua(a, b, c, d, e) { var f, g, h = (7 + c - d) % 7, i = ta(a, d, e), j = 1 + 7 * (b - 1) + h + i; return 0 >= j ? (f = a - 1, g = qa(f) + j) : j > qa(a) ? (f = a + 1, g = j - qa(a)) : (f = a, g = j), { year: f, dayOfYear: g } } function va(a, b, c) { var d, e, f = ta(a.year(), b, c), g = Math.floor((a.dayOfYear() - f - 1) / 7) + 1; return 1 > g ? (e = a.year() - 1, d = g + wa(e, b, c)) : g > wa(a.year(), b, c) ? (d = g - wa(a.year(), b, c), e = a.year() + 1) : (e = a.year(), d = g), { week: d, year: e } } function wa(a, b, c) { var d = ta(a, b, c), e = ta(a + 1, b, c); return (qa(a) - d + e) / 7 } function xa(a, b, c) { return null != a ? a : null != b ? b : c } function ya(b) { var c = new Date(a.now()); return b._useUTC ? [c.getUTCFullYear(), c.getUTCMonth(), c.getUTCDate()] : [c.getFullYear(), c.getMonth(), c.getDate()] } function za(a) { var b, c, d, e, f = []; if (!a._d) { for (d = ya(a), a._w && null == a._a[Dd] && null == a._a[Cd] && Aa(a), a._dayOfYear && (e = xa(a._a[Bd], d[Bd]), a._dayOfYear > qa(e) && (j(a)._overflowDayOfYear = !0), c = pa(e, 0, a._dayOfYear), a._a[Cd] = c.getUTCMonth(), a._a[Dd] = c.getUTCDate()), b = 0; 3 > b && null == a._a[b]; ++b) a._a[b] = f[b] = d[b]; for (; 7 > b; b++) a._a[b] = f[b] = null == a._a[b] ? 2 === b ? 1 : 0 : a._a[b]; 24 === a._a[Ed] && 0 === a._a[Fd] && 0 === a._a[Gd] && 0 === a._a[Hd] && (a._nextDay = !0, a._a[Ed] = 0), a._d = (a._useUTC ? pa : oa).apply(null, f), null != a._tzm && a._d.setUTCMinutes(a._d.getUTCMinutes() - a._tzm), a._nextDay && (a._a[Ed] = 24) } } function Aa(a) { var b, c, d, e, f, g, h, i; b = a._w, null != b.GG || null != b.W || null != b.E ? (f = 1, g = 4, c = xa(b.GG, a._a[Bd], va(Ja(), 1, 4).year), d = xa(b.W, 1), e = xa(b.E, 1), (1 > e || e > 7) && (i = !0)) : (f = a._locale._week.dow, g = a._locale._week.doy, c = xa(b.gg, a._a[Bd], va(Ja(), f, g).year), d = xa(b.w, 1), null != b.d ? (e = b.d, (0 > e || e > 6) && (i = !0)) : null != b.e ? (e = b.e + f, (b.e < 0 || b.e > 6) && (i = !0)) : e = f), 1 > d || d > wa(c, f, g) ? j(a)._overflowWeeks = !0 : null != i ? j(a)._overflowWeekday = !0 : (h = ua(c, d, e, f, g), a._a[Bd] = h.year, a._dayOfYear = h.dayOfYear) } function Ba(b) { if (b._f === a.ISO_8601) return void ma(b); b._a = [], j(b).empty = !0; var c, d, e, f, g, h = "" + b._i, i = h.length, k = 0; for (e = V(b._f, b._locale).match(ed) || [], c = 0; c < e.length; c++) f = e[c], d = (h.match(X(f, b)) || [])[0], d && (g = h.substr(0, h.indexOf(d)), g.length > 0 && j(b).unusedInput.push(g), h = h.slice(h.indexOf(d) + d.length), k += d.length), hd[f] ? (d ? j(b).empty = !1 : j(b).unusedTokens.push(f), aa(f, d, b)) : b._strict && !d && j(b).unusedTokens.push(f); j(b).charsLeftOver = i - k, h.length > 0 && j(b).unusedInput.push(h), j(b).bigHour === !0 && b._a[Ed] <= 12 && b._a[Ed] > 0 && (j(b).bigHour = void 0), b._a[Ed] = Ca(b._locale, b._a[Ed], b._meridiem), za(b), la(b) } function Ca(a, b, c) { var d; return null == c ? b : null != a.meridiemHour ? a.meridiemHour(b, c) : null != a.isPM ? (d = a.isPM(c), d && 12 > b && (b += 12), d || 12 !== b || (b = 0), b) : b } function Da(a) { var b, c, d, e, f; if (0 === a._f.length) return j(a).invalidFormat = !0, void (a._d = new Date(NaN)); for (e = 0; e < a._f.length; e++) f = 0, b = n({}, a), null != a._useUTC && (b._useUTC = a._useUTC), b._f = a._f[e], Ba(b), k(b) && (f += j(b).charsLeftOver, f += 10 * j(b).unusedTokens.length, j(b).score = f, (null == d || d > f) && (d = f, c = b)); g(a, c || b) } function Ea(a) { if (!a._d) { var b = L(a._i); a._a = e([b.year, b.month, b.day || b.date, b.hour, b.minute, b.second, b.millisecond], function (a) { return a && parseInt(a, 10) }), za(a) } } function Fa(a) { var b = new o(la(Ga(a))); return b._nextDay && (b.add(1, "d"), b._nextDay = void 0), b } function Ga(a) { var b = a._i, e = a._f; return a._locale = a._locale || H(a._l), null === b || void 0 === e && "" === b ? l({ nullInput: !0 }) : ("string" == typeof b && (a._i = b = a._locale.preparse(b)), p(b) ? new o(la(b)) : (c(e) ? Da(a) : e ? Ba(a) : d(b) ? a._d = b : Ha(a), k(a) || (a._d = null), a)) } function Ha(b) { var f = b._i; void 0 === f ? b._d = new Date(a.now()) : d(f) ? b._d = new Date(+f) : "string" == typeof f ? na(b) : c(f) ? (b._a = e(f.slice(0), function (a) { return parseInt(a, 10) }), za(b)) : "object" == typeof f ? Ea(b) : "number" == typeof f ? b._d = new Date(f) : a.createFromInputFallback(b) } function Ia(a, b, c, d, e) { var f = {}; return "boolean" == typeof c && (d = c, c = void 0), f._isAMomentObject = !0, f._useUTC = f._isUTC = e, f._l = c, f._i = a, f._f = b, f._strict = d, Fa(f) } function Ja(a, b, c, d) { return Ia(a, b, c, d, !1) } function Ka(a, b) { var d, e; if (1 === b.length && c(b[0]) && (b = b[0]), !b.length) return Ja(); for (d = b[0], e = 1; e < b.length; ++e) (!b[e].isValid() || b[e][a](d)) && (d = b[e]); return d } function La() { var a = [].slice.call(arguments, 0); return Ka("isBefore", a) } function Ma() { var a = [].slice.call(arguments, 0); return Ka("isAfter", a) } function Na(a) { var b = L(a), c = b.year || 0, d = b.quarter || 0, e = b.month || 0, f = b.week || 0, g = b.day || 0, h = b.hour || 0, i = b.minute || 0, j = b.second || 0, k = b.millisecond || 0; this._milliseconds = +k + 1e3 * j + 6e4 * i + 36e5 * h, this._days = +g + 7 * f, this._months = +e + 3 * d + 12 * c, this._data = {}, this._locale = H(), this._bubble() } function Oa(a) { return a instanceof Na } function Pa(a, b) { R(a, 0, 0, function () { var a = this.utcOffset(), c = "+"; return 0 > a && (a = -a, c = "-"), c + Q(~~(a / 60), 2) + b + Q(~~a % 60, 2) }) } function Qa(a, b) { var c = (b || "").match(a) || [], d = c[c.length - 1] || [], e = (d + "").match(Zd) || ["-", 0, 0], f = +(60 * e[1]) + r(e[2]); return "+" === e[0] ? f : -f } function Ra(b, c) { var e, f; return c._isUTC ? (e = c.clone(), f = (p(b) || d(b) ? +b : +Ja(b)) - +e, e._d.setTime(+e._d + f), a.updateOffset(e, !1), e) : Ja(b).local() } function Sa(a) { return 15 * -Math.round(a._d.getTimezoneOffset() / 15) } function Ta(b, c) { var d, e = this._offset || 0; return this.isValid() ? null != b ? ("string" == typeof b ? b = Qa(wd, b) : Math.abs(b) < 16 && (b = 60 * b), !this._isUTC && c && (d = Sa(this)), this._offset = b, this._isUTC = !0, null != d && this.add(d, "m"), e !== b && (!c || this._changeInProgress ? ib(this, cb(b - e, "m"), 1, !1) : this._changeInProgress || (this._changeInProgress = !0, a.updateOffset(this, !0), this._changeInProgress = null)), this) : this._isUTC ? e : Sa(this) : null != b ? this : NaN } function Ua(a, b) { return null != a ? ("string" != typeof a && (a = -a), this.utcOffset(a, b), this) : -this.utcOffset() } function Va(a) { return this.utcOffset(0, a) } function Wa(a) { return this._isUTC && (this.utcOffset(0, a), this._isUTC = !1, a && this.subtract(Sa(this), "m")), this } function Xa() { return this._tzm ? this.utcOffset(this._tzm) : "string" == typeof this._i && this.utcOffset(Qa(vd, this._i)), this } function Ya(a) { return this.isValid() ? (a = a ? Ja(a).utcOffset() : 0, (this.utcOffset() - a) % 60 === 0) : !1 } function Za() { return this.utcOffset() > this.clone().month(0).utcOffset() || this.utcOffset() > this.clone().month(5).utcOffset() } function $a() { if (!m(this._isDSTShifted)) return this._isDSTShifted; var a = {}; if (n(a, this), a = Ga(a), a._a) { var b = a._isUTC ? h(a._a) : Ja(a._a); this._isDSTShifted = this.isValid() && s(a._a, b.toArray()) > 0 } else this._isDSTShifted = !1; return this._isDSTShifted } function _a() { return this.isValid() ? !this._isUTC : !1 } function ab() { return this.isValid() ? this._isUTC : !1 } function bb() { return this.isValid() ? this._isUTC && 0 === this._offset : !1 } function cb(a, b) { var c, d, e, g = a, h = null; return Oa(a) ? g = { ms: a._milliseconds, d: a._days, M: a._months } : "number" == typeof a ? (g = {}, b ? g[b] = a : g.milliseconds = a) : (h = $d.exec(a)) ? (c = "-" === h[1] ? -1 : 1, g = { y: 0, d: r(h[Dd]) * c, h: r(h[Ed]) * c, m: r(h[Fd]) * c, s: r(h[Gd]) * c, ms: r(h[Hd]) * c }) : (h = _d.exec(a)) ? (c = "-" === h[1] ? -1 : 1, g = { y: db(h[2], c), M: db(h[3], c), w: db(h[4], c), d: db(h[5], c), h: db(h[6], c), m: db(h[7], c), s: db(h[8], c) }) : null == g ? g = {} : "object" == typeof g && ("from" in g || "to" in g) && (e = fb(Ja(g.from), Ja(g.to)), g = {}, g.ms = e.milliseconds, g.M = e.months), d = new Na(g), Oa(a) && f(a, "_locale") && (d._locale = a._locale), d } function db(a, b) { var c = a && parseFloat(a.replace(",", ".")); return (isNaN(c) ? 0 : c) * b } function eb(a, b) { var c = { milliseconds: 0, months: 0 }; return c.months = b.month() - a.month() + 12 * (b.year() - a.year()), a.clone().add(c.months, "M").isAfter(b) && --c.months, c.milliseconds = +b - +a.clone().add(c.months, "M"), c } function fb(a, b) { var c; return a.isValid() && b.isValid() ? (b = Ra(b, a), a.isBefore(b) ? c = eb(a, b) : (c = eb(b, a), c.milliseconds = -c.milliseconds, c.months = -c.months), c) : { milliseconds: 0, months: 0 } } function gb(a) { return 0 > a ? -1 * Math.round(-1 * a) : Math.round(a) } function hb(a, b) { return function (c, d) { var e, f; return null === d || isNaN(+d) || (v(b, "moment()." + b + "(period, number) is deprecated. Please use moment()." + b + "(number, period)."), f = c, c = d, d = f), c = "string" == typeof c ? +c : c, e = cb(c, d), ib(this, e, a), this } } function ib(b, c, d, e) { var f = c._milliseconds, g = gb(c._days), h = gb(c._months); b.isValid() && (e = null == e ? !0 : e, f && b._d.setTime(+b._d + f * d), g && O(b, "Date", N(b, "Date") + g * d), h && fa(b, N(b, "Month") + h * d), e && a.updateOffset(b, g || h)) } function jb(a, b) { var c = a || Ja(), d = Ra(c, this).startOf("day"), e = this.diff(d, "days", !0), f = -6 > e ? "sameElse" : -1 > e ? "lastWeek" : 0 > e ? "lastDay" : 1 > e ? "sameDay" : 2 > e ? "nextDay" : 7 > e ? "nextWeek" : "sameElse", g = b && (w(b[f]) ? b[f]() : b[f]); return this.format(g || this.localeData().calendar(f, this, Ja(c))) } function kb() { return new o(this) } function lb(a, b) { var c = p(a) ? a : Ja(a); return this.isValid() && c.isValid() ? (b = K(m(b) ? "millisecond" : b), "millisecond" === b ? +this > +c : +c < +this.clone().startOf(b)) : !1 } function mb(a, b) { var c = p(a) ? a : Ja(a); return this.isValid() && c.isValid() ? (b = K(m(b) ? "millisecond" : b), "millisecond" === b ? +c > +this : +this.clone().endOf(b) < +c) : !1 } function nb(a, b, c) { return this.isAfter(a, c) && this.isBefore(b, c) } function ob(a, b) { var c, d = p(a) ? a : Ja(a); return this.isValid() && d.isValid() ? (b = K(b || "millisecond"), "millisecond" === b ? +this === +d : (c = +d, +this.clone().startOf(b) <= c && c <= +this.clone().endOf(b))) : !1 } function pb(a, b) { return this.isSame(a, b) || this.isAfter(a, b) } function qb(a, b) { return this.isSame(a, b) || this.isBefore(a, b) } function rb(a, b, c) { var d, e, f, g; return this.isValid() ? (d = Ra(a, this), d.isValid() ? (e = 6e4 * (d.utcOffset() - this.utcOffset()), b = K(b), "year" === b || "month" === b || "quarter" === b ? (g = sb(this, d), "quarter" === b ? g /= 3 : "year" === b && (g /= 12)) : (f = this - d, g = "second" === b ? f / 1e3 : "minute" === b ? f / 6e4 : "hour" === b ? f / 36e5 : "day" === b ? (f - e) / 864e5 : "week" === b ? (f - e) / 6048e5 : f), c ? g : q(g)) : NaN) : NaN } function sb(a, b) { var c, d, e = 12 * (b.year() - a.year()) + (b.month() - a.month()), f = a.clone().add(e, "months"); return 0 > b - f ? (c = a.clone().add(e - 1, "months"), d = (b - f) / (f - c)) : (c = a.clone().add(e + 1, "months"), d = (b - f) / (c - f)), -(e + d) } function tb() { return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ") } function ub() { var a = this.clone().utc(); return 0 < a.year() && a.year() <= 9999 ? w(Date.prototype.toISOString) ? this.toDate().toISOString() : U(a, "YYYY-MM-DD[T]HH:mm:ss.SSS[Z]") : U(a, "YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]") } function vb(b) { var c = U(this, b || a.defaultFormat); return this.localeData().postformat(c) } function wb(a, b) { return this.isValid() && (p(a) && a.isValid() || Ja(a).isValid()) ? cb({ to: this, from: a }).locale(this.locale()).humanize(!b) : this.localeData().invalidDate() } function xb(a) { return this.from(Ja(), a) } function yb(a, b) { return this.isValid() && (p(a) && a.isValid() || Ja(a).isValid()) ? cb({ from: this, to: a }).locale(this.locale()).humanize(!b) : this.localeData().invalidDate() } function zb(a) { return this.to(Ja(), a) } function Ab(a) { var b; return void 0 === a ? this._locale._abbr : (b = H(a), null != b && (this._locale = b), this) } function Bb() { return this._locale } function Cb(a) { switch (a = K(a)) { case "year": this.month(0); case "quarter": case "month": this.date(1); case "week": case "isoWeek": case "day": this.hours(0); case "hour": this.minutes(0); case "minute": this.seconds(0); case "second": this.milliseconds(0) } return "week" === a && this.weekday(0), "isoWeek" === a && this.isoWeekday(1), "quarter" === a && this.month(3 * Math.floor(this.month() / 3)), this } function Db(a) { return a = K(a), void 0 === a || "millisecond" === a ? this : this.startOf(a).add(1, "isoWeek" === a ? "week" : a).subtract(1, "ms") } function Eb() { return +this._d - 6e4 * (this._offset || 0) } function Fb() { return Math.floor(+this / 1e3) } function Gb() { return this._offset ? new Date(+this) : this._d } function Hb() { var a = this; return [a.year(), a.month(), a.date(), a.hour(), a.minute(), a.second(), a.millisecond()] } function Ib() { var a = this; return { years: a.year(), months: a.month(), date: a.date(), hours: a.hours(), minutes: a.minutes(), seconds: a.seconds(), milliseconds: a.milliseconds() } } function Jb() { return this.isValid() ? this.toISOString() : null } function Kb() { return k(this) } function Lb() { return g({}, j(this)) } function Mb() { return j(this).overflow } function Nb() { return { input: this._i, format: this._f, locale: this._locale, isUTC: this._isUTC, strict: this._strict } } function Ob(a, b) { R(0, [a, a.length], 0, b) } function Pb(a) { return Tb.call(this, a, this.week(), this.weekday(), this.localeData()._week.dow, this.localeData()._week.doy) } function Qb(a) { return Tb.call(this, a, this.isoWeek(), this.isoWeekday(), 1, 4) } function Rb() { return wa(this.year(), 1, 4) } function Sb() { var a = this.localeData()._week; return wa(this.year(), a.dow, a.doy) } function Tb(a, b, c, d, e) { var f; return null == a ? va(this, d, e).year : (f = wa(a, d, e), b > f && (b = f), Ub.call(this, a, b, c, d, e)) } function Ub(a, b, c, d, e) { var f = ua(a, b, c, d, e), g = pa(f.year, 0, f.dayOfYear); return this.year(g.getUTCFullYear()), this.month(g.getUTCMonth()), this.date(g.getUTCDate()), this } function Vb(a) { return null == a ? Math.ceil((this.month() + 1) / 3) : this.month(3 * (a - 1) + this.month() % 3) } function Wb(a) { return va(a, this._week.dow, this._week.doy).week } function Xb() { return this._week.dow } function Yb() { return this._week.doy } function Zb(a) { var b = this.localeData().week(this); return null == a ? b : this.add(7 * (a - b), "d") } function $b(a) { var b = va(this, 1, 4).week; return null == a ? b : this.add(7 * (a - b), "d") } function _b(a, b) { return "string" != typeof a ? a : isNaN(a) ? (a = b.weekdaysParse(a), "number" == typeof a ? a : null) : parseInt(a, 10) } function ac(a, b) { return c(this._weekdays) ? this._weekdays[a.day()] : this._weekdays[this._weekdays.isFormat.test(b) ? "format" : "standalone"][a.day()] } function bc(a) { return this._weekdaysShort[a.day()] } function cc(a) { return this._weekdaysMin[a.day()] } function dc(a, b, c) { var d, e, f; for (this._weekdaysParse || (this._weekdaysParse = [], this._minWeekdaysParse = [], this._shortWeekdaysParse = [], this._fullWeekdaysParse = []), d = 0; 7 > d; d++) { if (e = Ja([2e3, 1]).day(d), c && !this._fullWeekdaysParse[d] && (this._fullWeekdaysParse[d] = new RegExp("^" + this.weekdays(e, "").replace(".", ".?") + "$", "i"), this._shortWeekdaysParse[d] = new RegExp("^" + this.weekdaysShort(e, "").replace(".", ".?") + "$", "i"), this._minWeekdaysParse[d] = new RegExp("^" + this.weekdaysMin(e, "").replace(".", ".?") + "$", "i")), this._weekdaysParse[d] || (f = "^" + this.weekdays(e, "") + "|^" + this.weekdaysShort(e, "") + "|^" + this.weekdaysMin(e, ""), this._weekdaysParse[d] = new RegExp(f.replace(".", ""), "i")), c && "dddd" === b && this._fullWeekdaysParse[d].test(a)) return d; if (c && "ddd" === b && this._shortWeekdaysParse[d].test(a)) return d; if (c && "dd" === b && this._minWeekdaysParse[d].test(a)) return d; if (!c && this._weekdaysParse[d].test(a)) return d } } function ec(a) { if (!this.isValid()) return null != a ? this : NaN; var b = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); return null != a ? (a = _b(a, this.localeData()), this.add(a - b, "d")) : b } function fc(a) { if (!this.isValid()) return null != a ? this : NaN; var b = (this.day() + 7 - this.localeData()._week.dow) % 7; return null == a ? b : this.add(a - b, "d") } function gc(a) { return this.isValid() ? null == a ? this.day() || 7 : this.day(this.day() % 7 ? a : a - 7) : null != a ? this : NaN } function hc(a) { var b = Math.round((this.clone().startOf("day") - this.clone().startOf("year")) / 864e5) + 1; return null == a ? b : this.add(a - b, "d") } function ic() { return this.hours() % 12 || 12 } function jc(a, b) { R(a, 0, 0, function () { return this.localeData().meridiem(this.hours(), this.minutes(), b) }) } function kc(a, b) { return b._meridiemParse } function lc(a) { return "p" === (a + "").toLowerCase().charAt(0) } function mc(a, b, c) { return a > 11 ? c ? "pm" : "PM" : c ? "am" : "AM" } function nc(a, b) { b[Hd] = r(1e3 * ("0." + a)) } function oc() { return this._isUTC ? "UTC" : "" } function pc() { return this._isUTC ? "Coordinated Universal Time" : "" } function qc(a) { return Ja(1e3 * a) } function rc() { return Ja.apply(null, arguments).parseZone() } function sc(a, b, c) { var d = this._calendar[a]; return w(d) ? d.call(b, c) : d } function tc(a) { var b = this._longDateFormat[a], c = this._longDateFormat[a.toUpperCase()]; return b || !c ? b : (this._longDateFormat[a] = c.replace(/MMMM|MM|DD|dddd/g, function (a) { return a.slice(1) }), this._longDateFormat[a]) } function uc() { return this._invalidDate } function vc(a) { return this._ordinal.replace("%d", a) } function wc(a) { return a } function xc(a, b, c, d) { var e = this._relativeTime[c]; return w(e) ? e(a, b, c, d) : e.replace(/%d/i, a) } function yc(a, b) { var c = this._relativeTime[a > 0 ? "future" : "past"]; return w(c) ? c(b) : c.replace(/%s/i, b) } function zc(a, b, c, d) { var e = H(), f = h().set(d, b); return e[c](f, a) } function Ac(a, b, c, d, e) { if ("number" == typeof a && (b = a, a = void 0), a = a || "", null != b) return zc(a, b, c, e); var f, g = []; for (f = 0; d > f; f++) g[f] = zc(a, f, c, e); return g } function Bc(a, b) { return Ac(a, b, "months", 12, "month") } function Cc(a, b) { return Ac(a, b, "monthsShort", 12, "month") } function Dc(a, b) { return Ac(a, b, "weekdays", 7, "day") } function Ec(a, b) { return Ac(a, b, "weekdaysShort", 7, "day") } function Fc(a, b) { return Ac(a, b, "weekdaysMin", 7, "day") } function Gc() { var a = this._data; return this._milliseconds = xe(this._milliseconds), this._days = xe(this._days), this._months = xe(this._months), a.milliseconds = xe(a.milliseconds), a.seconds = xe(a.seconds), a.minutes = xe(a.minutes), a.hours = xe(a.hours), a.months = xe(a.months), a.years = xe(a.years), this } function Hc(a, b, c, d) { var e = cb(b, c); return a._milliseconds += d * e._milliseconds, a._days += d * e._days, a._months += d * e._months, a._bubble() } function Ic(a, b) { return Hc(this, a, b, 1) } function Jc(a, b) { return Hc(this, a, b, -1) } function Kc(a) { return 0 > a ? Math.floor(a) : Math.ceil(a) } function Lc() { var a, b, c, d, e, f = this._milliseconds, g = this._days, h = this._months, i = this._data; return f >= 0 && g >= 0 && h >= 0 || 0 >= f && 0 >= g && 0 >= h || (f += 864e5 * Kc(Nc(h) + g), g = 0, h = 0), i.milliseconds = f % 1e3, a = q(f / 1e3), i.seconds = a % 60, b = q(a / 60), i.minutes = b % 60, c = q(b / 60), i.hours = c % 24, g += q(c / 24), e = q(Mc(g)), h += e, g -= Kc(Nc(e)), d = q(h / 12), h %= 12, i.days = g, i.months = h, i.years = d, this } function Mc(a) { return 4800 * a / 146097 } function Nc(a) { return 146097 * a / 4800 } function Oc(a) { var b, c, d = this._milliseconds; if (a = K(a), "month" === a || "year" === a) return b = this._days + d / 864e5, c = this._months + Mc(b), "month" === a ? c : c / 12; switch (b = this._days + Math.round(Nc(this._months)), a) { case "week": return b / 7 + d / 6048e5; case "day": return b + d / 864e5; case "hour": return 24 * b + d / 36e5; case "minute": return 1440 * b + d / 6e4; case "second": return 86400 * b + d / 1e3; case "millisecond": return Math.floor(864e5 * b) + d; default: throw new Error("Unknown unit " + a) } } function Pc() { return this._milliseconds + 864e5 * this._days + this._months % 12 * 2592e6 + 31536e6 * r(this._months / 12) } function Qc(a) { return function () { return this.as(a) } } function Rc(a) { return a = K(a), this[a + "s"]() } function Sc(a) { return function () { return this._data[a] } } function Tc() { return q(this.days() / 7) } function Uc(a, b, c, d, e) { return e.relativeTime(b || 1, !!c, a, d) } function Vc(a, b, c) { var d = cb(a).abs(), e = Ne(d.as("s")), f = Ne(d.as("m")), g = Ne(d.as("h")), h = Ne(d.as("d")), i = Ne(d.as("M")), j = Ne(d.as("y")), k = e < Oe.s && ["s", e] || 1 >= f && ["m"] || f < Oe.m && ["mm", f] || 1 >= g && ["h"] || g < Oe.h && ["hh", g] || 1 >= h && ["d"] || h < Oe.d && ["dd", h] || 1 >= i && ["M"] || i < Oe.M && ["MM", i] || 1 >= j && ["y"] || ["yy", j]; return k[2] = b, k[3] = +a > 0, k[4] = c, Uc.apply(null, k) } function Wc(a, b) { return void 0 === Oe[a] ? !1 : void 0 === b ? Oe[a] : (Oe[a] = b, !0) } function Xc(a) { var b = this.localeData(), c = Vc(this, !a, b); return a && (c = b.pastFuture(+this, c)), b.postformat(c) } function Yc() { var a, b, c, d = Pe(this._milliseconds) / 1e3, e = Pe(this._days), f = Pe(this._months); a = q(d / 60), b = q(a / 60), d %= 60, a %= 60, c = q(f / 12), f %= 12; var g = c, h = f, i = e, j = b, k = a, l = d, m = this.asSeconds(); return m ? (0 > m ? "-" : "") + "P" + (g ? g + "Y" : "") + (h ? h + "M" : "") + (i ? i + "D" : "") + (j || k || l ? "T" : "") + (j ? j + "H" : "") + (k ? k + "M" : "") + (l ? l + "S" : "") : "P0D" } var Zc, $c = a.momentProperties = [], _c = !1, ad = {}; a.suppressDeprecationWarnings = !1; var bd, cd = {}, dd = {}, ed = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, fd = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, gd = {}, hd = {}, id = /\d/, jd = /\d\d/, kd = /\d{3}/, ld = /\d{4}/, md = /[+-]?\d{6}/, nd = /\d\d?/, od = /\d\d\d\d?/, pd = /\d\d\d\d\d\d?/, qd = /\d{1,3}/, rd = /\d{1,4}/, sd = /[+-]?\d{1,6}/, td = /\d+/, ud = /[+-]?\d+/, vd = /Z|[+-]\d\d:?\d\d/gi, wd = /Z|[+-]\d\d(?::?\d\d)?/gi, xd = /[+-]?\d+(\.\d{1,3})?/, yd = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, zd = {}, Ad = {}, Bd = 0, Cd = 1, Dd = 2, Ed = 3, Fd = 4, Gd = 5, Hd = 6, Id = 7, Jd = 8; R("M", ["MM", 2], "Mo", function () { return this.month() + 1 }), R("MMM", 0, 0, function (a) { return this.localeData().monthsShort(this, a) }), R("MMMM", 0, 0, function (a) { return this.localeData().months(this, a) }), J("month", "M"), W("M", nd), W("MM", nd, jd), W("MMM", function (a, b) { return b.monthsShortRegex(a) }), W("MMMM", function (a, b) { return b.monthsRegex(a) }), $(["M", "MM"], function (a, b) { b[Cd] = r(a) - 1 }), $(["MMM", "MMMM"], function (a, b, c, d) { var e = c._locale.monthsParse(a, d, c._strict); null != e ? b[Cd] = e : j(c).invalidMonth = a }); var Kd = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/, Ld = "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), Md = "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), Nd = yd, Od = yd, Pd = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/, Qd = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/, Rd = /Z|[+-]\d\d(?::?\d\d)?/, Sd = [["YYYYYY-MM-DD", /[+-]\d{6}-\d\d-\d\d/], ["YYYY-MM-DD", /\d{4}-\d\d-\d\d/], ["GGGG-[W]WW-E", /\d{4}-W\d\d-\d/], ["GGGG-[W]WW", /\d{4}-W\d\d/, !1], ["YYYY-DDD", /\d{4}-\d{3}/], ["YYYY-MM", /\d{4}-\d\d/, !1], ["YYYYYYMMDD", /[+-]\d{10}/], ["YYYYMMDD", /\d{8}/], ["GGGG[W]WWE", /\d{4}W\d{3}/], ["GGGG[W]WW", /\d{4}W\d{2}/, !1], ["YYYYDDD", /\d{7}/]], Td = [["HH:mm:ss.SSSS", /\d\d:\d\d:\d\d\.\d+/], ["HH:mm:ss,SSSS", /\d\d:\d\d:\d\d,\d+/], ["HH:mm:ss", /\d\d:\d\d:\d\d/], ["HH:mm", /\d\d:\d\d/], ["HHmmss.SSSS", /\d\d\d\d\d\d\.\d+/], ["HHmmss,SSSS", /\d\d\d\d\d\d,\d+/], ["HHmmss", /\d\d\d\d\d\d/], ["HHmm", /\d\d\d\d/], ["HH", /\d\d/]], Ud = /^\/?Date\((\-?\d+)/i; a.createFromInputFallback = u("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.", function (a) { a._d = new Date(a._i + (a._useUTC ? " UTC" : "")) }), R("Y", 0, 0, function () { var a = this.year(); return 9999 >= a ? "" + a : "+" + a }), R(0, ["YY", 2], 0, function () { return this.year() % 100 }), R(0, ["YYYY", 4], 0, "year"), R(0, ["YYYYY", 5], 0, "year"), R(0, ["YYYYYY", 6, !0], 0, "year"), J("year", "y"), W("Y", ud), W("YY", nd, jd), W("YYYY", rd, ld), W("YYYYY", sd, md), W("YYYYYY", sd, md), $(["YYYYY", "YYYYYY"], Bd), $("YYYY", function (b, c) { - c[Bd] = 2 === b.length ? a.parseTwoDigitYear(b) : r(b); - }), $("YY", function (b, c) { c[Bd] = a.parseTwoDigitYear(b) }), $("Y", function (a, b) { b[Bd] = parseInt(a, 10) }), a.parseTwoDigitYear = function (a) { return r(a) + (r(a) > 68 ? 1900 : 2e3) }; var Vd = M("FullYear", !1); a.ISO_8601 = function () { }; var Wd = u("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548", function () { var a = Ja.apply(null, arguments); return this.isValid() && a.isValid() ? this > a ? this : a : l() }), Xd = u("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548", function () { var a = Ja.apply(null, arguments); return this.isValid() && a.isValid() ? a > this ? this : a : l() }), Yd = function () { return Date.now ? Date.now() : +new Date }; Pa("Z", ":"), Pa("ZZ", ""), W("Z", wd), W("ZZ", wd), $(["Z", "ZZ"], function (a, b, c) { c._useUTC = !0, c._tzm = Qa(wd, a) }); var Zd = /([\+\-]|\d\d)/gi; a.updateOffset = function () { }; var $d = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/, _d = /^(-)?P(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)W)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?$/; cb.fn = Na.prototype; var ae = hb(1, "add"), be = hb(-1, "subtract"); a.defaultFormat = "YYYY-MM-DDTHH:mm:ssZ"; var ce = u("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.", function (a) { return void 0 === a ? this.localeData() : this.locale(a) }); R(0, ["gg", 2], 0, function () { return this.weekYear() % 100 }), R(0, ["GG", 2], 0, function () { return this.isoWeekYear() % 100 }), Ob("gggg", "weekYear"), Ob("ggggg", "weekYear"), Ob("GGGG", "isoWeekYear"), Ob("GGGGG", "isoWeekYear"), J("weekYear", "gg"), J("isoWeekYear", "GG"), W("G", ud), W("g", ud), W("GG", nd, jd), W("gg", nd, jd), W("GGGG", rd, ld), W("gggg", rd, ld), W("GGGGG", sd, md), W("ggggg", sd, md), _(["gggg", "ggggg", "GGGG", "GGGGG"], function (a, b, c, d) { b[d.substr(0, 2)] = r(a) }), _(["gg", "GG"], function (b, c, d, e) { c[e] = a.parseTwoDigitYear(b) }), R("Q", 0, "Qo", "quarter"), J("quarter", "Q"), W("Q", id), $("Q", function (a, b) { b[Cd] = 3 * (r(a) - 1) }), R("w", ["ww", 2], "wo", "week"), R("W", ["WW", 2], "Wo", "isoWeek"), J("week", "w"), J("isoWeek", "W"), W("w", nd), W("ww", nd, jd), W("W", nd), W("WW", nd, jd), _(["w", "ww", "W", "WW"], function (a, b, c, d) { b[d.substr(0, 1)] = r(a) }); var de = { dow: 0, doy: 6 }; R("D", ["DD", 2], "Do", "date"), J("date", "D"), W("D", nd), W("DD", nd, jd), W("Do", function (a, b) { return a ? b._ordinalParse : b._ordinalParseLenient }), $(["D", "DD"], Dd), $("Do", function (a, b) { b[Dd] = r(a.match(nd)[0], 10) }); var ee = M("Date", !0); R("d", 0, "do", "day"), R("dd", 0, 0, function (a) { return this.localeData().weekdaysMin(this, a) }), R("ddd", 0, 0, function (a) { return this.localeData().weekdaysShort(this, a) }), R("dddd", 0, 0, function (a) { return this.localeData().weekdays(this, a) }), R("e", 0, 0, "weekday"), R("E", 0, 0, "isoWeekday"), J("day", "d"), J("weekday", "e"), J("isoWeekday", "E"), W("d", nd), W("e", nd), W("E", nd), W("dd", yd), W("ddd", yd), W("dddd", yd), _(["dd", "ddd", "dddd"], function (a, b, c, d) { var e = c._locale.weekdaysParse(a, d, c._strict); null != e ? b.d = e : j(c).invalidWeekday = a }), _(["d", "e", "E"], function (a, b, c, d) { b[d] = r(a) }); var fe = "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), ge = "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), he = "Su_Mo_Tu_We_Th_Fr_Sa".split("_"); R("DDD", ["DDDD", 3], "DDDo", "dayOfYear"), J("dayOfYear", "DDD"), W("DDD", qd), W("DDDD", kd), $(["DDD", "DDDD"], function (a, b, c) { c._dayOfYear = r(a) }), R("H", ["HH", 2], 0, "hour"), R("h", ["hh", 2], 0, ic), R("hmm", 0, 0, function () { return "" + ic.apply(this) + Q(this.minutes(), 2) }), R("hmmss", 0, 0, function () { return "" + ic.apply(this) + Q(this.minutes(), 2) + Q(this.seconds(), 2) }), R("Hmm", 0, 0, function () { return "" + this.hours() + Q(this.minutes(), 2) }), R("Hmmss", 0, 0, function () { return "" + this.hours() + Q(this.minutes(), 2) + Q(this.seconds(), 2) }), jc("a", !0), jc("A", !1), J("hour", "h"), W("a", kc), W("A", kc), W("H", nd), W("h", nd), W("HH", nd, jd), W("hh", nd, jd), W("hmm", od), W("hmmss", pd), W("Hmm", od), W("Hmmss", pd), $(["H", "HH"], Ed), $(["a", "A"], function (a, b, c) { c._isPm = c._locale.isPM(a), c._meridiem = a }), $(["h", "hh"], function (a, b, c) { b[Ed] = r(a), j(c).bigHour = !0 }), $("hmm", function (a, b, c) { var d = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d)), j(c).bigHour = !0 }), $("hmmss", function (a, b, c) { var d = a.length - 4, e = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d, 2)), b[Gd] = r(a.substr(e)), j(c).bigHour = !0 }), $("Hmm", function (a, b, c) { var d = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d)) }), $("Hmmss", function (a, b, c) { var d = a.length - 4, e = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d, 2)), b[Gd] = r(a.substr(e)) }); var ie = /[ap]\.?m?\.?/i, je = M("Hours", !0); R("m", ["mm", 2], 0, "minute"), J("minute", "m"), W("m", nd), W("mm", nd, jd), $(["m", "mm"], Fd); var ke = M("Minutes", !1); R("s", ["ss", 2], 0, "second"), J("second", "s"), W("s", nd), W("ss", nd, jd), $(["s", "ss"], Gd); var le = M("Seconds", !1); R("S", 0, 0, function () { return ~~(this.millisecond() / 100) }), R(0, ["SS", 2], 0, function () { return ~~(this.millisecond() / 10) }), R(0, ["SSS", 3], 0, "millisecond"), R(0, ["SSSS", 4], 0, function () { return 10 * this.millisecond() }), R(0, ["SSSSS", 5], 0, function () { return 100 * this.millisecond() }), R(0, ["SSSSSS", 6], 0, function () { return 1e3 * this.millisecond() }), R(0, ["SSSSSSS", 7], 0, function () { return 1e4 * this.millisecond() }), R(0, ["SSSSSSSS", 8], 0, function () { return 1e5 * this.millisecond() }), R(0, ["SSSSSSSSS", 9], 0, function () { return 1e6 * this.millisecond() }), J("millisecond", "ms"), W("S", qd, id), W("SS", qd, jd), W("SSS", qd, kd); var me; for (me = "SSSS"; me.length <= 9; me += "S") W(me, td); for (me = "S"; me.length <= 9; me += "S") $(me, nc); var ne = M("Milliseconds", !1); R("z", 0, 0, "zoneAbbr"), R("zz", 0, 0, "zoneName"); var oe = o.prototype; oe.add = ae, oe.calendar = jb, oe.clone = kb, oe.diff = rb, oe.endOf = Db, oe.format = vb, oe.from = wb, oe.fromNow = xb, oe.to = yb, oe.toNow = zb, oe.get = P, oe.invalidAt = Mb, oe.isAfter = lb, oe.isBefore = mb, oe.isBetween = nb, oe.isSame = ob, oe.isSameOrAfter = pb, oe.isSameOrBefore = qb, oe.isValid = Kb, oe.lang = ce, oe.locale = Ab, oe.localeData = Bb, oe.max = Xd, oe.min = Wd, oe.parsingFlags = Lb, oe.set = P, oe.startOf = Cb, oe.subtract = be, oe.toArray = Hb, oe.toObject = Ib, oe.toDate = Gb, oe.toISOString = ub, oe.toJSON = Jb, oe.toString = tb, oe.unix = Fb, oe.valueOf = Eb, oe.creationData = Nb, oe.year = Vd, oe.isLeapYear = sa, oe.weekYear = Pb, oe.isoWeekYear = Qb, oe.quarter = oe.quarters = Vb, oe.month = ga, oe.daysInMonth = ha, oe.week = oe.weeks = Zb, oe.isoWeek = oe.isoWeeks = $b, oe.weeksInYear = Sb, oe.isoWeeksInYear = Rb, oe.date = ee, oe.day = oe.days = ec, oe.weekday = fc, oe.isoWeekday = gc, oe.dayOfYear = hc, oe.hour = oe.hours = je, oe.minute = oe.minutes = ke, oe.second = oe.seconds = le, oe.millisecond = oe.milliseconds = ne, oe.utcOffset = Ta, oe.utc = Va, oe.local = Wa, oe.parseZone = Xa, oe.hasAlignedHourOffset = Ya, oe.isDST = Za, oe.isDSTShifted = $a, oe.isLocal = _a, oe.isUtcOffset = ab, oe.isUtc = bb, oe.isUTC = bb, oe.zoneAbbr = oc, oe.zoneName = pc, oe.dates = u("dates accessor is deprecated. Use date instead.", ee), oe.months = u("months accessor is deprecated. Use month instead", ga), oe.years = u("years accessor is deprecated. Use year instead", Vd), oe.zone = u("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779", Ua); var pe = oe, qe = { sameDay: "[Today at] LT", nextDay: "[Tomorrow at] LT", nextWeek: "dddd [at] LT", lastDay: "[Yesterday at] LT", lastWeek: "[Last] dddd [at] LT", sameElse: "L" }, re = { LTS: "h:mm:ss A", LT: "h:mm A", L: "MM/DD/YYYY", LL: "MMMM D, YYYY", LLL: "MMMM D, YYYY h:mm A", LLLL: "dddd, MMMM D, YYYY h:mm A" }, se = "Invalid date", te = "%d", ue = /\d{1,2}/, ve = { future: "in %s", past: "%s ago", s: "a few seconds", m: "a minute", mm: "%d minutes", h: "an hour", hh: "%d hours", d: "a day", dd: "%d days", M: "a month", MM: "%d months", y: "a year", yy: "%d years" }, we = A.prototype; we._calendar = qe, we.calendar = sc, we._longDateFormat = re, we.longDateFormat = tc, we._invalidDate = se, we.invalidDate = uc, we._ordinal = te, we.ordinal = vc, we._ordinalParse = ue, we.preparse = wc, we.postformat = wc, we._relativeTime = ve, we.relativeTime = xc, we.pastFuture = yc, we.set = y, we.months = ca, we._months = Ld, we.monthsShort = da, we._monthsShort = Md, we.monthsParse = ea, we._monthsRegex = Od, we.monthsRegex = ja, we._monthsShortRegex = Nd, we.monthsShortRegex = ia, we.week = Wb, we._week = de, we.firstDayOfYear = Yb, we.firstDayOfWeek = Xb, we.weekdays = ac, we._weekdays = fe, we.weekdaysMin = cc, we._weekdaysMin = he, we.weekdaysShort = bc, we._weekdaysShort = ge, we.weekdaysParse = dc, we.isPM = lc, we._meridiemParse = ie, we.meridiem = mc, E("en", { ordinalParse: /\d{1,2}(th|st|nd|rd)/, ordinal: function (a) { var b = a % 10, c = 1 === r(a % 100 / 10) ? "th" : 1 === b ? "st" : 2 === b ? "nd" : 3 === b ? "rd" : "th"; return a + c } }), a.lang = u("moment.lang is deprecated. Use moment.locale instead.", E), a.langData = u("moment.langData is deprecated. Use moment.localeData instead.", H); var xe = Math.abs, ye = Qc("ms"), ze = Qc("s"), Ae = Qc("m"), Be = Qc("h"), Ce = Qc("d"), De = Qc("w"), Ee = Qc("M"), Fe = Qc("y"), Ge = Sc("milliseconds"), He = Sc("seconds"), Ie = Sc("minutes"), Je = Sc("hours"), Ke = Sc("days"), Le = Sc("months"), Me = Sc("years"), Ne = Math.round, Oe = { s: 45, m: 45, h: 22, d: 26, M: 11 }, Pe = Math.abs, Qe = Na.prototype; Qe.abs = Gc, Qe.add = Ic, Qe.subtract = Jc, Qe.as = Oc, Qe.asMilliseconds = ye, Qe.asSeconds = ze, Qe.asMinutes = Ae, Qe.asHours = Be, Qe.asDays = Ce, Qe.asWeeks = De, Qe.asMonths = Ee, Qe.asYears = Fe, Qe.valueOf = Pc, Qe._bubble = Lc, Qe.get = Rc, Qe.milliseconds = Ge, Qe.seconds = He, Qe.minutes = Ie, Qe.hours = Je, Qe.days = Ke, Qe.weeks = Tc, Qe.months = Le, Qe.years = Me, Qe.humanize = Xc, Qe.toISOString = Yc, Qe.toString = Yc, Qe.toJSON = Yc, Qe.locale = Ab, Qe.localeData = Bb, Qe.toIsoString = u("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)", Yc), Qe.lang = ce, R("X", 0, 0, "unix"), R("x", 0, 0, "valueOf"), W("x", ud), W("X", xd), $("X", function (a, b, c) { c._d = new Date(1e3 * parseFloat(a, 10)) }), $("x", function (a, b, c) { c._d = new Date(r(a)) }), a.version = "2.12.0", b(Ja), a.fn = pe, a.min = La, a.max = Ma, a.now = Yd, a.utc = h, a.unix = qc, a.months = Bc, a.isDate = d, a.locale = E, a.invalid = l, a.duration = cb, a.isMoment = p, a.weekdays = Dc, a.parseZone = rc, a.localeData = H, a.isDuration = Oa, a.monthsShort = Cc, a.weekdaysMin = Fc, a.defineLocale = F, a.updateLocale = G, a.locales = I, a.weekdaysShort = Ec, a.normalizeUnits = K, a.relativeTimeThreshold = Wc, a.prototype = pe; var Re = a; return Re +//! moment.js +//! version : 2.12.0 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function (a, b) { "object" == typeof exports && "undefined" != typeof module ? module.exports = b() : "function" == typeof define && define.amd ? define(b) : a.moment = b() }(this, function () { + "use strict"; function a() { return Zc.apply(null, arguments) } function b(a) { Zc = a } function c(a) { return a instanceof Array || "[object Array]" === Object.prototype.toString.call(a) } function d(a) { return a instanceof Date || "[object Date]" === Object.prototype.toString.call(a) } function e(a, b) { var c, d = []; for (c = 0; c < a.length; ++c) d.push(b(a[c], c)); return d } function f(a, b) { return Object.prototype.hasOwnProperty.call(a, b) } function g(a, b) { for (var c in b) f(b, c) && (a[c] = b[c]); return f(b, "toString") && (a.toString = b.toString), f(b, "valueOf") && (a.valueOf = b.valueOf), a } function h(a, b, c, d) { return Ia(a, b, c, d, !0).utc() } function i() { return { empty: !1, unusedTokens: [], unusedInput: [], overflow: -2, charsLeftOver: 0, nullInput: !1, invalidMonth: null, invalidFormat: !1, userInvalidated: !1, iso: !1 } } function j(a) { return null == a._pf && (a._pf = i()), a._pf } function k(a) { if (null == a._isValid) { var b = j(a); a._isValid = !(isNaN(a._d.getTime()) || !(b.overflow < 0) || b.empty || b.invalidMonth || b.invalidWeekday || b.nullInput || b.invalidFormat || b.userInvalidated), a._strict && (a._isValid = a._isValid && 0 === b.charsLeftOver && 0 === b.unusedTokens.length && void 0 === b.bigHour) } return a._isValid } function l(a) { var b = h(NaN); return null != a ? g(j(b), a) : j(b).userInvalidated = !0, b } function m(a) { return void 0 === a } function n(a, b) { var c, d, e; if (m(b._isAMomentObject) || (a._isAMomentObject = b._isAMomentObject), m(b._i) || (a._i = b._i), m(b._f) || (a._f = b._f), m(b._l) || (a._l = b._l), m(b._strict) || (a._strict = b._strict), m(b._tzm) || (a._tzm = b._tzm), m(b._isUTC) || (a._isUTC = b._isUTC), m(b._offset) || (a._offset = b._offset), m(b._pf) || (a._pf = j(b)), m(b._locale) || (a._locale = b._locale), $c.length > 0) for (c in $c) d = $c[c], e = b[d], m(e) || (a[d] = e); return a } function o(b) { n(this, b), this._d = new Date(null != b._d ? b._d.getTime() : NaN), _c === !1 && (_c = !0, a.updateOffset(this), _c = !1) } function p(a) { return a instanceof o || null != a && null != a._isAMomentObject } function q(a) { return 0 > a ? Math.ceil(a) : Math.floor(a) } function r(a) { var b = +a, c = 0; return 0 !== b && isFinite(b) && (c = q(b)), c } function s(a, b, c) { var d, e = Math.min(a.length, b.length), f = Math.abs(a.length - b.length), g = 0; for (d = 0; e > d; d++) (c && a[d] !== b[d] || !c && r(a[d]) !== r(b[d])) && g++; return g + f } function t(b) { a.suppressDeprecationWarnings === !1 && "undefined" != typeof console && console.warn && console.warn("Deprecation warning: " + b) } function u(a, b) { var c = !0; return g(function () { return c && (t(a + "\nArguments: " + Array.prototype.slice.call(arguments).join(", ") + "\n" + (new Error).stack), c = !1), b.apply(this, arguments) }, b) } function v(a, b) { ad[a] || (t(b), ad[a] = !0) } function w(a) { return a instanceof Function || "[object Function]" === Object.prototype.toString.call(a) } function x(a) { return "[object Object]" === Object.prototype.toString.call(a) } function y(a) { var b, c; for (c in a) b = a[c], w(b) ? this[c] = b : this["_" + c] = b; this._config = a, this._ordinalParseLenient = new RegExp(this._ordinalParse.source + "|" + /\d{1,2}/.source) } function z(a, b) { var c, d = g({}, a); for (c in b) f(b, c) && (x(a[c]) && x(b[c]) ? (d[c] = {}, g(d[c], a[c]), g(d[c], b[c])) : null != b[c] ? d[c] = b[c] : delete d[c]); return d } function A(a) { null != a && this.set(a) } function B(a) { return a ? a.toLowerCase().replace("_", "-") : a } function C(a) { for (var b, c, d, e, f = 0; f < a.length;) { for (e = B(a[f]).split("-"), b = e.length, c = B(a[f + 1]), c = c ? c.split("-") : null; b > 0;) { if (d = D(e.slice(0, b).join("-"))) return d; if (c && c.length >= b && s(e, c, !0) >= b - 1) break; b-- } f++ } return null } function D(a) { var b = null; if (!cd[a] && "undefined" != typeof module && module && module.exports) try { b = bd._abbr, require("./locale/" + a), E(b) } catch (c) { } return cd[a] } function E(a, b) { var c; return a && (c = m(b) ? H(a) : F(a, b), c && (bd = c)), bd._abbr } function F(a, b) { return null !== b ? (b.abbr = a, null != cd[a] ? (v("defineLocaleOverride", "use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"), b = z(cd[a]._config, b)) : null != b.parentLocale && (null != cd[b.parentLocale] ? b = z(cd[b.parentLocale]._config, b) : v("parentLocaleUndefined", "specified parentLocale is not defined yet")), cd[a] = new A(b), E(a), cd[a]) : (delete cd[a], null) } function G(a, b) { if (null != b) { var c; null != cd[a] && (b = z(cd[a]._config, b)), c = new A(b), c.parentLocale = cd[a], cd[a] = c, E(a) } else null != cd[a] && (null != cd[a].parentLocale ? cd[a] = cd[a].parentLocale : null != cd[a] && delete cd[a]); return cd[a] } function H(a) { var b; if (a && a._locale && a._locale._abbr && (a = a._locale._abbr), !a) return bd; if (!c(a)) { if (b = D(a)) return b; a = [a] } return C(a) } function I() { return Object.keys(cd) } function J(a, b) { var c = a.toLowerCase(); dd[c] = dd[c + "s"] = dd[b] = a } function K(a) { return "string" == typeof a ? dd[a] || dd[a.toLowerCase()] : void 0 } function L(a) { var b, c, d = {}; for (c in a) f(a, c) && (b = K(c), b && (d[b] = a[c])); return d } function M(b, c) { return function (d) { return null != d ? (O(this, b, d), a.updateOffset(this, c), this) : N(this, b) } } function N(a, b) { return a.isValid() ? a._d["get" + (a._isUTC ? "UTC" : "") + b]() : NaN } function O(a, b, c) { a.isValid() && a._d["set" + (a._isUTC ? "UTC" : "") + b](c) } function P(a, b) { var c; if ("object" == typeof a) for (c in a) this.set(c, a[c]); else if (a = K(a), w(this[a])) return this[a](b); return this } function Q(a, b, c) { var d = "" + Math.abs(a), e = b - d.length, f = a >= 0; return (f ? c ? "+" : "" : "-") + Math.pow(10, Math.max(0, e)).toString().substr(1) + d } function R(a, b, c, d) { var e = d; "string" == typeof d && (e = function () { return this[d]() }), a && (hd[a] = e), b && (hd[b[0]] = function () { return Q(e.apply(this, arguments), b[1], b[2]) }), c && (hd[c] = function () { return this.localeData().ordinal(e.apply(this, arguments), a) }) } function S(a) { return a.match(/\[[\s\S]/) ? a.replace(/^\[|\]$/g, "") : a.replace(/\\/g, "") } function T(a) { var b, c, d = a.match(ed); for (b = 0, c = d.length; c > b; b++) hd[d[b]] ? d[b] = hd[d[b]] : d[b] = S(d[b]); return function (e) { var f = ""; for (b = 0; c > b; b++) f += d[b] instanceof Function ? d[b].call(e, a) : d[b]; return f } } function U(a, b) { return a.isValid() ? (b = V(b, a.localeData()), gd[b] = gd[b] || T(b), gd[b](a)) : a.localeData().invalidDate() } function V(a, b) { function c(a) { return b.longDateFormat(a) || a } var d = 5; for (fd.lastIndex = 0; d >= 0 && fd.test(a) ;) a = a.replace(fd, c), fd.lastIndex = 0, d -= 1; return a } function W(a, b, c) { zd[a] = w(b) ? b : function (a, d) { return a && c ? c : b } } function X(a, b) { return f(zd, a) ? zd[a](b._strict, b._locale) : new RegExp(Y(a)) } function Y(a) { return Z(a.replace("\\", "").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (a, b, c, d, e) { return b || c || d || e })) } function Z(a) { return a.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") } function $(a, b) { var c, d = b; for ("string" == typeof a && (a = [a]), "number" == typeof b && (d = function (a, c) { c[b] = r(a) }), c = 0; c < a.length; c++) Ad[a[c]] = d } function _(a, b) { $(a, function (a, c, d, e) { d._w = d._w || {}, b(a, d._w, d, e) }) } function aa(a, b, c) { null != b && f(Ad, a) && Ad[a](b, c._a, c, a) } function ba(a, b) { return new Date(Date.UTC(a, b + 1, 0)).getUTCDate() } function ca(a, b) { return c(this._months) ? this._months[a.month()] : this._months[Kd.test(b) ? "format" : "standalone"][a.month()] } function da(a, b) { return c(this._monthsShort) ? this._monthsShort[a.month()] : this._monthsShort[Kd.test(b) ? "format" : "standalone"][a.month()] } function ea(a, b, c) { var d, e, f; for (this._monthsParse || (this._monthsParse = [], this._longMonthsParse = [], this._shortMonthsParse = []), d = 0; 12 > d; d++) { if (e = h([2e3, d]), c && !this._longMonthsParse[d] && (this._longMonthsParse[d] = new RegExp("^" + this.months(e, "").replace(".", "") + "$", "i"), this._shortMonthsParse[d] = new RegExp("^" + this.monthsShort(e, "").replace(".", "") + "$", "i")), c || this._monthsParse[d] || (f = "^" + this.months(e, "") + "|^" + this.monthsShort(e, ""), this._monthsParse[d] = new RegExp(f.replace(".", ""), "i")), c && "MMMM" === b && this._longMonthsParse[d].test(a)) return d; if (c && "MMM" === b && this._shortMonthsParse[d].test(a)) return d; if (!c && this._monthsParse[d].test(a)) return d } } function fa(a, b) { var c; if (!a.isValid()) return a; if ("string" == typeof b) if (/^\d+$/.test(b)) b = r(b); else if (b = a.localeData().monthsParse(b), "number" != typeof b) return a; return c = Math.min(a.date(), ba(a.year(), b)), a._d["set" + (a._isUTC ? "UTC" : "") + "Month"](b, c), a } function ga(b) { return null != b ? (fa(this, b), a.updateOffset(this, !0), this) : N(this, "Month") } function ha() { return ba(this.year(), this.month()) } function ia(a) { return this._monthsParseExact ? (f(this, "_monthsRegex") || ka.call(this), a ? this._monthsShortStrictRegex : this._monthsShortRegex) : this._monthsShortStrictRegex && a ? this._monthsShortStrictRegex : this._monthsShortRegex } function ja(a) { return this._monthsParseExact ? (f(this, "_monthsRegex") || ka.call(this), a ? this._monthsStrictRegex : this._monthsRegex) : this._monthsStrictRegex && a ? this._monthsStrictRegex : this._monthsRegex } function ka() { function a(a, b) { return b.length - a.length } var b, c, d = [], e = [], f = []; for (b = 0; 12 > b; b++) c = h([2e3, b]), d.push(this.monthsShort(c, "")), e.push(this.months(c, "")), f.push(this.months(c, "")), f.push(this.monthsShort(c, "")); for (d.sort(a), e.sort(a), f.sort(a), b = 0; 12 > b; b++) d[b] = Z(d[b]), e[b] = Z(e[b]), f[b] = Z(f[b]); this._monthsRegex = new RegExp("^(" + f.join("|") + ")", "i"), this._monthsShortRegex = this._monthsRegex, this._monthsStrictRegex = new RegExp("^(" + e.join("|") + ")$", "i"), this._monthsShortStrictRegex = new RegExp("^(" + d.join("|") + ")$", "i") } function la(a) { var b, c = a._a; return c && -2 === j(a).overflow && (b = c[Cd] < 0 || c[Cd] > 11 ? Cd : c[Dd] < 1 || c[Dd] > ba(c[Bd], c[Cd]) ? Dd : c[Ed] < 0 || c[Ed] > 24 || 24 === c[Ed] && (0 !== c[Fd] || 0 !== c[Gd] || 0 !== c[Hd]) ? Ed : c[Fd] < 0 || c[Fd] > 59 ? Fd : c[Gd] < 0 || c[Gd] > 59 ? Gd : c[Hd] < 0 || c[Hd] > 999 ? Hd : -1, j(a)._overflowDayOfYear && (Bd > b || b > Dd) && (b = Dd), j(a)._overflowWeeks && -1 === b && (b = Id), j(a)._overflowWeekday && -1 === b && (b = Jd), j(a).overflow = b), a } function ma(a) { var b, c, d, e, f, g, h = a._i, i = Pd.exec(h) || Qd.exec(h); if (i) { for (j(a).iso = !0, b = 0, c = Sd.length; c > b; b++) if (Sd[b][1].exec(i[1])) { e = Sd[b][0], d = Sd[b][2] !== !1; break } if (null == e) return void (a._isValid = !1); if (i[3]) { for (b = 0, c = Td.length; c > b; b++) if (Td[b][1].exec(i[3])) { f = (i[2] || " ") + Td[b][0]; break } if (null == f) return void (a._isValid = !1) } if (!d && null != f) return void (a._isValid = !1); if (i[4]) { if (!Rd.exec(i[4])) return void (a._isValid = !1); g = "Z" } a._f = e + (f || "") + (g || ""), Ba(a) } else a._isValid = !1 } function na(b) { var c = Ud.exec(b._i); return null !== c ? void (b._d = new Date(+c[1])) : (ma(b), void (b._isValid === !1 && (delete b._isValid, a.createFromInputFallback(b)))) } function oa(a, b, c, d, e, f, g) { var h = new Date(a, b, c, d, e, f, g); return 100 > a && a >= 0 && isFinite(h.getFullYear()) && h.setFullYear(a), h } function pa(a) { var b = new Date(Date.UTC.apply(null, arguments)); return 100 > a && a >= 0 && isFinite(b.getUTCFullYear()) && b.setUTCFullYear(a), b } function qa(a) { return ra(a) ? 366 : 365 } function ra(a) { return a % 4 === 0 && a % 100 !== 0 || a % 400 === 0 } function sa() { return ra(this.year()) } function ta(a, b, c) { var d = 7 + b - c, e = (7 + pa(a, 0, d).getUTCDay() - b) % 7; return -e + d - 1 } function ua(a, b, c, d, e) { var f, g, h = (7 + c - d) % 7, i = ta(a, d, e), j = 1 + 7 * (b - 1) + h + i; return 0 >= j ? (f = a - 1, g = qa(f) + j) : j > qa(a) ? (f = a + 1, g = j - qa(a)) : (f = a, g = j), { year: f, dayOfYear: g } } function va(a, b, c) { var d, e, f = ta(a.year(), b, c), g = Math.floor((a.dayOfYear() - f - 1) / 7) + 1; return 1 > g ? (e = a.year() - 1, d = g + wa(e, b, c)) : g > wa(a.year(), b, c) ? (d = g - wa(a.year(), b, c), e = a.year() + 1) : (e = a.year(), d = g), { week: d, year: e } } function wa(a, b, c) { var d = ta(a, b, c), e = ta(a + 1, b, c); return (qa(a) - d + e) / 7 } function xa(a, b, c) { return null != a ? a : null != b ? b : c } function ya(b) { var c = new Date(a.now()); return b._useUTC ? [c.getUTCFullYear(), c.getUTCMonth(), c.getUTCDate()] : [c.getFullYear(), c.getMonth(), c.getDate()] } function za(a) { var b, c, d, e, f = []; if (!a._d) { for (d = ya(a), a._w && null == a._a[Dd] && null == a._a[Cd] && Aa(a), a._dayOfYear && (e = xa(a._a[Bd], d[Bd]), a._dayOfYear > qa(e) && (j(a)._overflowDayOfYear = !0), c = pa(e, 0, a._dayOfYear), a._a[Cd] = c.getUTCMonth(), a._a[Dd] = c.getUTCDate()), b = 0; 3 > b && null == a._a[b]; ++b) a._a[b] = f[b] = d[b]; for (; 7 > b; b++) a._a[b] = f[b] = null == a._a[b] ? 2 === b ? 1 : 0 : a._a[b]; 24 === a._a[Ed] && 0 === a._a[Fd] && 0 === a._a[Gd] && 0 === a._a[Hd] && (a._nextDay = !0, a._a[Ed] = 0), a._d = (a._useUTC ? pa : oa).apply(null, f), null != a._tzm && a._d.setUTCMinutes(a._d.getUTCMinutes() - a._tzm), a._nextDay && (a._a[Ed] = 24) } } function Aa(a) { var b, c, d, e, f, g, h, i; b = a._w, null != b.GG || null != b.W || null != b.E ? (f = 1, g = 4, c = xa(b.GG, a._a[Bd], va(Ja(), 1, 4).year), d = xa(b.W, 1), e = xa(b.E, 1), (1 > e || e > 7) && (i = !0)) : (f = a._locale._week.dow, g = a._locale._week.doy, c = xa(b.gg, a._a[Bd], va(Ja(), f, g).year), d = xa(b.w, 1), null != b.d ? (e = b.d, (0 > e || e > 6) && (i = !0)) : null != b.e ? (e = b.e + f, (b.e < 0 || b.e > 6) && (i = !0)) : e = f), 1 > d || d > wa(c, f, g) ? j(a)._overflowWeeks = !0 : null != i ? j(a)._overflowWeekday = !0 : (h = ua(c, d, e, f, g), a._a[Bd] = h.year, a._dayOfYear = h.dayOfYear) } function Ba(b) { if (b._f === a.ISO_8601) return void ma(b); b._a = [], j(b).empty = !0; var c, d, e, f, g, h = "" + b._i, i = h.length, k = 0; for (e = V(b._f, b._locale).match(ed) || [], c = 0; c < e.length; c++) f = e[c], d = (h.match(X(f, b)) || [])[0], d && (g = h.substr(0, h.indexOf(d)), g.length > 0 && j(b).unusedInput.push(g), h = h.slice(h.indexOf(d) + d.length), k += d.length), hd[f] ? (d ? j(b).empty = !1 : j(b).unusedTokens.push(f), aa(f, d, b)) : b._strict && !d && j(b).unusedTokens.push(f); j(b).charsLeftOver = i - k, h.length > 0 && j(b).unusedInput.push(h), j(b).bigHour === !0 && b._a[Ed] <= 12 && b._a[Ed] > 0 && (j(b).bigHour = void 0), b._a[Ed] = Ca(b._locale, b._a[Ed], b._meridiem), za(b), la(b) } function Ca(a, b, c) { var d; return null == c ? b : null != a.meridiemHour ? a.meridiemHour(b, c) : null != a.isPM ? (d = a.isPM(c), d && 12 > b && (b += 12), d || 12 !== b || (b = 0), b) : b } function Da(a) { var b, c, d, e, f; if (0 === a._f.length) return j(a).invalidFormat = !0, void (a._d = new Date(NaN)); for (e = 0; e < a._f.length; e++) f = 0, b = n({}, a), null != a._useUTC && (b._useUTC = a._useUTC), b._f = a._f[e], Ba(b), k(b) && (f += j(b).charsLeftOver, f += 10 * j(b).unusedTokens.length, j(b).score = f, (null == d || d > f) && (d = f, c = b)); g(a, c || b) } function Ea(a) { if (!a._d) { var b = L(a._i); a._a = e([b.year, b.month, b.day || b.date, b.hour, b.minute, b.second, b.millisecond], function (a) { return a && parseInt(a, 10) }), za(a) } } function Fa(a) { var b = new o(la(Ga(a))); return b._nextDay && (b.add(1, "d"), b._nextDay = void 0), b } function Ga(a) { var b = a._i, e = a._f; return a._locale = a._locale || H(a._l), null === b || void 0 === e && "" === b ? l({ nullInput: !0 }) : ("string" == typeof b && (a._i = b = a._locale.preparse(b)), p(b) ? new o(la(b)) : (c(e) ? Da(a) : e ? Ba(a) : d(b) ? a._d = b : Ha(a), k(a) || (a._d = null), a)) } function Ha(b) { var f = b._i; void 0 === f ? b._d = new Date(a.now()) : d(f) ? b._d = new Date(+f) : "string" == typeof f ? na(b) : c(f) ? (b._a = e(f.slice(0), function (a) { return parseInt(a, 10) }), za(b)) : "object" == typeof f ? Ea(b) : "number" == typeof f ? b._d = new Date(f) : a.createFromInputFallback(b) } function Ia(a, b, c, d, e) { var f = {}; return "boolean" == typeof c && (d = c, c = void 0), f._isAMomentObject = !0, f._useUTC = f._isUTC = e, f._l = c, f._i = a, f._f = b, f._strict = d, Fa(f) } function Ja(a, b, c, d) { return Ia(a, b, c, d, !1) } function Ka(a, b) { var d, e; if (1 === b.length && c(b[0]) && (b = b[0]), !b.length) return Ja(); for (d = b[0], e = 1; e < b.length; ++e) (!b[e].isValid() || b[e][a](d)) && (d = b[e]); return d } function La() { var a = [].slice.call(arguments, 0); return Ka("isBefore", a) } function Ma() { var a = [].slice.call(arguments, 0); return Ka("isAfter", a) } function Na(a) { var b = L(a), c = b.year || 0, d = b.quarter || 0, e = b.month || 0, f = b.week || 0, g = b.day || 0, h = b.hour || 0, i = b.minute || 0, j = b.second || 0, k = b.millisecond || 0; this._milliseconds = +k + 1e3 * j + 6e4 * i + 36e5 * h, this._days = +g + 7 * f, this._months = +e + 3 * d + 12 * c, this._data = {}, this._locale = H(), this._bubble() } function Oa(a) { return a instanceof Na } function Pa(a, b) { R(a, 0, 0, function () { var a = this.utcOffset(), c = "+"; return 0 > a && (a = -a, c = "-"), c + Q(~~(a / 60), 2) + b + Q(~~a % 60, 2) }) } function Qa(a, b) { var c = (b || "").match(a) || [], d = c[c.length - 1] || [], e = (d + "").match(Zd) || ["-", 0, 0], f = +(60 * e[1]) + r(e[2]); return "+" === e[0] ? f : -f } function Ra(b, c) { var e, f; return c._isUTC ? (e = c.clone(), f = (p(b) || d(b) ? +b : +Ja(b)) - +e, e._d.setTime(+e._d + f), a.updateOffset(e, !1), e) : Ja(b).local() } function Sa(a) { return 15 * -Math.round(a._d.getTimezoneOffset() / 15) } function Ta(b, c) { var d, e = this._offset || 0; return this.isValid() ? null != b ? ("string" == typeof b ? b = Qa(wd, b) : Math.abs(b) < 16 && (b = 60 * b), !this._isUTC && c && (d = Sa(this)), this._offset = b, this._isUTC = !0, null != d && this.add(d, "m"), e !== b && (!c || this._changeInProgress ? ib(this, cb(b - e, "m"), 1, !1) : this._changeInProgress || (this._changeInProgress = !0, a.updateOffset(this, !0), this._changeInProgress = null)), this) : this._isUTC ? e : Sa(this) : null != b ? this : NaN } function Ua(a, b) { return null != a ? ("string" != typeof a && (a = -a), this.utcOffset(a, b), this) : -this.utcOffset() } function Va(a) { return this.utcOffset(0, a) } function Wa(a) { return this._isUTC && (this.utcOffset(0, a), this._isUTC = !1, a && this.subtract(Sa(this), "m")), this } function Xa() { return this._tzm ? this.utcOffset(this._tzm) : "string" == typeof this._i && this.utcOffset(Qa(vd, this._i)), this } function Ya(a) { return this.isValid() ? (a = a ? Ja(a).utcOffset() : 0, (this.utcOffset() - a) % 60 === 0) : !1 } function Za() { return this.utcOffset() > this.clone().month(0).utcOffset() || this.utcOffset() > this.clone().month(5).utcOffset() } function $a() { if (!m(this._isDSTShifted)) return this._isDSTShifted; var a = {}; if (n(a, this), a = Ga(a), a._a) { var b = a._isUTC ? h(a._a) : Ja(a._a); this._isDSTShifted = this.isValid() && s(a._a, b.toArray()) > 0 } else this._isDSTShifted = !1; return this._isDSTShifted } function _a() { return this.isValid() ? !this._isUTC : !1 } function ab() { return this.isValid() ? this._isUTC : !1 } function bb() { return this.isValid() ? this._isUTC && 0 === this._offset : !1 } function cb(a, b) { var c, d, e, g = a, h = null; return Oa(a) ? g = { ms: a._milliseconds, d: a._days, M: a._months } : "number" == typeof a ? (g = {}, b ? g[b] = a : g.milliseconds = a) : (h = $d.exec(a)) ? (c = "-" === h[1] ? -1 : 1, g = { y: 0, d: r(h[Dd]) * c, h: r(h[Ed]) * c, m: r(h[Fd]) * c, s: r(h[Gd]) * c, ms: r(h[Hd]) * c }) : (h = _d.exec(a)) ? (c = "-" === h[1] ? -1 : 1, g = { y: db(h[2], c), M: db(h[3], c), w: db(h[4], c), d: db(h[5], c), h: db(h[6], c), m: db(h[7], c), s: db(h[8], c) }) : null == g ? g = {} : "object" == typeof g && ("from" in g || "to" in g) && (e = fb(Ja(g.from), Ja(g.to)), g = {}, g.ms = e.milliseconds, g.M = e.months), d = new Na(g), Oa(a) && f(a, "_locale") && (d._locale = a._locale), d } function db(a, b) { var c = a && parseFloat(a.replace(",", ".")); return (isNaN(c) ? 0 : c) * b } function eb(a, b) { var c = { milliseconds: 0, months: 0 }; return c.months = b.month() - a.month() + 12 * (b.year() - a.year()), a.clone().add(c.months, "M").isAfter(b) && --c.months, c.milliseconds = +b - +a.clone().add(c.months, "M"), c } function fb(a, b) { var c; return a.isValid() && b.isValid() ? (b = Ra(b, a), a.isBefore(b) ? c = eb(a, b) : (c = eb(b, a), c.milliseconds = -c.milliseconds, c.months = -c.months), c) : { milliseconds: 0, months: 0 } } function gb(a) { return 0 > a ? -1 * Math.round(-1 * a) : Math.round(a) } function hb(a, b) { return function (c, d) { var e, f; return null === d || isNaN(+d) || (v(b, "moment()." + b + "(period, number) is deprecated. Please use moment()." + b + "(number, period)."), f = c, c = d, d = f), c = "string" == typeof c ? +c : c, e = cb(c, d), ib(this, e, a), this } } function ib(b, c, d, e) { var f = c._milliseconds, g = gb(c._days), h = gb(c._months); b.isValid() && (e = null == e ? !0 : e, f && b._d.setTime(+b._d + f * d), g && O(b, "Date", N(b, "Date") + g * d), h && fa(b, N(b, "Month") + h * d), e && a.updateOffset(b, g || h)) } function jb(a, b) { var c = a || Ja(), d = Ra(c, this).startOf("day"), e = this.diff(d, "days", !0), f = -6 > e ? "sameElse" : -1 > e ? "lastWeek" : 0 > e ? "lastDay" : 1 > e ? "sameDay" : 2 > e ? "nextDay" : 7 > e ? "nextWeek" : "sameElse", g = b && (w(b[f]) ? b[f]() : b[f]); return this.format(g || this.localeData().calendar(f, this, Ja(c))) } function kb() { return new o(this) } function lb(a, b) { var c = p(a) ? a : Ja(a); return this.isValid() && c.isValid() ? (b = K(m(b) ? "millisecond" : b), "millisecond" === b ? +this > +c : +c < +this.clone().startOf(b)) : !1 } function mb(a, b) { var c = p(a) ? a : Ja(a); return this.isValid() && c.isValid() ? (b = K(m(b) ? "millisecond" : b), "millisecond" === b ? +c > +this : +this.clone().endOf(b) < +c) : !1 } function nb(a, b, c) { return this.isAfter(a, c) && this.isBefore(b, c) } function ob(a, b) { var c, d = p(a) ? a : Ja(a); return this.isValid() && d.isValid() ? (b = K(b || "millisecond"), "millisecond" === b ? +this === +d : (c = +d, +this.clone().startOf(b) <= c && c <= +this.clone().endOf(b))) : !1 } function pb(a, b) { return this.isSame(a, b) || this.isAfter(a, b) } function qb(a, b) { return this.isSame(a, b) || this.isBefore(a, b) } function rb(a, b, c) { var d, e, f, g; return this.isValid() ? (d = Ra(a, this), d.isValid() ? (e = 6e4 * (d.utcOffset() - this.utcOffset()), b = K(b), "year" === b || "month" === b || "quarter" === b ? (g = sb(this, d), "quarter" === b ? g /= 3 : "year" === b && (g /= 12)) : (f = this - d, g = "second" === b ? f / 1e3 : "minute" === b ? f / 6e4 : "hour" === b ? f / 36e5 : "day" === b ? (f - e) / 864e5 : "week" === b ? (f - e) / 6048e5 : f), c ? g : q(g)) : NaN) : NaN } function sb(a, b) { var c, d, e = 12 * (b.year() - a.year()) + (b.month() - a.month()), f = a.clone().add(e, "months"); return 0 > b - f ? (c = a.clone().add(e - 1, "months"), d = (b - f) / (f - c)) : (c = a.clone().add(e + 1, "months"), d = (b - f) / (c - f)), -(e + d) } function tb() { return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ") } function ub() { var a = this.clone().utc(); return 0 < a.year() && a.year() <= 9999 ? w(Date.prototype.toISOString) ? this.toDate().toISOString() : U(a, "YYYY-MM-DD[T]HH:mm:ss.SSS[Z]") : U(a, "YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]") } function vb(b) { var c = U(this, b || a.defaultFormat); return this.localeData().postformat(c) } function wb(a, b) { return this.isValid() && (p(a) && a.isValid() || Ja(a).isValid()) ? cb({ to: this, from: a }).locale(this.locale()).humanize(!b) : this.localeData().invalidDate() } function xb(a) { return this.from(Ja(), a) } function yb(a, b) { return this.isValid() && (p(a) && a.isValid() || Ja(a).isValid()) ? cb({ from: this, to: a }).locale(this.locale()).humanize(!b) : this.localeData().invalidDate() } function zb(a) { return this.to(Ja(), a) } function Ab(a) { var b; return void 0 === a ? this._locale._abbr : (b = H(a), null != b && (this._locale = b), this) } function Bb() { return this._locale } function Cb(a) { switch (a = K(a)) { case "year": this.month(0); case "quarter": case "month": this.date(1); case "week": case "isoWeek": case "day": this.hours(0); case "hour": this.minutes(0); case "minute": this.seconds(0); case "second": this.milliseconds(0) } return "week" === a && this.weekday(0), "isoWeek" === a && this.isoWeekday(1), "quarter" === a && this.month(3 * Math.floor(this.month() / 3)), this } function Db(a) { return a = K(a), void 0 === a || "millisecond" === a ? this : this.startOf(a).add(1, "isoWeek" === a ? "week" : a).subtract(1, "ms") } function Eb() { return +this._d - 6e4 * (this._offset || 0) } function Fb() { return Math.floor(+this / 1e3) } function Gb() { return this._offset ? new Date(+this) : this._d } function Hb() { var a = this; return [a.year(), a.month(), a.date(), a.hour(), a.minute(), a.second(), a.millisecond()] } function Ib() { var a = this; return { years: a.year(), months: a.month(), date: a.date(), hours: a.hours(), minutes: a.minutes(), seconds: a.seconds(), milliseconds: a.milliseconds() } } function Jb() { return this.isValid() ? this.toISOString() : null } function Kb() { return k(this) } function Lb() { return g({}, j(this)) } function Mb() { return j(this).overflow } function Nb() { return { input: this._i, format: this._f, locale: this._locale, isUTC: this._isUTC, strict: this._strict } } function Ob(a, b) { R(0, [a, a.length], 0, b) } function Pb(a) { return Tb.call(this, a, this.week(), this.weekday(), this.localeData()._week.dow, this.localeData()._week.doy) } function Qb(a) { return Tb.call(this, a, this.isoWeek(), this.isoWeekday(), 1, 4) } function Rb() { return wa(this.year(), 1, 4) } function Sb() { var a = this.localeData()._week; return wa(this.year(), a.dow, a.doy) } function Tb(a, b, c, d, e) { var f; return null == a ? va(this, d, e).year : (f = wa(a, d, e), b > f && (b = f), Ub.call(this, a, b, c, d, e)) } function Ub(a, b, c, d, e) { var f = ua(a, b, c, d, e), g = pa(f.year, 0, f.dayOfYear); return this.year(g.getUTCFullYear()), this.month(g.getUTCMonth()), this.date(g.getUTCDate()), this } function Vb(a) { return null == a ? Math.ceil((this.month() + 1) / 3) : this.month(3 * (a - 1) + this.month() % 3) } function Wb(a) { return va(a, this._week.dow, this._week.doy).week } function Xb() { return this._week.dow } function Yb() { return this._week.doy } function Zb(a) { var b = this.localeData().week(this); return null == a ? b : this.add(7 * (a - b), "d") } function $b(a) { var b = va(this, 1, 4).week; return null == a ? b : this.add(7 * (a - b), "d") } function _b(a, b) { return "string" != typeof a ? a : isNaN(a) ? (a = b.weekdaysParse(a), "number" == typeof a ? a : null) : parseInt(a, 10) } function ac(a, b) { return c(this._weekdays) ? this._weekdays[a.day()] : this._weekdays[this._weekdays.isFormat.test(b) ? "format" : "standalone"][a.day()] } function bc(a) { return this._weekdaysShort[a.day()] } function cc(a) { return this._weekdaysMin[a.day()] } function dc(a, b, c) { var d, e, f; for (this._weekdaysParse || (this._weekdaysParse = [], this._minWeekdaysParse = [], this._shortWeekdaysParse = [], this._fullWeekdaysParse = []), d = 0; 7 > d; d++) { if (e = Ja([2e3, 1]).day(d), c && !this._fullWeekdaysParse[d] && (this._fullWeekdaysParse[d] = new RegExp("^" + this.weekdays(e, "").replace(".", ".?") + "$", "i"), this._shortWeekdaysParse[d] = new RegExp("^" + this.weekdaysShort(e, "").replace(".", ".?") + "$", "i"), this._minWeekdaysParse[d] = new RegExp("^" + this.weekdaysMin(e, "").replace(".", ".?") + "$", "i")), this._weekdaysParse[d] || (f = "^" + this.weekdays(e, "") + "|^" + this.weekdaysShort(e, "") + "|^" + this.weekdaysMin(e, ""), this._weekdaysParse[d] = new RegExp(f.replace(".", ""), "i")), c && "dddd" === b && this._fullWeekdaysParse[d].test(a)) return d; if (c && "ddd" === b && this._shortWeekdaysParse[d].test(a)) return d; if (c && "dd" === b && this._minWeekdaysParse[d].test(a)) return d; if (!c && this._weekdaysParse[d].test(a)) return d } } function ec(a) { if (!this.isValid()) return null != a ? this : NaN; var b = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); return null != a ? (a = _b(a, this.localeData()), this.add(a - b, "d")) : b } function fc(a) { if (!this.isValid()) return null != a ? this : NaN; var b = (this.day() + 7 - this.localeData()._week.dow) % 7; return null == a ? b : this.add(a - b, "d") } function gc(a) { return this.isValid() ? null == a ? this.day() || 7 : this.day(this.day() % 7 ? a : a - 7) : null != a ? this : NaN } function hc(a) { var b = Math.round((this.clone().startOf("day") - this.clone().startOf("year")) / 864e5) + 1; return null == a ? b : this.add(a - b, "d") } function ic() { return this.hours() % 12 || 12 } function jc(a, b) { R(a, 0, 0, function () { return this.localeData().meridiem(this.hours(), this.minutes(), b) }) } function kc(a, b) { return b._meridiemParse } function lc(a) { return "p" === (a + "").toLowerCase().charAt(0) } function mc(a, b, c) { return a > 11 ? c ? "pm" : "PM" : c ? "am" : "AM" } function nc(a, b) { b[Hd] = r(1e3 * ("0." + a)) } function oc() { return this._isUTC ? "UTC" : "" } function pc() { return this._isUTC ? "Coordinated Universal Time" : "" } function qc(a) { return Ja(1e3 * a) } function rc() { return Ja.apply(null, arguments).parseZone() } function sc(a, b, c) { var d = this._calendar[a]; return w(d) ? d.call(b, c) : d } function tc(a) { var b = this._longDateFormat[a], c = this._longDateFormat[a.toUpperCase()]; return b || !c ? b : (this._longDateFormat[a] = c.replace(/MMMM|MM|DD|dddd/g, function (a) { return a.slice(1) }), this._longDateFormat[a]) } function uc() { return this._invalidDate } function vc(a) { return this._ordinal.replace("%d", a) } function wc(a) { return a } function xc(a, b, c, d) { var e = this._relativeTime[c]; return w(e) ? e(a, b, c, d) : e.replace(/%d/i, a) } function yc(a, b) { var c = this._relativeTime[a > 0 ? "future" : "past"]; return w(c) ? c(b) : c.replace(/%s/i, b) } function zc(a, b, c, d) { var e = H(), f = h().set(d, b); return e[c](f, a) } function Ac(a, b, c, d, e) { if ("number" == typeof a && (b = a, a = void 0), a = a || "", null != b) return zc(a, b, c, e); var f, g = []; for (f = 0; d > f; f++) g[f] = zc(a, f, c, e); return g } function Bc(a, b) { return Ac(a, b, "months", 12, "month") } function Cc(a, b) { return Ac(a, b, "monthsShort", 12, "month") } function Dc(a, b) { return Ac(a, b, "weekdays", 7, "day") } function Ec(a, b) { return Ac(a, b, "weekdaysShort", 7, "day") } function Fc(a, b) { return Ac(a, b, "weekdaysMin", 7, "day") } function Gc() { var a = this._data; return this._milliseconds = xe(this._milliseconds), this._days = xe(this._days), this._months = xe(this._months), a.milliseconds = xe(a.milliseconds), a.seconds = xe(a.seconds), a.minutes = xe(a.minutes), a.hours = xe(a.hours), a.months = xe(a.months), a.years = xe(a.years), this } function Hc(a, b, c, d) { var e = cb(b, c); return a._milliseconds += d * e._milliseconds, a._days += d * e._days, a._months += d * e._months, a._bubble() } function Ic(a, b) { return Hc(this, a, b, 1) } function Jc(a, b) { return Hc(this, a, b, -1) } function Kc(a) { return 0 > a ? Math.floor(a) : Math.ceil(a) } function Lc() { var a, b, c, d, e, f = this._milliseconds, g = this._days, h = this._months, i = this._data; return f >= 0 && g >= 0 && h >= 0 || 0 >= f && 0 >= g && 0 >= h || (f += 864e5 * Kc(Nc(h) + g), g = 0, h = 0), i.milliseconds = f % 1e3, a = q(f / 1e3), i.seconds = a % 60, b = q(a / 60), i.minutes = b % 60, c = q(b / 60), i.hours = c % 24, g += q(c / 24), e = q(Mc(g)), h += e, g -= Kc(Nc(e)), d = q(h / 12), h %= 12, i.days = g, i.months = h, i.years = d, this } function Mc(a) { return 4800 * a / 146097 } function Nc(a) { return 146097 * a / 4800 } function Oc(a) { var b, c, d = this._milliseconds; if (a = K(a), "month" === a || "year" === a) return b = this._days + d / 864e5, c = this._months + Mc(b), "month" === a ? c : c / 12; switch (b = this._days + Math.round(Nc(this._months)), a) { case "week": return b / 7 + d / 6048e5; case "day": return b + d / 864e5; case "hour": return 24 * b + d / 36e5; case "minute": return 1440 * b + d / 6e4; case "second": return 86400 * b + d / 1e3; case "millisecond": return Math.floor(864e5 * b) + d; default: throw new Error("Unknown unit " + a) } } function Pc() { return this._milliseconds + 864e5 * this._days + this._months % 12 * 2592e6 + 31536e6 * r(this._months / 12) } function Qc(a) { return function () { return this.as(a) } } function Rc(a) { return a = K(a), this[a + "s"]() } function Sc(a) { return function () { return this._data[a] } } function Tc() { return q(this.days() / 7) } function Uc(a, b, c, d, e) { return e.relativeTime(b || 1, !!c, a, d) } function Vc(a, b, c) { var d = cb(a).abs(), e = Ne(d.as("s")), f = Ne(d.as("m")), g = Ne(d.as("h")), h = Ne(d.as("d")), i = Ne(d.as("M")), j = Ne(d.as("y")), k = e < Oe.s && ["s", e] || 1 >= f && ["m"] || f < Oe.m && ["mm", f] || 1 >= g && ["h"] || g < Oe.h && ["hh", g] || 1 >= h && ["d"] || h < Oe.d && ["dd", h] || 1 >= i && ["M"] || i < Oe.M && ["MM", i] || 1 >= j && ["y"] || ["yy", j]; return k[2] = b, k[3] = +a > 0, k[4] = c, Uc.apply(null, k) } function Wc(a, b) { return void 0 === Oe[a] ? !1 : void 0 === b ? Oe[a] : (Oe[a] = b, !0) } function Xc(a) { var b = this.localeData(), c = Vc(this, !a, b); return a && (c = b.pastFuture(+this, c)), b.postformat(c) } function Yc() { var a, b, c, d = Pe(this._milliseconds) / 1e3, e = Pe(this._days), f = Pe(this._months); a = q(d / 60), b = q(a / 60), d %= 60, a %= 60, c = q(f / 12), f %= 12; var g = c, h = f, i = e, j = b, k = a, l = d, m = this.asSeconds(); return m ? (0 > m ? "-" : "") + "P" + (g ? g + "Y" : "") + (h ? h + "M" : "") + (i ? i + "D" : "") + (j || k || l ? "T" : "") + (j ? j + "H" : "") + (k ? k + "M" : "") + (l ? l + "S" : "") : "P0D" } var Zc, $c = a.momentProperties = [], _c = !1, ad = {}; a.suppressDeprecationWarnings = !1; var bd, cd = {}, dd = {}, ed = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, fd = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, gd = {}, hd = {}, id = /\d/, jd = /\d\d/, kd = /\d{3}/, ld = /\d{4}/, md = /[+-]?\d{6}/, nd = /\d\d?/, od = /\d\d\d\d?/, pd = /\d\d\d\d\d\d?/, qd = /\d{1,3}/, rd = /\d{1,4}/, sd = /[+-]?\d{1,6}/, td = /\d+/, ud = /[+-]?\d+/, vd = /Z|[+-]\d\d:?\d\d/gi, wd = /Z|[+-]\d\d(?::?\d\d)?/gi, xd = /[+-]?\d+(\.\d{1,3})?/, yd = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, zd = {}, Ad = {}, Bd = 0, Cd = 1, Dd = 2, Ed = 3, Fd = 4, Gd = 5, Hd = 6, Id = 7, Jd = 8; R("M", ["MM", 2], "Mo", function () { return this.month() + 1 }), R("MMM", 0, 0, function (a) { return this.localeData().monthsShort(this, a) }), R("MMMM", 0, 0, function (a) { return this.localeData().months(this, a) }), J("month", "M"), W("M", nd), W("MM", nd, jd), W("MMM", function (a, b) { return b.monthsShortRegex(a) }), W("MMMM", function (a, b) { return b.monthsRegex(a) }), $(["M", "MM"], function (a, b) { b[Cd] = r(a) - 1 }), $(["MMM", "MMMM"], function (a, b, c, d) { var e = c._locale.monthsParse(a, d, c._strict); null != e ? b[Cd] = e : j(c).invalidMonth = a }); var Kd = /D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/, Ld = "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), Md = "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), Nd = yd, Od = yd, Pd = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/, Qd = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/, Rd = /Z|[+-]\d\d(?::?\d\d)?/, Sd = [["YYYYYY-MM-DD", /[+-]\d{6}-\d\d-\d\d/], ["YYYY-MM-DD", /\d{4}-\d\d-\d\d/], ["GGGG-[W]WW-E", /\d{4}-W\d\d-\d/], ["GGGG-[W]WW", /\d{4}-W\d\d/, !1], ["YYYY-DDD", /\d{4}-\d{3}/], ["YYYY-MM", /\d{4}-\d\d/, !1], ["YYYYYYMMDD", /[+-]\d{10}/], ["YYYYMMDD", /\d{8}/], ["GGGG[W]WWE", /\d{4}W\d{3}/], ["GGGG[W]WW", /\d{4}W\d{2}/, !1], ["YYYYDDD", /\d{7}/]], Td = [["HH:mm:ss.SSSS", /\d\d:\d\d:\d\d\.\d+/], ["HH:mm:ss,SSSS", /\d\d:\d\d:\d\d,\d+/], ["HH:mm:ss", /\d\d:\d\d:\d\d/], ["HH:mm", /\d\d:\d\d/], ["HHmmss.SSSS", /\d\d\d\d\d\d\.\d+/], ["HHmmss,SSSS", /\d\d\d\d\d\d,\d+/], ["HHmmss", /\d\d\d\d\d\d/], ["HHmm", /\d\d\d\d/], ["HH", /\d\d/]], Ud = /^\/?Date\((\-?\d+)/i; a.createFromInputFallback = u("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.", function (a) { a._d = new Date(a._i + (a._useUTC ? " UTC" : "")) }), R("Y", 0, 0, function () { var a = this.year(); return 9999 >= a ? "" + a : "+" + a }), R(0, ["YY", 2], 0, function () { return this.year() % 100 }), R(0, ["YYYY", 4], 0, "year"), R(0, ["YYYYY", 5], 0, "year"), R(0, ["YYYYYY", 6, !0], 0, "year"), J("year", "y"), W("Y", ud), W("YY", nd, jd), W("YYYY", rd, ld), W("YYYYY", sd, md), W("YYYYYY", sd, md), $(["YYYYY", "YYYYYY"], Bd), $("YYYY", function (b, c) { + c[Bd] = 2 === b.length ? a.parseTwoDigitYear(b) : r(b); + }), $("YY", function (b, c) { c[Bd] = a.parseTwoDigitYear(b) }), $("Y", function (a, b) { b[Bd] = parseInt(a, 10) }), a.parseTwoDigitYear = function (a) { return r(a) + (r(a) > 68 ? 1900 : 2e3) }; var Vd = M("FullYear", !1); a.ISO_8601 = function () { }; var Wd = u("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548", function () { var a = Ja.apply(null, arguments); return this.isValid() && a.isValid() ? this > a ? this : a : l() }), Xd = u("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548", function () { var a = Ja.apply(null, arguments); return this.isValid() && a.isValid() ? a > this ? this : a : l() }), Yd = function () { return Date.now ? Date.now() : +new Date }; Pa("Z", ":"), Pa("ZZ", ""), W("Z", wd), W("ZZ", wd), $(["Z", "ZZ"], function (a, b, c) { c._useUTC = !0, c._tzm = Qa(wd, a) }); var Zd = /([\+\-]|\d\d)/gi; a.updateOffset = function () { }; var $d = /^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/, _d = /^(-)?P(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)W)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?$/; cb.fn = Na.prototype; var ae = hb(1, "add"), be = hb(-1, "subtract"); a.defaultFormat = "YYYY-MM-DDTHH:mm:ssZ"; var ce = u("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.", function (a) { return void 0 === a ? this.localeData() : this.locale(a) }); R(0, ["gg", 2], 0, function () { return this.weekYear() % 100 }), R(0, ["GG", 2], 0, function () { return this.isoWeekYear() % 100 }), Ob("gggg", "weekYear"), Ob("ggggg", "weekYear"), Ob("GGGG", "isoWeekYear"), Ob("GGGGG", "isoWeekYear"), J("weekYear", "gg"), J("isoWeekYear", "GG"), W("G", ud), W("g", ud), W("GG", nd, jd), W("gg", nd, jd), W("GGGG", rd, ld), W("gggg", rd, ld), W("GGGGG", sd, md), W("ggggg", sd, md), _(["gggg", "ggggg", "GGGG", "GGGGG"], function (a, b, c, d) { b[d.substr(0, 2)] = r(a) }), _(["gg", "GG"], function (b, c, d, e) { c[e] = a.parseTwoDigitYear(b) }), R("Q", 0, "Qo", "quarter"), J("quarter", "Q"), W("Q", id), $("Q", function (a, b) { b[Cd] = 3 * (r(a) - 1) }), R("w", ["ww", 2], "wo", "week"), R("W", ["WW", 2], "Wo", "isoWeek"), J("week", "w"), J("isoWeek", "W"), W("w", nd), W("ww", nd, jd), W("W", nd), W("WW", nd, jd), _(["w", "ww", "W", "WW"], function (a, b, c, d) { b[d.substr(0, 1)] = r(a) }); var de = { dow: 0, doy: 6 }; R("D", ["DD", 2], "Do", "date"), J("date", "D"), W("D", nd), W("DD", nd, jd), W("Do", function (a, b) { return a ? b._ordinalParse : b._ordinalParseLenient }), $(["D", "DD"], Dd), $("Do", function (a, b) { b[Dd] = r(a.match(nd)[0], 10) }); var ee = M("Date", !0); R("d", 0, "do", "day"), R("dd", 0, 0, function (a) { return this.localeData().weekdaysMin(this, a) }), R("ddd", 0, 0, function (a) { return this.localeData().weekdaysShort(this, a) }), R("dddd", 0, 0, function (a) { return this.localeData().weekdays(this, a) }), R("e", 0, 0, "weekday"), R("E", 0, 0, "isoWeekday"), J("day", "d"), J("weekday", "e"), J("isoWeekday", "E"), W("d", nd), W("e", nd), W("E", nd), W("dd", yd), W("ddd", yd), W("dddd", yd), _(["dd", "ddd", "dddd"], function (a, b, c, d) { var e = c._locale.weekdaysParse(a, d, c._strict); null != e ? b.d = e : j(c).invalidWeekday = a }), _(["d", "e", "E"], function (a, b, c, d) { b[d] = r(a) }); var fe = "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), ge = "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), he = "Su_Mo_Tu_We_Th_Fr_Sa".split("_"); R("DDD", ["DDDD", 3], "DDDo", "dayOfYear"), J("dayOfYear", "DDD"), W("DDD", qd), W("DDDD", kd), $(["DDD", "DDDD"], function (a, b, c) { c._dayOfYear = r(a) }), R("H", ["HH", 2], 0, "hour"), R("h", ["hh", 2], 0, ic), R("hmm", 0, 0, function () { return "" + ic.apply(this) + Q(this.minutes(), 2) }), R("hmmss", 0, 0, function () { return "" + ic.apply(this) + Q(this.minutes(), 2) + Q(this.seconds(), 2) }), R("Hmm", 0, 0, function () { return "" + this.hours() + Q(this.minutes(), 2) }), R("Hmmss", 0, 0, function () { return "" + this.hours() + Q(this.minutes(), 2) + Q(this.seconds(), 2) }), jc("a", !0), jc("A", !1), J("hour", "h"), W("a", kc), W("A", kc), W("H", nd), W("h", nd), W("HH", nd, jd), W("hh", nd, jd), W("hmm", od), W("hmmss", pd), W("Hmm", od), W("Hmmss", pd), $(["H", "HH"], Ed), $(["a", "A"], function (a, b, c) { c._isPm = c._locale.isPM(a), c._meridiem = a }), $(["h", "hh"], function (a, b, c) { b[Ed] = r(a), j(c).bigHour = !0 }), $("hmm", function (a, b, c) { var d = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d)), j(c).bigHour = !0 }), $("hmmss", function (a, b, c) { var d = a.length - 4, e = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d, 2)), b[Gd] = r(a.substr(e)), j(c).bigHour = !0 }), $("Hmm", function (a, b, c) { var d = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d)) }), $("Hmmss", function (a, b, c) { var d = a.length - 4, e = a.length - 2; b[Ed] = r(a.substr(0, d)), b[Fd] = r(a.substr(d, 2)), b[Gd] = r(a.substr(e)) }); var ie = /[ap]\.?m?\.?/i, je = M("Hours", !0); R("m", ["mm", 2], 0, "minute"), J("minute", "m"), W("m", nd), W("mm", nd, jd), $(["m", "mm"], Fd); var ke = M("Minutes", !1); R("s", ["ss", 2], 0, "second"), J("second", "s"), W("s", nd), W("ss", nd, jd), $(["s", "ss"], Gd); var le = M("Seconds", !1); R("S", 0, 0, function () { return ~~(this.millisecond() / 100) }), R(0, ["SS", 2], 0, function () { return ~~(this.millisecond() / 10) }), R(0, ["SSS", 3], 0, "millisecond"), R(0, ["SSSS", 4], 0, function () { return 10 * this.millisecond() }), R(0, ["SSSSS", 5], 0, function () { return 100 * this.millisecond() }), R(0, ["SSSSSS", 6], 0, function () { return 1e3 * this.millisecond() }), R(0, ["SSSSSSS", 7], 0, function () { return 1e4 * this.millisecond() }), R(0, ["SSSSSSSS", 8], 0, function () { return 1e5 * this.millisecond() }), R(0, ["SSSSSSSSS", 9], 0, function () { return 1e6 * this.millisecond() }), J("millisecond", "ms"), W("S", qd, id), W("SS", qd, jd), W("SSS", qd, kd); var me; for (me = "SSSS"; me.length <= 9; me += "S") W(me, td); for (me = "S"; me.length <= 9; me += "S") $(me, nc); var ne = M("Milliseconds", !1); R("z", 0, 0, "zoneAbbr"), R("zz", 0, 0, "zoneName"); var oe = o.prototype; oe.add = ae, oe.calendar = jb, oe.clone = kb, oe.diff = rb, oe.endOf = Db, oe.format = vb, oe.from = wb, oe.fromNow = xb, oe.to = yb, oe.toNow = zb, oe.get = P, oe.invalidAt = Mb, oe.isAfter = lb, oe.isBefore = mb, oe.isBetween = nb, oe.isSame = ob, oe.isSameOrAfter = pb, oe.isSameOrBefore = qb, oe.isValid = Kb, oe.lang = ce, oe.locale = Ab, oe.localeData = Bb, oe.max = Xd, oe.min = Wd, oe.parsingFlags = Lb, oe.set = P, oe.startOf = Cb, oe.subtract = be, oe.toArray = Hb, oe.toObject = Ib, oe.toDate = Gb, oe.toISOString = ub, oe.toJSON = Jb, oe.toString = tb, oe.unix = Fb, oe.valueOf = Eb, oe.creationData = Nb, oe.year = Vd, oe.isLeapYear = sa, oe.weekYear = Pb, oe.isoWeekYear = Qb, oe.quarter = oe.quarters = Vb, oe.month = ga, oe.daysInMonth = ha, oe.week = oe.weeks = Zb, oe.isoWeek = oe.isoWeeks = $b, oe.weeksInYear = Sb, oe.isoWeeksInYear = Rb, oe.date = ee, oe.day = oe.days = ec, oe.weekday = fc, oe.isoWeekday = gc, oe.dayOfYear = hc, oe.hour = oe.hours = je, oe.minute = oe.minutes = ke, oe.second = oe.seconds = le, oe.millisecond = oe.milliseconds = ne, oe.utcOffset = Ta, oe.utc = Va, oe.local = Wa, oe.parseZone = Xa, oe.hasAlignedHourOffset = Ya, oe.isDST = Za, oe.isDSTShifted = $a, oe.isLocal = _a, oe.isUtcOffset = ab, oe.isUtc = bb, oe.isUTC = bb, oe.zoneAbbr = oc, oe.zoneName = pc, oe.dates = u("dates accessor is deprecated. Use date instead.", ee), oe.months = u("months accessor is deprecated. Use month instead", ga), oe.years = u("years accessor is deprecated. Use year instead", Vd), oe.zone = u("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779", Ua); var pe = oe, qe = { sameDay: "[Today at] LT", nextDay: "[Tomorrow at] LT", nextWeek: "dddd [at] LT", lastDay: "[Yesterday at] LT", lastWeek: "[Last] dddd [at] LT", sameElse: "L" }, re = { LTS: "h:mm:ss A", LT: "h:mm A", L: "MM/DD/YYYY", LL: "MMMM D, YYYY", LLL: "MMMM D, YYYY h:mm A", LLLL: "dddd, MMMM D, YYYY h:mm A" }, se = "Invalid date", te = "%d", ue = /\d{1,2}/, ve = { future: "in %s", past: "%s ago", s: "a few seconds", m: "a minute", mm: "%d minutes", h: "an hour", hh: "%d hours", d: "a day", dd: "%d days", M: "a month", MM: "%d months", y: "a year", yy: "%d years" }, we = A.prototype; we._calendar = qe, we.calendar = sc, we._longDateFormat = re, we.longDateFormat = tc, we._invalidDate = se, we.invalidDate = uc, we._ordinal = te, we.ordinal = vc, we._ordinalParse = ue, we.preparse = wc, we.postformat = wc, we._relativeTime = ve, we.relativeTime = xc, we.pastFuture = yc, we.set = y, we.months = ca, we._months = Ld, we.monthsShort = da, we._monthsShort = Md, we.monthsParse = ea, we._monthsRegex = Od, we.monthsRegex = ja, we._monthsShortRegex = Nd, we.monthsShortRegex = ia, we.week = Wb, we._week = de, we.firstDayOfYear = Yb, we.firstDayOfWeek = Xb, we.weekdays = ac, we._weekdays = fe, we.weekdaysMin = cc, we._weekdaysMin = he, we.weekdaysShort = bc, we._weekdaysShort = ge, we.weekdaysParse = dc, we.isPM = lc, we._meridiemParse = ie, we.meridiem = mc, E("en", { ordinalParse: /\d{1,2}(th|st|nd|rd)/, ordinal: function (a) { var b = a % 10, c = 1 === r(a % 100 / 10) ? "th" : 1 === b ? "st" : 2 === b ? "nd" : 3 === b ? "rd" : "th"; return a + c } }), a.lang = u("moment.lang is deprecated. Use moment.locale instead.", E), a.langData = u("moment.langData is deprecated. Use moment.localeData instead.", H); var xe = Math.abs, ye = Qc("ms"), ze = Qc("s"), Ae = Qc("m"), Be = Qc("h"), Ce = Qc("d"), De = Qc("w"), Ee = Qc("M"), Fe = Qc("y"), Ge = Sc("milliseconds"), He = Sc("seconds"), Ie = Sc("minutes"), Je = Sc("hours"), Ke = Sc("days"), Le = Sc("months"), Me = Sc("years"), Ne = Math.round, Oe = { s: 45, m: 45, h: 22, d: 26, M: 11 }, Pe = Math.abs, Qe = Na.prototype; Qe.abs = Gc, Qe.add = Ic, Qe.subtract = Jc, Qe.as = Oc, Qe.asMilliseconds = ye, Qe.asSeconds = ze, Qe.asMinutes = Ae, Qe.asHours = Be, Qe.asDays = Ce, Qe.asWeeks = De, Qe.asMonths = Ee, Qe.asYears = Fe, Qe.valueOf = Pc, Qe._bubble = Lc, Qe.get = Rc, Qe.milliseconds = Ge, Qe.seconds = He, Qe.minutes = Ie, Qe.hours = Je, Qe.days = Ke, Qe.weeks = Tc, Qe.months = Le, Qe.years = Me, Qe.humanize = Xc, Qe.toISOString = Yc, Qe.toString = Yc, Qe.toJSON = Yc, Qe.locale = Ab, Qe.localeData = Bb, Qe.toIsoString = u("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)", Yc), Qe.lang = ce, R("X", 0, 0, "unix"), R("x", 0, 0, "valueOf"), W("x", ud), W("X", xd), $("X", function (a, b, c) { c._d = new Date(1e3 * parseFloat(a, 10)) }), $("x", function (a, b, c) { c._d = new Date(r(a)) }), a.version = "2.12.0", b(Ja), a.fn = pe, a.min = La, a.max = Ma, a.now = Yd, a.utc = h, a.unix = qc, a.months = Bc, a.isDate = d, a.locale = E, a.invalid = l, a.duration = cb, a.isMoment = p, a.weekdays = Dc, a.parseZone = rc, a.localeData = H, a.isDuration = Oa, a.monthsShort = Cc, a.weekdaysMin = Fc, a.defineLocale = F, a.updateLocale = G, a.locales = I, a.weekdaysShort = Ec, a.normalizeUnits = K, a.relativeTimeThreshold = Wc, a.prototype = pe; var Re = a; return Re }); \ No newline at end of file diff --git a/PlexRequests.UI/Content/pace.min.js b/PlexRequests.UI/Content/pace.min.js index 5a3ef3d4d..c94b70ea2 100644 --- a/PlexRequests.UI/Content/pace.min.js +++ b/PlexRequests.UI/Content/pace.min.js @@ -1,2 +1,2 @@ -/*! pace 1.0.0 */ +/*! pace 1.0.0 */ (function () { var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X = [].slice, Y = {}.hasOwnProperty, Z = function (a, b) { function c() { this.constructor = a } for (var d in b) Y.call(b, d) && (a[d] = b[d]); return c.prototype = b.prototype, a.prototype = new c, a.__super__ = b.prototype, a }, $ = [].indexOf || function (a) { for (var b = 0, c = this.length; c > b; b++) if (b in this && this[b] === a) return b; return -1 }; for (u = { catchupTime: 100, initialRate: .03, minTime: 250, ghostTime: 100, maxProgressPerFrame: 20, easeFactor: 1.25, startOnPageLoad: !0, restartOnPushState: !0, restartOnRequestAfter: 500, target: "body", elements: { checkInterval: 100, selectors: ["body"] }, eventLag: { minSamples: 10, sampleCount: 3, lagThreshold: 3 }, ajax: { trackMethods: ["GET"], trackWebSockets: !0, ignoreURLs: [] } }, C = function () { var a; return null != (a = "undefined" != typeof performance && null !== performance && "function" == typeof performance.now ? performance.now() : void 0) ? a : +new Date }, E = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame, t = window.cancelAnimationFrame || window.mozCancelAnimationFrame, null == E && (E = function (a) { return setTimeout(a, 50) }, t = function (a) { return clearTimeout(a) }), G = function (a) { var b, c; return b = C(), (c = function () { var d; return d = C() - b, d >= 33 ? (b = C(), a(d, function () { return E(c) })) : setTimeout(c, 33 - d) })() }, F = function () { var a, b, c; return c = arguments[0], b = arguments[1], a = 3 <= arguments.length ? X.call(arguments, 2) : [], "function" == typeof c[b] ? c[b].apply(c, a) : c[b] }, v = function () { var a, b, c, d, e, f, g; for (b = arguments[0], d = 2 <= arguments.length ? X.call(arguments, 1) : [], f = 0, g = d.length; g > f; f++) if (c = d[f]) for (a in c) Y.call(c, a) && (e = c[a], null != b[a] && "object" == typeof b[a] && null != e && "object" == typeof e ? v(b[a], e) : b[a] = e); return b }, q = function (a) { var b, c, d, e, f; for (c = b = 0, e = 0, f = a.length; f > e; e++) d = a[e], c += Math.abs(d), b++; return c / b }, x = function (a, b) { var c, d, e; if (null == a && (a = "options"), null == b && (b = !0), e = document.querySelector("[data-pace-" + a + "]")) { if (c = e.getAttribute("data-pace-" + a), !b) return c; try { return JSON.parse(c) } catch (f) { return d = f, "undefined" != typeof console && null !== console ? console.error("Error parsing inline pace options", d) : void 0 } } }, g = function () { function a() { } return a.prototype.on = function (a, b, c, d) { var e; return null == d && (d = !1), null == this.bindings && (this.bindings = {}), null == (e = this.bindings)[a] && (e[a] = []), this.bindings[a].push({ handler: b, ctx: c, once: d }) }, a.prototype.once = function (a, b, c) { return this.on(a, b, c, !0) }, a.prototype.off = function (a, b) { var c, d, e; if (null != (null != (d = this.bindings) ? d[a] : void 0)) { if (null == b) return delete this.bindings[a]; for (c = 0, e = []; c < this.bindings[a].length;) e.push(this.bindings[a][c].handler === b ? this.bindings[a].splice(c, 1) : c++); return e } }, a.prototype.trigger = function () { var a, b, c, d, e, f, g, h, i; if (c = arguments[0], a = 2 <= arguments.length ? X.call(arguments, 1) : [], null != (g = this.bindings) ? g[c] : void 0) { for (e = 0, i = []; e < this.bindings[c].length;) h = this.bindings[c][e], d = h.handler, b = h.ctx, f = h.once, d.apply(null != b ? b : this, a), i.push(f ? this.bindings[c].splice(e, 1) : e++); return i } }, a }(), j = window.Pace || {}, window.Pace = j, v(j, g.prototype), D = j.options = v({}, u, window.paceOptions, x()), U = ["ajax", "document", "eventLag", "elements"], Q = 0, S = U.length; S > Q; Q++) K = U[Q], D[K] === !0 && (D[K] = u[K]); i = function (a) { function b() { return V = b.__super__.constructor.apply(this, arguments) } return Z(b, a), b }(Error), b = function () { function a() { this.progress = 0 } return a.prototype.getElement = function () { var a; if (null == this.el) { if (a = document.querySelector(D.target), !a) throw new i; this.el = document.createElement("div"), this.el.className = "pace pace-active", document.body.className = document.body.className.replace(/pace-done/g, ""), document.body.className += " pace-running", this.el.innerHTML = '
\n
\n
\n
', null != a.firstChild ? a.insertBefore(this.el, a.firstChild) : a.appendChild(this.el) } return this.el }, a.prototype.finish = function () { var a; return a = this.getElement(), a.className = a.className.replace("pace-active", ""), a.className += " pace-inactive", document.body.className = document.body.className.replace("pace-running", ""), document.body.className += " pace-done" }, a.prototype.update = function (a) { return this.progress = a, this.render() }, a.prototype.destroy = function () { try { this.getElement().parentNode.removeChild(this.getElement()) } catch (a) { i = a } return this.el = void 0 }, a.prototype.render = function () { var a, b, c, d, e, f, g; if (null == document.querySelector(D.target)) return !1; for (a = this.getElement(), d = "translate3d(" + this.progress + "%, 0, 0)", g = ["webkitTransform", "msTransform", "transform"], e = 0, f = g.length; f > e; e++) b = g[e], a.children[0].style[b] = d; return (!this.lastRenderedProgress || this.lastRenderedProgress | 0 !== this.progress | 0) && (a.children[0].setAttribute("data-progress-text", "" + (0 | this.progress) + "%"), this.progress >= 100 ? c = "99" : (c = this.progress < 10 ? "0" : "", c += 0 | this.progress), a.children[0].setAttribute("data-progress", "" + c)), this.lastRenderedProgress = this.progress }, a.prototype.done = function () { return this.progress >= 100 }, a }(), h = function () { function a() { this.bindings = {} } return a.prototype.trigger = function (a, b) { var c, d, e, f, g; if (null != this.bindings[a]) { for (f = this.bindings[a], g = [], d = 0, e = f.length; e > d; d++) c = f[d], g.push(c.call(this, b)); return g } }, a.prototype.on = function (a, b) { var c; return null == (c = this.bindings)[a] && (c[a] = []), this.bindings[a].push(b) }, a }(), P = window.XMLHttpRequest, O = window.XDomainRequest, N = window.WebSocket, w = function (a, b) { var c, d, e, f; f = []; for (d in b.prototype) try { e = b.prototype[d], f.push(null == a[d] && "function" != typeof e ? a[d] = e : void 0) } catch (g) { c = g } return f }, A = [], j.ignore = function () { var a, b, c; return b = arguments[0], a = 2 <= arguments.length ? X.call(arguments, 1) : [], A.unshift("ignore"), c = b.apply(null, a), A.shift(), c }, j.track = function () { var a, b, c; return b = arguments[0], a = 2 <= arguments.length ? X.call(arguments, 1) : [], A.unshift("track"), c = b.apply(null, a), A.shift(), c }, J = function (a) { var b; if (null == a && (a = "GET"), "track" === A[0]) return "force"; if (!A.length && D.ajax) { if ("socket" === a && D.ajax.trackWebSockets) return !0; if (b = a.toUpperCase(), $.call(D.ajax.trackMethods, b) >= 0) return !0 } return !1 }, k = function (a) { function b() { var a, c = this; b.__super__.constructor.apply(this, arguments), a = function (a) { var b; return b = a.open, a.open = function (d, e) { return J(d) && c.trigger("request", { type: d, url: e, request: a }), b.apply(a, arguments) } }, window.XMLHttpRequest = function (b) { var c; return c = new P(b), a(c), c }; try { w(window.XMLHttpRequest, P) } catch (d) { } if (null != O) { window.XDomainRequest = function () { var b; return b = new O, a(b), b }; try { w(window.XDomainRequest, O) } catch (d) { } } if (null != N && D.ajax.trackWebSockets) { window.WebSocket = function (a, b) { var d; return d = null != b ? new N(a, b) : new N(a), J("socket") && c.trigger("request", { type: "socket", url: a, protocols: b, request: d }), d }; try { w(window.WebSocket, N) } catch (d) { } } } return Z(b, a), b }(h), R = null, y = function () { return null == R && (R = new k), R }, I = function (a) { var b, c, d, e; for (e = D.ajax.ignoreURLs, c = 0, d = e.length; d > c; c++) if (b = e[c], "string" == typeof b) { if (-1 !== a.indexOf(b)) return !0 } else if (b.test(a)) return !0; return !1 }, y().on("request", function (b) { var c, d, e, f, g; return f = b.type, e = b.request, g = b.url, I(g) ? void 0 : j.running || D.restartOnRequestAfter === !1 && "force" !== J(f) ? void 0 : (d = arguments, c = D.restartOnRequestAfter || 0, "boolean" == typeof c && (c = 0), setTimeout(function () { var b, c, g, h, i, k; if (b = "socket" === f ? e.readyState < 2 : 0 < (h = e.readyState) && 4 > h) { for (j.restart(), i = j.sources, k = [], c = 0, g = i.length; g > c; c++) { if (K = i[c], K instanceof a) { K.watch.apply(K, d); break } k.push(void 0) } return k } }, c)) }), a = function () { function a() { var a = this; this.elements = [], y().on("request", function () { return a.watch.apply(a, arguments) }) } return a.prototype.watch = function (a) { var b, c, d, e; return d = a.type, b = a.request, e = a.url, I(e) ? void 0 : (c = "socket" === d ? new n(b) : new o(b), this.elements.push(c)) }, a }(), o = function () { function a(a) { var b, c, d, e, f, g, h = this; if (this.progress = 0, null != window.ProgressEvent) for (c = null, a.addEventListener("progress", function (a) { return h.progress = a.lengthComputable ? 100 * a.loaded / a.total : h.progress + (100 - h.progress) / 2 }, !1), g = ["load", "abort", "timeout", "error"], d = 0, e = g.length; e > d; d++) b = g[d], a.addEventListener(b, function () { return h.progress = 100 }, !1); else f = a.onreadystatechange, a.onreadystatechange = function () { var b; return 0 === (b = a.readyState) || 4 === b ? h.progress = 100 : 3 === a.readyState && (h.progress = 50), "function" == typeof f ? f.apply(null, arguments) : void 0 } } return a }(), n = function () { function a(a) { var b, c, d, e, f = this; for (this.progress = 0, e = ["error", "open"], c = 0, d = e.length; d > c; c++) b = e[c], a.addEventListener(b, function () { return f.progress = 100 }, !1) } return a }(), d = function () { function a(a) { var b, c, d, f; for (null == a && (a = {}), this.elements = [], null == a.selectors && (a.selectors = []), f = a.selectors, c = 0, d = f.length; d > c; c++) b = f[c], this.elements.push(new e(b)) } return a }(), e = function () { function a(a) { this.selector = a, this.progress = 0, this.check() } return a.prototype.check = function () { var a = this; return document.querySelector(this.selector) ? this.done() : setTimeout(function () { return a.check() }, D.elements.checkInterval) }, a.prototype.done = function () { return this.progress = 100 }, a }(), c = function () { function a() { var a, b, c = this; this.progress = null != (b = this.states[document.readyState]) ? b : 100, a = document.onreadystatechange, document.onreadystatechange = function () { return null != c.states[document.readyState] && (c.progress = c.states[document.readyState]), "function" == typeof a ? a.apply(null, arguments) : void 0 } } return a.prototype.states = { loading: 0, interactive: 50, complete: 100 }, a }(), f = function () { function a() { var a, b, c, d, e, f = this; this.progress = 0, a = 0, e = [], d = 0, c = C(), b = setInterval(function () { var g; return g = C() - c - 50, c = C(), e.push(g), e.length > D.eventLag.sampleCount && e.shift(), a = q(e), ++d >= D.eventLag.minSamples && a < D.eventLag.lagThreshold ? (f.progress = 100, clearInterval(b)) : f.progress = 100 * (3 / (a + 3)) }, 50) } return a }(), m = function () { function a(a) { this.source = a, this.last = this.sinceLastUpdate = 0, this.rate = D.initialRate, this.catchup = 0, this.progress = this.lastProgress = 0, null != this.source && (this.progress = F(this.source, "progress")) } return a.prototype.tick = function (a, b) { var c; return null == b && (b = F(this.source, "progress")), b >= 100 && (this.done = !0), b === this.last ? this.sinceLastUpdate += a : (this.sinceLastUpdate && (this.rate = (b - this.last) / this.sinceLastUpdate), this.catchup = (b - this.progress) / D.catchupTime, this.sinceLastUpdate = 0, this.last = b), b > this.progress && (this.progress += this.catchup * a), c = 1 - Math.pow(this.progress / 100, D.easeFactor), this.progress += c * this.rate * a, this.progress = Math.min(this.lastProgress + D.maxProgressPerFrame, this.progress), this.progress = Math.max(0, this.progress), this.progress = Math.min(100, this.progress), this.lastProgress = this.progress, this.progress }, a }(), L = null, H = null, r = null, M = null, p = null, s = null, j.running = !1, z = function () { return D.restartOnPushState ? j.restart() : void 0 }, null != window.history.pushState && (T = window.history.pushState, window.history.pushState = function () { return z(), T.apply(window.history, arguments) }), null != window.history.replaceState && (W = window.history.replaceState, window.history.replaceState = function () { return z(), W.apply(window.history, arguments) }), l = { ajax: a, elements: d, document: c, eventLag: f }, (B = function () { var a, c, d, e, f, g, h, i; for (j.sources = L = [], g = ["ajax", "elements", "document", "eventLag"], c = 0, e = g.length; e > c; c++) a = g[c], D[a] !== !1 && L.push(new l[a](D[a])); for (i = null != (h = D.extraSources) ? h : [], d = 0, f = i.length; f > d; d++) K = i[d], L.push(new K(D)); return j.bar = r = new b, H = [], M = new m })(), j.stop = function () { return j.trigger("stop"), j.running = !1, r.destroy(), s = !0, null != p && ("function" == typeof t && t(p), p = null), B() }, j.restart = function () { return j.trigger("restart"), j.stop(), j.start() }, j.go = function () { var a; return j.running = !0, r.render(), a = C(), s = !1, p = G(function (b, c) { var d, e, f, g, h, i, k, l, n, o, p, q, t, u, v, w; for (l = 100 - r.progress, e = p = 0, f = !0, i = q = 0, u = L.length; u > q; i = ++q) for (K = L[i], o = null != H[i] ? H[i] : H[i] = [], h = null != (w = K.elements) ? w : [K], k = t = 0, v = h.length; v > t; k = ++t) g = h[k], n = null != o[k] ? o[k] : o[k] = new m(g), f &= n.done, n.done || (e++, p += n.tick(b)); return d = p / e, r.update(M.tick(b, d)), r.done() || f || s ? (r.update(100), j.trigger("done"), setTimeout(function () { return r.finish(), j.running = !1, j.trigger("hide") }, Math.max(D.ghostTime, Math.max(D.minTime - (C() - a), 0)))) : c() }) }, j.start = function (a) { v(D, a), j.running = !0; try { r.render() } catch (b) { i = b } return document.querySelector(".pace") ? (j.trigger("start"), j.go()) : setTimeout(j.start, 50) }, "function" == typeof define && define.amd ? define(function () { return j }) : "object" == typeof exports ? module.exports = j : D.startOnPageLoad && j.start() }).call(this); \ No newline at end of file diff --git a/PlexRequests.UI/Content/pace.scss b/PlexRequests.UI/Content/pace.scss index 4ae613086..5e96b8bd4 100644 --- a/PlexRequests.UI/Content/pace.scss +++ b/PlexRequests.UI/Content/pace.scss @@ -1,24 +1,24 @@ -$primary-colour: rgb(255, 164, 0); - -.pace { - -webkit-pointer-events: none; - pointer-events: none; - - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; -} - -.pace-inactive { - display: none; -} - -.pace .pace-progress { - background: $primary-colour; - position: fixed; - z-index: 2000; - top: 0; - right: 100%; - width: 100%; - height: 2px; +$primary-colour: rgb(255, 164, 0); + +.pace { + -webkit-pointer-events: none; + pointer-events: none; + + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.pace-inactive { + display: none; +} + +.pace .pace-progress { + background: $primary-colour; + position: fixed; + z-index: 2000; + top: 0; + right: 100%; + width: 100%; + height: 2px; } \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/HeadphonesSender.cs b/PlexRequests.UI/Helpers/HeadphonesSender.cs index 17674e891..29c428612 100644 --- a/PlexRequests.UI/Helpers/HeadphonesSender.cs +++ b/PlexRequests.UI/Helpers/HeadphonesSender.cs @@ -1,176 +1,176 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: HeadphonesSender.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.Linq; -using System.Threading; -using System.Threading.Tasks; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Store; - -namespace PlexRequests.UI.Helpers -{ - public class HeadphonesSender - { - public HeadphonesSender(IHeadphonesApi api, HeadphonesSettings settings, IRequestService request) - { - Api = api; - Settings = settings; - RequestService = request; - } - - private int WaitTime => 2000; - private int CounterMax => 60; - - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private IHeadphonesApi Api { get; } - private IRequestService RequestService { get; } - private HeadphonesSettings Settings { get; } - - public async Task AddAlbum(RequestedModel request) - { - var addArtistResult = await AddArtist(request); - if (!addArtistResult) - { - return false; - } - - // Artist is now active - // Add album - var albumResult = await Api.AddAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId); - if (!albumResult) - { - Log.Error("Couldn't add the album to headphones"); - } - - // Set the status to wanted and search - var status = await SetAlbumStatus(request); - if (!status) - { - return false; - } - - // Approve it - request.Approved = true; - - // Update the record - var updated = RequestService.UpdateRequest(request); - - return updated; - } - - private async Task AddArtist(RequestedModel request) - { - var index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); - var artistExists = index.Any(x => x.ArtistID == request.ArtistId); - if (!artistExists) - { - var artistAdd = Api.AddArtist(Settings.ApiKey, Settings.FullUri, request.ArtistId); - Log.Info("Artist add result : {0}", artistAdd); - } - - var counter = 0; - while (index.All(x => x.ArtistID != request.ArtistId)) - { - Thread.Sleep(WaitTime); - counter++; - Log.Trace("Artist is still not present in the index. Counter = {0}", counter); - index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); - - if (counter > CounterMax) - { - Log.Trace("Artist is still not present in the index. Counter = {0}. Returning false", counter); - Log.Warn("We have tried adding the artist but it seems they are still not in headphones."); - return false; - } - } - - counter = 0; - var artistStatus = index.Where(x => x.ArtistID == request.ArtistId).Select(x => x.Status).FirstOrDefault(); - while (artistStatus != "Active") - { - Thread.Sleep(WaitTime); - counter++; - Log.Trace("Artist status {1}. Counter = {0}", counter, artistStatus); - index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); - artistStatus = index.Where(x => x.ArtistID == request.ArtistId).Select(x => x.Status).FirstOrDefault(); - if (counter > CounterMax) - { - Log.Trace("Artist status is still not active. Counter = {0}. Returning false", counter); - Log.Warn("The artist status is still not Active. We have waited long enough, seems to be a big delay in headphones."); - return false; - } - } - - var addedArtist = index.FirstOrDefault(x => x.ArtistID == request.ArtistId); - var artistName = addedArtist?.ArtistName ?? string.Empty; - counter = 0; - while (artistName.Contains("Fetch failed")) - { - Thread.Sleep(WaitTime); - await Api.RefreshArtist(Settings.ApiKey, Settings.FullUri, request.ArtistId); - - index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); - - artistName = index?.FirstOrDefault(x => x.ArtistID == request.ArtistId)?.ArtistName ?? string.Empty; - counter++; - if (counter > CounterMax) - { - Log.Trace("Artist fetch has failed. Counter = {0}. Returning false", counter); - Log.Warn("Artist in headphones fetch has failed, we have tried refreshing the artist but no luck."); - return false; - } - } - - return true; - } - - private async Task SetAlbumStatus(RequestedModel request) - { - var counter = 0; - var setStatus = await Api.QueueAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId); - - while (!setStatus) - { - Thread.Sleep(WaitTime); - counter++; - Log.Trace("Setting Album status. Counter = {0}", counter); - setStatus = await Api.QueueAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId); - if (counter > CounterMax) - { - Log.Trace("Album status is still not active. Counter = {0}. Returning false", counter); - Log.Warn("We tried to se the status for the album but headphones didn't want to snatch it."); - return false; - } - } - return true; - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: HeadphonesSender.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.Linq; +using System.Threading; +using System.Threading.Tasks; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Store; + +namespace PlexRequests.UI.Helpers +{ + public class HeadphonesSender + { + public HeadphonesSender(IHeadphonesApi api, HeadphonesSettings settings, IRequestService request) + { + Api = api; + Settings = settings; + RequestService = request; + } + + private int WaitTime => 2000; + private int CounterMax => 60; + + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private IHeadphonesApi Api { get; } + private IRequestService RequestService { get; } + private HeadphonesSettings Settings { get; } + + public async Task AddAlbum(RequestedModel request) + { + var addArtistResult = await AddArtist(request); + if (!addArtistResult) + { + return false; + } + + // Artist is now active + // Add album + var albumResult = await Api.AddAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId); + if (!albumResult) + { + Log.Error("Couldn't add the album to headphones"); + } + + // Set the status to wanted and search + var status = await SetAlbumStatus(request); + if (!status) + { + return false; + } + + // Approve it + request.Approved = true; + + // Update the record + var updated = RequestService.UpdateRequest(request); + + return updated; + } + + private async Task AddArtist(RequestedModel request) + { + var index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); + var artistExists = index.Any(x => x.ArtistID == request.ArtistId); + if (!artistExists) + { + var artistAdd = Api.AddArtist(Settings.ApiKey, Settings.FullUri, request.ArtistId); + Log.Info("Artist add result : {0}", artistAdd); + } + + var counter = 0; + while (index.All(x => x.ArtistID != request.ArtistId)) + { + Thread.Sleep(WaitTime); + counter++; + Log.Trace("Artist is still not present in the index. Counter = {0}", counter); + index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); + + if (counter > CounterMax) + { + Log.Trace("Artist is still not present in the index. Counter = {0}. Returning false", counter); + Log.Warn("We have tried adding the artist but it seems they are still not in headphones."); + return false; + } + } + + counter = 0; + var artistStatus = index.Where(x => x.ArtistID == request.ArtistId).Select(x => x.Status).FirstOrDefault(); + while (artistStatus != "Active") + { + Thread.Sleep(WaitTime); + counter++; + Log.Trace("Artist status {1}. Counter = {0}", counter, artistStatus); + index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); + artistStatus = index.Where(x => x.ArtistID == request.ArtistId).Select(x => x.Status).FirstOrDefault(); + if (counter > CounterMax) + { + Log.Trace("Artist status is still not active. Counter = {0}. Returning false", counter); + Log.Warn("The artist status is still not Active. We have waited long enough, seems to be a big delay in headphones."); + return false; + } + } + + var addedArtist = index.FirstOrDefault(x => x.ArtistID == request.ArtistId); + var artistName = addedArtist?.ArtistName ?? string.Empty; + counter = 0; + while (artistName.Contains("Fetch failed")) + { + Thread.Sleep(WaitTime); + await Api.RefreshArtist(Settings.ApiKey, Settings.FullUri, request.ArtistId); + + index = await Api.GetIndex(Settings.ApiKey, Settings.FullUri); + + artistName = index?.FirstOrDefault(x => x.ArtistID == request.ArtistId)?.ArtistName ?? string.Empty; + counter++; + if (counter > CounterMax) + { + Log.Trace("Artist fetch has failed. Counter = {0}. Returning false", counter); + Log.Warn("Artist in headphones fetch has failed, we have tried refreshing the artist but no luck."); + return false; + } + } + + return true; + } + + private async Task SetAlbumStatus(RequestedModel request) + { + var counter = 0; + var setStatus = await Api.QueueAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId); + + while (!setStatus) + { + Thread.Sleep(WaitTime); + counter++; + Log.Trace("Setting Album status. Counter = {0}", counter); + setStatus = await Api.QueueAlbum(Settings.ApiKey, Settings.FullUri, request.MusicBrainzId); + if (counter > CounterMax) + { + Log.Trace("Album status is still not active. Counter = {0}. Returning false", counter); + Log.Warn("We tried to se the status for the album but headphones didn't want to snatch it."); + return false; + } + } + return true; + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Jobs/Scheduler.cs b/PlexRequests.UI/Jobs/Scheduler.cs index bfa26a54d..669500168 100644 --- a/PlexRequests.UI/Jobs/Scheduler.cs +++ b/PlexRequests.UI/Jobs/Scheduler.cs @@ -1,4 +1,5 @@ #region Copyright + // /************************************************************************ // Copyright (c) 2016 Jamie Rees // File: Scheduler.cs @@ -23,18 +24,17 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ + #endregion + using System; using System.Collections.Generic; using System.Linq; - using NLog; - using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Services.Jobs; using PlexRequests.UI.Helpers; - using Quartz; using Quartz.Impl; @@ -61,20 +61,17 @@ namespace PlexRequests.UI.Jobs var jobList = new List { - JobBuilder.Create().WithIdentity("PlexAvailabilityChecker", "Plex").Build(), - JobBuilder.Create().WithIdentity("PlexEpisodeCacher", "Cache").Build(), - JobBuilder.Create().WithIdentity("SickRageCacher", "Cache").Build(), - JobBuilder.Create().WithIdentity("SonarrCacher", "Cache").Build(), - JobBuilder.Create().WithIdentity("CouchPotatoCacher", "Cache").Build(), - JobBuilder.Create().WithIdentity("StoreBackup", "Database").Build(), - JobBuilder.Create().WithIdentity("StoreCleanup", "Database").Build(), - JobBuilder.Create().WithIdentity("UserRequestLimiter", "Request").Build(), + JobBuilder.Create().WithIdentity("PlexAvailabilityChecker", "Plex").Build(), + JobBuilder.Create().WithIdentity("PlexEpisodeCacher", "Cache").Build(), + JobBuilder.Create().WithIdentity("SickRageCacher", "Cache").Build(), + JobBuilder.Create().WithIdentity("SonarrCacher", "Cache").Build(), + JobBuilder.Create().WithIdentity("CouchPotatoCacher", "Cache").Build(), + JobBuilder.Create().WithIdentity("StoreBackup", "Database").Build(), + JobBuilder.Create().WithIdentity("StoreCleanup", "Database").Build(), + JobBuilder.Create().WithIdentity("UserRequestLimiter", "Request").Build(), + JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build() }; - if (!string.IsNullOrEmpty(s.RecentlyAddedCron)) - { - jobList.Add(JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build()); - } jobs.AddRange(jobList); @@ -115,6 +112,47 @@ namespace PlexRequests.UI.Jobs var settingsService = Service.Resolve>(); var s = settingsService.GetSettings(); + if (s.CouchPotatoCacher == 0) + { + s.CouchPotatoCacher = 60; + } + if (s.PlexAvailabilityChecker == 0) + { + s.PlexAvailabilityChecker = 60; + } + if (s.PlexEpisodeCacher == 0) + { + s.PlexEpisodeCacher = 11; + } + if (string.IsNullOrEmpty(s.RecentlyAddedCron)) + { + var cron = + (Quartz.Impl.Triggers.CronTriggerImpl) + CronScheduleBuilder.WeeklyOnDayAndHourAndMinute(DayOfWeek.Friday, 7, 0).Build(); + s.RecentlyAddedCron = cron.CronExpressionString; // Weekly CRON at 7 am on Mondays + } + if (s.SickRageCacher == 0) + { + s.SickRageCacher = 60; + } + if (s.SonarrCacher == 0) + { + s.SonarrCacher = 60; + } + if (s.StoreBackup == 0) + { + s.StoreBackup = 24; + } + if (s.StoreCleanup == 0) + { + s.StoreCleanup = 24; + } + if (s.UserRequestLimitResetter == 0) + { + s.UserRequestLimitResetter = 12; + } + + var triggers = new List(); var plexAvailabilityChecker = @@ -175,22 +213,15 @@ namespace PlexRequests.UI.Jobs .Build(); - var cronJob = string.IsNullOrEmpty(s.RecentlyAddedCron); - if (!cronJob) - { - var rencentlyAdded = - TriggerBuilder.Create() - .WithIdentity("RecentlyAddedModel", "Email") - .StartNow() - .WithCronSchedule(s.RecentlyAddedCron) - .WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever()) - .Build(); - - triggers.Add(rencentlyAdded); - } - - + var rencentlyAdded = + TriggerBuilder.Create() + .WithIdentity("RecentlyAddedModel", "Email") + .StartNow() + .WithCronSchedule(s.RecentlyAddedCron) + .WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever()) + .Build(); + triggers.Add(rencentlyAdded); triggers.Add(plexAvailabilityChecker); triggers.Add(srCacher); triggers.Add(sonarrCacher); diff --git a/PlexRequests.UI/Models/DatatablesModel.cs b/PlexRequests.UI/Models/DatatablesModel.cs index 8d1193bf0..3437f2873 100644 --- a/PlexRequests.UI/Models/DatatablesModel.cs +++ b/PlexRequests.UI/Models/DatatablesModel.cs @@ -1,36 +1,36 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: DatatablesModel.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; - -namespace PlexRequests.UI.Models -{ - public class DatatablesModel - { - public List Data { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: DatatablesModel.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; + +namespace PlexRequests.UI.Models +{ + public class DatatablesModel + { + public List Data { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Models/JsonResponseModel.cs b/PlexRequests.UI/Models/JsonResponseModel.cs index affaa3bde..9595d3dd8 100644 --- a/PlexRequests.UI/Models/JsonResponseModel.cs +++ b/PlexRequests.UI/Models/JsonResponseModel.cs @@ -1,34 +1,34 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: JsonResponseModel.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.UI.Models -{ - public class JsonResponseModel - { - public bool Result { get; set; } - public string Message { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: JsonResponseModel.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.UI.Models +{ + public class JsonResponseModel + { + public bool Result { get; set; } + public string Message { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Models/MovieSearchType.cs b/PlexRequests.UI/Models/MovieSearchType.cs index 9441ee15f..f0484d6c8 100644 --- a/PlexRequests.UI/Models/MovieSearchType.cs +++ b/PlexRequests.UI/Models/MovieSearchType.cs @@ -1,35 +1,35 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexType.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.UI.Models -{ - public enum MovieSearchType - { - Upcoming, - CurrentlyPlaying, - Search - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexType.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.UI.Models +{ + public enum MovieSearchType + { + Upcoming, + CurrentlyPlaying, + Search + } } \ No newline at end of file diff --git a/PlexRequests.UI/Models/PlexAuth.cs b/PlexRequests.UI/Models/PlexAuth.cs index a97cc8799..0b5b33df4 100644 --- a/PlexRequests.UI/Models/PlexAuth.cs +++ b/PlexRequests.UI/Models/PlexAuth.cs @@ -1,34 +1,34 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexAuth.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.UI.Models -{ - public class PlexAuth - { - public string username { get; set; } - public string password { get; set; } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexAuth.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.UI.Models +{ + public class PlexAuth + { + public string username { get; set; } + public string password { get; set; } + } +} diff --git a/PlexRequests.UI/Models/QualityModel.cs b/PlexRequests.UI/Models/QualityModel.cs index 80d7996b6..bffa664ea 100644 --- a/PlexRequests.UI/Models/QualityModel.cs +++ b/PlexRequests.UI/Models/QualityModel.cs @@ -1,8 +1,8 @@ -namespace PlexRequests.UI.Models -{ - public class QualityModel - { - public string Id { get; set; } - public string Name { get; set; } - } -} +namespace PlexRequests.UI.Models +{ + public class QualityModel + { + public string Id { get; set; } + public string Name { get; set; } + } +} diff --git a/PlexRequests.UI/Models/SearchMusicViewModel.cs b/PlexRequests.UI/Models/SearchMusicViewModel.cs index 66d140a12..55bcb012c 100644 --- a/PlexRequests.UI/Models/SearchMusicViewModel.cs +++ b/PlexRequests.UI/Models/SearchMusicViewModel.cs @@ -1,41 +1,41 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SearchMusicViewModel.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.UI.Models -{ - public class SearchMusicViewModel : SearchViewModel - { - public string Id { get; set; } - public string Overview { get; set; } - public string CoverArtUrl { get; set; } - public string Title { get; set; } - public string Artist { get; set; } - public string ReleaseDate { get; set; } - public int TrackCount { get; set; } - public string ReleaseType { get; set; } - public string Country { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SearchMusicViewModel.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.UI.Models +{ + public class SearchMusicViewModel : SearchViewModel + { + public string Id { get; set; } + public string Overview { get; set; } + public string CoverArtUrl { get; set; } + public string Title { get; set; } + public string Artist { get; set; } + public string ReleaseDate { get; set; } + public int TrackCount { get; set; } + public string ReleaseType { get; set; } + public string Country { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/CouchPotatoValidator.cs b/PlexRequests.UI/Validators/CouchPotatoValidator.cs index 5f7c7ef2e..319ac2875 100644 --- a/PlexRequests.UI/Validators/CouchPotatoValidator.cs +++ b/PlexRequests.UI/Validators/CouchPotatoValidator.cs @@ -1,42 +1,42 @@ -#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 CouchPotatoValidator : AbstractValidator - { - public CouchPotatoValidator() - { - RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); - RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); - RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); - } - } +#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 CouchPotatoValidator : AbstractValidator + { + public CouchPotatoValidator() + { + RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); + RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/HeadphonesValidator.cs b/PlexRequests.UI/Validators/HeadphonesValidator.cs index 5dccd85a4..d42101add 100644 --- a/PlexRequests.UI/Validators/HeadphonesValidator.cs +++ b/PlexRequests.UI/Validators/HeadphonesValidator.cs @@ -1,42 +1,42 @@ -#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 HeadphonesValidator : AbstractValidator - { - public HeadphonesValidator() - { - RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); - RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); - RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); - } - } +#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 HeadphonesValidator : AbstractValidator + { + public HeadphonesValidator() + { + RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); + RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/PlexValidator.cs b/PlexRequests.UI/Validators/PlexValidator.cs index 53f541855..0f3d1024c 100644 --- a/PlexRequests.UI/Validators/PlexValidator.cs +++ b/PlexRequests.UI/Validators/PlexValidator.cs @@ -1,41 +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 PlexValidator : AbstractValidator - { - public PlexValidator() - { - RuleFor(request => request.Ip).NotNull().WithMessage("You must specify a IP/Host name."); - RuleFor(request => request.Port).NotNull().WithMessage("You must specify a Port."); - } - } +#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 PlexValidator : AbstractValidator + { + public PlexValidator() + { + RuleFor(request => request.Ip).NotNull().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotNull().WithMessage("You must specify a Port."); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/PushbulletSettingsValidator.cs b/PlexRequests.UI/Validators/PushbulletSettingsValidator.cs index 551268319..19b534aa6 100644 --- a/PlexRequests.UI/Validators/PushbulletSettingsValidator.cs +++ b/PlexRequests.UI/Validators/PushbulletSettingsValidator.cs @@ -1,40 +1,40 @@ -#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 PushbulletSettingsValidator : AbstractValidator - { - public PushbulletSettingsValidator() - { - RuleFor(request => request.AccessToken).NotEmpty().WithMessage("You must specify a Access Token."); - } - } +#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 PushbulletSettingsValidator : AbstractValidator + { + public PushbulletSettingsValidator() + { + RuleFor(request => request.AccessToken).NotEmpty().WithMessage("You must specify a Access Token."); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/PushoverSettingsValidator.cs b/PlexRequests.UI/Validators/PushoverSettingsValidator.cs index 55a218aa0..b0c205f84 100644 --- a/PlexRequests.UI/Validators/PushoverSettingsValidator.cs +++ b/PlexRequests.UI/Validators/PushoverSettingsValidator.cs @@ -1,41 +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."); - } - } +#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/Validators/SickRageValidator.cs b/PlexRequests.UI/Validators/SickRageValidator.cs index 28056e095..baf684618 100644 --- a/PlexRequests.UI/Validators/SickRageValidator.cs +++ b/PlexRequests.UI/Validators/SickRageValidator.cs @@ -1,43 +1,43 @@ -#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 SickRageValidator : AbstractValidator - { - public SickRageValidator() - { - RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); - RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); - RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); - RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile."); - } - } +#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 SickRageValidator : AbstractValidator + { + public SickRageValidator() + { + RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); + RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); + RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile."); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/SonarrValidator.cs b/PlexRequests.UI/Validators/SonarrValidator.cs index b252bd3b0..c3db0c2dc 100644 --- a/PlexRequests.UI/Validators/SonarrValidator.cs +++ b/PlexRequests.UI/Validators/SonarrValidator.cs @@ -1,43 +1,43 @@ -#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 SonarrValidator : AbstractValidator - { - public SonarrValidator() - { - RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); - RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); - RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); - RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile."); - } - } +#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 SonarrValidator : AbstractValidator + { + public SonarrValidator() + { + RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); + RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); + RuleFor(request => request.QualityProfile).NotEmpty().WithMessage("You must specify a Quality Profile."); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/PushbulletNotifications.cshtml b/PlexRequests.UI/Views/Admin/PushbulletNotifications.cshtml index a4a53a902..57d64329d 100644 --- a/PlexRequests.UI/Views/Admin/PushbulletNotifications.cshtml +++ b/PlexRequests.UI/Views/Admin/PushbulletNotifications.cshtml @@ -1,110 +1,110 @@ -@using PlexRequests.UI.Helpers -@Html.Partial("_Sidebar") - -
-
-
- Pushbullet Notifications - -
-
- - @if (Model.Enabled) - { - - } - else - { - - } - -
-
- -
- - You can get this by navigating to Pushbullet -
- -
-
- -
- - This is optional, if left blank we will send a Push notification to all devices. -
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
-
- - \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml b/PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml index 8417bb227..545812ce3 100644 --- a/PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml +++ b/PlexRequests.UI/Views/Admin/PushoverNotifications.cshtml @@ -1,110 +1,110 @@ -@using PlexRequests.UI.Helpers -@Html.Partial("_Sidebar") - -
-
-
- Pushover Notifications - -
-
- - @if (Model.Enabled) - { - - } - else - { - - } - -
-
- -
- - Enter your API Key from Pushover. -
- -
-
- -
- - Your user or group key from Pushover. -
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
-
- - \ No newline at end of file diff --git a/PlexRequests.UI/compilerconfig.json b/PlexRequests.UI/compilerconfig.json index 6c0dd8d1c..b9bfcf1fd 100644 --- a/PlexRequests.UI/compilerconfig.json +++ b/PlexRequests.UI/compilerconfig.json @@ -1,38 +1,38 @@ -[ - { - "outputFile": "Content/pace.css", - "inputFile": "Content/pace.scss" - }, - { - "outputFile": "Content/moment.min.es5.js", - "inputFile": "Content/moment.min.js" - }, - { - "outputFile": "Content/Themes/OriginalBootstrapCustom.css", - "inputFile": "Content/Themes/OriginalBootstrapCustom.scss" - }, - { - "outputFile": "Content/Themes/PlexBootstrapCustom.css", - "inputFile": "Content/Themes/PlexBootstrapCustom.scss" - }, - { - "outputFile": "Content/base.css", - "inputFile": "Content/base.scss" - }, - { - "outputFile": "Content/Themes/plex.css", - "inputFile": "Content/Themes/plex.scss" - }, - { - "outputFile": "Content/Themes/original.css", - "inputFile": "Content/Themes/original.scss" - }, - { - "outputFile": "Content/_bootstrap-datetimepicker.css", - "inputFile": "Content/_bootstrap-datetimepicker.less" - }, - { - "outputFile": "Content/datepicker.css", - "inputFile": "Content/datepicker.scss" - } +[ + { + "outputFile": "Content/pace.css", + "inputFile": "Content/pace.scss" + }, + { + "outputFile": "Content/moment.min.es5.js", + "inputFile": "Content/moment.min.js" + }, + { + "outputFile": "Content/Themes/OriginalBootstrapCustom.css", + "inputFile": "Content/Themes/OriginalBootstrapCustom.scss" + }, + { + "outputFile": "Content/Themes/PlexBootstrapCustom.css", + "inputFile": "Content/Themes/PlexBootstrapCustom.scss" + }, + { + "outputFile": "Content/base.css", + "inputFile": "Content/base.scss" + }, + { + "outputFile": "Content/Themes/plex.css", + "inputFile": "Content/Themes/plex.scss" + }, + { + "outputFile": "Content/Themes/original.css", + "inputFile": "Content/Themes/original.scss" + }, + { + "outputFile": "Content/_bootstrap-datetimepicker.css", + "inputFile": "Content/_bootstrap-datetimepicker.less" + }, + { + "outputFile": "Content/datepicker.css", + "inputFile": "Content/datepicker.scss" + } ] \ No newline at end of file diff --git a/PlexRequests.UI/compilerconfig.json.defaults b/PlexRequests.UI/compilerconfig.json.defaults index 1f91b752f..c75eb7d51 100644 --- a/PlexRequests.UI/compilerconfig.json.defaults +++ b/PlexRequests.UI/compilerconfig.json.defaults @@ -1,49 +1,49 @@ -{ - "compilers": { - "less": { - "autoPrefix": "", - "cssComb": "none", - "ieCompat": true, - "strictMath": false, - "strictUnits": false, - "relativeUrls": true, - "rootPath": "", - "sourceMapRoot": "", - "sourceMapBasePath": "", - "sourceMap": false - }, - "sass": { - "includePath": "", - "indentType": "space", - "indentWidth": 2, - "outputStyle": "nested", - "Precision": 5, - "relativeUrls": true, - "sourceMapRoot": "", - "sourceMap": false - }, - "stylus": { - "sourceMap": false - }, - "babel": { - "sourceMap": false - }, - "coffeescript": { - "bare": false, - "runtimeMode": "node", - "sourceMap": false - } - }, - "minifiers": { - "css": { - "enabled": true, - "termSemicolons": true, - "gzip": false - }, - "javascript": { - "enabled": true, - "termSemicolons": true, - "gzip": false - } - } +{ + "compilers": { + "less": { + "autoPrefix": "", + "cssComb": "none", + "ieCompat": true, + "strictMath": false, + "strictUnits": false, + "relativeUrls": true, + "rootPath": "", + "sourceMapRoot": "", + "sourceMapBasePath": "", + "sourceMap": false + }, + "sass": { + "includePath": "", + "indentType": "space", + "indentWidth": 2, + "outputStyle": "nested", + "Precision": 5, + "relativeUrls": true, + "sourceMapRoot": "", + "sourceMap": false + }, + "stylus": { + "sourceMap": false + }, + "babel": { + "sourceMap": false + }, + "coffeescript": { + "bare": false, + "runtimeMode": "node", + "sourceMap": false + } + }, + "minifiers": { + "css": { + "enabled": true, + "termSemicolons": true, + "gzip": false + }, + "javascript": { + "enabled": true, + "termSemicolons": true, + "gzip": false + } + } } \ No newline at end of file From 2a841e724a7d528ba2fb5742f84bdbe58a5e9f5d Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 2 Nov 2016 22:03:15 +0000 Subject: [PATCH 03/68] fixed #643 --- PlexRequests.UI/Modules/AdminModule.cs | 2 +- PlexRequests.UI/Views/Admin/NewsletterSettings.cshtml | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 9caffcfde..71031f2cd 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -844,7 +844,7 @@ namespace PlexRequests.UI.Modules return Response.AsJson(result ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Newsletter!" } : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } + } private Response CreateApiKey() diff --git a/PlexRequests.UI/Views/Admin/NewsletterSettings.cshtml b/PlexRequests.UI/Views/Admin/NewsletterSettings.cshtml index 8573817a7..25c54b211 100644 --- a/PlexRequests.UI/Views/Admin/NewsletterSettings.cshtml +++ b/PlexRequests.UI/Views/Admin/NewsletterSettings.cshtml @@ -42,9 +42,13 @@
- -
- + +
+
+ + You can add multiple email address by using the ; delimiter +
+
From cd71f6211ab1c0f9384716eeaeb110c92769509e Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 2 Nov 2016 23:01:48 +0000 Subject: [PATCH 04/68] Update appveyor.yml --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6fc035931..5e04c953f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,9 +3,9 @@ configuration: Release assembly_info: patch: true file: '**\AssemblyInfo.*' - assembly_version: '1.9.6' + assembly_version: '1.9.7' assembly_file_version: '{version}' - assembly_informational_version: '1.9.6' + assembly_informational_version: '1.9.7 before_build: - cmd: appveyor-retry nuget restore build: From 10cd459b8c1f47783fc2ae4bcd6c09e9acfb7010 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 2 Nov 2016 23:03:58 +0000 Subject: [PATCH 05/68] Update appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5e04c953f..1af2ba46a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,7 +5,7 @@ assembly_info: file: '**\AssemblyInfo.*' assembly_version: '1.9.7' assembly_file_version: '{version}' - assembly_informational_version: '1.9.7 + assembly_informational_version: '1.9.7' before_build: - cmd: appveyor-retry nuget restore build: From 4979000e1379672ecc11ffe3d692226492fe22a2 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 2 Nov 2016 23:15:29 +0000 Subject: [PATCH 06/68] potential fix for #629 --- PlexRequests.Services/Jobs/RecentlyAdded.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/PlexRequests.Services/Jobs/RecentlyAdded.cs b/PlexRequests.Services/Jobs/RecentlyAdded.cs index 70c7a4fa4..73e6fba52 100644 --- a/PlexRequests.Services/Jobs/RecentlyAdded.cs +++ b/PlexRequests.Services/Jobs/RecentlyAdded.cs @@ -257,13 +257,17 @@ namespace PlexRequests.Services.Jobs if (newletterSettings.SendToPlexUsers) { var users = Api.GetUsers(plexSettings.PlexAuthToken); - foreach (var user in users.User) + if (users != null) { - message.Bcc.Add(new MailboxAddress(user.Username, user.Email)); + foreach (var user in users.User) + { + message.Bcc.Add(new MailboxAddress(user.Username, user.Email)); + } } } - if (newletterSettings.CustomUsersEmailAddresses.Any()) + if (newletterSettings.CustomUsersEmailAddresses != null + && newletterSettings.CustomUsersEmailAddresses.Any()) { foreach (var user in newletterSettings.CustomUsersEmailAddresses) { From 0950f202785cb2358e29e86395e660fc59fa854a Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 7 Nov 2016 17:21:05 +0000 Subject: [PATCH 07/68] Almost finished #659 --- PlexRequests.Core/IStatusChecker.cs | 11 ++ PlexRequests.Core/PlexRequests.Core.csproj | 10 +- .../SettingModels/SystemSettings.cs | 38 +++++ PlexRequests.Core/StatusChecker.cs | 83 ---------- .../StatusChecker/AppveyorArtifactResult.cs | 35 ++++ .../StatusChecker/AppveyorBranchResult.cs | 138 ++++++++++++++++ .../StatusChecker/StatusChecker.cs | 153 ++++++++++++++++++ PlexRequests.Core/packages.config | 1 + .../Modules/{ => Admin}/AdminModule.cs | 25 +-- .../Modules/Admin/SystemStatusModule.cs | 100 ++++++++++++ .../Modules/UpdateCheckerModule.cs | 7 +- .../NinjectModules/ConfigurationModule.cs | 3 + PlexRequests.UI/PlexRequests.UI.csproj | 3 +- PlexRequests.UI/Views/Admin/Status.cshtml | 55 ++++++- 14 files changed, 543 insertions(+), 119 deletions(-) create mode 100644 PlexRequests.Core/IStatusChecker.cs create mode 100644 PlexRequests.Core/SettingModels/SystemSettings.cs delete mode 100644 PlexRequests.Core/StatusChecker.cs create mode 100644 PlexRequests.Core/StatusChecker/AppveyorArtifactResult.cs create mode 100644 PlexRequests.Core/StatusChecker/AppveyorBranchResult.cs create mode 100644 PlexRequests.Core/StatusChecker/StatusChecker.cs rename PlexRequests.UI/Modules/{ => Admin}/AdminModule.cs (97%) create mode 100644 PlexRequests.UI/Modules/Admin/SystemStatusModule.cs diff --git a/PlexRequests.Core/IStatusChecker.cs b/PlexRequests.Core/IStatusChecker.cs new file mode 100644 index 000000000..eec365b68 --- /dev/null +++ b/PlexRequests.Core/IStatusChecker.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Octokit; +using PlexRequests.Core.Models; + +namespace PlexRequests.Core +{ + public interface IStatusChecker + { + Task GetStatus(); + } +} \ No newline at end of file diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 4def9282d..064f319e3 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -54,6 +54,10 @@ ..\packages\Quartz.2.3.3\lib\net40\Quartz.dll True + + ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll + True + @@ -82,6 +86,7 @@ + @@ -120,9 +125,12 @@ + - + + + diff --git a/PlexRequests.Core/SettingModels/SystemSettings.cs b/PlexRequests.Core/SettingModels/SystemSettings.cs new file mode 100644 index 000000000..fe60baabc --- /dev/null +++ b/PlexRequests.Core/SettingModels/SystemSettings.cs @@ -0,0 +1,38 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SystemSettings.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.Core.Models; + +namespace PlexRequests.Core.SettingModels +{ + public class SystemSettings : Settings + { + public bool UseEarlyAccessPreviewBuilds { get; set; } + + public StatusModel Status { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/StatusChecker.cs b/PlexRequests.Core/StatusChecker.cs deleted file mode 100644 index bf9f0485e..000000000 --- a/PlexRequests.Core/StatusChecker.cs +++ /dev/null @@ -1,83 +0,0 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: StatusChecker.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Linq; -using System.Threading.Tasks; - -using Octokit; - -using PlexRequests.Core.Models; -using PlexRequests.Helpers; - -namespace PlexRequests.Core -{ - public class StatusChecker - { - public StatusChecker() - { - Git = new GitHubClient(new ProductHeaderValue("PlexRequests-StatusChecker")); - } - private IGitHubClient Git { get; } - private const string Owner = "tidusjar"; - private const string RepoName = "PlexRequests.Net"; - - public async Task GetLatestRelease() - { - var releases = await Git.Repository.Release.GetAll(Owner, RepoName); - return releases.FirstOrDefault(); - } - - public async Task GetStatus() - { - var assemblyVersion = AssemblyHelper.GetProductVersion(); - var model = new StatusModel - { - Version = assemblyVersion, - }; - - var latestRelease = await GetLatestRelease(); - if (latestRelease == null) - { - return new StatusModel { Version = "Unknown" }; - } - var latestVersionArray = latestRelease.Name.Split(new[] { 'v' }, StringSplitOptions.RemoveEmptyEntries); - var latestVersion = latestVersionArray.Length > 1 ? latestVersionArray[1] : string.Empty; - - if (!latestVersion.Equals(assemblyVersion, StringComparison.InvariantCultureIgnoreCase)) - { - model.UpdateAvailable = true; - model.UpdateUri = latestRelease.HtmlUrl; - } - - model.ReleaseNotes = latestRelease.Body; - model.DownloadUri = latestRelease.Assets[0].BrowserDownloadUrl; - model.ReleaseTitle = latestRelease.Name; - - return model; - } - } -} \ No newline at end of file diff --git a/PlexRequests.Core/StatusChecker/AppveyorArtifactResult.cs b/PlexRequests.Core/StatusChecker/AppveyorArtifactResult.cs new file mode 100644 index 000000000..9e67262d6 --- /dev/null +++ b/PlexRequests.Core/StatusChecker/AppveyorArtifactResult.cs @@ -0,0 +1,35 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AppveyorArtifactResult.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.Core.StatusChecker +{ + public class AppveyorArtifactResult + { + public string fileName { get; set; } + public string type { get; set; } + public int size { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/StatusChecker/AppveyorBranchResult.cs b/PlexRequests.Core/StatusChecker/AppveyorBranchResult.cs new file mode 100644 index 000000000..766e5a804 --- /dev/null +++ b/PlexRequests.Core/StatusChecker/AppveyorBranchResult.cs @@ -0,0 +1,138 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AppveyorBranchResult.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; + +namespace PlexRequests.Core.StatusChecker +{ + public class NuGetFeed + { + public string id { get; set; } + public string name { get; set; } + public bool publishingEnabled { get; set; } + public string created { get; set; } + } + + public class AccessRightDefinition + { + public string name { get; set; } + public string description { get; set; } + } + + public class AccessRight + { + public string name { get; set; } + public bool allowed { get; set; } + } + + public class RoleAce + { + public int roleId { get; set; } + public string name { get; set; } + public bool isAdmin { get; set; } + public List accessRights { get; set; } + } + + public class SecurityDescriptor + { + public List accessRightDefinitions { get; set; } + public List roleAces { get; set; } + } + + public class Project + { + public int projectId { get; set; } + public int accountId { get; set; } + public string accountName { get; set; } + public List builds { get; set; } + public string name { get; set; } + public string slug { get; set; } + public string repositoryType { get; set; } + public string repositoryScm { get; set; } + public string repositoryName { get; set; } + public string repositoryBranch { get; set; } + public bool isPrivate { get; set; } + public bool skipBranchesWithoutAppveyorYml { get; set; } + public bool enableSecureVariablesInPullRequests { get; set; } + public bool enableSecureVariablesInPullRequestsFromSameRepo { get; set; } + public bool enableDeploymentInPullRequests { get; set; } + public bool rollingBuilds { get; set; } + public bool alwaysBuildClosedPullRequests { get; set; } + public string tags { get; set; } + public NuGetFeed nuGetFeed { get; set; } + public SecurityDescriptor securityDescriptor { get; set; } + public string created { get; set; } + public string updated { get; set; } + } + + public class Job + { + public string jobId { get; set; } + public string name { get; set; } + public bool allowFailure { get; set; } + public int messagesCount { get; set; } + public int compilationMessagesCount { get; set; } + public int compilationErrorsCount { get; set; } + public int compilationWarningsCount { get; set; } + public int testsCount { get; set; } + public int passedTestsCount { get; set; } + public int failedTestsCount { get; set; } + public int artifactsCount { get; set; } + public string status { get; set; } + public string started { get; set; } + public string finished { get; set; } + public string created { get; set; } + public string updated { get; set; } + } + + public class Build + { + public int buildId { get; set; } + public List jobs { get; set; } + public int buildNumber { get; set; } + public string version { get; set; } + public string message { get; set; } + public string branch { get; set; } + public bool isTag { get; set; } + public string commitId { get; set; } + public string authorName { get; set; } + public string committerName { get; set; } + public string committed { get; set; } + public List messages { get; set; } + public string status { get; set; } + public string started { get; set; } + public string finished { get; set; } + public string created { get; set; } + public string updated { get; set; } + } + + public class AppveyorBranchResult + { + public Project project { get; set; } + public Build build { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/StatusChecker/StatusChecker.cs b/PlexRequests.Core/StatusChecker/StatusChecker.cs new file mode 100644 index 000000000..fcf41f65b --- /dev/null +++ b/PlexRequests.Core/StatusChecker/StatusChecker.cs @@ -0,0 +1,153 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: StatusChecker.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Octokit; +using PlexRequests.Api; +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using RestSharp; + +namespace PlexRequests.Core.StatusChecker +{ + public class StatusChecker : IStatusChecker + { + public StatusChecker(ISettingsService ss) + { + SystemSettings = ss; + Git = new GitHubClient(new ProductHeaderValue("PlexRequests-StatusChecker")); + } + + private ISettingsService SystemSettings { get; } + + private IGitHubClient Git { get; } + private const string Owner = "tidusjar"; + private const string RepoName = "PlexRequests.Net"; + private const string AppveyorApiUrl = "https://ci.appveyor.com/api"; + + private const string Api = + "48Ku58C0794nBrXra8IxWav+dc6NqgkRw+PZB3/bQwbt/D0IrnJQkgtjzo0bd6nkooLMKsC8M+Ab7jyBO+ROjY14VRuxffpDopX9r0iG/fjBl6mZVvqkm+VTDNstDtzp"; + + public async Task GetStatus() + { + var settings = await SystemSettings.GetSettingsAsync(); + var isEap = settings.UseEarlyAccessPreviewBuilds; + + if (isEap) + { + // Early Access Preview Releases + return GetLatestEapRelease(); + } + + // Stable releases + return await GetLatestGithubRelease(); + } + + private async Task GetLatestGithubRelease() + { + var assemblyVersion = AssemblyHelper.GetProductVersion(); + var model = new StatusModel + { + Version = assemblyVersion, + }; + + var releases = await Git.Repository.Release.GetAll(Owner, RepoName); + var latestRelease = releases.FirstOrDefault(); + + if (latestRelease == null) + { + return new StatusModel { Version = "Unknown" }; + } + var latestVersionArray = latestRelease.Name.Split(new[] { 'v' }, StringSplitOptions.RemoveEmptyEntries); + var latestVersion = latestVersionArray.Length > 1 ? latestVersionArray[1] : string.Empty; + + if (!latestVersion.Equals(assemblyVersion, StringComparison.InvariantCultureIgnoreCase)) + { + model.UpdateAvailable = true; + model.UpdateUri = latestRelease.HtmlUrl; + } + + model.ReleaseNotes = latestRelease.Body; + model.DownloadUri = latestRelease.Assets[0].BrowserDownloadUrl; + model.ReleaseTitle = latestRelease.Name; + + return model; + } + + private StatusModel GetLatestEapRelease() + { + var request = new ApiRequest(); + + // Get latest EAP Build + var eapBranchRequest = new RestRequest + { + Resource = "/projects/tidusjar/requestplex/branch/EAP", + Method = Method.GET + }; + + var api = StringCipher.Decrypt(Api,"Appveyor"); + eapBranchRequest.AddHeader("Authorization", $"Bearer {api}"); + eapBranchRequest.AddHeader("Content-Type", "application/json"); + + var branchResult = request.ExecuteJson(eapBranchRequest, new Uri(AppveyorApiUrl)); + + var jobId = branchResult.build.jobs.FirstOrDefault()?.jobId ?? string.Empty; + + if (string.IsNullOrEmpty(jobId)) + { + return new StatusModel {UpdateAvailable = false}; + } + + // Get artifacts from the EAP Build + var eapAtrifactRequest = new RestRequest + { + Resource = $"/buildjobs/{jobId}/artifacts", + Method = Method.GET + }; + eapAtrifactRequest.AddHeader("Authorization", $"Bearer {api}"); + eapAtrifactRequest.AddHeader("Content-Type", "application/json"); + + var artifactResult = request.ExecuteJson>(eapAtrifactRequest, new Uri(AppveyorApiUrl)).FirstOrDefault(); + + var downloadLink = $"{AppveyorApiUrl}/buildjobs/{jobId}/artifacts/{artifactResult.fileName}"; + + return new StatusModel + { + DownloadUri = downloadLink, + ReleaseNotes = "Early Access Preview (See recent commits for details)", + ReleaseTitle = "Plex Requests Early Access Preview", + Version = branchResult.build.version, + UpdateAvailable = true, + UpdateUri = downloadLink + }; + } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/packages.config b/PlexRequests.Core/packages.config index 6f3ca4a6c..e8f81f5a9 100644 --- a/PlexRequests.Core/packages.config +++ b/PlexRequests.Core/packages.config @@ -8,5 +8,6 @@ + \ No newline at end of file diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/Admin/AdminModule.cs similarity index 97% rename from PlexRequests.UI/Modules/AdminModule.cs rename to PlexRequests.UI/Modules/Admin/AdminModule.cs index e48cd1fb7..06fe01780 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/Admin/AdminModule.cs @@ -186,7 +186,6 @@ namespace PlexRequests.UI.Modules Get["/emailnotification"] = _ => EmailNotifications(); Post["/emailnotification"] = _ => SaveEmailNotifications(); Post["/testemailnotification", true] = async (x, ct) => await TestEmailNotifications(); - Get["/status", true] = async (x, ct) => await Status(); Get["/pushbulletnotification"] = _ => PushbulletNotifications(); Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); @@ -209,7 +208,7 @@ namespace PlexRequests.UI.Modules Post["/createapikey"] = x => CreateApiKey(); - Post["/autoupdate"] = x => AutoUpdate(); + Post["/testslacknotification", true] = async (x, ct) => await TestSlackNotification(); @@ -568,28 +567,6 @@ namespace PlexRequests.UI.Modules : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); } - private async Task Status() - { - var checker = new StatusChecker(); - var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30); - var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true }); - status.ReleaseNotes = md.Transform(status.ReleaseNotes); - return View["Status", status]; - } - - private Response AutoUpdate() - { - var url = Request.Form["url"]; - - var startInfo = Type.GetType("Mono.Runtime") != null - ? new ProcessStartInfo("mono PlexRequests.Updater.exe") { Arguments = url } - : new ProcessStartInfo("PlexRequests.Updater.exe") { Arguments = url }; - - Process.Start(startInfo); - - Environment.Exit(0); - return Nancy.Response.NoBody; - } private Negotiator PushbulletNotifications() { diff --git a/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs b/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs new file mode 100644 index 000000000..e3a04e39d --- /dev/null +++ b/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs @@ -0,0 +1,100 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SystemStatusModule.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using MarkdownSharp; +using Nancy; +using Nancy.ModelBinding; +using Nancy.Responses.Negotiation; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Core.StatusChecker; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Permissions; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules.Admin +{ + public class SystemStatusModule : BaseModule + { + public SystemStatusModule(ISettingsService settingsService, ICacheProvider cache, ISettingsService ss) : base("admin", settingsService) + { + Cache = cache; + SystemSettings = ss; + + Security.HasPermissionsResponse(Permissions.Administrator); + + + Get["/status", true] = async (x, ct) => await Status(); + Post["/save", true] = async (x, ct) => await Save(); + + Post["/autoupdate"] = x => AutoUpdate(); + } + + private ICacheProvider Cache { get; } + private ISettingsService SystemSettings { get; } + + private async Task Status() + { + var settings = await SystemSettings.GetSettingsAsync(); + var checker = new StatusChecker(SystemSettings); + var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30); + var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true }); + status.ReleaseNotes = md.Transform(status.ReleaseNotes); + + settings.Status = status; + + return View["Status", settings]; + } + + private async Task Save() + { + var settings = this.Bind(); + + await SystemSettings.SaveSettingsAsync(settings); + + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully Saved your settings"}); + } + + private Response AutoUpdate() + { + var url = Request.Form["url"]; + + var startInfo = Type.GetType("Mono.Runtime") != null + ? new ProcessStartInfo("mono PlexRequests.Updater.exe") { Arguments = url } + : new ProcessStartInfo("PlexRequests.Updater.exe") { Arguments = url }; + + Process.Start(startInfo); + + Environment.Exit(0); + return Nancy.Response.NoBody; + } + + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/UpdateCheckerModule.cs b/PlexRequests.UI/Modules/UpdateCheckerModule.cs index ddf869f28..3aa6a657d 100644 --- a/PlexRequests.UI/Modules/UpdateCheckerModule.cs +++ b/PlexRequests.UI/Modules/UpdateCheckerModule.cs @@ -33,6 +33,7 @@ using NLog; using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.Core.StatusChecker; using PlexRequests.Helpers; using PlexRequests.UI.Models; @@ -40,9 +41,10 @@ namespace PlexRequests.UI.Modules { public class UpdateCheckerModule : BaseAuthModule { - public UpdateCheckerModule(ICacheProvider provider, ISettingsService pr) : base("updatechecker", pr) + public UpdateCheckerModule(ICacheProvider provider, ISettingsService pr, ISettingsService settings) : base("updatechecker", pr) { Cache = provider; + SystemSettings = settings; Get["/", true] = async (x,ct) => await CheckLatestVersion(); } @@ -50,6 +52,7 @@ namespace PlexRequests.UI.Modules private ICacheProvider Cache { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); + private ISettingsService SystemSettings { get; } private async Task CheckLatestVersion() { @@ -62,7 +65,7 @@ namespace PlexRequests.UI.Modules #if DEBUG return Response.AsJson(new JsonUpdateAvailableModel {UpdateAvailable = false}); #endif - var checker = new StatusChecker(); + var checker = new StatusChecker(SystemSettings); var release = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async() => await checker.GetStatus(), 30); return Response.AsJson(release.UpdateAvailable diff --git a/PlexRequests.UI/NinjectModules/ConfigurationModule.cs b/PlexRequests.UI/NinjectModules/ConfigurationModule.cs index 57cb76235..0ed22372a 100644 --- a/PlexRequests.UI/NinjectModules/ConfigurationModule.cs +++ b/PlexRequests.UI/NinjectModules/ConfigurationModule.cs @@ -33,6 +33,7 @@ using Ninject.Modules; using PlexRequests.Core; using PlexRequests.Core.Migration; +using PlexRequests.Core.StatusChecker; using PlexRequests.Helpers; using PlexRequests.Services.Interfaces; using PlexRequests.Services.Notification; @@ -56,6 +57,8 @@ namespace PlexRequests.UI.NinjectModules Bind().To().InSingletonScope(); Bind().To(); + + Bind().To(); } } } \ No newline at end of file diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index bc78a9f77..a18aec630 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -244,6 +244,7 @@ + @@ -324,7 +325,7 @@ - + diff --git a/PlexRequests.UI/Views/Admin/Status.cshtml b/PlexRequests.UI/Views/Admin/Status.cshtml index c6e3f6588..2051ea385 100644 --- a/PlexRequests.UI/Views/Admin/Status.cshtml +++ b/PlexRequests.UI/Views/Admin/Status.cshtml @@ -1,4 +1,5 @@ @using PlexRequests.UI.Helpers +@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase @Html.Partial("_Sidebar")
@@ -8,16 +9,35 @@
- +
+
+
+
+ + @if (Model.UseEarlyAccessPreviewBuilds) + { + + } + else + { + + } + +
+
+ +
+ +
- @if (Model.UpdateAvailable) + @if (Model.Status.UpdateAvailable) { - +
- @**@ //TODO + } else { @@ -26,14 +46,14 @@
- @if (Model.UpdateAvailable) + @if (Model.Status.UpdateAvailable) {

- @Model.ReleaseTitle + @Model.Status.ReleaseTitle


- @Html.Raw(Model.ReleaseNotes) + @Html.Raw(Model.Status.ReleaseNotes) } @@ -58,7 +78,7 @@ $.ajax({ type: "Post", url: "autoupdate", - data: { url: "@Model.DownloadUri" }, + data: { url: "@Model.Status.DownloadUri" }, dataType: "json", error: function () { setTimeout( @@ -68,4 +88,23 @@ } }); }); + + $('#saveSettings').click(function (e) { + e.preventDefault(); + var $form = $("#mainForm"); + $.ajax({ + type: $form.prop("method"), + url: $form.prop("action"), + data: $form.serialize(), + dataType: "json", + success: function (response) { + if (response.result === true) { + generateNotify(response.message, "success"); + + } else { + generateNotify(response.message, "warning"); + } + } + }); + }); \ No newline at end of file From 1c7fb2e93e4c19b0dff12f9e6bae249f027e1b03 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 8 Nov 2016 13:35:35 +0000 Subject: [PATCH 08/68] Finished #659 #236 has been modified slightly. Needs testing on Different systems --- PlexRequests.Core/Models/StatusModel.cs | 3 +- PlexRequests.Core/PlexRequests.Core.csproj | 1 + .../SettingModels/SystemSettings.cs | 26 +++++++++- .../StatusChecker/StatusChecker.cs | 48 +++++++++++++----- PlexRequests.UI/Content/base.css | 17 +++++++ PlexRequests.UI/Content/base.min.css | 2 +- PlexRequests.UI/Content/base.scss | 22 +++++++-- .../Modules/Admin/SystemStatusModule.cs | 29 ++++++++++- PlexRequests.UI/Views/Admin/Status.cshtml | 49 +++++++++++++------ 9 files changed, 160 insertions(+), 37 deletions(-) diff --git a/PlexRequests.Core/Models/StatusModel.cs b/PlexRequests.Core/Models/StatusModel.cs index 9cbcf644d..ee55ab373 100644 --- a/PlexRequests.Core/Models/StatusModel.cs +++ b/PlexRequests.Core/Models/StatusModel.cs @@ -28,7 +28,8 @@ namespace PlexRequests.Core.Models { public class StatusModel { - public string Version { get; set; } + public string CurrentVersion { get; set; } + public string NewVersion { get; set; } public bool UpdateAvailable { get; set; } public string UpdateUri { get; set; } public string DownloadUri { get; set; } diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 064f319e3..2800771ca 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -59,6 +59,7 @@ True + diff --git a/PlexRequests.Core/SettingModels/SystemSettings.cs b/PlexRequests.Core/SettingModels/SystemSettings.cs index fe60baabc..883c32ba8 100644 --- a/PlexRequests.Core/SettingModels/SystemSettings.cs +++ b/PlexRequests.Core/SettingModels/SystemSettings.cs @@ -25,14 +25,38 @@ // ************************************************************************/ #endregion +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using PlexRequests.Core.Models; namespace PlexRequests.Core.SettingModels { public class SystemSettings : Settings { - public bool UseEarlyAccessPreviewBuilds { get; set; } + public Branches Branch { get; set; } public StatusModel Status { get; set; } + + public List BranchDropdown { get; set; } + } + + public class BranchDropdown + { + public bool Selected { get; set; } + public string Name { get; set; } + public Branches Value { get; set; } + } + + public enum Branches + { + [Display(Name = "Stable")] + Stable, + + [Display(Name = "Early Access Preview")] + EarlyAccessPreview, + + [Display(Name = "Development")] + Dev, } } \ No newline at end of file diff --git a/PlexRequests.Core/StatusChecker/StatusChecker.cs b/PlexRequests.Core/StatusChecker/StatusChecker.cs index fcf41f65b..cf009464b 100644 --- a/PlexRequests.Core/StatusChecker/StatusChecker.cs +++ b/PlexRequests.Core/StatusChecker/StatusChecker.cs @@ -59,12 +59,12 @@ namespace PlexRequests.Core.StatusChecker public async Task GetStatus() { var settings = await SystemSettings.GetSettingsAsync(); - var isEap = settings.UseEarlyAccessPreviewBuilds; + var stable = settings.Branch == Branches.Stable; - if (isEap) + if (!stable) { // Early Access Preview Releases - return GetLatestEapRelease(); + return GetAppveyorRelease(settings.Branch); } // Stable releases @@ -76,7 +76,7 @@ namespace PlexRequests.Core.StatusChecker var assemblyVersion = AssemblyHelper.GetProductVersion(); var model = new StatusModel { - Version = assemblyVersion, + CurrentVersion = assemblyVersion, }; var releases = await Git.Repository.Release.GetAll(Owner, RepoName); @@ -84,7 +84,7 @@ namespace PlexRequests.Core.StatusChecker if (latestRelease == null) { - return new StatusModel { Version = "Unknown" }; + return new StatusModel { NewVersion = "Unknown" }; } var latestVersionArray = latestRelease.Name.Split(new[] { 'v' }, StringSplitOptions.RemoveEmptyEntries); var latestVersion = latestVersionArray.Length > 1 ? latestVersionArray[1] : string.Empty; @@ -93,6 +93,7 @@ namespace PlexRequests.Core.StatusChecker { model.UpdateAvailable = true; model.UpdateUri = latestRelease.HtmlUrl; + model.NewVersion = latestVersion; } model.ReleaseNotes = latestRelease.Body; @@ -102,17 +103,27 @@ namespace PlexRequests.Core.StatusChecker return model; } - private StatusModel GetLatestEapRelease() + private StatusModel GetAppveyorRelease(Branches branch) { var request = new ApiRequest(); // Get latest EAP Build var eapBranchRequest = new RestRequest { - Resource = "/projects/tidusjar/requestplex/branch/EAP", Method = Method.GET }; + + switch (branch) + { + case Branches.Dev: + eapBranchRequest.Resource = "/projects/tidusjar/requestplex/branch/dev"; + break; + case Branches.EarlyAccessPreview: + eapBranchRequest.Resource = "/projects/tidusjar/requestplex/branch/EAP"; + break; + } + var api = StringCipher.Decrypt(Api,"Appveyor"); eapBranchRequest.AddHeader("Authorization", $"Bearer {api}"); eapBranchRequest.AddHeader("Content-Type", "application/json"); @@ -139,15 +150,26 @@ namespace PlexRequests.Core.StatusChecker var downloadLink = $"{AppveyorApiUrl}/buildjobs/{jobId}/artifacts/{artifactResult.fileName}"; - return new StatusModel + var branchDisplay = EnumHelper.GetDisplayValue(branch); + var localVersion = AssemblyHelper.GetProductVersion(); + var localVersionExtended = $"{localVersion.Remove(localVersion.Length - 2, 2)}00"; + + var model = new StatusModel { DownloadUri = downloadLink, - ReleaseNotes = "Early Access Preview (See recent commits for details)", - ReleaseTitle = "Plex Requests Early Access Preview", - Version = branchResult.build.version, - UpdateAvailable = true, - UpdateUri = downloadLink + ReleaseNotes = $"{branchDisplay} (See recent commits for details)", + ReleaseTitle = $"Plex Requests {branchDisplay}", + NewVersion = branchResult.build.version, + UpdateUri = downloadLink, + CurrentVersion = localVersionExtended }; + + if (!localVersionExtended.Equals(branchResult.build.version, StringComparison.CurrentCultureIgnoreCase)) + { + model.UpdateAvailable = true; + } + + return model; } } } \ No newline at end of file diff --git a/PlexRequests.UI/Content/base.css b/PlexRequests.UI/Content/base.css index 7a9495d25..602ee6369 100644 --- a/PlexRequests.UI/Content/base.css +++ b/PlexRequests.UI/Content/base.css @@ -436,3 +436,20 @@ label { position: relative; margin-right: 0; } } +#lightbox { + background-color: grey; + filter: alpha(opacity=50); + /* IE */ + opacity: 0.5; + /* Safari, Opera */ + -moz-opacity: 0.50; + /* FireFox */ + top: 0px; + left: 0px; + z-index: 20; + height: 100%; + width: 100%; + background-repeat: no-repeat; + background-position: center; + position: absolute; } + diff --git a/PlexRequests.UI/Content/base.min.css b/PlexRequests.UI/Content/base.min.css index 583daf53d..5cd192ff8 100644 --- a/PlexRequests.UI/Content/base.min.css +++ b/PlexRequests.UI/Content/base.min.css @@ -1 +1 @@ -@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}.landing-block .media{max-width:450px;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;}hr{border:1px dashed #777;}.btn{border-radius:.25rem !important;}.btn-group-separated .btn,.btn-group-separated .btn+.btn{margin-left:3px;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li{font-size:13px;line-height:21px;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.nav-tabs>li>a>.fa{padding:3px 5px 3px 3px;}.nav-tabs>li.nav-tab-right{float:right;}.nav-tabs>li.nav-tab-right a{margin-right:0;margin-left:2px;}.nav-tabs>li.nav-tab-icononly .fa{padding:3px;}.navbar .nav a .fa,.dropdown-menu a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.dropdown-menu a .fa{top:2px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}.form-control-search{padding:13px 105px 13px 16px;height:100%;}.form-control-withbuttons{padding-right:105px;}.input-group-addon .btn-group{position:absolute;right:45px;z-index:3;top:10px;box-shadow:0 0 0;}.input-group-addon .btn-group .btn{border:1px solid rgba(255,255,255,.7) !important;padding:3px 12px;color:rgba(255,255,255,.7) !important;}.btn-split .btn{border-radius:0 !important;}.btn-split .btn:not(.dropdown-toggle){border-radius:.25rem 0 0 .25rem !important;}.btn-split .btn.dropdown-toggle{border-radius:0 .25rem .25rem 0 !important;padding:12px 8px;}#updateAvailable{background-color:#df691a;text-align:center;font-size:15px;padding:3px 0;}.checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:3px;}.checkbox input[type=checkbox]{display:none;}.checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.input-group-sm{padding-top:2px;padding-bottom:2px;}.tab-pane .form-horizontal .form-group{margin-right:15px;margin-left:15px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#4e5d6c;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #4e5d6c !important;}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{color:#fff !important;}.landing-header{display:block;margin:60px auto;}.landing-block{background:#2f2f2f !important;padding:5px;}.landing-block .media{margin:30px auto;max-width:450px;}.landing-block .media .media-left{display:inline-block;float:left;width:70px;}.landing-block .media .media-left i.fa{font-size:3em;}.landing-title{font-weight:bold;}.checkbox-custom{margin-top:0 !important;margin-bottom:0 !important;}.tooltip_templates{display:none;}.shadow{-moz-box-shadow:3px 3px 5px 6px #191919;-webkit-box-shadow:3px 3px 5px 6px #191919;box-shadow:3px 3px 5px 6px #191919;}.img-circle{border-radius:50%;}#wrapper{padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled{padding-right:250px;}#sidebar-wrapper{z-index:1000;position:fixed;right:250px;width:0;height:100%;margin-right:-250px;overflow-y:auto;background:#4e5d6c;padding-left:15px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled #sidebar-wrapper{width:500px;}#page-content-wrapper{width:100%;position:absolute;padding:15px;}#wrapper.toggled #page-content-wrapper{position:absolute;margin-left:-250px;}.sidebar-nav{position:absolute;top:0;width:500px;margin:0;padding-left:0;list-style:none;}.sidebar-nav li{text-indent:20px;line-height:40px;}.sidebar-nav li a{display:block;text-decoration:none;color:#999;}.sidebar-nav li a:hover{text-decoration:none;color:#fff;background:rgba(255,255,255,.2);}.sidebar-nav li a:active,.sidebar-nav li a:focus{text-decoration:none;}.sidebar-nav>.sidebar-brand{height:65px;font-size:18px;line-height:60px;}.sidebar-nav>.sidebar-brand a{color:#999;}.sidebar-nav>.sidebar-brand a:hover{color:#fff;background:none;}@media(min-width:768px){#wrapper{padding-right:250px;}#wrapper.toggled{padding-right:0;}#sidebar-wrapper{width:500px;}#wrapper.toggled #sidebar-wrapper{width:0;}#page-content-wrapper{padding:20px;position:relative;}#wrapper.toggled #page-content-wrapper{position:relative;margin-right:0;}} \ No newline at end of file +@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}.landing-block .media{max-width:450px;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;}hr{border:1px dashed #777;}.btn{border-radius:.25rem !important;}.btn-group-separated .btn,.btn-group-separated .btn+.btn{margin-left:3px;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li{font-size:13px;line-height:21px;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.nav-tabs>li>a>.fa{padding:3px 5px 3px 3px;}.nav-tabs>li.nav-tab-right{float:right;}.nav-tabs>li.nav-tab-right a{margin-right:0;margin-left:2px;}.nav-tabs>li.nav-tab-icononly .fa{padding:3px;}.navbar .nav a .fa,.dropdown-menu a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.dropdown-menu a .fa{top:2px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}.form-control-search{padding:13px 105px 13px 16px;height:100%;}.form-control-withbuttons{padding-right:105px;}.input-group-addon .btn-group{position:absolute;right:45px;z-index:3;top:10px;box-shadow:0 0 0;}.input-group-addon .btn-group .btn{border:1px solid rgba(255,255,255,.7) !important;padding:3px 12px;color:rgba(255,255,255,.7) !important;}.btn-split .btn{border-radius:0 !important;}.btn-split .btn:not(.dropdown-toggle){border-radius:.25rem 0 0 .25rem !important;}.btn-split .btn.dropdown-toggle{border-radius:0 .25rem .25rem 0 !important;padding:12px 8px;}#updateAvailable{background-color:#df691a;text-align:center;font-size:15px;padding:3px 0;}.checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:3px;}.checkbox input[type=checkbox]{display:none;}.checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.input-group-sm{padding-top:2px;padding-bottom:2px;}.tab-pane .form-horizontal .form-group{margin-right:15px;margin-left:15px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#4e5d6c;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #4e5d6c !important;}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{color:#fff !important;}.landing-header{display:block;margin:60px auto;}.landing-block{background:#2f2f2f !important;padding:5px;}.landing-block .media{margin:30px auto;max-width:450px;}.landing-block .media .media-left{display:inline-block;float:left;width:70px;}.landing-block .media .media-left i.fa{font-size:3em;}.landing-title{font-weight:bold;}.checkbox-custom{margin-top:0 !important;margin-bottom:0 !important;}.tooltip_templates{display:none;}.shadow{-moz-box-shadow:3px 3px 5px 6px #191919;-webkit-box-shadow:3px 3px 5px 6px #191919;box-shadow:3px 3px 5px 6px #191919;}.img-circle{border-radius:50%;}#wrapper{padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled{padding-right:250px;}#sidebar-wrapper{z-index:1000;position:fixed;right:250px;width:0;height:100%;margin-right:-250px;overflow-y:auto;background:#4e5d6c;padding-left:15px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled #sidebar-wrapper{width:500px;}#page-content-wrapper{width:100%;position:absolute;padding:15px;}#wrapper.toggled #page-content-wrapper{position:absolute;margin-left:-250px;}.sidebar-nav{position:absolute;top:0;width:500px;margin:0;padding-left:0;list-style:none;}.sidebar-nav li{text-indent:20px;line-height:40px;}.sidebar-nav li a{display:block;text-decoration:none;color:#999;}.sidebar-nav li a:hover{text-decoration:none;color:#fff;background:rgba(255,255,255,.2);}.sidebar-nav li a:active,.sidebar-nav li a:focus{text-decoration:none;}.sidebar-nav>.sidebar-brand{height:65px;font-size:18px;line-height:60px;}.sidebar-nav>.sidebar-brand a{color:#999;}.sidebar-nav>.sidebar-brand a:hover{color:#fff;background:none;}@media(min-width:768px){#wrapper{padding-right:250px;}#wrapper.toggled{padding-right:0;}#sidebar-wrapper{width:500px;}#wrapper.toggled #sidebar-wrapper{width:0;}#page-content-wrapper{padding:20px;position:relative;}#wrapper.toggled #page-content-wrapper{position:relative;margin-right:0;}}#lightbox{background-color:#808080;filter:alpha(opacity=50);opacity:.5;-moz-opacity:.5;top:0;left:0;z-index:20;height:100%;width:100%;background-repeat:no-repeat;background-position:center;position:absolute;} \ No newline at end of file diff --git a/PlexRequests.UI/Content/base.scss b/PlexRequests.UI/Content/base.scss index e293c9e44..64dd1b83e 100644 --- a/PlexRequests.UI/Content/base.scss +++ b/PlexRequests.UI/Content/base.scss @@ -6,9 +6,7 @@ $info-colour: #5bc0de; $warning-colour: #f0ad4e; $danger-colour: #d9534f; $success-colour: #5cb85c; -$i: -!important -; +$i:!important; @media (min-width: 768px ) { .row { @@ -551,4 +549,20 @@ $border-radius: 10px; position: relative; margin-right: 0; } -} \ No newline at end of file +} + +#lightbox { + + background-color: grey; + filter:alpha(opacity=50); /* IE */ + opacity: 0.5; /* Safari, Opera */ + -moz-opacity:0.50; /* FireFox */ + top: 0px; + left: 0px; + z-index: 20; + height: 100%; + width: 100%; + background-repeat:no-repeat; + background-position:center; + position:absolute; +} diff --git a/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs b/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs index e3a04e39d..05ae0f125 100644 --- a/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs +++ b/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs @@ -26,6 +26,7 @@ #endregion using System; +using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; using MarkdownSharp; @@ -49,8 +50,7 @@ namespace PlexRequests.UI.Modules.Admin SystemSettings = ss; Security.HasPermissionsResponse(Permissions.Administrator); - - + Get["/status", true] = async (x, ct) => await Status(); Post["/save", true] = async (x, ct) => await Save(); @@ -70,6 +70,28 @@ namespace PlexRequests.UI.Modules.Admin settings.Status = status; + settings.BranchDropdown = new List + { + new BranchDropdown + { + Name = EnumHelper.GetDisplayValue(Branches.Stable), + Value = Branches.Stable, + Selected = settings.Branch == Branches.Stable + }, + new BranchDropdown + { + Name = EnumHelper.GetDisplayValue(Branches.EarlyAccessPreview), + Value = Branches.EarlyAccessPreview, + Selected = settings.Branch == Branches.EarlyAccessPreview + }, + new BranchDropdown + { + Name = EnumHelper.GetDisplayValue(Branches.Dev), + Value = Branches.Dev, + Selected = settings.Branch == Branches.Dev + }, + }; + return View["Status", settings]; } @@ -79,6 +101,9 @@ namespace PlexRequests.UI.Modules.Admin await SystemSettings.SaveSettingsAsync(settings); + // Clear the cache + Cache.Remove(CacheKeys.LastestProductVersion); + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully Saved your settings"}); } diff --git a/PlexRequests.UI/Views/Admin/Status.cshtml b/PlexRequests.UI/Views/Admin/Status.cshtml index 2051ea385..93760a064 100644 --- a/PlexRequests.UI/Views/Admin/Status.cshtml +++ b/PlexRequests.UI/Views/Admin/Status.cshtml @@ -1,30 +1,43 @@ @using PlexRequests.UI.Helpers @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase @Html.Partial("_Sidebar") - +
Status
- - + +
- -
+ + @if (Model.Status.UpdateAvailable) + {
-
+ + +
+ } - @if (Model.UseEarlyAccessPreviewBuilds) - { - - } - else - { - - } + +
+ +
+
@@ -68,6 +81,7 @@ e.preventDefault(); $('body').append(""); $('#autoUpdate').prop("disabled", "disabled"); + document.getElementById("lightbox").style.display = ""; var count = 0; setInterval(function () { count++; @@ -92,10 +106,15 @@ $('#saveSettings').click(function (e) { e.preventDefault(); var $form = $("#mainForm"); + + var branches = $("#branches option:selected").val(); + + var data = $form.serialize(); + data = data + "&branch=" + branches; $.ajax({ type: $form.prop("method"), url: $form.prop("action"), - data: $form.serialize(), + data: data, dataType: "json", success: function (response) { if (response.result === true) { From 2bd7ece9d086f2a78336975d8fa0328134f1db74 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 8 Nov 2016 14:15:33 +0000 Subject: [PATCH 09/68] Finished #633 (First part of the queuing) --- PlexRequests.Core/Models/NotificationType.cs | 3 +- PlexRequests.Core/PlexRequests.Core.csproj | 5 + .../Queue/ITransientFaultQueue.cs | 17 + .../Queue/TransientFaultQueue.cs | 62 ++- PlexRequests.Core/packages.config | 1 + .../Notification/EmailMessageNotification.cs | 35 +- PlexRequests.Store/Models/RequestQueue.cs | 7 + PlexRequests.Store/SqlTables.sql | 1 + PlexRequests.UI/Modules/SearchModule.cs | 466 +++++++++++++----- .../NinjectModules/ServicesModule.cs | 4 +- 10 files changed, 453 insertions(+), 148 deletions(-) create mode 100644 PlexRequests.Core/Queue/ITransientFaultQueue.cs diff --git a/PlexRequests.Core/Models/NotificationType.cs b/PlexRequests.Core/Models/NotificationType.cs index a01d153dd..eace7b018 100644 --- a/PlexRequests.Core/Models/NotificationType.cs +++ b/PlexRequests.Core/Models/NotificationType.cs @@ -34,6 +34,7 @@ namespace PlexRequests.Core.Models RequestApproved, AdminNote, Test, - + RequestDeclined, + ItemAddedToFaultQueue } } diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 2800771ca..8faf4e83a 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -39,6 +39,10 @@ ..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll True + + ..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll + True + ..\Assemblies\Mono.Data.Sqlite.dll @@ -105,6 +109,7 @@ + diff --git a/PlexRequests.Core/Queue/ITransientFaultQueue.cs b/PlexRequests.Core/Queue/ITransientFaultQueue.cs new file mode 100644 index 000000000..ac62add0f --- /dev/null +++ b/PlexRequests.Core/Queue/ITransientFaultQueue.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using PlexRequests.Store; +using PlexRequests.Store.Models; + +namespace PlexRequests.Core.Queue +{ + public interface ITransientFaultQueue + { + void Dequeue(); + Task DequeueAsync(); + IEnumerable GetQueue(); + Task> GetQueueAsync(); + void QueueItem(RequestedModel request, RequestType type, FaultType faultType); + Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType); + } +} \ No newline at end of file diff --git a/PlexRequests.Core/Queue/TransientFaultQueue.cs b/PlexRequests.Core/Queue/TransientFaultQueue.cs index e3621ee0f..108387f03 100644 --- a/PlexRequests.Core/Queue/TransientFaultQueue.cs +++ b/PlexRequests.Core/Queue/TransientFaultQueue.cs @@ -26,7 +26,9 @@ #endregion using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Dapper; using PlexRequests.Helpers; using PlexRequests.Store; using PlexRequests.Store.Models; @@ -34,7 +36,7 @@ using PlexRequests.Store.Repository; namespace PlexRequests.Core.Queue { - public class TransientFaultQueue + public class TransientFaultQueue : ITransientFaultQueue { public TransientFaultQueue(IRepository queue) { @@ -44,44 +46,84 @@ namespace PlexRequests.Core.Queue private IRepository RequestQueue { get; } - public void QueueItem(RequestedModel request, RequestType type) + public void QueueItem(RequestedModel request, RequestType type, FaultType faultType) { + //Ensure there is not a duplicate queued item + var existingItem = RequestQueue.Custom( + connection => + { + connection.Open(); + var result = connection.Query("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + + return result; + }).FirstOrDefault(); + + if (existingItem != null) + { + // It's already in the queue + return; + } + var queue = new RequestQueue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId + PrimaryIdentifier = request.ProviderId, + FaultType = faultType }; RequestQueue.Insert(queue); } - public async Task QueueItemAsync(RequestedModel request, RequestType type) + public async Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType) { + //Ensure there is not a duplicate queued item + var existingItem = await RequestQueue.CustomAsync(async connection => + { + connection.Open(); + var result = await connection.QueryAsync("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + + return result; + }); + + if (existingItem.FirstOrDefault() != null) + { + // It's already in the queue + return; + } + var queue = new RequestQueue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId + PrimaryIdentifier = request.ProviderId, + FaultType = faultType }; await RequestQueue.InsertAsync(queue); } - public IEnumerable Dequeue() + public IEnumerable GetQueue() { var items = RequestQueue.GetAll(); - RequestQueue.DeleteAll("RequestQueue"); return items; } - public async Task> DequeueAsync() + public async Task> GetQueueAsync() { var items = RequestQueue.GetAllAsync(); + + return await items; + } - await RequestQueue.DeleteAllAsync("RequestQueue"); + public void Dequeue() + { + RequestQueue.DeleteAll("RequestQueue"); + } - return await items; + public async Task DequeueAsync() + { + await RequestQueue.DeleteAllAsync("RequestQueue"); } } } \ No newline at end of file diff --git a/PlexRequests.Core/packages.config b/PlexRequests.Core/packages.config index e8f81f5a9..d698d0fd5 100644 --- a/PlexRequests.Core/packages.config +++ b/PlexRequests.Core/packages.config @@ -2,6 +2,7 @@ + diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 3f6bf9c91..52727dba8 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -87,6 +87,14 @@ namespace PlexRequests.Services.Notification case NotificationType.Test: await EmailTest(model, emailSettings); break; + case NotificationType.RequestDeclined: + throw new NotImplementedException(); + + case NotificationType.ItemAddedToFaultQueue: + await EmailAddedToRequestQueue(model, emailSettings); + break; + default: + throw new ArgumentOutOfRangeException(); } } @@ -129,7 +137,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!", $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }; var message = new MimeMessage { @@ -150,7 +158,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: New issue for {model.Title}!", $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!"}; var message = new MimeMessage { @@ -164,6 +172,27 @@ namespace PlexRequests.Services.Notification await Send(message, settings); } + private async Task EmailAddedToRequestQueue(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Plex Requests: A request could not be added.", + $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying", + model.ImgSrc); + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying" }; + + var message = new MimeMessage + { + Body = body.ToMessageBody(), + Subject = $"Plex Requests: A request could not be added" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) { if (!settings.EnableUserEmailNotifications) @@ -175,7 +204,7 @@ namespace PlexRequests.Services.Notification $"Plex Requests: {model.Title} is now available!", $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)", model.ImgSrc); - var body = new BodyBuilder { HtmlBody = html, TextBody = "This email is only available on devices that support HTML." }; + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }; var message = new MimeMessage { diff --git a/PlexRequests.Store/Models/RequestQueue.cs b/PlexRequests.Store/Models/RequestQueue.cs index bdbf25a2f..15067fbc7 100644 --- a/PlexRequests.Store/Models/RequestQueue.cs +++ b/PlexRequests.Store/Models/RequestQueue.cs @@ -38,5 +38,12 @@ namespace PlexRequests.Store.Models public byte[] Content { get; set; } + public FaultType FaultType { get; set; } + } + + public enum FaultType + { + RequestFault, + MissingInformation } } \ No newline at end of file diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index a22258a5d..220664ba1 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -138,6 +138,7 @@ CREATE TABLE IF NOT EXISTS RequestQueue Id INTEGER PRIMARY KEY AUTOINCREMENT, PrimaryIdentifier INTEGER NOT NULL, Type INTEGER NOT NULL, + FaultType INTEGER NOT NULL, Content BLOB NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index e10e5b92d..ef35e08b6 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -1,4 +1,5 @@ #region Copyright + // /************************************************************************ // Copyright (c) 2016 Jamie Rees // File: SearchModule.cs @@ -23,7 +24,9 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ + #endregion + using System; using System.Collections.Generic; using System.Globalization; @@ -54,13 +57,13 @@ using Newtonsoft.Json; using PlexRequests.Api.Models.Sonarr; using PlexRequests.Api.Models.Tv; using PlexRequests.Core.Models; +using PlexRequests.Core.Queue; using PlexRequests.Helpers.Analytics; using PlexRequests.Helpers.Permissions; using PlexRequests.Store.Models; using PlexRequests.Store.Repository; using TMDbLib.Objects.General; -using TMDbLib.Objects.Search; using Action = PlexRequests.Helpers.Analytics.Action; using EpisodesModel = PlexRequests.Store.EpisodesModel; @@ -73,10 +76,13 @@ namespace PlexRequests.UI.Modules ISettingsService prSettings, IAvailabilityChecker checker, IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, - INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, + INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, + ISettingsService hpService, ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, - ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, - IIssueService issue, IAnalytics a, IRepository rl) : base("search", prSettings) + ISettingsService plexService, ISettingsService auth, + IRepository u, ISettingsService email, + IIssueService issue, IAnalytics a, IRepository rl, ITransientFaultQueue tfQueue) + : base("search", prSettings) { Auth = auth; PlexService = plexService; @@ -104,6 +110,7 @@ namespace PlexRequests.UI.Modules IssueService = issue; Analytics = a; RequestLimitRepo = rl; + FaultQueue = tfQueue; TvApi = new TvMazeApi(); @@ -118,7 +125,8 @@ namespace PlexRequests.UI.Modules Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); - Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); + Post["request/tv", true] = + async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); Post["request/tvEpisodes", true] = async (x, ct) => await RequestTvShow(0, "episode"); Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); @@ -128,6 +136,7 @@ namespace PlexRequests.UI.Modules Get["/seasons"] = x => GetSeasons(); Get["/episodes", true] = async (x, ct) => await GetEpisodes(); } + private TvMazeApi TvApi { get; } private IPlexApi PlexApi { get; } private TheMovieDbApi MovieApi { get; } @@ -154,6 +163,7 @@ namespace PlexRequests.UI.Modules private IRepository UsersToNotifyRepo { get; } private IIssueService IssueService { get; } private IAnalytics Analytics { get; } + private ITransientFaultQueue FaultQueue { get; } private IRepository RequestLimitRepo { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); @@ -167,19 +177,22 @@ namespace PlexRequests.UI.Modules private async Task UpcomingMovies() { - Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, + CookieHelper.GetAnalyticClientId(Cookies)); return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); } private async Task CurrentlyPlayingMovies() { - Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, + CookieHelper.GetAnalyticClientId(Cookies)); return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); } private async Task SearchMovie(string searchTerm) { - Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); return await ProcessMovies(MovieSearchType.Search, searchTerm); } @@ -192,24 +205,24 @@ namespace PlexRequests.UI.Modules case MovieSearchType.Search: var movies = await MovieApi.SearchMovie(searchTerm).ConfigureAwait(false); apiMovies = movies.Select(x => - new MovieResult - { - Adult = x.Adult, - BackdropPath = x.BackdropPath, - GenreIds = x.GenreIds, - Id = x.Id, - OriginalLanguage = x.OriginalLanguage, - OriginalTitle = x.OriginalTitle, - Overview = x.Overview, - Popularity = x.Popularity, - PosterPath = x.PosterPath, - ReleaseDate = x.ReleaseDate, - Title = x.Title, - Video = x.Video, - VoteAverage = x.VoteAverage, - VoteCount = x.VoteCount - }) - .ToList(); + new MovieResult + { + Adult = x.Adult, + BackdropPath = x.BackdropPath, + GenreIds = x.GenreIds, + Id = x.Id, + OriginalLanguage = x.OriginalLanguage, + OriginalTitle = x.OriginalTitle, + Overview = x.Overview, + Popularity = x.Popularity, + PosterPath = x.PosterPath, + ReleaseDate = x.ReleaseDate, + Title = x.Title, + Video = x.Video, + VoteAverage = x.VoteAverage, + VoteCount = x.VoteCount + }) + .ToList(); break; case MovieSearchType.CurrentlyPlaying: apiMovies = await MovieApi.GetCurrentPlayingMovies(); @@ -239,8 +252,9 @@ namespace PlexRequests.UI.Modules var imdbId = string.Empty; if (counter <= 5) // Let's only do it for the first 5 items { - var movieInfoTask = await MovieApi.GetMovieInformation(movie.Id).ConfigureAwait(false); // TODO needs to be careful about this, it's adding extra time to search... - // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 + var movieInfoTask = await MovieApi.GetMovieInformation(movie.Id).ConfigureAwait(false); + // TODO needs to be careful about this, it's adding extra time to search... + // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2 imdbId = movieInfoTask.ImdbId; counter++; } @@ -263,7 +277,8 @@ namespace PlexRequests.UI.Modules VoteCount = movie.VoteCount }; var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); - var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(), imdbId); + var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString(), + imdbId); if (plexMovie != null) { viewMovie.Available = true; @@ -288,7 +303,8 @@ namespace PlexRequests.UI.Modules return Response.AsJson(viewMovies); } - private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) + private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, + Dictionary moviesInDb) { if (usersCanViewOnlyOwnRequests) { @@ -302,7 +318,8 @@ namespace PlexRequests.UI.Modules private async Task SearchTvShow(string searchTerm) { - Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); var plexSettings = await PlexService.GetSettingsAsync(); var prSettings = await PrService.GetSettingsAsync(); var providerId = string.Empty; @@ -360,7 +377,8 @@ namespace PlexRequests.UI.Modules providerId = viewT.Id.ToString(); } - var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId); + var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), + providerId); if (plexShow != null) { viewT.Available = true; @@ -377,7 +395,8 @@ namespace PlexRequests.UI.Modules viewT.Episodes = dbt.Episodes.ToList(); viewT.Approved = dbt.Approved; } - if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db + if (sonarrCached.Select(x => x.TvdbId).Contains(tvdbid) || sickRageCache.Contains(tvdbid)) + // compare to the sonarr/sickrage db { viewT.Requested = true; } @@ -391,7 +410,8 @@ namespace PlexRequests.UI.Modules private async Task SearchAlbum(string searchTerm) { - Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, + CookieHelper.GetAnalyticClientId(Cookies)); var apiAlbums = new List(); await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => { @@ -448,7 +468,7 @@ namespace PlexRequests.UI.Modules if (Security.DoesNotHavePermissions(Permissions.ReadOnlyUser, User)) { return - Response.AsJson(new JsonResponseModel() + Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to request a movie!" @@ -457,12 +477,19 @@ namespace PlexRequests.UI.Modules var settings = await PrService.GetSettingsAsync(); if (!await CheckRequestLimit(settings, RequestType.Movie)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for Movies! Please contact your admin." }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = "You have reached your weekly request limit for Movies! Please contact your admin." + }); } - Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, + CookieHelper.GetAnalyticClientId(Cookies)); var movieInfo = await MovieApi.GetMovieInformation(movieId); - var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; + var fullMovieName = + $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; var existingRequest = await RequestService.CheckRequestAsync(movieId); if (existingRequest != null) @@ -474,7 +501,15 @@ namespace PlexRequests.UI.Modules await RequestService.UpdateRequestAsync(existingRequest); } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = + settings.UsersCanViewOnlyOwnRequests + ? $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" + : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}" + }); } try @@ -482,13 +517,23 @@ namespace PlexRequests.UI.Modules var movies = Checker.GetPlexMovies(); if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullMovieName} is already in Plex!" + }); } } catch (Exception e) { Log.Error(e); - return Response.AsJson(new JsonResponseModel { Result = false, Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName) }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName) + }); } //#endif @@ -508,41 +553,58 @@ namespace PlexRequests.UI.Modules Issues = IssueState.None, }; - - if (ShouldAutoApprove(RequestType.Movie, settings)) + try { - var cpSettings = await CpService.GetSettingsAsync(); - model.Approved = true; - if (cpSettings.Enabled) + if (ShouldAutoApprove(RequestType.Movie, settings)) { - Log.Info("Adding movie to CP (No approval required)"); - var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, - cpSettings.FullUri, cpSettings.ProfileId); - Log.Debug("Adding movie to CP result {0}", result); - if (result) + var cpSettings = await CpService.GetSettingsAsync(); + model.Approved = true; + if (cpSettings.Enabled) { - return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); - } + Log.Info("Adding movie to CP (No approval required)"); + var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, + cpSettings.FullUri, cpSettings.ProfileId); + Log.Debug("Adding movie to CP result {0}", result); + if (result) + { + return + await + AddRequest(model, settings, + $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); + } - return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = Resources.UI.Search_CouchPotatoError - }); + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_CouchPotatoError + }); + } + model.Approved = true; + return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); } - model.Approved = true; - return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); - } - try - { + return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"); } catch (Exception e) { Log.Fatal(e); + await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_CouchPotatoError }); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.Movie, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}" + }); } } @@ -576,9 +638,15 @@ namespace PlexRequests.UI.Modules var settings = await PrService.GetSettingsAsync(); if (!await CheckRequestLimit(settings, RequestType.TvShow)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_WeeklyRequestLimitTVShow }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_WeeklyRequestLimitTVShow + }); } - Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, + CookieHelper.GetAnalyticClientId(Cookies)); var sonarrSettings = SonarrService.GetSettingsAsync(); @@ -590,7 +658,13 @@ namespace PlexRequests.UI.Modules var s = await sonarrSettings; if (!s.Enabled) { - return Response.AsJson(new JsonResponseModel { Message = "This is currently only supported with Sonarr, Please enable Sonarr for this feature", Result = false }); + return + Response.AsJson(new JsonResponseModel + { + Message = + "This is currently only supported with Sonarr, Please enable Sonarr for this feature", + Result = false + }); } } @@ -599,14 +673,8 @@ namespace PlexRequests.UI.Modules DateTime.TryParse(showInfo.premiered, out firstAir); string fullShowName = $"{showInfo.name} ({firstAir.Year})"; - if (showInfo.externals?.thetvdb == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Our TV Provider (TVMaze) doesn't have a TheTVDBId for this TV Show :( We cannot add the TV Show automatically sorry! Please report this problem to the server admin so he/she can sort it out!" }); - } - var model = new RequestedModel { - ProviderId = showInfo.externals?.thetvdb ?? 0, Type = RequestType.TvShow, Overview = showInfo.summary.RemoveHtml(), PosterPath = showInfo.image?.medium, @@ -622,6 +690,25 @@ namespace PlexRequests.UI.Modules TvDbId = showId.ToString() }; + if (showInfo.externals?.thetvdb == null) + { + await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.MissingInformation); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + model.ProviderId = showInfo.externals?.thetvdb ?? 0; var seasonsList = new List(); switch (seasons) @@ -642,9 +729,14 @@ namespace PlexRequests.UI.Modules foreach (var ep in episodeModel?.Episodes ?? new Models.EpisodesModel[0]) { - model.Episodes.Add(new EpisodesModel { EpisodeNumber = ep.EpisodeNumber, SeasonNumber = ep.SeasonNumber }); + model.Episodes.Add(new EpisodesModel + { + EpisodeNumber = ep.EpisodeNumber, + SeasonNumber = ep.SeasonNumber + }); } - Analytics.TrackEventAsync(Category.Requests, Action.TvShow, $"Episode request for {model.Title}", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Requests, Action.TvShow, $"Episode request for {model.Title}", + Username, CookieHelper.GetAnalyticClientId(Cookies)); break; default: model.SeasonsRequested = seasons; @@ -691,7 +783,12 @@ namespace PlexRequests.UI.Modules else { // We no episodes to approve - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); } } else if (model.SeasonList.Except(existingRequest.SeasonList).Any()) @@ -720,9 +817,19 @@ namespace PlexRequests.UI.Modules var cachedEpisodes = cachedEpisodesTask.ToList(); foreach (var d in difference) // difference is from an existing request { - if (cachedEpisodes.Any(x => x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && x.ProviderId == providerId)) + if ( + cachedEpisodes.Any( + x => + x.SeasonNumber == d.SeasonNumber && x.EpisodeNumber == d.EpisodeNumber && + x.ProviderId == providerId)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" + }); } } @@ -738,66 +845,118 @@ namespace PlexRequests.UI.Modules var result = Checker.IsEpisodeAvailable(showId.ToString(), s.SeasonNumber, s.EpisodeNumber); if (result) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); } } } - else if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId, model.SeasonList)) + else if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), + providerId, model.SeasonList)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" + }); } } } catch (Exception) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullShowName) + }); } - - if (ShouldAutoApprove(RequestType.TvShow, settings)) + try { - model.Approved = true; - var s = await sonarrSettings; - var sender = new TvSenderOld(SonarrApi, SickrageApi); // TODO put back - if (s.Enabled) + if (ShouldAutoApprove(RequestType.TvShow, settings)) { - var result = await sender.SendToSonarr(s, model); - if (!string.IsNullOrEmpty(result?.title)) + model.Approved = true; + var s = await sonarrSettings; + var sender = new TvSenderOld(SonarrApi, SickrageApi); // TODO put back + if (s.Enabled) { - if (existingRequest != null) + var result = await sender.SendToSonarr(s, model); + if (!string.IsNullOrEmpty(result?.title)) { - return await UpdateRequest(model, settings, + if (existingRequest != null) + { + return await UpdateRequest(model, settings, + $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + return + await + AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + Log.Debug("Error with sending to sonarr."); + return + Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages ?? new List())); } - Log.Debug("Error with sending to sonarr."); - return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages ?? new List())); - } - var srSettings = SickRageService.GetSettings(); - if (srSettings.Enabled) - { - var result = sender.SendToSickRage(srSettings, model); - if (result?.result == "success") + var srSettings = SickRageService.GetSettings(); + if (srSettings.Enabled) { - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + var result = sender.SendToSickRage(srSettings, model); + if (result?.result == "success") + { + return + await + AddRequest(model, settings, + $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = result?.message ?? Resources.UI.Search_SickrageError + }); } - return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message ?? Resources.UI.Search_SickrageError }); - } - if (!srSettings.Enabled && !s.Enabled) - { - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); - } + if (!srSettings.Enabled && !s.Enabled) + { + return + await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp }); + return + Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_TvNotSetUp }); + } + return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + } + catch (Exception e) + { + await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.RequestFault); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + Log.Error(e); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); } - return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } - private async Task AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, string fullShowName, bool episodeReq = false) + private async Task AddUserToRequest(RequestedModel existingRequest, PlexRequestSettings settings, + string fullShowName, bool episodeReq = false) { // check if the current user is already marked as a requester for this show, if not, add them if (!existingRequest.UserHasRequested(Username)) @@ -812,12 +971,15 @@ namespace PlexRequests.UI.Modules $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } - return await UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_AlreadyRequested}"); + return + await UpdateRequest(existingRequest, settings, $"{fullShowName} {Resources.UI.Search_AlreadyRequested}"); } private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) { - var sendNotification = ShouldAutoApprove(type, prSettings) ? !prSettings.IgnoreNotifyForAutoApprovedRequests : true; + var sendNotification = ShouldAutoApprove(type, prSettings) + ? !prSettings.IgnoreNotifyForAutoApprovedRequests + : true; var claims = Context.CurrentUser?.Claims; if (claims != null) { @@ -836,9 +998,15 @@ namespace PlexRequests.UI.Modules var settings = await PrService.GetSettingsAsync(); if (!await CheckRequestLimit(settings, RequestType.Album)) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_WeeklyRequestLimitAlbums }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_WeeklyRequestLimitAlbums + }); } - Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, CookieHelper.GetAnalyticClientId(Cookies)); + Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, + CookieHelper.GetAnalyticClientId(Cookies)); var existingRequest = await RequestService.CheckRequestAsync(releaseId); if (existingRequest != null) @@ -848,7 +1016,15 @@ namespace PlexRequests.UI.Modules existingRequest.RequestedUsers.Add(Username); await RequestService.UpdateRequestAsync(existingRequest); } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" }); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = + settings.UsersCanViewOnlyOwnRequests + ? $"{existingRequest.Title} {Resources.UI.Search_SuccessfullyAdded}" + : $"{existingRequest.Title} {Resources.UI.Search_AlreadyRequested}" + }); } var albumInfo = MusicBrainzApi.GetAlbum(releaseId); @@ -858,11 +1034,17 @@ namespace PlexRequests.UI.Modules var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; if (artist == null) { - return Response.AsJson(new JsonResponseModel { Result = false, Message = Resources.UI.Search_MusicBrainzError }); + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = Resources.UI.Search_MusicBrainzError + }); } var albums = Checker.GetPlexAlbums(); - var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); + var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), + artist.name); if (alreadyInPlex) { @@ -892,28 +1074,46 @@ namespace PlexRequests.UI.Modules ArtistId = artist.id }; - if (ShouldAutoApprove(RequestType.Album, settings)) + try { - model.Approved = true; - var hpSettings = HeadphonesService.GetSettings(); - - if (!hpSettings.Enabled) + if (ShouldAutoApprove(RequestType.Album, settings)) { - await RequestService.AddRequestAsync(model); - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}" - }); + model.Approved = true; + var hpSettings = HeadphonesService.GetSettings(); + + if (!hpSettings.Enabled) + { + await RequestService.AddRequestAsync(model); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); + await sender.AddAlbum(model); + return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); } - var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); - await sender.AddAlbum(model); return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); } + catch (Exception e) + { + Log.Error(e); + await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); - return await AddRequest(model, settings, $"{model.Title} {Resources.UI.Search_SuccessfullyAdded}"); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.Album, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + throw; + } } private string GetMusicBrainzCoverArt(string id) diff --git a/PlexRequests.UI/NinjectModules/ServicesModule.cs b/PlexRequests.UI/NinjectModules/ServicesModule.cs index a180335d6..a7ba11dea 100644 --- a/PlexRequests.UI/NinjectModules/ServicesModule.cs +++ b/PlexRequests.UI/NinjectModules/ServicesModule.cs @@ -25,7 +25,7 @@ // ************************************************************************/ #endregion using Ninject.Modules; - +using PlexRequests.Core.Queue; using PlexRequests.Helpers.Analytics; using PlexRequests.Services.Interfaces; using PlexRequests.Services.Jobs; @@ -51,6 +51,8 @@ namespace PlexRequests.UI.NinjectModules Bind().To(); Bind().To(); Bind().To(); + + Bind().To(); } } } \ No newline at end of file From c5b65a335fc64a46f76722ad9e818ba4d9406a34 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 8 Nov 2016 14:32:03 +0000 Subject: [PATCH 10/68] Finished #556 --- .../PlexRequests.Core.Tests.csproj | 1 - PlexRequests.Core.Tests/StatusCheckerTests.cs | 46 ----------------- .../Interfaces/INotificationEngine.cs | 5 +- .../Jobs/PlexAvailabilityChecker.cs | 2 +- .../Notification/EmailMessageNotification.cs | 50 +++++++++++++++++-- .../Notification/NotificationEngine.cs | 16 +++--- PlexRequests.UI.Tests/SearchModuleTests.cs | 5 +- PlexRequests.UI/Modules/ApprovalModule.cs | 4 +- PlexRequests.UI/Modules/RequestsModule.cs | 2 +- 9 files changed, 66 insertions(+), 65 deletions(-) delete mode 100644 PlexRequests.Core.Tests/StatusCheckerTests.cs diff --git a/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj b/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj index 0e6f4d3e0..5002e3c21 100644 --- a/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj +++ b/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj @@ -60,7 +60,6 @@ - diff --git a/PlexRequests.Core.Tests/StatusCheckerTests.cs b/PlexRequests.Core.Tests/StatusCheckerTests.cs deleted file mode 100644 index 38b9b4674..000000000 --- a/PlexRequests.Core.Tests/StatusCheckerTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AuthenticationSettingsTests.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 NUnit.Framework; - -namespace PlexRequests.Core.Tests -{ - [TestFixture] - public class StatusCheckerTests - { - [Test] - [Ignore("API Limit")] - public void CheckStatusTest() - { - var checker = new StatusChecker(); - var status = checker.GetStatus(); - - Assert.That(status, Is.Not.Null); - } - } -} diff --git a/PlexRequests.Services/Interfaces/INotificationEngine.cs b/PlexRequests.Services/Interfaces/INotificationEngine.cs index ba53eb9bf..5a4198d00 100644 --- a/PlexRequests.Services/Interfaces/INotificationEngine.cs +++ b/PlexRequests.Services/Interfaces/INotificationEngine.cs @@ -27,13 +27,14 @@ using System.Collections.Generic; using System.Threading.Tasks; +using PlexRequests.Core.Models; using PlexRequests.Store; namespace PlexRequests.Services.Interfaces { public interface INotificationEngine { - Task NotifyUsers(IEnumerable modelChanged, string apiKey); - Task NotifyUsers(RequestedModel modelChanged, string apiKey); + Task NotifyUsers(IEnumerable modelChanged, string apiKey, NotificationType type); + Task NotifyUsers(RequestedModel modelChanged, string apiKey, NotificationType type); } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 161ea0ec3..a0d3ed85a 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -153,7 +153,7 @@ namespace PlexRequests.Services.Jobs if (modifiedModel.Any()) { - NotificationEngine.NotifyUsers(modifiedModel, plexSettings.PlexAuthToken); + NotificationEngine.NotifyUsers(modifiedModel, plexSettings.PlexAuthToken, NotificationType.RequestAvailable); RequestService.BatchUpdate(modifiedModel); } diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 52727dba8..cc22155f2 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -79,8 +79,8 @@ namespace PlexRequests.Services.Notification await EmailAvailableRequest(model, emailSettings); break; case NotificationType.RequestApproved: - throw new NotImplementedException(); - + await EmailRequestApproved(model, emailSettings); + break; case NotificationType.AdminNote: throw new NotImplementedException(); @@ -88,8 +88,8 @@ namespace PlexRequests.Services.Notification await EmailTest(model, emailSettings); break; case NotificationType.RequestDeclined: - throw new NotImplementedException(); - + await EmailRequestDeclined(model, emailSettings); + break; case NotificationType.ItemAddedToFaultQueue: await EmailAddedToRequestQueue(model, emailSettings); break; @@ -193,6 +193,48 @@ namespace PlexRequests.Services.Notification await Send(message, settings); } + private async Task EmailRequestDeclined(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Plex Requests: Your request has been declined", + $"Hello! Your request for {model.Title} has been declined, Sorry!", + model.ImgSrc); + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! Your request for {model.Title} has been declined, Sorry!", }; + + var message = new MimeMessage + { + Body = body.ToMessageBody(), + Subject = $"Plex Requests: Your request has been declined" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); + + + await Send(message, settings); + } + + private async Task EmailRequestApproved(NotificationModel model, EmailNotificationSettings settings) + { + var email = new EmailBasicTemplate(); + var html = email.LoadTemplate( + "Plex Requests: Your request has been approved!", + $"Hello! Your request for {model.Title} has been approved!", + model.ImgSrc); + var body = new BodyBuilder { HtmlBody = html, TextBody = $"Hello! Your request for {model.Title} has been approved!", }; + + var message = new MimeMessage + { + Body = body.ToMessageBody(), + Subject = $"Plex Requests: Your request has been approved!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); + + + await Send(message, settings); + } + private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) { if (!settings.EnableUserEmailNotifications) diff --git a/PlexRequests.Services/Notification/NotificationEngine.cs b/PlexRequests.Services/Notification/NotificationEngine.cs index 50dc4fc49..f0ba5bec5 100644 --- a/PlexRequests.Services/Notification/NotificationEngine.cs +++ b/PlexRequests.Services/Notification/NotificationEngine.cs @@ -55,7 +55,7 @@ namespace PlexRequests.Services.Notification private static Logger Log = LogManager.GetCurrentClassLogger(); private INotificationService Notification { get; } - public async Task NotifyUsers(IEnumerable modelChanged, string apiKey) + public async Task NotifyUsers(IEnumerable modelChanged, string apiKey, NotificationType type) { try { @@ -75,7 +75,7 @@ namespace PlexRequests.Services.Notification if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase)) { Log.Info("This user is the Plex server owner"); - await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title, model.PosterPath); + await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title, model.PosterPath, type); return; } @@ -88,7 +88,7 @@ namespace PlexRequests.Services.Notification } Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title); - await PublishUserNotification(email.Username, email.Email, model.Title, model.PosterPath); + await PublishUserNotification(email.Username, email.Email, model.Title, model.PosterPath, type); } } } @@ -98,7 +98,7 @@ namespace PlexRequests.Services.Notification } } - public async Task NotifyUsers(RequestedModel model, string apiKey) + public async Task NotifyUsers(RequestedModel model, string apiKey, NotificationType type) { try { @@ -117,7 +117,7 @@ namespace PlexRequests.Services.Notification if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase)) { Log.Info("This user is the Plex server owner"); - await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title, model.PosterPath); + await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title, model.PosterPath, type); return; } @@ -130,7 +130,7 @@ namespace PlexRequests.Services.Notification } Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title); - await PublishUserNotification(email.Username, email.Email, model.Title, model.PosterPath); + await PublishUserNotification(email.Username, email.Email, model.Title, model.PosterPath, type); } } catch (Exception e) @@ -139,13 +139,13 @@ namespace PlexRequests.Services.Notification } } - private async Task PublishUserNotification(string username, string email, string title, string img) + private async Task PublishUserNotification(string username, string email, string title, string img, NotificationType type) { var notificationModel = new NotificationModel { User = username, UserEmail = email, - NotificationType = NotificationType.RequestAvailable, + NotificationType = type, Title = title, ImgSrc = img }; diff --git a/PlexRequests.UI.Tests/SearchModuleTests.cs b/PlexRequests.UI.Tests/SearchModuleTests.cs index cffa9487d..08c1ddfdd 100644 --- a/PlexRequests.UI.Tests/SearchModuleTests.cs +++ b/PlexRequests.UI.Tests/SearchModuleTests.cs @@ -33,6 +33,7 @@ using NUnit.Framework; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.Plex; using PlexRequests.Core; +using PlexRequests.Core.Queue; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Helpers.Analytics; @@ -73,6 +74,7 @@ namespace PlexRequests.UI.Tests private Mock> _emailSettings; private Mock _issueService; private Mock _cache; + private Mock _faultQueue; private Mock> RequestLimitRepo { get; set; } private SearchModule Search { get; set; } private readonly Fixture F = new Fixture(); @@ -145,6 +147,7 @@ namespace PlexRequests.UI.Tests RequestLimitRepo = new Mock>(); _emailSettings = new Mock>(); _issueService = new Mock(); + _faultQueue = new Mock(); CreateModule(); } @@ -155,7 +158,7 @@ namespace PlexRequests.UI.Tests _sickRageSettingsMock.Object, _cpApi.Object, _srApi.Object, _notificationService.Object, _music.Object, _hpAPi.Object, _headphonesSettings.Object, _cpCache.Object, _sonarrCache.Object, _srCache.Object, _plexApi.Object, _plexSettingsMock.Object, _authMock.Object, - _userRepo.Object, _emailSettings.Object, _issueService.Object, _analytics.Object, RequestLimitRepo.Object); + _userRepo.Object, _emailSettings.Object, _issueService.Object, _analytics.Object, RequestLimitRepo.Object, _faultQueue.Object); } diff --git a/PlexRequests.UI/Modules/ApprovalModule.cs b/PlexRequests.UI/Modules/ApprovalModule.cs index e0ba7314d..09ceef150 100644 --- a/PlexRequests.UI/Modules/ApprovalModule.cs +++ b/PlexRequests.UI/Modules/ApprovalModule.cs @@ -37,6 +37,7 @@ using NLog; using PlexRequests.Api; using PlexRequests.Api.Interfaces; using PlexRequests.Core; +using PlexRequests.Core.Queue; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Store; @@ -50,7 +51,7 @@ namespace PlexRequests.UI.Modules public ApprovalModule(IRequestService service, ISettingsService cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi, ISettingsService sonarrSettings, ISickRageApi srApi, ISettingsService srSettings, - ISettingsService hpSettings, IHeadphonesApi hpApi, ISettingsService pr) : base("approval", pr) + ISettingsService hpSettings, IHeadphonesApi hpApi, ISettingsService pr, ITransientFaultQueue faultQueue) : base("approval", pr) { this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser); @@ -85,6 +86,7 @@ namespace PlexRequests.UI.Modules private ISickRageApi SickRageApi { get; } private ICouchPotatoApi CpApi { get; } private IHeadphonesApi HeadphoneApi { get; } + private ITransientFaultQueue FaultQueue { get} /// /// Approves the specified request identifier. diff --git a/PlexRequests.UI/Modules/RequestsModule.cs b/PlexRequests.UI/Modules/RequestsModule.cs index 13aecdc03..068c3de16 100644 --- a/PlexRequests.UI/Modules/RequestsModule.cs +++ b/PlexRequests.UI/Modules/RequestsModule.cs @@ -386,7 +386,7 @@ namespace PlexRequests.UI.Modules var result = await Service.UpdateRequestAsync(originalRequest); var plexService = await PlexSettings.GetSettingsAsync(); - await NotificationEngine.NotifyUsers(originalRequest, plexService.PlexAuthToken); + await NotificationEngine.NotifyUsers(originalRequest, plexService.PlexAuthToken, available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined); return Response.AsJson(result ? new { Result = true, Available = available, Message = string.Empty } : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" }); From bcfd675d3d06a6ba75bb56fd1bdd5549032fdae4 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 8 Nov 2016 14:36:01 +0000 Subject: [PATCH 11/68] Finished the notification for the fault queue --- .../Notification/PushbulletNotification.cs | 12 ++++++++++++ .../Notification/PushoverNotification.cs | 11 +++++++++++ .../Notification/SlackNotification.cs | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/PlexRequests.Services/Notification/PushbulletNotification.cs b/PlexRequests.Services/Notification/PushbulletNotification.cs index e2ae3c4c4..ff1d2e48d 100644 --- a/PlexRequests.Services/Notification/PushbulletNotification.cs +++ b/PlexRequests.Services/Notification/PushbulletNotification.cs @@ -81,6 +81,11 @@ namespace PlexRequests.Services.Notification case NotificationType.Test: await PushTestAsync(pushSettings); break; + case NotificationType.RequestDeclined: + break; + case NotificationType.ItemAddedToFaultQueue: + await PushFaultQueue(model, pushSettings); + break; default: throw new ArgumentOutOfRangeException(); } @@ -125,6 +130,13 @@ namespace PlexRequests.Services.Notification await Push(settings, message, pushTitle); } + private async Task PushFaultQueue(NotificationModel model, PushbulletNotificationSettings settings) + { + var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"; + var pushTitle = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} {model.Title} has been requested but could not be added!"; + await Push(settings, message, pushTitle); + } + private async Task Push(PushbulletNotificationSettings settings, string message, string title) { try diff --git a/PlexRequests.Services/Notification/PushoverNotification.cs b/PlexRequests.Services/Notification/PushoverNotification.cs index 2adafb11e..f04ed8560 100644 --- a/PlexRequests.Services/Notification/PushoverNotification.cs +++ b/PlexRequests.Services/Notification/PushoverNotification.cs @@ -81,6 +81,11 @@ namespace PlexRequests.Services.Notification case NotificationType.Test: await PushTestAsync(model, pushSettings); break; + case NotificationType.RequestDeclined: + break; + case NotificationType.ItemAddedToFaultQueue: + await PushFaultQueue(model, pushSettings); + break; default: throw new ArgumentOutOfRangeException(); } @@ -122,6 +127,12 @@ namespace PlexRequests.Services.Notification await Push(settings, message); } + private async Task PushFaultQueue(NotificationModel model, PushoverNotificationSettings settings) + { + var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"; + await Push(settings, message); + } + private async Task Push(PushoverNotificationSettings settings, string message) { try diff --git a/PlexRequests.Services/Notification/SlackNotification.cs b/PlexRequests.Services/Notification/SlackNotification.cs index f8c90d3ca..41ce4cc05 100644 --- a/PlexRequests.Services/Notification/SlackNotification.cs +++ b/PlexRequests.Services/Notification/SlackNotification.cs @@ -88,6 +88,11 @@ namespace PlexRequests.Services.Notification case NotificationType.Test: await PushTest(pushSettings); break; + case NotificationType.RequestDeclined: + break; + case NotificationType.ItemAddedToFaultQueue: + await PushFaultQueue(model, pushSettings); + break; default: throw new ArgumentOutOfRangeException(); } @@ -111,6 +116,12 @@ namespace PlexRequests.Services.Notification await Push(settings, message); } + private async Task PushFaultQueue(NotificationModel model, SlackNotificationSettings settings) + { + var message = $"Hello! The user '{model.User}' has requested {model.Title} but it could not be added. This has been added into the requests queue and will keep retrying"; + await Push(settings, message); + } + private async Task Push(SlackNotificationSettings config, string message) { try From 5a5f7f5610cb19892d3c23e6ab8e55e55876361b Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 8 Nov 2016 14:46:42 +0000 Subject: [PATCH 12/68] Fixed build --- PlexRequests.UI/Modules/ApprovalModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexRequests.UI/Modules/ApprovalModule.cs b/PlexRequests.UI/Modules/ApprovalModule.cs index 09ceef150..5105b929a 100644 --- a/PlexRequests.UI/Modules/ApprovalModule.cs +++ b/PlexRequests.UI/Modules/ApprovalModule.cs @@ -86,7 +86,7 @@ namespace PlexRequests.UI.Modules private ISickRageApi SickRageApi { get; } private ICouchPotatoApi CpApi { get; } private IHeadphonesApi HeadphoneApi { get; } - private ITransientFaultQueue FaultQueue { get} + private ITransientFaultQueue FaultQueue { get; } /// /// Approves the specified request identifier. From 50dec5f530e52eebd21f9fb6a9e9b6061c5d92ca Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 9 Nov 2016 14:32:23 +0000 Subject: [PATCH 13/68] Started on the queue for requests #483 TV Requests with missing information has been completed --- PlexRequests.Core/PlexRequests.Core.csproj | 2 + .../Queue/ITransientFaultQueue.cs | 4 +- .../Queue/TransientFaultQueue.cs | 12 +- .../SettingModels/ScheduledJobsSettings.cs | 1 + .../Helpers => PlexRequests.Core}/TvSender.cs | 7 +- .../TvSenderOld.cs | 2 +- .../Jobs/FaultQueueHandler.cs | 214 ++++++++++++++++++ .../PlexRequests.Services.csproj | 1 + PlexRequests.Store/Models/RequestQueue.cs | 4 +- PlexRequests.Store/SqlTables.sql | 5 +- PlexRequests.UI.Tests/TvSenderTests.cs | 27 +-- PlexRequests.UI/Jobs/Scheduler.cs | 20 +- PlexRequests.UI/Modules/SearchModule.cs | 46 ++-- PlexRequests.UI/PlexRequests.UI.csproj | 2 - 14 files changed, 276 insertions(+), 71 deletions(-) rename {PlexRequests.UI/Helpers => PlexRequests.Core}/TvSender.cs (99%) rename {PlexRequests.UI/Helpers => PlexRequests.Core}/TvSenderOld.cs (99%) create mode 100644 PlexRequests.Services/Jobs/FaultQueueHandler.cs diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 8faf4e83a..17bb7ceca 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -137,6 +137,8 @@ + + diff --git a/PlexRequests.Core/Queue/ITransientFaultQueue.cs b/PlexRequests.Core/Queue/ITransientFaultQueue.cs index ac62add0f..649209307 100644 --- a/PlexRequests.Core/Queue/ITransientFaultQueue.cs +++ b/PlexRequests.Core/Queue/ITransientFaultQueue.cs @@ -11,7 +11,7 @@ namespace PlexRequests.Core.Queue Task DequeueAsync(); IEnumerable GetQueue(); Task> GetQueueAsync(); - void QueueItem(RequestedModel request, RequestType type, FaultType faultType); - Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType); + void QueueItem(RequestedModel request, string id, RequestType type, FaultType faultType); + Task QueueItemAsync(RequestedModel request, string id, RequestType type, FaultType faultType); } } \ No newline at end of file diff --git a/PlexRequests.Core/Queue/TransientFaultQueue.cs b/PlexRequests.Core/Queue/TransientFaultQueue.cs index 108387f03..57fd2eb34 100644 --- a/PlexRequests.Core/Queue/TransientFaultQueue.cs +++ b/PlexRequests.Core/Queue/TransientFaultQueue.cs @@ -46,14 +46,14 @@ namespace PlexRequests.Core.Queue private IRepository RequestQueue { get; } - public void QueueItem(RequestedModel request, RequestType type, FaultType faultType) + public void QueueItem(RequestedModel request, string id, RequestType type, FaultType faultType) { //Ensure there is not a duplicate queued item var existingItem = RequestQueue.Custom( connection => { connection.Open(); - var result = connection.Query("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + var result = connection.Query("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = id }); return result; }).FirstOrDefault(); @@ -68,19 +68,19 @@ namespace PlexRequests.Core.Queue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId, + PrimaryIdentifier = id, FaultType = faultType }; RequestQueue.Insert(queue); } - public async Task QueueItemAsync(RequestedModel request, RequestType type, FaultType faultType) + public async Task QueueItemAsync(RequestedModel request, string id, RequestType type, FaultType faultType) { //Ensure there is not a duplicate queued item var existingItem = await RequestQueue.CustomAsync(async connection => { connection.Open(); - var result = await connection.QueryAsync("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = request.ProviderId }); + var result = await connection.QueryAsync("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = id }); return result; }); @@ -95,7 +95,7 @@ namespace PlexRequests.Core.Queue { Type = type, Content = ByteConverterHelper.ReturnBytes(request), - PrimaryIdentifier = request.ProviderId, + PrimaryIdentifier = id, FaultType = faultType }; await RequestQueue.InsertAsync(queue); diff --git a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs index 234dc5774..2f1f2ccdc 100644 --- a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs +++ b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs @@ -42,5 +42,6 @@ namespace PlexRequests.Core.SettingModels [Obsolete("We use the CRON job now")] public int RecentlyAdded { get; set; } public string RecentlyAddedCron { get; set; } + public int FaultQueueHandler { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.Core/TvSender.cs similarity index 99% rename from PlexRequests.UI/Helpers/TvSender.cs rename to PlexRequests.Core/TvSender.cs index b67311d64..7ec462582 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.Core/TvSender.cs @@ -24,18 +24,19 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.SickRage; using PlexRequests.Api.Models.Sonarr; using PlexRequests.Core.SettingModels; using PlexRequests.Store; -using System.Linq; -using System.Threading.Tasks; -namespace PlexRequests.UI.Helpers +namespace PlexRequests.Core { public class TvSender { diff --git a/PlexRequests.UI/Helpers/TvSenderOld.cs b/PlexRequests.Core/TvSenderOld.cs similarity index 99% rename from PlexRequests.UI/Helpers/TvSenderOld.cs rename to PlexRequests.Core/TvSenderOld.cs index ced1c81dd..b603272b1 100644 --- a/PlexRequests.UI/Helpers/TvSenderOld.cs +++ b/PlexRequests.Core/TvSenderOld.cs @@ -36,7 +36,7 @@ using PlexRequests.Api.Models.Sonarr; using PlexRequests.Core.SettingModels; using PlexRequests.Store; -namespace PlexRequests.UI.Helpers +namespace PlexRequests.Core { public class TvSenderOld { diff --git a/PlexRequests.Services/Jobs/FaultQueueHandler.cs b/PlexRequests.Services/Jobs/FaultQueueHandler.cs new file mode 100644 index 000000000..0a540d5ad --- /dev/null +++ b/PlexRequests.Services/Jobs/FaultQueueHandler.cs @@ -0,0 +1,214 @@ +#region Copyright + +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserRequestLimitResetter.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; + +using NLog; +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; + +using Quartz; + +namespace PlexRequests.Services.Jobs +{ + public class FaultQueueHandler : IJob + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public FaultQueueHandler(IJobRecord record, IRepository repo, ISonarrApi sonarrApi, + ISickRageApi srApi, + ISettingsService sonarrSettings, ISettingsService srSettings) + { + Record = record; + Repo = repo; + SonarrApi = sonarrApi; + SrApi = srApi; + SickrageSettings = srSettings; + SonarrSettings = sonarrSettings; + } + + private IRepository Repo { get; } + private IJobRecord Record { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SrApi { get; } + private ISettingsService SonarrSettings { get; set; } + private ISettingsService SickrageSettings { get; set; } + + + public void Execute(IJobExecutionContext context) + { + try + { + var faultedRequests = Repo.GetAll().ToList(); + + + var missingInfo = faultedRequests.Where(x => x.FaultType == FaultType.MissingInformation).ToList(); + ProcessMissingInformation(missingInfo); + + var transientErrors = faultedRequests.Where(x => x.FaultType == FaultType.RequestFault).ToList(); + ProcessTransientErrors(transientErrors); + + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Record.Record(JobNames.RequestLimitReset); + } + } + + + private void ProcessMissingInformation(List requests) + { + var sonarrSettings = SonarrSettings.GetSettings(); + var sickrageSettings = SickrageSettings.GetSettings(); + + if (!requests.Any()) + { + return; + } + var tv = requests.Where(x => x.Type == RequestType.TvShow); + + // TV + var tvApi = new TvMazeApi(); + foreach (var t in tv) + { + var providerId = int.Parse(t.PrimaryIdentifier); + var showInfo = tvApi.ShowLookupByTheTvDbId(providerId); + + if (showInfo.externals?.thetvdb != null) + { + // We now have the info + var tvModel = ByteConverterHelper.ReturnObject(t.Content); + tvModel.ProviderId = showInfo.externals.thetvdb.Value; + var result = ProcessTvShow(tvModel, sonarrSettings, sickrageSettings); + + if (!result) + { + // we now have the info but couldn't add it, so add it back into the queue but with a different fault + t.Content = ByteConverterHelper.ReturnBytes(tvModel); + t.FaultType = FaultType.RequestFault; + t.LastRetry = DateTime.UtcNow; + Repo.Update(t); + } + else + { + // Successful, remove from the fault queue + Repo.Delete(t); + } + } + } + } + + private bool ProcessTvShow(RequestedModel tvModel, SonarrSettings sonarr, SickRageSettings sickrage) + { + try + { + + var sender = new TvSenderOld(SonarrApi, SrApi); + if (sonarr.Enabled) + { + var task = sender.SendToSonarr(sonarr, tvModel, sonarr.QualityProfile); + var a = task.Result; + if (string.IsNullOrEmpty(a?.title)) + { + // Couldn't send it + return false; + } + return true; + } + + if (sickrage.Enabled) + { + var result = sender.SendToSickRage(sickrage, tvModel); + if (result?.result != "success") + { + // Couldn't send it + return false; + } + return true; + } + + return false; + } + catch (Exception e) + { + Log.Error(e); + return false; // It fails so it will get added back into the queue + } + } + + private void ProcessTransientErrors(List requests) + { + var sonarrSettings = SonarrSettings.GetSettings(); + var sickrageSettings = SickrageSettings.GetSettings(); + + if (!requests.Any()) + { + return; + } + var tv = requests.Where(x => x.Type == RequestType.TvShow); + var movie = requests.Where(x => x.Type == RequestType.Movie); + var album = requests.Where(x => x.Type == RequestType.Album); + + + + foreach (var t in tv) + { + var tvModel = ByteConverterHelper.ReturnObject(t.Content); + var result = ProcessTvShow(tvModel, sonarrSettings, sickrageSettings); + + if (!result) + { + // we now have the info but couldn't add it, so do nothing now. + t.LastRetry = DateTime.UtcNow; + Repo.Update(t); + } + else + { + // Successful, remove from the fault queue + Repo.Delete(t); + } + } + + + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj index d53128879..1f542944b 100644 --- a/PlexRequests.Services/PlexRequests.Services.csproj +++ b/PlexRequests.Services/PlexRequests.Services.csproj @@ -93,6 +93,7 @@ + diff --git a/PlexRequests.Store/Models/RequestQueue.cs b/PlexRequests.Store/Models/RequestQueue.cs index 15067fbc7..f98517ab3 100644 --- a/PlexRequests.Store/Models/RequestQueue.cs +++ b/PlexRequests.Store/Models/RequestQueue.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System; using Dapper.Contrib.Extensions; namespace PlexRequests.Store.Models @@ -32,13 +33,14 @@ namespace PlexRequests.Store.Models [Table("RequestQueue")] public class RequestQueue : Entity { - public int PrimaryIdentifier { get; set; } + public string PrimaryIdentifier { get; set; } public RequestType Type { get; set; } public byte[] Content { get; set; } public FaultType FaultType { get; set; } + public DateTime? LastRetry { get; set; } } public enum FaultType diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 220664ba1..f0e852717 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -136,10 +136,11 @@ CREATE INDEX IF NOT EXISTS PlexEpisodes_ProviderId ON PlexEpisodes (ProviderId); CREATE TABLE IF NOT EXISTS RequestQueue ( Id INTEGER PRIMARY KEY AUTOINCREMENT, - PrimaryIdentifier INTEGER NOT NULL, + PrimaryIdentifier VARCHAR(100) NOT NULL, Type INTEGER NOT NULL, FaultType INTEGER NOT NULL, - Content BLOB NOT NULL + Content BLOB NOT NULL, + LastRetry VARCHAR(100) ); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); diff --git a/PlexRequests.UI.Tests/TvSenderTests.cs b/PlexRequests.UI.Tests/TvSenderTests.cs index 678a196d9..a63ef719b 100644 --- a/PlexRequests.UI.Tests/TvSenderTests.cs +++ b/PlexRequests.UI.Tests/TvSenderTests.cs @@ -35,6 +35,7 @@ using NUnit.Framework; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.Sonarr; +using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Store; using PlexRequests.UI.Helpers; @@ -146,32 +147,6 @@ namespace PlexRequests.UI.Tests true, It.IsAny()), Times.Once); } - [Test] - public async Task RequestEpisodesWithExistingSeriesTest() - { - var episodesReturned = new List - { - new SonarrEpisodes {episodeNumber = 1, seasonNumber = 2, monitored = false, id=22} - }; - SonarrMock.Setup(x => x.GetEpisodes(It.IsAny(), It.IsAny(), - It.IsAny())).Returns(episodesReturned); - SonarrMock.Setup(x => x.GetEpisode("22", It.IsAny(), It.IsAny())).Returns(new SonarrEpisode {id=22}); - - - Sender = new TvSender(SonarrMock.Object, SickrageMock.Object); - - var model = new RequestedModel - { - Episodes = new List { new EpisodesModel { EpisodeNumber = 1, SeasonNumber = 2 } } - }; - - var series = new Series(); - await Sender.RequestEpisodesWithExistingSeries(model, series, GetSonarrSettings()); - - SonarrMock.Verify(x => x.UpdateEpisode(It.Is(e => e.monitored), It.IsAny(), It.IsAny())); - SonarrMock.Verify(x => x.GetEpisode("22", It.IsAny(), It.IsAny()),Times.Once); - SonarrMock.Verify(x => x.SearchForEpisodes(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } private SonarrSettings GetSonarrSettings() { diff --git a/PlexRequests.UI/Jobs/Scheduler.cs b/PlexRequests.UI/Jobs/Scheduler.cs index 669500168..5ea4ba540 100644 --- a/PlexRequests.UI/Jobs/Scheduler.cs +++ b/PlexRequests.UI/Jobs/Scheduler.cs @@ -69,11 +69,10 @@ namespace PlexRequests.UI.Jobs JobBuilder.Create().WithIdentity("StoreBackup", "Database").Build(), JobBuilder.Create().WithIdentity("StoreCleanup", "Database").Build(), JobBuilder.Create().WithIdentity("UserRequestLimiter", "Request").Build(), - JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build() + JobBuilder.Create().WithIdentity("RecentlyAddedModel", "Email").Build(), + JobBuilder.Create().WithIdentity("FaultQueueHandler", "Fault").Build(), }; - - - + jobs.AddRange(jobList); return jobs; @@ -151,7 +150,10 @@ namespace PlexRequests.UI.Jobs { s.UserRequestLimitResetter = 12; } - + if (s.FaultQueueHandler == 0) + { + s.FaultQueueHandler = 6; + } var triggers = new List(); @@ -221,6 +223,13 @@ namespace PlexRequests.UI.Jobs .WithSimpleSchedule(x => x.WithIntervalInHours(2).RepeatForever()) .Build(); + var fault = + TriggerBuilder.Create() + .WithIdentity("FaultQueueHandler", "Fault") + .StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) + .WithSimpleSchedule(x => x.WithIntervalInHours(s.FaultQueueHandler).RepeatForever()) + .Build(); + triggers.Add(rencentlyAdded); triggers.Add(plexAvailabilityChecker); triggers.Add(srCacher); @@ -230,6 +239,7 @@ namespace PlexRequests.UI.Jobs triggers.Add(storeCleanup); triggers.Add(userRequestLimiter); triggers.Add(plexEpCacher); + triggers.Add(fault); return triggers; } diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index ef35e08b6..fcd82d964 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -589,7 +589,7 @@ namespace PlexRequests.UI.Modules catch (Exception e) { Log.Fatal(e); - await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); + await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault); await NotificationService.Publish(new NotificationModel { @@ -690,26 +690,6 @@ namespace PlexRequests.UI.Modules TvDbId = showId.ToString() }; - if (showInfo.externals?.thetvdb == null) - { - await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.MissingInformation); - await NotificationService.Publish(new NotificationModel - { - DateTime = DateTime.Now, - User = Username, - RequestType = RequestType.TvShow, - Title = model.Title, - NotificationType = NotificationType.ItemAddedToFaultQueue - }); - return Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" - }); - } - - model.ProviderId = showInfo.externals?.thetvdb ?? 0; - var seasonsList = new List(); switch (seasons) { @@ -876,6 +856,26 @@ namespace PlexRequests.UI.Modules }); } + if (showInfo.externals?.thetvdb == null) + { + await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.MissingInformation); + await NotificationService.Publish(new NotificationModel + { + DateTime = DateTime.Now, + User = Username, + RequestType = RequestType.TvShow, + Title = model.Title, + NotificationType = NotificationType.ItemAddedToFaultQueue + }); + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}" + }); + } + + model.ProviderId = showInfo.externals?.thetvdb ?? 0; + try { if (ShouldAutoApprove(RequestType.TvShow, settings)) @@ -936,7 +936,7 @@ namespace PlexRequests.UI.Modules } catch (Exception e) { - await FaultQueue.QueueItemAsync(model, RequestType.TvShow, FaultType.RequestFault); + await FaultQueue.QueueItemAsync(model, showInfo.id.ToString(), RequestType.TvShow, FaultType.RequestFault); await NotificationService.Publish(new NotificationModel { DateTime = DateTime.Now, @@ -1102,7 +1102,7 @@ namespace PlexRequests.UI.Modules catch (Exception e) { Log.Error(e); - await FaultQueue.QueueItemAsync(model, RequestType.Movie, FaultType.RequestFault); + await FaultQueue.QueueItemAsync(model, albumInfo.id, RequestType.Movie, FaultType.RequestFault); await NotificationService.Publish(new NotificationModel { diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index a18aec630..949f29117 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -216,8 +216,6 @@ - - From dd92bb179c23d21f8a1b6dc7bb2063d0d24d76a3 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 10 Nov 2016 12:55:01 +0000 Subject: [PATCH 14/68] Finished the queue #483 --- .../HeadphonesSender.cs | 6 +- PlexRequests.Core/PlexRequests.Core.csproj | 1 + .../Jobs/FaultQueueHandler.cs | 119 ++++++++++++++---- PlexRequests.Services/Jobs/JobNames.cs | 1 + PlexRequests.UI/Modules/SearchModule.cs | 9 +- PlexRequests.UI/PlexRequests.UI.csproj | 1 - 6 files changed, 105 insertions(+), 32 deletions(-) rename {PlexRequests.UI/Helpers => PlexRequests.Core}/HeadphonesSender.cs (99%) diff --git a/PlexRequests.UI/Helpers/HeadphonesSender.cs b/PlexRequests.Core/HeadphonesSender.cs similarity index 99% rename from PlexRequests.UI/Helpers/HeadphonesSender.cs rename to PlexRequests.Core/HeadphonesSender.cs index 29c428612..19e438be2 100644 --- a/PlexRequests.UI/Helpers/HeadphonesSender.cs +++ b/PlexRequests.Core/HeadphonesSender.cs @@ -24,18 +24,16 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + using System.Linq; using System.Threading; using System.Threading.Tasks; - using NLog; - using PlexRequests.Api.Interfaces; -using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Store; -namespace PlexRequests.UI.Helpers +namespace PlexRequests.Core { public class HeadphonesSender { diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 17bb7ceca..506ac3a62 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -90,6 +90,7 @@ + diff --git a/PlexRequests.Services/Jobs/FaultQueueHandler.cs b/PlexRequests.Services/Jobs/FaultQueueHandler.cs index 0a540d5ad..9238a12ec 100644 --- a/PlexRequests.Services/Jobs/FaultQueueHandler.cs +++ b/PlexRequests.Services/Jobs/FaultQueueHandler.cs @@ -51,32 +51,43 @@ namespace PlexRequests.Services.Jobs private static readonly Logger Log = LogManager.GetCurrentClassLogger(); public FaultQueueHandler(IJobRecord record, IRepository repo, ISonarrApi sonarrApi, - ISickRageApi srApi, - ISettingsService sonarrSettings, ISettingsService srSettings) + ISickRageApi srApi, ISettingsService sonarrSettings, ISettingsService srSettings, + ICouchPotatoApi cpApi, ISettingsService cpsettings, IRequestService requestService, + ISettingsService hpSettings, IHeadphonesApi headphonesApi) { Record = record; Repo = repo; SonarrApi = sonarrApi; SrApi = srApi; + CpApi = cpApi; + HpApi = headphonesApi; + + RequestService = requestService; + SickrageSettings = srSettings; SonarrSettings = sonarrSettings; + CpSettings = cpsettings; + HeadphoneSettings = hpSettings; } private IRepository Repo { get; } private IJobRecord Record { get; } private ISonarrApi SonarrApi { get; } private ISickRageApi SrApi { get; } - private ISettingsService SonarrSettings { get; set; } - private ISettingsService SickrageSettings { get; set; } - + private ICouchPotatoApi CpApi { get; } + private IHeadphonesApi HpApi { get; } + private IRequestService RequestService { get; } + private ISettingsService SonarrSettings { get; } + private ISettingsService SickrageSettings { get; } + private ISettingsService CpSettings { get; } + private ISettingsService HeadphoneSettings { get; } public void Execute(IJobExecutionContext context) { try { var faultedRequests = Repo.GetAll().ToList(); - - + var missingInfo = faultedRequests.Where(x => x.FaultType == FaultType.MissingInformation).ToList(); ProcessMissingInformation(missingInfo); @@ -90,7 +101,7 @@ namespace PlexRequests.Services.Jobs } finally { - Record.Record(JobNames.RequestLimitReset); + Record.Record(JobNames.FaultQueueHandler); } } @@ -163,9 +174,66 @@ namespace PlexRequests.Services.Jobs // Couldn't send it return false; } + + // Approve it + tvModel.Approved = true; + RequestService.UpdateRequest(tvModel); return true; } + return false; + } + catch (Exception e) + { + Log.Error(e); + return false; // It fails so it will get added back into the queue + } + } + + private bool ProcessMovies(RequestedModel model, CouchPotatoSettings cp) + { + try + { + if (cp.Enabled) + { + var result = CpApi.AddMovie(model.ImdbId, cp.ApiKey, model.Title, + cp.FullUri, cp.ProfileId); + + if (result) + { + // Approve it now + model.Approved = true; + RequestService.UpdateRequest(model); + }; + return result; + } + return false; + } + catch (Exception e) + { + Log.Error(e); + return false; // It fails so it will get added back into the queue + } + } + + private bool ProcessAlbums(RequestedModel model, HeadphonesSettings hp) + { + try + { + if (hp.Enabled) + { + var sender = new HeadphonesSender(HpApi, hp, RequestService); + var result = sender.AddAlbum(model).Result; + + if (result) + { + // Approve it now + model.Approved = true; + RequestService.UpdateRequest(model); + }; + + return result; + } return false; } catch (Exception e) @@ -179,36 +247,45 @@ namespace PlexRequests.Services.Jobs { var sonarrSettings = SonarrSettings.GetSettings(); var sickrageSettings = SickrageSettings.GetSettings(); + var cpSettings = CpSettings.GetSettings(); + var hpSettings = HeadphoneSettings.GetSettings(); if (!requests.Any()) { return; } - var tv = requests.Where(x => x.Type == RequestType.TvShow); - var movie = requests.Where(x => x.Type == RequestType.Movie); - var album = requests.Where(x => x.Type == RequestType.Album); - - - foreach (var t in tv) + foreach (var request in requests) { - var tvModel = ByteConverterHelper.ReturnObject(t.Content); - var result = ProcessTvShow(tvModel, sonarrSettings, sickrageSettings); + var model = ByteConverterHelper.ReturnObject(request.Content); + var result = false; + switch (request.Type) + { + case RequestType.Movie: + result = ProcessMovies(model, cpSettings); + break; + case RequestType.TvShow: + result = ProcessTvShow(model, sonarrSettings, sickrageSettings); + break; + case RequestType.Album: + result = ProcessAlbums(model, hpSettings); + break; + default: + throw new ArgumentOutOfRangeException(); + } if (!result) { // we now have the info but couldn't add it, so do nothing now. - t.LastRetry = DateTime.UtcNow; - Repo.Update(t); + request.LastRetry = DateTime.UtcNow; + Repo.Update(request); } else { // Successful, remove from the fault queue - Repo.Delete(t); + Repo.Delete(request); } } - - } } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/JobNames.cs b/PlexRequests.Services/Jobs/JobNames.cs index 77f177713..0f8b4c96e 100644 --- a/PlexRequests.Services/Jobs/JobNames.cs +++ b/PlexRequests.Services/Jobs/JobNames.cs @@ -37,5 +37,6 @@ namespace PlexRequests.Services.Jobs public const string RequestLimitReset = "Request Limit Reset"; public const string EpisodeCacher = "Plex Episode Cacher"; public const string RecentlyAddedEmail = "Recently Added Email Notification"; + public const string FaultQueueHandler = "Request Fault Queue Handler"; } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index fcd82d964..1e0e4c490 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -909,9 +909,7 @@ namespace PlexRequests.UI.Modules var result = sender.SendToSickRage(srSettings, model); if (result?.result == "success") { - return - await - AddRequest(model, settings, + return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } return @@ -924,8 +922,7 @@ namespace PlexRequests.UI.Modules if (!srSettings.Enabled && !s.Enabled) { - return - await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); + return await AddRequest(model, settings, $"{fullShowName} {Resources.UI.Search_SuccessfullyAdded}"); } return @@ -1102,7 +1099,7 @@ namespace PlexRequests.UI.Modules catch (Exception e) { Log.Error(e); - await FaultQueue.QueueItemAsync(model, albumInfo.id, RequestType.Movie, FaultType.RequestFault); + await FaultQueue.QueueItemAsync(model, albumInfo.id, RequestType.Album, FaultType.RequestFault); await NotificationService.Publish(new NotificationModel { diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 949f29117..75ecd4701 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -210,7 +210,6 @@ - From 8a61371048142058329c35787917acbff55ff615 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 10 Nov 2016 13:37:51 +0000 Subject: [PATCH 15/68] Finished #483 --- .../Queue/TransientFaultQueue.cs | 2 +- PlexRequests.Store/Models/RequestQueue.cs | 2 +- PlexRequests.Store/SqlTables.sql | 2 +- .../Models/FaultedRequestsViewModel.cs | 56 ++++++++++ .../Modules/Admin/FaultQueueModule.cs | 74 +++++++++++++ PlexRequests.UI/Modules/DonationLinkModule.cs | 3 - PlexRequests.UI/Modules/SearchModule.cs | 2 +- PlexRequests.UI/PlexRequests.UI.csproj | 5 + .../Views/Admin/RequestFaultQueue.cshtml | 103 ++++++++++++++++++ PlexRequests.UI/Views/Admin/_Sidebar.cshtml | 1 + 10 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 PlexRequests.UI/Models/FaultedRequestsViewModel.cs create mode 100644 PlexRequests.UI/Modules/Admin/FaultQueueModule.cs create mode 100644 PlexRequests.UI/Views/Admin/RequestFaultQueue.cshtml diff --git a/PlexRequests.Core/Queue/TransientFaultQueue.cs b/PlexRequests.Core/Queue/TransientFaultQueue.cs index 57fd2eb34..5c2f1de71 100644 --- a/PlexRequests.Core/Queue/TransientFaultQueue.cs +++ b/PlexRequests.Core/Queue/TransientFaultQueue.cs @@ -80,7 +80,7 @@ namespace PlexRequests.Core.Queue var existingItem = await RequestQueue.CustomAsync(async connection => { connection.Open(); - var result = await connection.QueryAsync("select * from RequestQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = id }); + var result = await connection.QueryAsync("select * from RequestFaultQueue where PrimaryIdentifier = @ProviderId", new { ProviderId = id }); return result; }); diff --git a/PlexRequests.Store/Models/RequestQueue.cs b/PlexRequests.Store/Models/RequestQueue.cs index f98517ab3..cbbfb62b6 100644 --- a/PlexRequests.Store/Models/RequestQueue.cs +++ b/PlexRequests.Store/Models/RequestQueue.cs @@ -30,7 +30,7 @@ using Dapper.Contrib.Extensions; namespace PlexRequests.Store.Models { - [Table("RequestQueue")] + [Table("RequestFaultQueue")] public class RequestQueue : Entity { public string PrimaryIdentifier { get; set; } diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index f0e852717..84a31306f 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -133,7 +133,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS PlexEpisodes_Id ON PlexEpisodes (Id); CREATE INDEX IF NOT EXISTS PlexEpisodes_ProviderId ON PlexEpisodes (ProviderId); -CREATE TABLE IF NOT EXISTS RequestQueue +CREATE TABLE IF NOT EXISTS RequestFaultQueue ( Id INTEGER PRIMARY KEY AUTOINCREMENT, PrimaryIdentifier VARCHAR(100) NOT NULL, diff --git a/PlexRequests.UI/Models/FaultedRequestsViewModel.cs b/PlexRequests.UI/Models/FaultedRequestsViewModel.cs new file mode 100644 index 000000000..d0f5de428 --- /dev/null +++ b/PlexRequests.UI/Models/FaultedRequestsViewModel.cs @@ -0,0 +1,56 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: FaultedRequestsViewModel.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 PlexRequests.Store; +using PlexRequests.Store.Models; + +namespace PlexRequests.UI.Models +{ + public class FaultedRequestsViewModel + { + public int Id { get; set; } + public string PrimaryIdentifier { get; set; } + public RequestTypeViewModel Type { get; set; } + public string Title { get; set; } + public FaultTypeViewModel FaultType { get; set; } + public DateTime? LastRetry { get; set; } + } + + public enum RequestTypeViewModel + { + Movie, + TvShow, + Album + } + + public enum FaultTypeViewModel + { + RequestFault, + MissingInformation + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/Admin/FaultQueueModule.cs b/PlexRequests.UI/Modules/Admin/FaultQueueModule.cs new file mode 100644 index 000000000..355e823e2 --- /dev/null +++ b/PlexRequests.UI/Modules/Admin/FaultQueueModule.cs @@ -0,0 +1,74 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SystemStatusModule.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 Nancy.Responses.Negotiation; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Permissions; +using PlexRequests.Store; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules.Admin +{ + public class FaultQueueModule : BaseModule + { + public FaultQueueModule(ISettingsService settingsService, ICacheProvider cache, IRepository requestQueue) : base("admin", settingsService) + { + Cache = cache; + RequestQueue = requestQueue; + + Security.HasPermissionsResponse(Permissions.Administrator); + + Get["Index", "/faultqueue"] = x => Index(); + } + + private ICacheProvider Cache { get; } + private IRepository RequestQueue { get; } + + private Negotiator Index() + { + var requests = RequestQueue.GetAll(); + + var model = requests.Select(r => new FaultedRequestsViewModel + { + FaultType = (FaultTypeViewModel)(int)r.FaultType, + Type = (RequestTypeViewModel)(int)r.Type, + Title = ByteConverterHelper.ReturnObject(r.Content).Title, + Id = r.Id, + PrimaryIdentifier = r.PrimaryIdentifier, + LastRetry = r.LastRetry + }).ToList(); + + return View["RequestFaultQueue", model]; + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/DonationLinkModule.cs b/PlexRequests.UI/Modules/DonationLinkModule.cs index 9ede62e41..5b3aec76a 100644 --- a/PlexRequests.UI/Modules/DonationLinkModule.cs +++ b/PlexRequests.UI/Modules/DonationLinkModule.cs @@ -2,14 +2,11 @@ using System.Threading.Tasks; using Nancy; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NLog; using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; -using PlexRequests.UI.Models; namespace PlexRequests.UI.Modules { diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 1e0e4c490..0e72cf9c9 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -616,7 +616,7 @@ namespace PlexRequests.UI.Modules /// private async Task RequestTvShow(int showId, string seasons) { - if (Security.DoesNotHavePermissions(Permissions.ReadOnlyUser, User)) + if (Security.HasPermissions(User, Permissions.ReadOnlyUser)) { return Response.AsJson(new JsonResponseModel() diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 75ecd4701..82f401655 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -229,6 +229,7 @@ + @@ -241,6 +242,7 @@ + @@ -724,6 +726,9 @@ PreserveNewest + + Always + web.config diff --git a/PlexRequests.UI/Views/Admin/RequestFaultQueue.cshtml b/PlexRequests.UI/Views/Admin/RequestFaultQueue.cshtml new file mode 100644 index 000000000..0e1329c4d --- /dev/null +++ b/PlexRequests.UI/Views/Admin/RequestFaultQueue.cshtml @@ -0,0 +1,103 @@ +@using PlexRequests.UI.Helpers +@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase> +@Html.Partial("_Sidebar") +
+
+ Release Fault Queue + + + + + + + + + + + + + @foreach (var m in Model) + { + + + + + + + } + +
+ Request Title + + Type + + Fault Type + + LastRetry +
+ @m.Title + + @m.Type + + @m.FaultType + + @m.LastRetry +
+ +
+
+ +@**@ \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/_Sidebar.cshtml b/PlexRequests.UI/Views/Admin/_Sidebar.cshtml index d65cd9848..836fc856f 100644 --- a/PlexRequests.UI/Views/Admin/_Sidebar.cshtml +++ b/PlexRequests.UI/Views/Admin/_Sidebar.cshtml @@ -18,5 +18,6 @@ @Html.GetSidebarUrl(Context, "/admin/logs", "Logs") @Html.GetSidebarUrl(Context, "/admin/status", "Status") @Html.GetSidebarUrl(Context, "/admin/scheduledjobs", "Scheduled Jobs") + @Html.GetSidebarUrl(Context, "/admin/faultqueue", "Request Fault Queue")
\ No newline at end of file From cb50fc61fbdaa7d1ba9b7ac67859c73e0417e150 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 10 Nov 2016 14:37:28 +0000 Subject: [PATCH 16/68] Final Tweaks #483 --- PlexRequests.Core/PlexRequests.Core.csproj | 1 + PlexRequests.Core/RequestHelper.cs | 82 +++++++++++++++++++ .../Jobs/FaultQueueHandler.cs | 24 ++++-- PlexRequests.UI/Jobs/Scheduler.cs | 3 +- PlexRequests.UI/Modules/SearchModule.cs | 25 +----- 5 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 PlexRequests.Core/RequestHelper.cs diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 506ac3a62..5f093a928 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -112,6 +112,7 @@ + diff --git a/PlexRequests.Core/RequestHelper.cs b/PlexRequests.Core/RequestHelper.cs new file mode 100644 index 000000000..0e99df6ca --- /dev/null +++ b/PlexRequests.Core/RequestHelper.cs @@ -0,0 +1,82 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RequestHelper.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using PlexRequests.Core.SettingModels; +using PlexRequests.Store; + +namespace PlexRequests.Core +{ + public static class RequestHelper + { + public static bool ShouldAutoApprove(this RequestType requestType, PlexRequestSettings prSettings, bool isAdmin, string username) + { + // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval + if (isAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase))) return true; + + // check by request type if the category requires approval or not + switch (requestType) + { + case RequestType.Movie: + return !prSettings.RequireMovieApproval; + case RequestType.TvShow: + return !prSettings.RequireTvShowApproval; + case RequestType.Album: + return !prSettings.RequireMusicApproval; + default: + return false; + } + } + + public static bool ShouldAutoApprove(this RequestType requestType, PlexRequestSettings prSettings, bool isAdmin, List username) + { + // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval + if (isAdmin) return true; + + + if(prSettings.ApprovalWhiteList.Intersect(username).Any()) + { + return true; + } + + // check by request type if the category requires approval or not + switch (requestType) + { + case RequestType.Movie: + return !prSettings.RequireMovieApproval; + case RequestType.TvShow: + return !prSettings.RequireTvShowApproval; + case RequestType.Album: + return !prSettings.RequireMusicApproval; + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/FaultQueueHandler.cs b/PlexRequests.Services/Jobs/FaultQueueHandler.cs index 9238a12ec..f30a4f2d9 100644 --- a/PlexRequests.Services/Jobs/FaultQueueHandler.cs +++ b/PlexRequests.Services/Jobs/FaultQueueHandler.cs @@ -37,6 +37,7 @@ using PlexRequests.Api.Interfaces; using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; +using PlexRequests.Helpers.Permissions; using PlexRequests.Services.Interfaces; using PlexRequests.Store; using PlexRequests.Store.Models; @@ -53,7 +54,7 @@ namespace PlexRequests.Services.Jobs public FaultQueueHandler(IJobRecord record, IRepository repo, ISonarrApi sonarrApi, ISickRageApi srApi, ISettingsService sonarrSettings, ISettingsService srSettings, ICouchPotatoApi cpApi, ISettingsService cpsettings, IRequestService requestService, - ISettingsService hpSettings, IHeadphonesApi headphonesApi) + ISettingsService hpSettings, IHeadphonesApi headphonesApi, ISettingsService prSettings) { Record = record; Repo = repo; @@ -68,6 +69,7 @@ namespace PlexRequests.Services.Jobs SonarrSettings = sonarrSettings; CpSettings = cpsettings; HeadphoneSettings = hpSettings; + PrSettings = prSettings.GetSettings(); } private IRepository Repo { get; } @@ -77,6 +79,7 @@ namespace PlexRequests.Services.Jobs private ICouchPotatoApi CpApi { get; } private IHeadphonesApi HpApi { get; } private IRequestService RequestService { get; } + private PlexRequestSettings PrSettings { get; } private ISettingsService SonarrSettings { get; } private ISettingsService SickrageSettings { get; } private ISettingsService CpSettings { get; } @@ -87,7 +90,7 @@ namespace PlexRequests.Services.Jobs try { var faultedRequests = Repo.GetAll().ToList(); - + var missingInfo = faultedRequests.Where(x => x.FaultType == FaultType.MissingInformation).ToList(); ProcessMissingInformation(missingInfo); @@ -108,13 +111,14 @@ namespace PlexRequests.Services.Jobs private void ProcessMissingInformation(List requests) { - var sonarrSettings = SonarrSettings.GetSettings(); - var sickrageSettings = SickrageSettings.GetSettings(); - if (!requests.Any()) { return; } + + var sonarrSettings = SonarrSettings.GetSettings(); + var sickrageSettings = SickrageSettings.GetSettings(); + var tv = requests.Where(x => x.Type == RequestType.TvShow); // TV @@ -122,7 +126,7 @@ namespace PlexRequests.Services.Jobs foreach (var t in tv) { var providerId = int.Parse(t.PrimaryIdentifier); - var showInfo = tvApi.ShowLookupByTheTvDbId(providerId); + var showInfo = tvApi.ShowLookup(providerId); if (showInfo.externals?.thetvdb != null) { @@ -227,8 +231,10 @@ namespace PlexRequests.Services.Jobs if (result) { - // Approve it now - model.Approved = true; + + if (model.Type.ShouldAutoApprove(PrSettings, false, model.RequestedUsers)) + // Approve it now + model.Approved = true; RequestService.UpdateRequest(model); }; @@ -258,7 +264,7 @@ namespace PlexRequests.Services.Jobs foreach (var request in requests) { var model = ByteConverterHelper.ReturnObject(request.Content); - var result = false; + bool result; switch (request.Type) { case RequestType.Movie: diff --git a/PlexRequests.UI/Jobs/Scheduler.cs b/PlexRequests.UI/Jobs/Scheduler.cs index 5ea4ba540..8127abf9a 100644 --- a/PlexRequests.UI/Jobs/Scheduler.cs +++ b/PlexRequests.UI/Jobs/Scheduler.cs @@ -226,7 +226,8 @@ namespace PlexRequests.UI.Jobs var fault = TriggerBuilder.Create() .WithIdentity("FaultQueueHandler", "Fault") - .StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) + //.StartAt(DateBuilder.FutureDate(10, IntervalUnit.Minute)) + .StartNow() .WithSimpleSchedule(x => x.WithIntervalInHours(s.FaultQueueHandler).RepeatForever()) .Build(); diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 0e72cf9c9..eb7c13960 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -878,7 +878,7 @@ namespace PlexRequests.UI.Modules try { - if (ShouldAutoApprove(RequestType.TvShow, settings)) + if (RequestType.TvShow.ShouldAutoApprove(settings, IsAdmin, Username)) { model.Approved = true; var s = await sonarrSettings; @@ -974,7 +974,7 @@ namespace PlexRequests.UI.Modules private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) { - var sendNotification = ShouldAutoApprove(type, prSettings) + var sendNotification = type.ShouldAutoApprove(prSettings, IsAdmin, Username) ? !prSettings.IgnoreNotifyForAutoApprovedRequests : true; var claims = Context.CurrentUser?.Claims; @@ -1073,7 +1073,7 @@ namespace PlexRequests.UI.Modules try { - if (ShouldAutoApprove(RequestType.Album, settings)) + if (RequestType.Album.ShouldAutoApprove(settings, IsAdmin, Username)) { model.Approved = true; var hpSettings = HeadphonesService.GetSettings(); @@ -1127,25 +1127,6 @@ namespace PlexRequests.UI.Modules return img; } - private bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings) - { - // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval - if (IsAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase))) return true; - - // check by request type if the category requires approval or not - switch (requestType) - { - case RequestType.Movie: - return !prSettings.RequireMovieApproval; - case RequestType.TvShow: - return !prSettings.RequireTvShowApproval; - case RequestType.Album: - return !prSettings.RequireMusicApproval; - default: - return false; - } - } - private async Task NotifyUser(bool notify) { Analytics.TrackEventAsync(Category.Search, Action.Save, "NotifyUser", Username, CookieHelper.GetAnalyticClientId(Cookies), notify ? 1 : 0); From b307b19feff4f6e2fcdbe2acafdbc8faf9dd1fd5 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 10 Nov 2016 14:45:16 +0000 Subject: [PATCH 17/68] convert the for to foreach for better readability. Still need to rework this area --- .../Jobs/PlexAvailabilityChecker.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index a0d3ed85a..7ba84cb79 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -419,11 +419,11 @@ namespace PlexRequests.Services.Jobs results = GetLibraries(plexSettings); if (plexSettings.AdvancedSearch) { - for (var i = 0; i < results.Count; i++) + foreach (PlexSearch t in results) { - for (var j = 0; j < results[i].Directory.Count; j++) + foreach (Directory1 t1 in t.Directory) { - var currentItem = results[i].Directory[j]; + var currentItem = t1; var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, currentItem.RatingKey); @@ -434,25 +434,21 @@ namespace PlexRequests.Services.Jobs currentItem.RatingKey); // We do not want "all episodes" this as a season - var filtered = - seasons.Directory.Where( - x => - !x.Title.Equals("All episodes", - StringComparison.CurrentCultureIgnoreCase)); + var filtered = seasons.Directory.Where( x => !x.Title.Equals("All episodes", StringComparison.CurrentCultureIgnoreCase)); - results[i].Directory[j].Seasons.AddRange(filtered); + t1.Seasons.AddRange(filtered); } var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Directory.Guid); - results[i].Directory[j].ProviderId = providerId; + t1.ProviderId = providerId; } - for (var j = 0; j < results[i].Video.Count; j++) + foreach (Video t1 in t.Video) { - var currentItem = results[i].Video[j]; + var currentItem = t1; var metaData = PlexApi.GetMetadata(plexSettings.PlexAuthToken, plexSettings.FullUri, currentItem.RatingKey); var providerId = PlexHelper.GetProviderIdFromPlexGuid(metaData.Video.Guid); - results[i].Video[j].ProviderId = providerId; + t1.ProviderId = providerId; } } } From 92016abe345626af34613d692f49233a57c4d019 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Thu, 10 Nov 2016 14:54:58 +0000 Subject: [PATCH 18/68] Fixed build --- PlexRequests.UI/Modules/SearchModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index eb7c13960..8d398d1f3 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -555,7 +555,7 @@ namespace PlexRequests.UI.Modules }; try { - if (ShouldAutoApprove(RequestType.Movie, settings)) + if (RequestType.Movie.ShouldAutoApprove(settings, IsAdmin, Username)) { var cpSettings = await CpService.GetSettingsAsync(); model.Approved = true; From 6d2bc0eb7219dccc6799af87ba93c22043846fea Mon Sep 17 00:00:00 2001 From: TidusJar Date: Fri, 11 Nov 2016 20:59:50 +0000 Subject: [PATCH 19/68] Migrate users --- .../Migrations/Version1100.cs | 20 +++++++++++++++++-- appveyor.yml | 6 +++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index fe03daf88..804dfa438 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -27,23 +27,27 @@ using System.Data; using PlexRequests.Store; +using PlexRequests.Store.Repository; namespace PlexRequests.Core.Migration.Migrations { [Migration(11000, "v1.10.0.0")] public class Version1100 : BaseMigration, IMigration { - public Version1100() + public Version1100(IUserRepository userRepo) { } public int Version => 11000; - + public IUserRepository UserRepo {get;set;} public void Start(IDbConnection con) { UpdateDb(con); + // Update the current admin permissions set + UpdateAdmin(con); + UpdateSchema(con, Version); } @@ -54,5 +58,17 @@ namespace PlexRequests.Core.Migration.Migrations con.AlterTable("Users", "ADD", "Features", true, "INTEGER"); } + + private void UpdateAdmin(IDbConnection con) + { + var users = UserRepo.GetAll(); + + foreach (var user in users) + { + user.Permissions = Permissions.Administrator | ReportIssue | RequestMusic + | RequestTvShow + | RequestMovie; + } + } } } \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 1af2ba46a..4f00bdb6f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,11 +1,11 @@ -version: 1.9.{build} +version: 1.10.{build} configuration: Release assembly_info: patch: true file: '**\AssemblyInfo.*' - assembly_version: '1.9.7' + assembly_version: '1.10.0' assembly_file_version: '{version}' - assembly_informational_version: '1.9.7' + assembly_informational_version: '1.10.0' before_build: - cmd: appveyor-retry nuget restore build: From 84ea45ebaa18e87fa7be11999824052a95060e19 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 14 Nov 2016 10:06:35 +0000 Subject: [PATCH 20/68] Fixed #665 --- .../MigrationRunner.cs | 4 --- .../Migrations/Version1100.cs | 25 ++++++++++++------- .../PlexRequests.Core.Migration.csproj | 4 +++ .../Repository/UserRepository.cs | 3 +++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/PlexRequests.Core.Migration/MigrationRunner.cs b/PlexRequests.Core.Migration/MigrationRunner.cs index 5a1a67f10..5535782a6 100644 --- a/PlexRequests.Core.Migration/MigrationRunner.cs +++ b/PlexRequests.Core.Migration/MigrationRunner.cs @@ -39,10 +39,6 @@ namespace PlexRequests.Core.Migration foreach (var param in ctor.GetParameters()) { - Console.WriteLine(string.Format( - "Param {0} is named {1} and is of type {2}", - param.Position, param.Name, param.ParameterType)); - var dep = Kernel.Get(param.ParameterType); dependencies.Add(dep); } diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index 804dfa438..3238b9c27 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -26,6 +26,8 @@ #endregion using System.Data; +using System.Linq; +using PlexRequests.Helpers.Permissions; using PlexRequests.Store; using PlexRequests.Store.Repository; @@ -36,17 +38,17 @@ namespace PlexRequests.Core.Migration.Migrations { public Version1100(IUserRepository userRepo) { - + UserRepo = userRepo; } public int Version => 11000; - public IUserRepository UserRepo {get;set;} + private IUserRepository UserRepo { get; } public void Start(IDbConnection con) { UpdateDb(con); // Update the current admin permissions set - UpdateAdmin(con); + UpdateAdmin(); UpdateSchema(con, Version); } @@ -56,19 +58,24 @@ namespace PlexRequests.Core.Migration.Migrations // Create the two new columns con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER"); con.AlterTable("Users", "ADD", "Features", true, "INTEGER"); - + } - private void UpdateAdmin(IDbConnection con) + private void UpdateAdmin() { - var users = UserRepo.GetAll(); + var users = UserRepo.GetAll().ToList(); foreach (var user in users) { - user.Permissions = Permissions.Administrator | ReportIssue | RequestMusic - | RequestTvShow - | RequestMovie; + user.Permissions = (int) + (Permissions.Administrator + | Permissions.ReportIssue + | Permissions.RequestMusic + | Permissions.RequestTvShow + | Permissions.RequestMovie); } + + UserRepo.UpdateAll(users); } } } \ No newline at end of file diff --git a/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj b/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj index 2881f95ed..e724145c4 100644 --- a/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj +++ b/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj @@ -74,6 +74,10 @@ {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} PlexRequests.Core + + {1252336D-42A3-482A-804C-836E60173DFA} + PlexRequests.Helpers + {92433867-2B7B-477B-A566-96C382427525} PlexRequests.Store diff --git a/PlexRequests.Store/Repository/UserRepository.cs b/PlexRequests.Store/Repository/UserRepository.cs index b05609c82..d661a30ec 100644 --- a/PlexRequests.Store/Repository/UserRepository.cs +++ b/PlexRequests.Store/Repository/UserRepository.cs @@ -105,6 +105,9 @@ namespace PlexRequests.Store.Repository IEnumerable Custom(Func> func); long Insert(UsersModel entity); void Delete(UsersModel entity); + IEnumerable GetAll(); + bool UpdateAll(IEnumerable entity); + bool Update(UsersModel entity); } } From 05bdfcd550b52a5158ada1f78b0f458d98da8b07 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 14 Nov 2016 10:44:10 +0000 Subject: [PATCH 21/68] started on #646. Fixed #657 --- .../Migrations/Version1100.cs | 26 ++++++++++++++++++- PlexRequests.Core/CacheKeys.cs | 2 +- .../Interfaces/IJobRecord.cs | 1 + .../Jobs/CouchPotatoCacher.cs | 2 ++ .../Jobs/FaultQueueHandler.cs | 3 +++ PlexRequests.Services/Jobs/JobRecord.cs | 17 ++++++++++++ .../Jobs/PlexAvailabilityChecker.cs | 4 ++- .../Jobs/PlexEpisodeCacher.cs | 2 ++ PlexRequests.Services/Jobs/RecentlyAdded.cs | 3 +++ PlexRequests.Services/Jobs/SickRageCacher.cs | 2 ++ PlexRequests.Services/Jobs/SonarrCacher.cs | 2 ++ PlexRequests.Services/Jobs/StoreBackup.cs | 2 ++ PlexRequests.Services/Jobs/StoreCleanup.cs | 2 ++ .../Jobs/UserRequestLimitResetter.cs | 2 ++ PlexRequests.Store/Models/ScheduledJobs.cs | 1 + PlexRequests.Store/SqlTables.sql | 3 ++- 16 files changed, 70 insertions(+), 4 deletions(-) diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index 3238b9c27..a631b05d6 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System; using System.Data; using System.Linq; using PlexRequests.Helpers.Permissions; @@ -36,12 +37,14 @@ namespace PlexRequests.Core.Migration.Migrations [Migration(11000, "v1.10.0.0")] public class Version1100 : BaseMigration, IMigration { - public Version1100(IUserRepository userRepo) + public Version1100(IUserRepository userRepo, IRequestService requestService) { UserRepo = userRepo; + RequestService = requestService; } public int Version => 11000; private IUserRepository UserRepo { get; } + private IRequestService RequestService { get; } public void Start(IDbConnection con) { @@ -50,6 +53,7 @@ namespace PlexRequests.Core.Migration.Migrations // Update the current admin permissions set UpdateAdmin(); + UpdateSchema(con, Version); } @@ -59,6 +63,26 @@ namespace PlexRequests.Core.Migration.Migrations con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER"); con.AlterTable("Users", "ADD", "Features", true, "INTEGER"); + // Add the new 'running' item into the scheduled jobs so we can check if the cachers are running + con.AlterTable("ScheduledJobs", "ADD", "Running", true, "INTEGER"); + + + + //https://image.tmdb.org/t/p/w150/https://image.tmdb.org/t/p/w150//aqhAqttDq7zgsTaBHtCD8wmTk6k.jpg + + // UI = https://image.tmdb.org/t/p/w150/{{posterPath}} + // Update old invalid posters + var allRequests = RequestService.GetAll().ToList(); + + foreach (var req in allRequests) + { + if (req.PosterPath.Contains("https://image.tmdb.org/t/p/w150/")) + { + var newImg = req.PosterPath.Replace("https://image.tmdb.org/t/p/w150/", string.Empty); + req.PosterPath = newImg; + } + } + RequestService.BatchUpdate(allRequests); } private void UpdateAdmin() diff --git a/PlexRequests.Core/CacheKeys.cs b/PlexRequests.Core/CacheKeys.cs index 0f718f1f1..e40dd9efd 100644 --- a/PlexRequests.Core/CacheKeys.cs +++ b/PlexRequests.Core/CacheKeys.cs @@ -30,7 +30,7 @@ namespace PlexRequests.Core { public struct TimeFrameMinutes { - public const int SchedulerCaching = 60; + public const int SchedulerCaching = 120; } public const string PlexLibaries = nameof(PlexLibaries); diff --git a/PlexRequests.Services/Interfaces/IJobRecord.cs b/PlexRequests.Services/Interfaces/IJobRecord.cs index da6bc6b3f..a47a31bbe 100644 --- a/PlexRequests.Services/Interfaces/IJobRecord.cs +++ b/PlexRequests.Services/Interfaces/IJobRecord.cs @@ -36,5 +36,6 @@ namespace PlexRequests.Services.Interfaces void Record(string jobName); Task> GetJobsAsync(); IEnumerable GetJobs(); + void SetRunning(bool running, string jobName); } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs index 5ad3d99d8..3977f277f 100644 --- a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs +++ b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs @@ -81,6 +81,7 @@ namespace PlexRequests.Services.Jobs finally { Job.Record(JobNames.CpCacher); + Job.SetRunning(false, JobNames.CpCacher); } } } @@ -108,6 +109,7 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { + Job.SetRunning(true, JobNames.CpCacher); Queued(); } } diff --git a/PlexRequests.Services/Jobs/FaultQueueHandler.cs b/PlexRequests.Services/Jobs/FaultQueueHandler.cs index f30a4f2d9..e774043f3 100644 --- a/PlexRequests.Services/Jobs/FaultQueueHandler.cs +++ b/PlexRequests.Services/Jobs/FaultQueueHandler.cs @@ -87,6 +87,8 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { + + Record.SetRunning(true, JobNames.CpCacher); try { var faultedRequests = Repo.GetAll().ToList(); @@ -105,6 +107,7 @@ namespace PlexRequests.Services.Jobs finally { Record.Record(JobNames.FaultQueueHandler); + Record.SetRunning(false, JobNames.CpCacher); } } diff --git a/PlexRequests.Services/Jobs/JobRecord.cs b/PlexRequests.Services/Jobs/JobRecord.cs index 3538a7b0b..629ac8446 100644 --- a/PlexRequests.Services/Jobs/JobRecord.cs +++ b/PlexRequests.Services/Jobs/JobRecord.cs @@ -60,6 +60,23 @@ namespace PlexRequests.Services.Jobs } } + public void SetRunning(bool running, string jobName) + { + var allJobs = Repo.GetAll(); + var storeJob = allJobs.FirstOrDefault(x => x.Name == jobName); + if (storeJob != null) + { + storeJob.Running = running; + Repo.Update(storeJob); + } + else + { + var job = new ScheduledJobs { Running = running, Name = jobName }; + Repo.Insert(job); + } + } + + public async Task> GetJobsAsync() { return await Repo.GetAllAsync(); diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 7ba84cb79..108b115dd 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -158,7 +158,7 @@ namespace PlexRequests.Services.Jobs } Job.Record(JobNames.PlexChecker); - + Job.SetRunning(false, JobNames.CpCacher); } public List GetPlexMovies() @@ -503,6 +503,8 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { + + Job.SetRunning(true, JobNames.CpCacher); try { CheckAndUpdateAll(); diff --git a/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs b/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs index 3ed43db50..3bf086752 100644 --- a/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs +++ b/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs @@ -145,6 +145,7 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { + Job.SetRunning(true, JobNames.CpCacher); try { var s = Plex.GetSettings(); @@ -171,6 +172,7 @@ namespace PlexRequests.Services.Jobs finally { Job.Record(JobNames.EpisodeCacher); + Job.SetRunning(false, JobNames.CpCacher); } } } diff --git a/PlexRequests.Services/Jobs/RecentlyAdded.cs b/PlexRequests.Services/Jobs/RecentlyAdded.cs index 73e6fba52..204b851be 100644 --- a/PlexRequests.Services/Jobs/RecentlyAdded.cs +++ b/PlexRequests.Services/Jobs/RecentlyAdded.cs @@ -78,6 +78,8 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { + + JobRecord.SetRunning(true, JobNames.CpCacher); try { var settings = NewsletterSettings.GetSettings(); @@ -95,6 +97,7 @@ namespace PlexRequests.Services.Jobs finally { JobRecord.Record(JobNames.RecentlyAddedEmail); + JobRecord.SetRunning(false, JobNames.CpCacher); } } diff --git a/PlexRequests.Services/Jobs/SickRageCacher.cs b/PlexRequests.Services/Jobs/SickRageCacher.cs index cece5100d..10ac125c3 100644 --- a/PlexRequests.Services/Jobs/SickRageCacher.cs +++ b/PlexRequests.Services/Jobs/SickRageCacher.cs @@ -58,6 +58,7 @@ namespace PlexRequests.Services.Jobs public void Queued() { + Job.SetRunning(true, JobNames.CpCacher); Log.Trace("Getting the settings"); var settings = SrSettings.GetSettings(); @@ -79,6 +80,7 @@ namespace PlexRequests.Services.Jobs finally { Job.Record(JobNames.SrCacher); + Job.SetRunning(false, JobNames.CpCacher); } } } diff --git a/PlexRequests.Services/Jobs/SonarrCacher.cs b/PlexRequests.Services/Jobs/SonarrCacher.cs index d94561604..9c7cd2cc1 100644 --- a/PlexRequests.Services/Jobs/SonarrCacher.cs +++ b/PlexRequests.Services/Jobs/SonarrCacher.cs @@ -62,6 +62,7 @@ namespace PlexRequests.Services.Jobs public void Queued() { + Job.SetRunning(true, JobNames.CpCacher); var settings = SonarrSettings.GetSettings(); if (settings.Enabled) { @@ -80,6 +81,7 @@ namespace PlexRequests.Services.Jobs finally { Job.Record(JobNames.SonarrCacher); + Job.SetRunning(false, JobNames.CpCacher); } } } diff --git a/PlexRequests.Services/Jobs/StoreBackup.cs b/PlexRequests.Services/Jobs/StoreBackup.cs index dea5d7df0..1bc45f54b 100644 --- a/PlexRequests.Services/Jobs/StoreBackup.cs +++ b/PlexRequests.Services/Jobs/StoreBackup.cs @@ -53,6 +53,7 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { + JobRecord.SetRunning(true, JobNames.CpCacher); TakeBackup(); Cleanup(); } @@ -91,6 +92,7 @@ namespace PlexRequests.Services.Jobs finally { JobRecord.Record(JobNames.StoreBackup); + JobRecord.SetRunning(false, JobNames.CpCacher); } } diff --git a/PlexRequests.Services/Jobs/StoreCleanup.cs b/PlexRequests.Services/Jobs/StoreCleanup.cs index 5c6b0987b..b329eaa95 100644 --- a/PlexRequests.Services/Jobs/StoreCleanup.cs +++ b/PlexRequests.Services/Jobs/StoreCleanup.cs @@ -78,12 +78,14 @@ namespace PlexRequests.Services.Jobs finally { JobRecord.Record(JobNames.StoreCleanup); + JobRecord.SetRunning(false, JobNames.CpCacher); } } public void Execute(IJobExecutionContext context) { + JobRecord.SetRunning(true, JobNames.CpCacher); Cleanup(); } } diff --git a/PlexRequests.Services/Jobs/UserRequestLimitResetter.cs b/PlexRequests.Services/Jobs/UserRequestLimitResetter.cs index 96d613b72..f32320fc8 100644 --- a/PlexRequests.Services/Jobs/UserRequestLimitResetter.cs +++ b/PlexRequests.Services/Jobs/UserRequestLimitResetter.cs @@ -98,6 +98,7 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { + Record.SetRunning(true, JobNames.CpCacher); try { var settings = Settings.GetSettings(); @@ -115,6 +116,7 @@ namespace PlexRequests.Services.Jobs finally { Record.Record(JobNames.RequestLimitReset); + Record.SetRunning(false, JobNames.CpCacher); } } } diff --git a/PlexRequests.Store/Models/ScheduledJobs.cs b/PlexRequests.Store/Models/ScheduledJobs.cs index 7c242f0bd..10c6f745c 100644 --- a/PlexRequests.Store/Models/ScheduledJobs.cs +++ b/PlexRequests.Store/Models/ScheduledJobs.cs @@ -35,5 +35,6 @@ namespace PlexRequests.Store.Models { public string Name { get; set; } public DateTime LastRun { get; set; } + public bool Running { get; set; } } } diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 84a31306f..4d180b1d0 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -80,7 +80,8 @@ CREATE TABLE IF NOT EXISTS ScheduledJobs ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name varchar(100) NOT NULL, - LastRun varchar(100) NOT NULL + LastRun varchar(100) NOT NULL, + Running INTEGER ); CREATE UNIQUE INDEX IF NOT EXISTS ScheduledJobs_Id ON ScheduledJobs (Id); From 55f13091405bd575ed66933150e0d05047ac86b4 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Mon, 14 Nov 2016 13:16:02 +0000 Subject: [PATCH 22/68] finished #646 and fixed #664 --- .../MigrationRunner.cs | 41 ++--- .../Migrations/Version1100.cs | 22 ++- .../PlexRequests.Core.Migration.csproj | 4 + PlexRequests.Core.Migration/packages.config | 1 + PlexRequests.Core/Setup.cs | 153 +----------------- .../Jobs/CouchPotatoCacher.cs | 2 +- .../Jobs/PlexAvailabilityChecker.cs | 11 +- .../Jobs/PlexEpisodeCacher.cs | 5 +- PlexRequests.Services/Jobs/RecentlyAdded.cs | 9 +- PlexRequests.Services/Jobs/SickRageCacher.cs | 6 +- PlexRequests.Services/Jobs/SonarrCacher.cs | 4 +- PlexRequests.UI/Content/Themes/plex.css | 6 + PlexRequests.UI/Content/Themes/plex.min.css | 2 +- PlexRequests.UI/Content/Themes/plex.scss | 7 + PlexRequests.UI/Content/base.css | 6 + PlexRequests.UI/Content/base.min.css | 2 +- PlexRequests.UI/Content/base.scss | 7 + PlexRequests.UI/Helpers/HtmlSecurityHelper.cs | 25 ++- PlexRequests.UI/Helpers/SecurityExtensions.cs | 30 +++- PlexRequests.UI/Modules/Admin/AdminModule.cs | 17 +- .../Modules/Admin/FaultQueueModule.cs | 2 +- .../Modules/Admin/SystemStatusModule.cs | 4 +- PlexRequests.UI/Modules/BaseModule.cs | 5 +- ...UpdateCheckerModule.cs => LayoutModule.cs} | 42 ++++- PlexRequests.UI/Modules/LoginModule.cs | 2 +- PlexRequests.UI/Modules/UserLoginModule.cs | 8 +- PlexRequests.UI/PlexRequests.UI.csproj | 3 +- PlexRequests.UI/Resources/UI.da.resx | 30 ++++ PlexRequests.UI/Resources/UI.de.resx | 32 +++- PlexRequests.UI/Resources/UI.es.resx | 27 ++++ PlexRequests.UI/Resources/UI.fr.resx | 30 ++++ PlexRequests.UI/Resources/UI.it.resx | 30 ++++ PlexRequests.UI/Resources/UI.nl.resx | 30 ++++ PlexRequests.UI/Resources/UI.pt.resx | 27 ++++ PlexRequests.UI/Resources/UI.resx | 3 + PlexRequests.UI/Resources/UI.sv.resx | 30 ++++ PlexRequests.UI/Resources/UI1.Designer.cs | 9 ++ .../Validators/PlexRequestsValidator.cs | 2 +- .../Shared/Partial/_LayoutScripts.cshtml | 29 +++- .../Views/Shared/Partial/_Navbar.cshtml | 46 +++--- PlexRequests.UI/Views/Shared/_Layout.cshtml | 2 - 41 files changed, 493 insertions(+), 260 deletions(-) rename PlexRequests.UI/Modules/{UpdateCheckerModule.cs => LayoutModule.cs} (65%) diff --git a/PlexRequests.Core.Migration/MigrationRunner.cs b/PlexRequests.Core.Migration/MigrationRunner.cs index 5535782a6..d53ab8d3d 100644 --- a/PlexRequests.Core.Migration/MigrationRunner.cs +++ b/PlexRequests.Core.Migration/MigrationRunner.cs @@ -23,37 +23,30 @@ namespace PlexRequests.Core.Migration { var con = Db.DbConnection(); var versions = GetMigrations(); - - var dbVersion = con.GetVersionInfo().OrderByDescending(x => x.Version).FirstOrDefault(); - if (dbVersion == null) - { - dbVersion = new TableCreation.VersionInfo { Version = 0 }; - } + + var dbVersion = con.GetVersionInfo().OrderByDescending(x => x.Version).FirstOrDefault() ?? + new TableCreation.VersionInfo { Version = 0 }; foreach (var v in versions) { +#if !DEBUG if (v.Value.Version > dbVersion.Version) { - // Assuming only one constructor - var ctor = v.Key.GetConstructors().FirstOrDefault(); - var dependencies = new List(); - - foreach (var param in ctor.GetParameters()) - { - var dep = Kernel.Get(param.ParameterType); - dependencies.Add(dep); - } +#endif + // Assuming only one constructor + var ctor = v.Key.GetConstructors().FirstOrDefault(); + var dependencies = ctor.GetParameters().Select(param => Kernel.Get(param.ParameterType)).ToList(); - var method = v.Key.GetMethod("Start"); - if (method != null) - { - object result = null; - var classInstance = Activator.CreateInstance(v.Key, dependencies.Any() ? dependencies.ToArray() : null); - - var parametersArray = new object[] { Db.DbConnection() }; + var method = v.Key.GetMethod("Start"); + if (method != null) + { + var classInstance = Activator.CreateInstance(v.Key, dependencies.Any() ? dependencies.ToArray() : null); + var parametersArray = new object[] { Db.DbConnection() }; - method.Invoke(classInstance, parametersArray); - } + method.Invoke(classInstance, parametersArray); + } +#if !DEBUG } +#endif } } diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index a631b05d6..d4365f357 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -27,7 +27,10 @@ using System; using System.Data; +using NLog; using System.Linq; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; using PlexRequests.Helpers.Permissions; using PlexRequests.Store; using PlexRequests.Store.Repository; @@ -37,14 +40,16 @@ namespace PlexRequests.Core.Migration.Migrations [Migration(11000, "v1.10.0.0")] public class Version1100 : BaseMigration, IMigration { - public Version1100(IUserRepository userRepo, IRequestService requestService) + public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService log) { UserRepo = userRepo; RequestService = requestService; + Log = log; } public int Version => 11000; private IUserRepository UserRepo { get; } private IRequestService RequestService { get; } + private ISettingsService Log { get; } public void Start(IDbConnection con) { @@ -52,9 +57,17 @@ namespace PlexRequests.Core.Migration.Migrations // Update the current admin permissions set UpdateAdmin(); + ResetLogLevel(); + UpdateSchema(con, Version); + } + private void ResetLogLevel() + { + var logSettings = Log.GetSettings(); + logSettings.Level = LogLevel.Error.Ordinal; + Log.SaveSettings(logSettings); - UpdateSchema(con, Version); + LoggingHelper.ReconfigureLogLevel(LogLevel.FromOrdinal(logSettings.Level)); } private void UpdateDb(IDbConnection con) @@ -63,11 +76,6 @@ namespace PlexRequests.Core.Migration.Migrations con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER"); con.AlterTable("Users", "ADD", "Features", true, "INTEGER"); - // Add the new 'running' item into the scheduled jobs so we can check if the cachers are running - con.AlterTable("ScheduledJobs", "ADD", "Running", true, "INTEGER"); - - - //https://image.tmdb.org/t/p/w150/https://image.tmdb.org/t/p/w150//aqhAqttDq7zgsTaBHtCD8wmTk6k.jpg // UI = https://image.tmdb.org/t/p/w150/{{posterPath}} diff --git a/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj b/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj index e724145c4..fa934dfc0 100644 --- a/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj +++ b/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj @@ -45,6 +45,10 @@ ..\packages\Ninject.3.2.0.0\lib\net45-full\Ninject.dll + + ..\packages\NLog.4.3.11\lib\net45\NLog.dll + True + ..\packages\Quartz.2.3.3\lib\net40\Quartz.dll True diff --git a/PlexRequests.Core.Migration/packages.config b/PlexRequests.Core.Migration/packages.config index dfdfc5ae9..6d547be19 100644 --- a/PlexRequests.Core.Migration/packages.config +++ b/PlexRequests.Core.Migration/packages.config @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index 56f3ab95e..b9f2b7518 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -60,52 +60,13 @@ namespace PlexRequests.Core // Shrink DB TableCreation.Vacuum(Db.DbConnection()); } - - - // The below code is obsolete, we should use PlexRequests.Core.Migrations.MigrationRunner - var version = CheckSchema(); - if (version > 0) - { - if (version > 1899 && version <= 1900) - { - MigrateToVersion1900(); - } - - if(version > 1899 && version <= 1910) - { - MigrateToVersion1910(); - } - } - + + // Add the new 'running' item into the scheduled jobs so we can check if the cachers are running + Db.DbConnection().AlterTable("ScheduledJobs", "ADD", "Running", true, "INTEGER"); + return Db.DbConnection().ConnectionString; } - public static string ConnectionString => Db.DbConnection().ConnectionString; - - - private int CheckSchema() - { - var productVersion = AssemblyHelper.GetProductVersion(); - var trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(productVersion, string.Empty).PadRight(4, '0'); - var version = int.Parse(trimStatus); - - var connection = Db.DbConnection(); - var schema = connection.GetSchemaVersion(); - if (schema == null) - { - connection.CreateSchema(version); // Set the default. - schema = connection.GetSchemaVersion(); - } - if (version > schema.SchemaVersion) - { - Db.DbConnection().UpdateSchemaVersion(version); - schema = connection.GetSchemaVersion(); - } - version = schema.SchemaVersion; - - return version; - } - private void CreateDefaultSettingsPage(string baseUrl) { var defaultSettings = new PlexRequestSettings @@ -148,7 +109,6 @@ namespace PlexRequests.Core Task.Run(() => { CacheSonarrQualityProfiles(mc); }); Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); }); // we don't need to cache sickrage profiles, those are static - // TODO: cache headphones profiles? } catch (Exception) { @@ -156,12 +116,12 @@ namespace PlexRequests.Core } } - private void CacheSonarrQualityProfiles(MemoryCacheProvider cacheProvider) + private void CacheSonarrQualityProfiles(ICacheProvider cacheProvider) { try { Log.Info("Executing GetSettings call to Sonarr for quality profiles"); - var sonarrSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + var sonarrSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider)); var sonarrSettings = sonarrSettingsService.GetSettings(); if (sonarrSettings.Enabled) { @@ -178,12 +138,12 @@ namespace PlexRequests.Core } } - private void CacheCouchPotatoQualityProfiles(MemoryCacheProvider cacheProvider) + private void CacheCouchPotatoQualityProfiles(ICacheProvider cacheProvider) { try { Log.Info("Executing GetSettings call to CouchPotato for quality profiles"); - var cpSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + var cpSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), cacheProvider)); var cpSettings = cpSettingsService.GetSettings(); if (cpSettings.Enabled) { @@ -199,102 +159,5 @@ namespace PlexRequests.Core Log.Error(ex, "Failed to cache CouchPotato quality profiles!"); } } - - - /// - /// Migrates to version 1.9. - /// Move the Plex auth token to the new field. - /// Reconfigure the log level - /// Set the wizard flag to true if we already have settings - /// - public void MigrateToVersion1900() - { - // Need to change the Plex Token location - var authSettings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - var auth = authSettings.GetSettings(); - var plexSettings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - - if (auth != null) - { - //If we have an authToken we do not need to go through the setup - if (!string.IsNullOrEmpty(auth.OldPlexAuthToken)) - { - var prServuce = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - var settings = prServuce.GetSettings(); - settings.Wizard = true; - prServuce.SaveSettings(settings); - } - - // Clear out the old token and save it to the new field - var currentSettings = plexSettings.GetSettings(); - if (!string.IsNullOrEmpty(auth.OldPlexAuthToken)) - { - currentSettings.PlexAuthToken = auth.OldPlexAuthToken; - plexSettings.SaveSettings(currentSettings); - - // Clear out the old value - auth.OldPlexAuthToken = string.Empty; - authSettings.SaveSettings(auth); - } - - } - - - // Set the log level - try - { - var settingsService = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - var logSettings = settingsService.GetSettings(); - logSettings.Level = LogLevel.Error.Ordinal; - settingsService.SaveSettings(logSettings); - - LoggingHelper.ReconfigureLogLevel(LogLevel.FromOrdinal(logSettings.Level)); - } - catch (Exception e) - { - Log.Error(e); - } - - - // Enable analytics; - try - { - - var prSettings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - var settings = prSettings.GetSettings(); - settings.CollectAnalyticData = true; - var updated = prSettings.SaveSettings(settings); - - } - catch (Exception e) - { - Log.Error(e); - } - } - - /// - /// Migrates to version1910. - /// - public void MigrateToVersion1910() - { - try - { - // Get the new machine Identifier - var settings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - var plex = settings.GetSettings(); - if (!string.IsNullOrEmpty(plex.PlexAuthToken)) - { - var api = new PlexApi(new ApiRequest()); - var server = api.GetServer(plex.PlexAuthToken); // Get the server info - plex.MachineIdentifier = server.Server.FirstOrDefault(x => x.AccessToken == plex.PlexAuthToken)?.MachineIdentifier; - - settings.SaveSettings(plex); // Save the new settings - } - } - catch (Exception e) - { - Log.Error(e); - } - } } } diff --git a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs index 3977f277f..a45e74509 100644 --- a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs +++ b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs @@ -65,6 +65,7 @@ namespace PlexRequests.Services.Jobs var settings = CpSettings.GetSettings(); if (settings.Enabled) { + Job.SetRunning(true, JobNames.CpCacher); Log.Trace("Getting all movies from CouchPotato"); try { @@ -109,7 +110,6 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { - Job.SetRunning(true, JobNames.CpCacher); Queued(); } } diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 108b115dd..8ecb00bb5 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -87,7 +87,6 @@ namespace PlexRequests.Services.Jobs Log.Debug("Validation of the plex settings failed."); return; } - var libraries = CachedLibraries(plexSettings, true); //force setting the cache (10 min intervals via scheduler) if (libraries == null || !libraries.Any()) @@ -156,9 +155,6 @@ namespace PlexRequests.Services.Jobs NotificationEngine.NotifyUsers(modifiedModel, plexSettings.PlexAuthToken, NotificationType.RequestAvailable); RequestService.BatchUpdate(modifiedModel); } - - Job.Record(JobNames.PlexChecker); - Job.SetRunning(false, JobNames.CpCacher); } public List GetPlexMovies() @@ -504,7 +500,7 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { - Job.SetRunning(true, JobNames.CpCacher); + Job.SetRunning(true, JobNames.PlexChecker); try { CheckAndUpdateAll(); @@ -513,6 +509,11 @@ namespace PlexRequests.Services.Jobs { Log.Error(e); } + finally + { + Job.Record(JobNames.PlexChecker); + Job.SetRunning(false, JobNames.PlexChecker); + } } } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs b/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs index 3bf086752..e91d61e3e 100644 --- a/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs +++ b/PlexRequests.Services/Jobs/PlexEpisodeCacher.cs @@ -145,7 +145,7 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { - Job.SetRunning(true, JobNames.CpCacher); + try { var s = Plex.GetSettings(); @@ -163,6 +163,7 @@ namespace PlexRequests.Services.Jobs return; } } + Job.SetRunning(true, JobNames.EpisodeCacher); CacheEpisodes(s); } catch (Exception e) @@ -172,7 +173,7 @@ namespace PlexRequests.Services.Jobs finally { Job.Record(JobNames.EpisodeCacher); - Job.SetRunning(false, JobNames.CpCacher); + Job.SetRunning(false, JobNames.EpisodeCacher); } } } diff --git a/PlexRequests.Services/Jobs/RecentlyAdded.cs b/PlexRequests.Services/Jobs/RecentlyAdded.cs index 204b851be..fb1d22c1b 100644 --- a/PlexRequests.Services/Jobs/RecentlyAdded.cs +++ b/PlexRequests.Services/Jobs/RecentlyAdded.cs @@ -78,8 +78,6 @@ namespace PlexRequests.Services.Jobs public void Execute(IJobExecutionContext context) { - - JobRecord.SetRunning(true, JobNames.CpCacher); try { var settings = NewsletterSettings.GetSettings(); @@ -87,7 +85,7 @@ namespace PlexRequests.Services.Jobs { return; } - + JobRecord.SetRunning(true, JobNames.RecentlyAddedEmail); Start(settings); } catch (Exception e) @@ -97,7 +95,7 @@ namespace PlexRequests.Services.Jobs finally { JobRecord.Record(JobNames.RecentlyAddedEmail); - JobRecord.SetRunning(false, JobNames.CpCacher); + JobRecord.SetRunning(false, JobNames.RecentlyAddedEmail); } } @@ -264,7 +262,8 @@ namespace PlexRequests.Services.Jobs { foreach (var user in users.User) { - message.Bcc.Add(new MailboxAddress(user.Username, user.Email)); + if (user.Email != null) + message.Bcc.Add(new MailboxAddress(user.Username, user.Email)); } } } diff --git a/PlexRequests.Services/Jobs/SickRageCacher.cs b/PlexRequests.Services/Jobs/SickRageCacher.cs index 10ac125c3..6fedf4b27 100644 --- a/PlexRequests.Services/Jobs/SickRageCacher.cs +++ b/PlexRequests.Services/Jobs/SickRageCacher.cs @@ -58,12 +58,14 @@ namespace PlexRequests.Services.Jobs public void Queued() { - Job.SetRunning(true, JobNames.CpCacher); Log.Trace("Getting the settings"); var settings = SrSettings.GetSettings(); if (settings.Enabled) { + + Job.SetRunning(true, JobNames.SrCacher); + Log.Trace("Getting all shows from SickRage"); try { @@ -80,7 +82,7 @@ namespace PlexRequests.Services.Jobs finally { Job.Record(JobNames.SrCacher); - Job.SetRunning(false, JobNames.CpCacher); + Job.SetRunning(false, JobNames.SrCacher); } } } diff --git a/PlexRequests.Services/Jobs/SonarrCacher.cs b/PlexRequests.Services/Jobs/SonarrCacher.cs index 9c7cd2cc1..9e0304f37 100644 --- a/PlexRequests.Services/Jobs/SonarrCacher.cs +++ b/PlexRequests.Services/Jobs/SonarrCacher.cs @@ -62,10 +62,10 @@ namespace PlexRequests.Services.Jobs public void Queued() { - Job.SetRunning(true, JobNames.CpCacher); var settings = SonarrSettings.GetSettings(); if (settings.Enabled) { + Job.SetRunning(true, JobNames.SonarrCacher); try { var series = SonarrApi.GetSeries(settings.ApiKey, settings.FullUri); @@ -81,7 +81,7 @@ namespace PlexRequests.Services.Jobs finally { Job.Record(JobNames.SonarrCacher); - Job.SetRunning(false, JobNames.CpCacher); + Job.SetRunning(false, JobNames.SonarrCacher); } } } diff --git a/PlexRequests.UI/Content/Themes/plex.css b/PlexRequests.UI/Content/Themes/plex.css index dbd72c8b4..be07c9a3a 100644 --- a/PlexRequests.UI/Content/Themes/plex.css +++ b/PlexRequests.UI/Content/Themes/plex.css @@ -182,3 +182,9 @@ button.list-group-item:focus { #sidebar-wrapper { background: #252424; } +#cacherRunning { + background-color: #333333; + text-align: center; + font-size: 15px; + padding: 3px 0; } + diff --git a/PlexRequests.UI/Content/Themes/plex.min.css b/PlexRequests.UI/Content/Themes/plex.min.css index 006124205..d42c984ba 100644 --- a/PlexRequests.UI/Content/Themes/plex.min.css +++ b/PlexRequests.UI/Content/Themes/plex.min.css @@ -1 +1 @@ -.form-control-custom{background-color:#333 !important;}.form-control-custom-disabled{background-color:#252424 !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#df691a;}.scroll-top-wrapper{background-color:#333;}.scroll-top-wrapper:hover{background-color:#df691a;}body{font-family:Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;color:#eee;background-color:#1f1f1f;}.table-striped>tbody>tr:nth-of-type(odd){background-color:#333;}.table-hover>tbody>tr:hover{background-color:#282828;}fieldset{padding:15px;}legend{border-bottom:1px solid #333;}.form-control{color:#fefefe;background-color:#333;}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{margin-left:-0;}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:-15px;}.dropdown-menu{background-color:#282828;}.dropdown-menu .divider{background-color:#333;}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#333;}.input-group-addon{background-color:#333;}.nav>li>a:hover,.nav>li>a:focus{background-color:#df691a;}.nav-tabs>li>a:hover{border-color:#df691a #df691a transparent;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background-color:#df691a;border:1px solid #df691a;}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #df691a;}.navbar-default{background-color:#0a0a0a;}.navbar-default .navbar-brand{color:#df691a;}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#f0ad4e;background-color:#282828;}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{background-color:#282828;}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#df691a;color:#fff;}.pagination>li>a,.pagination>li>span{background-color:#282828;}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#333;}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#fefefe;background-color:#333;}.list-group-item{background-color:#282828;}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{background-color:#333;}.input-addon,.input-group-addon{color:#df691a;}.modal-header,.modal-footer{background-color:#282828;}.modal-content{position:relative;background-color:#282828;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:0;}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:300;color:#ebebeb;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#333;border-radius:10px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#333;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #333 !important;}#sidebar-wrapper{background:#252424;} \ No newline at end of file +.form-control-custom{background-color:#333 !important;}.form-control-custom-disabled{background-color:#252424 !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#df691a;}.scroll-top-wrapper{background-color:#333;}.scroll-top-wrapper:hover{background-color:#df691a;}body{font-family:Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;color:#eee;background-color:#1f1f1f;}.table-striped>tbody>tr:nth-of-type(odd){background-color:#333;}.table-hover>tbody>tr:hover{background-color:#282828;}fieldset{padding:15px;}legend{border-bottom:1px solid #333;}.form-control{color:#fefefe;background-color:#333;}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{margin-left:-0;}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:-15px;}.dropdown-menu{background-color:#282828;}.dropdown-menu .divider{background-color:#333;}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#333;}.input-group-addon{background-color:#333;}.nav>li>a:hover,.nav>li>a:focus{background-color:#df691a;}.nav-tabs>li>a:hover{border-color:#df691a #df691a transparent;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background-color:#df691a;border:1px solid #df691a;}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #df691a;}.navbar-default{background-color:#0a0a0a;}.navbar-default .navbar-brand{color:#df691a;}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#f0ad4e;background-color:#282828;}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{background-color:#282828;}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#df691a;color:#fff;}.pagination>li>a,.pagination>li>span{background-color:#282828;}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{background-color:#333;}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#fefefe;background-color:#333;}.list-group-item{background-color:#282828;}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{background-color:#333;}.input-addon,.input-group-addon{color:#df691a;}.modal-header,.modal-footer{background-color:#282828;}.modal-content{position:relative;background-color:#282828;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:0;}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:300;color:#ebebeb;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#333;border-radius:10px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#333;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #333 !important;}#sidebar-wrapper{background:#252424;}#cacherRunning{background-color:#333;text-align:center;font-size:15px;padding:3px 0;} \ No newline at end of file diff --git a/PlexRequests.UI/Content/Themes/plex.scss b/PlexRequests.UI/Content/Themes/plex.scss index 067dad666..8a5ffbe8b 100644 --- a/PlexRequests.UI/Content/Themes/plex.scss +++ b/PlexRequests.UI/Content/Themes/plex.scss @@ -228,3 +228,10 @@ button.list-group-item:focus { #sidebar-wrapper { background: $bg-colour-disabled; } + +#cacherRunning { + background-color: $bg-colour; + text-align: center; + font-size: 15px; + padding: 3px 0; +} \ No newline at end of file diff --git a/PlexRequests.UI/Content/base.css b/PlexRequests.UI/Content/base.css index 602ee6369..d48d329f8 100644 --- a/PlexRequests.UI/Content/base.css +++ b/PlexRequests.UI/Content/base.css @@ -257,6 +257,12 @@ label { font-size: 15px; padding: 3px 0; } +#cacherRunning { + background-color: #4e5d6c; + text-align: center; + font-size: 15px; + padding: 3px 0; } + .checkbox label { display: inline-block; cursor: pointer; diff --git a/PlexRequests.UI/Content/base.min.css b/PlexRequests.UI/Content/base.min.css index 5cd192ff8..b47ab325c 100644 --- a/PlexRequests.UI/Content/base.min.css +++ b/PlexRequests.UI/Content/base.min.css @@ -1 +1 @@ -@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}.landing-block .media{max-width:450px;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;}hr{border:1px dashed #777;}.btn{border-radius:.25rem !important;}.btn-group-separated .btn,.btn-group-separated .btn+.btn{margin-left:3px;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li{font-size:13px;line-height:21px;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.nav-tabs>li>a>.fa{padding:3px 5px 3px 3px;}.nav-tabs>li.nav-tab-right{float:right;}.nav-tabs>li.nav-tab-right a{margin-right:0;margin-left:2px;}.nav-tabs>li.nav-tab-icononly .fa{padding:3px;}.navbar .nav a .fa,.dropdown-menu a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.dropdown-menu a .fa{top:2px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}.form-control-search{padding:13px 105px 13px 16px;height:100%;}.form-control-withbuttons{padding-right:105px;}.input-group-addon .btn-group{position:absolute;right:45px;z-index:3;top:10px;box-shadow:0 0 0;}.input-group-addon .btn-group .btn{border:1px solid rgba(255,255,255,.7) !important;padding:3px 12px;color:rgba(255,255,255,.7) !important;}.btn-split .btn{border-radius:0 !important;}.btn-split .btn:not(.dropdown-toggle){border-radius:.25rem 0 0 .25rem !important;}.btn-split .btn.dropdown-toggle{border-radius:0 .25rem .25rem 0 !important;padding:12px 8px;}#updateAvailable{background-color:#df691a;text-align:center;font-size:15px;padding:3px 0;}.checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:3px;}.checkbox input[type=checkbox]{display:none;}.checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.input-group-sm{padding-top:2px;padding-bottom:2px;}.tab-pane .form-horizontal .form-group{margin-right:15px;margin-left:15px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#4e5d6c;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #4e5d6c !important;}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{color:#fff !important;}.landing-header{display:block;margin:60px auto;}.landing-block{background:#2f2f2f !important;padding:5px;}.landing-block .media{margin:30px auto;max-width:450px;}.landing-block .media .media-left{display:inline-block;float:left;width:70px;}.landing-block .media .media-left i.fa{font-size:3em;}.landing-title{font-weight:bold;}.checkbox-custom{margin-top:0 !important;margin-bottom:0 !important;}.tooltip_templates{display:none;}.shadow{-moz-box-shadow:3px 3px 5px 6px #191919;-webkit-box-shadow:3px 3px 5px 6px #191919;box-shadow:3px 3px 5px 6px #191919;}.img-circle{border-radius:50%;}#wrapper{padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled{padding-right:250px;}#sidebar-wrapper{z-index:1000;position:fixed;right:250px;width:0;height:100%;margin-right:-250px;overflow-y:auto;background:#4e5d6c;padding-left:15px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled #sidebar-wrapper{width:500px;}#page-content-wrapper{width:100%;position:absolute;padding:15px;}#wrapper.toggled #page-content-wrapper{position:absolute;margin-left:-250px;}.sidebar-nav{position:absolute;top:0;width:500px;margin:0;padding-left:0;list-style:none;}.sidebar-nav li{text-indent:20px;line-height:40px;}.sidebar-nav li a{display:block;text-decoration:none;color:#999;}.sidebar-nav li a:hover{text-decoration:none;color:#fff;background:rgba(255,255,255,.2);}.sidebar-nav li a:active,.sidebar-nav li a:focus{text-decoration:none;}.sidebar-nav>.sidebar-brand{height:65px;font-size:18px;line-height:60px;}.sidebar-nav>.sidebar-brand a{color:#999;}.sidebar-nav>.sidebar-brand a:hover{color:#fff;background:none;}@media(min-width:768px){#wrapper{padding-right:250px;}#wrapper.toggled{padding-right:0;}#sidebar-wrapper{width:500px;}#wrapper.toggled #sidebar-wrapper{width:0;}#page-content-wrapper{padding:20px;position:relative;}#wrapper.toggled #page-content-wrapper{position:relative;margin-right:0;}}#lightbox{background-color:#808080;filter:alpha(opacity=50);opacity:.5;-moz-opacity:.5;top:0;left:0;z-index:20;height:100%;width:100%;background-repeat:no-repeat;background-position:center;position:absolute;} \ No newline at end of file +@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}.landing-block .media{max-width:450px;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;}hr{border:1px dashed #777;}.btn{border-radius:.25rem !important;}.btn-group-separated .btn,.btn-group-separated .btn+.btn{margin-left:3px;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li{font-size:13px;line-height:21px;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.nav-tabs>li>a>.fa{padding:3px 5px 3px 3px;}.nav-tabs>li.nav-tab-right{float:right;}.nav-tabs>li.nav-tab-right a{margin-right:0;margin-left:2px;}.nav-tabs>li.nav-tab-icononly .fa{padding:3px;}.navbar .nav a .fa,.dropdown-menu a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.dropdown-menu a .fa{top:2px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}.form-control-search{padding:13px 105px 13px 16px;height:100%;}.form-control-withbuttons{padding-right:105px;}.input-group-addon .btn-group{position:absolute;right:45px;z-index:3;top:10px;box-shadow:0 0 0;}.input-group-addon .btn-group .btn{border:1px solid rgba(255,255,255,.7) !important;padding:3px 12px;color:rgba(255,255,255,.7) !important;}.btn-split .btn{border-radius:0 !important;}.btn-split .btn:not(.dropdown-toggle){border-radius:.25rem 0 0 .25rem !important;}.btn-split .btn.dropdown-toggle{border-radius:0 .25rem .25rem 0 !important;padding:12px 8px;}#updateAvailable{background-color:#df691a;text-align:center;font-size:15px;padding:3px 0;}#cacherRunning{background-color:#4e5d6c;text-align:center;font-size:15px;padding:3px 0;}.checkbox label{display:inline-block;cursor:pointer;position:relative;padding-left:25px;margin-right:15px;font-size:13px;margin-bottom:10px;}.checkbox label:before{content:"";display:inline-block;width:18px;height:18px;margin-right:10px;position:absolute;left:0;bottom:1px;border:2px solid #eee;border-radius:3px;}.checkbox input[type=checkbox]{display:none;}.checkbox input[type=checkbox]:checked+label:before{content:"✓";font-size:13px;color:#fafafa;text-align:center;line-height:13px;}.input-group-sm{padding-top:2px;padding-bottom:2px;}.tab-pane .form-horizontal .form-group{margin-right:15px;margin-left:15px;}.bootstrap-datetimepicker-widget.dropdown-menu{background-color:#4e5d6c;}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-bottom:6px solid #4e5d6c !important;}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{color:#fff !important;}.landing-header{display:block;margin:60px auto;}.landing-block{background:#2f2f2f !important;padding:5px;}.landing-block .media{margin:30px auto;max-width:450px;}.landing-block .media .media-left{display:inline-block;float:left;width:70px;}.landing-block .media .media-left i.fa{font-size:3em;}.landing-title{font-weight:bold;}.checkbox-custom{margin-top:0 !important;margin-bottom:0 !important;}.tooltip_templates{display:none;}.shadow{-moz-box-shadow:3px 3px 5px 6px #191919;-webkit-box-shadow:3px 3px 5px 6px #191919;box-shadow:3px 3px 5px 6px #191919;}.img-circle{border-radius:50%;}#wrapper{padding-left:0;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled{padding-right:250px;}#sidebar-wrapper{z-index:1000;position:fixed;right:250px;width:0;height:100%;margin-right:-250px;overflow-y:auto;background:#4e5d6c;padding-left:15px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease;}#wrapper.toggled #sidebar-wrapper{width:500px;}#page-content-wrapper{width:100%;position:absolute;padding:15px;}#wrapper.toggled #page-content-wrapper{position:absolute;margin-left:-250px;}.sidebar-nav{position:absolute;top:0;width:500px;margin:0;padding-left:0;list-style:none;}.sidebar-nav li{text-indent:20px;line-height:40px;}.sidebar-nav li a{display:block;text-decoration:none;color:#999;}.sidebar-nav li a:hover{text-decoration:none;color:#fff;background:rgba(255,255,255,.2);}.sidebar-nav li a:active,.sidebar-nav li a:focus{text-decoration:none;}.sidebar-nav>.sidebar-brand{height:65px;font-size:18px;line-height:60px;}.sidebar-nav>.sidebar-brand a{color:#999;}.sidebar-nav>.sidebar-brand a:hover{color:#fff;background:none;}@media(min-width:768px){#wrapper{padding-right:250px;}#wrapper.toggled{padding-right:0;}#sidebar-wrapper{width:500px;}#wrapper.toggled #sidebar-wrapper{width:0;}#page-content-wrapper{padding:20px;position:relative;}#wrapper.toggled #page-content-wrapper{position:relative;margin-right:0;}}#lightbox{background-color:#808080;filter:alpha(opacity=50);opacity:.5;-moz-opacity:.5;top:0;left:0;z-index:20;height:100%;width:100%;background-repeat:no-repeat;background-position:center;position:absolute;} \ No newline at end of file diff --git a/PlexRequests.UI/Content/base.scss b/PlexRequests.UI/Content/base.scss index 64dd1b83e..b1e7abe3e 100644 --- a/PlexRequests.UI/Content/base.scss +++ b/PlexRequests.UI/Content/base.scss @@ -327,6 +327,13 @@ $border-radius: 10px; padding: 3px 0; } +#cacherRunning { + background-color: $form-color; + text-align: center; + font-size: 15px; + padding: 3px 0; +} + .checkbox label { display: inline-block; cursor: pointer; diff --git a/PlexRequests.UI/Helpers/HtmlSecurityHelper.cs b/PlexRequests.UI/Helpers/HtmlSecurityHelper.cs index 3d1751257..ef8d93bc7 100644 --- a/PlexRequests.UI/Helpers/HtmlSecurityHelper.cs +++ b/PlexRequests.UI/Helpers/HtmlSecurityHelper.cs @@ -25,6 +25,8 @@ // ************************************************************************/ #endregion +using Nancy; +using Nancy.Linker; using Nancy.Security; using Nancy.ViewEngines.Razor; using Ninject; @@ -41,22 +43,37 @@ namespace PlexRequests.UI.Helpers get { var userRepo = ServiceLocator.Instance.Resolve(); - return _security ?? (_security = new SecurityExtensions(userRepo, null)); + var linker = ServiceLocator.Instance.Resolve(); + return _security ?? (_security = new SecurityExtensions(userRepo, null, linker)); } } private static SecurityExtensions _security; - public static bool HasAnyPermission(this HtmlHelpers helper, int permission) + public static bool HasAnyPermission(this HtmlHelpers helper, int permission, bool authenticated = true) { - return helper.CurrentUser.IsAuthenticated() - && Security.HasPermissions(helper.CurrentUser, (Permissions) permission); + if (authenticated) + { + return helper.CurrentUser.IsAuthenticated() + && Security.HasPermissions(helper.CurrentUser, (Permissions) permission); + } + return Security.HasPermissions(helper.CurrentUser, (Permissions)permission); } public static bool DoesNotHavePermission(this HtmlHelpers helper, int permission) { return Security.DoesNotHavePermissions(permission, helper.CurrentUser); } + + public static bool IsAdmin(this HtmlHelpers helper, bool isAuthenticated = true) + { + return HasAnyPermission(helper, (int) Permissions.Administrator, isAuthenticated); + } + + public static bool IsLoggedIn(this HtmlHelpers helper, NancyContext context) + { + return Security.IsLoggedIn(context); + } } } \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/SecurityExtensions.cs b/PlexRequests.UI/Helpers/SecurityExtensions.cs index 36f01a1a5..445aadc9c 100644 --- a/PlexRequests.UI/Helpers/SecurityExtensions.cs +++ b/PlexRequests.UI/Helpers/SecurityExtensions.cs @@ -30,6 +30,8 @@ using System.Collections.Generic; using System.Linq; using Nancy; using Nancy.Extensions; +using Nancy.Linker; +using Nancy.Responses; using Nancy.Security; using Ninject; using PlexRequests.Helpers.Permissions; @@ -40,14 +42,16 @@ namespace PlexRequests.UI.Helpers { public class SecurityExtensions { - public SecurityExtensions(IUserRepository userRepository, NancyModule context) + public SecurityExtensions(IUserRepository userRepository, NancyModule context, IResourceLinker linker) { UserRepository = userRepository; Module = context; + Linker = linker; } private IUserRepository UserRepository { get; } private NancyModule Module { get; } + private IResourceLinker Linker { get; } public bool IsLoggedIn(NancyContext context) { @@ -117,7 +121,7 @@ namespace PlexRequests.UI.Helpers if (dbUser == null) return false; var permissions = (Permissions)dbUser.Permissions; - var result = permissions.HasFlag((Permissions)perm); + var result = permissions.HasFlag(perm); return !result; } @@ -134,10 +138,11 @@ namespace PlexRequests.UI.Helpers return result; } - public void HasPermissionsResponse(Permissions perm) + public Response HasPermissionsRedirect(Permissions perm, NancyContext context, string routeName, HttpStatusCode code) { - Module.AddBeforeHookOrExecute( - ForbiddenIfNot(ctx => + var url = Linker.BuildRelativeUri(context, routeName); + + var response = ForbiddenIfNot(ctx => { if (ctx.CurrentUser == null) return false; @@ -145,13 +150,24 @@ namespace PlexRequests.UI.Helpers if (dbUser == null) return false; - var permissions = (Permissions)dbUser.Permissions; + var permissions = (Permissions) dbUser.Permissions; var result = permissions.HasFlag(perm); return result; - }), "Requires Claims"); + }); + + var r = response(context); + return r.StatusCode == code + ? new RedirectResponse(url.ToString()) + : null; } + public Response AdminLoginRedirect(Permissions perm, NancyContext context) + { + // This will redirect us to the Login Page if we don't have the correct permission passed in (e.g. Admin with Http 403 status code). + return HasPermissionsRedirect(perm, context, "LocalLogin", HttpStatusCode.Forbidden); + } + // BELOW IS A COPY FROM THE SecurityHooks CLASS! /// diff --git a/PlexRequests.UI/Modules/Admin/AdminModule.cs b/PlexRequests.UI/Modules/Admin/AdminModule.cs index 1357d3e7d..f621d99cf 100644 --- a/PlexRequests.UI/Modules/Admin/AdminModule.cs +++ b/PlexRequests.UI/Modules/Admin/AdminModule.cs @@ -66,6 +66,7 @@ using PlexRequests.UI.Helpers; using PlexRequests.UI.Models; using Quartz; using Action = PlexRequests.Helpers.Analytics.Action; +using HttpStatusCode = Nancy.HttpStatusCode; namespace PlexRequests.UI.Modules { @@ -154,8 +155,8 @@ namespace PlexRequests.UI.Modules NotifySettings = notifyService; RecentlyAdded = recentlyAdded; - Security.HasPermissionsResponse(Permissions.Administrator); - + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); + Get["/"] = _ => Admin(); Get["/authentication", true] = async (x, ct) => await Authentication(); @@ -826,13 +827,11 @@ namespace PlexRequests.UI.Modules return Response.AsJson(result ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Newsletter!" } : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } + } private Response CreateApiKey() { - Security.HasPermissionsResponse(Permissions.Administrator); - Analytics.TrackEventAsync(Category.Admin, Action.Create, "Created API Key", Username, CookieHelper.GetAnalyticClientId(Cookies)); var apiKey = Guid.NewGuid().ToString("N"); var settings = PrService.GetSettings(); @@ -978,11 +977,11 @@ namespace PlexRequests.UI.Modules if (!isValid) { return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = + { + Result = false, + Message = $"CRON {settings.RecentlyAddedCron} is not valid. Please ensure you are using a valid CRON." - }); + }); } } var result = await ScheduledJobSettings.SaveSettingsAsync(settings); diff --git a/PlexRequests.UI/Modules/Admin/FaultQueueModule.cs b/PlexRequests.UI/Modules/Admin/FaultQueueModule.cs index 355e823e2..b65238b23 100644 --- a/PlexRequests.UI/Modules/Admin/FaultQueueModule.cs +++ b/PlexRequests.UI/Modules/Admin/FaultQueueModule.cs @@ -46,7 +46,7 @@ namespace PlexRequests.UI.Modules.Admin Cache = cache; RequestQueue = requestQueue; - Security.HasPermissionsResponse(Permissions.Administrator); + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); Get["Index", "/faultqueue"] = x => Index(); } diff --git a/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs b/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs index 05ae0f125..7c5c793c5 100644 --- a/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs +++ b/PlexRequests.UI/Modules/Admin/SystemStatusModule.cs @@ -49,8 +49,8 @@ namespace PlexRequests.UI.Modules.Admin Cache = cache; SystemSettings = ss; - Security.HasPermissionsResponse(Permissions.Administrator); - + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); + Get["/status", true] = async (x, ct) => await Status(); Post["/save", true] = async (x, ct) => await Save(); diff --git a/PlexRequests.UI/Modules/BaseModule.cs b/PlexRequests.UI/Modules/BaseModule.cs index 35bd4817f..2c2fb7467 100644 --- a/PlexRequests.UI/Modules/BaseModule.cs +++ b/PlexRequests.UI/Modules/BaseModule.cs @@ -30,6 +30,7 @@ using System.Linq; using System.Threading; using Nancy; +using Nancy.Linker; using Nancy.Security; using Ninject; using PlexRequests.Core; @@ -151,14 +152,14 @@ namespace PlexRequests.UI.Modules get { var userRepo = ServiceLocator.Instance.Resolve(); - return _security ?? (_security = new SecurityExtensions(userRepo, this)); + var linker = ServiceLocator.Instance.Resolve(); + return _security ?? (_security = new SecurityExtensions(userRepo, this, linker)); } } private SecurityExtensions _security; - protected bool LoggedIn => Context?.CurrentUser != null; protected string Culture { get; set; } diff --git a/PlexRequests.UI/Modules/UpdateCheckerModule.cs b/PlexRequests.UI/Modules/LayoutModule.cs similarity index 65% rename from PlexRequests.UI/Modules/UpdateCheckerModule.cs rename to PlexRequests.UI/Modules/LayoutModule.cs index 3aa6a657d..03b3a95c6 100644 --- a/PlexRequests.UI/Modules/UpdateCheckerModule.cs +++ b/PlexRequests.UI/Modules/LayoutModule.cs @@ -1,7 +1,7 @@ #region Copyright // /************************************************************************ // Copyright (c) 2016 Jamie Rees -// File: UpdateCheckerModule.cs +// File: LayoutModule.cs // Created By: Jamie Rees // // Permission is hereby granted, free of charge, to any person obtaining @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion using System; +using System.Linq; using System.Threading.Tasks; using Nancy; @@ -35,24 +36,29 @@ using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Core.StatusChecker; using PlexRequests.Helpers; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Jobs; using PlexRequests.UI.Models; namespace PlexRequests.UI.Modules { - public class UpdateCheckerModule : BaseAuthModule + public class LayoutModule : BaseAuthModule { - public UpdateCheckerModule(ICacheProvider provider, ISettingsService pr, ISettingsService settings) : base("updatechecker", pr) + public LayoutModule(ICacheProvider provider, ISettingsService pr, ISettingsService settings, IJobRecord rec) : base("layout", pr) { Cache = provider; SystemSettings = settings; + Job = rec; Get["/", true] = async (x,ct) => await CheckLatestVersion(); + Get["/cacher", true] = async (x,ct) => await CacherRunning(); } private ICacheProvider Cache { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); private ISettingsService SystemSettings { get; } + private IJobRecord Job { get; } private async Task CheckLatestVersion() { @@ -79,5 +85,35 @@ namespace PlexRequests.UI.Modules return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false }); } } + + private async Task CacherRunning() + { + try + { + var jobs = await Job.GetJobsAsync(); + + // Check to see if any are running + var runningJobs = jobs.Where(x => x.Running); + + // We only want the cachers + var cacherJobs = runningJobs.Where(x => + x.Name.Equals(JobNames.CpCacher) + || x.Name.Equals(JobNames.EpisodeCacher) + || x.Name.Equals(JobNames.PlexChecker) + || x.Name.Equals(JobNames.SonarrCacher) + || x.Name.Equals(JobNames.SrCacher)); + + + return Response.AsJson(cacherJobs.Any() + ? new { CurrentlyRunning = true, IsAdmin} + : new { CurrentlyRunning = false, IsAdmin }); + } + catch (Exception e) + { + Log.Warn("Exception Thrown when attempting to check the status"); + Log.Warn(e); + return Response.AsJson(new { CurrentlyRunning = false, IsAdmin }); + } + } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs index e6030207f..37ab086a3 100644 --- a/PlexRequests.UI/Modules/LoginModule.cs +++ b/PlexRequests.UI/Modules/LoginModule.cs @@ -52,7 +52,7 @@ namespace PlexRequests.UI.Modules : base(pr) { UserMapper = m; - Get["/login"] = _ => + Get["LocalLogin","/login"] = _ => { if (LoggedIn) { diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs index d4e55182c..f377cf2f3 100644 --- a/PlexRequests.UI/Modules/UserLoginModule.cs +++ b/PlexRequests.UI/Modules/UserLoginModule.cs @@ -99,7 +99,7 @@ namespace PlexRequests.UI.Modules { Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass; var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); - return Response.AsRedirect(uri.ToString()); // TODO Check this + return Response.AsRedirect(uri.ToString()); } var authenticated = false; @@ -112,7 +112,7 @@ namespace PlexRequests.UI.Modules Log.Debug("User is in denied list, not allowing them to authenticate"); Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass; var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); - return Response.AsRedirect(uri.ToString()); // TODO Check this + return Response.AsRedirect(uri.ToString()); } var password = string.Empty; @@ -178,7 +178,7 @@ namespace PlexRequests.UI.Modules { var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass; - return Response.AsRedirect(uri.ToString()); // TODO Check this + return Response.AsRedirect(uri.ToString()); } var landingSettings = await LandingPageSettings.GetSettingsAsync(); @@ -192,7 +192,7 @@ namespace PlexRequests.UI.Modules } } var retVal = Linker.BuildRelativeUri(Context, "SearchIndex"); - return Response.AsRedirect(retVal.ToString()); // TODO Check this + return Response.AsRedirect(retVal.ToString()); } diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 82f401655..04f4a0cc6 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -258,7 +258,7 @@ - + @@ -747,6 +747,7 @@ PublicResXFileCodeGenerator UI1.Designer.cs + Designer diff --git a/PlexRequests.UI/Resources/UI.da.resx b/PlexRequests.UI/Resources/UI.da.resx index 14e0154ac..5937e76be 100644 --- a/PlexRequests.UI/Resources/UI.da.resx +++ b/PlexRequests.UI/Resources/UI.da.resx @@ -459,4 +459,34 @@ Der er ingen oplysninger om udgivelsesdatoen + + Udsigt I Plex + + + Doner til Bibliotek vedligeholder + + + Tilgængelig på + + + Movie status + + + Ikke anmodet endnu + + + Afventer godkendelse + + + Behandler forespørgsel + + + Anmodning afvist! + + + Tv-show status! + + + En baggrund proces kører i øjeblikket, så der kan være nogle uventede problemer. Dette bør ikke tage for lang tid. + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.de.resx b/PlexRequests.UI/Resources/UI.de.resx index 039e49e46..fa6f71547 100644 --- a/PlexRequests.UI/Resources/UI.de.resx +++ b/PlexRequests.UI/Resources/UI.de.resx @@ -459,4 +459,34 @@ Es gibt noch keine Informationen für diesen Release-Termin - + + Ansicht In Plex + + + Spenden zur Bibliothek Maintainer + + + Verfügbar auf Plex + + + Film-Status! + + + Noch nicht heraus! + + + Genehmigung ausstehend + + + Die Verarbeitung Anfrage + + + Anfrage verweigert. + + + TV-Show-Status + + + Ein Hintergrundprozess gerade läuft, so könnte es einige unerwartete Verhalten sein. Dies sollte nicht zu lange dauern. + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.es.resx b/PlexRequests.UI/Resources/UI.es.resx index a1706e45d..e7908b3f5 100644 --- a/PlexRequests.UI/Resources/UI.es.resx +++ b/PlexRequests.UI/Resources/UI.es.resx @@ -459,4 +459,31 @@ No hay información disponible para la fecha de lanzamiento + + Donar a la biblioteca Mantenedor + + + Disponible en Plex + + + estado de película + + + No solicitado + + + PENDIENTES DE APROBACIÓN + + + solicitud de procesamiento + + + Solicitud denegada + + + estado de programa de televisión + + + Un proceso en segundo plano se está ejecutando actualmente, por lo que podría ser un comportamiento inesperado. Esto no debería tomar mucho tiempo. + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.fr.resx b/PlexRequests.UI/Resources/UI.fr.resx index e4a54012d..5767fb4d2 100644 --- a/PlexRequests.UI/Resources/UI.fr.resx +++ b/PlexRequests.UI/Resources/UI.fr.resx @@ -459,4 +459,34 @@ Il n'y a pas d'information disponible pour la date de sortie + + Voir Dans Plex + + + Faire un don à la bibliothèque mainteneur! + + + Disponible sur Plex + + + le statut de film + + + Pas encore demandé + + + En attente de validation + + + Traitement de la demande + + + Requête refusée + + + TV show status + + + Un processus d'arrière-plan est en cours d'exécution, de sorte qu'il pourrait y avoir des comportements inattendus. Cela ne devrait pas prendre trop de temps. + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.it.resx b/PlexRequests.UI/Resources/UI.it.resx index fe73d4586..febcf378a 100644 --- a/PlexRequests.UI/Resources/UI.it.resx +++ b/PlexRequests.UI/Resources/UI.it.resx @@ -459,4 +459,34 @@ Non ci sono informazioni disponibili per la data di uscita + + Guarda In Plex + + + Donare alla libreria Maintainer + + + Disponibile su Plex + + + lo stato di film + + + Non ancora richiesto + + + Approvazione in sospeso + + + Elaborazione richiesta + + + Richiesta negata + + + Show televisivo di stato + + + Un processo in background è in esecuzione, quindi ci potrebbero essere alcuni comportamenti imprevisti. Questo non dovrebbe richiedere troppo tempo. + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.nl.resx b/PlexRequests.UI/Resources/UI.nl.resx index 9ddc8688e..e64511a14 100644 --- a/PlexRequests.UI/Resources/UI.nl.resx +++ b/PlexRequests.UI/Resources/UI.nl.resx @@ -459,4 +459,34 @@ Er is geen informatie beschikbaar voor de release datum + + Bekijk In Plex + + + Doneer aan Library Maintainer + + + Beschikbaar vanaf + + + Movie-status! + + + Nog niet gevraagd + + + Wacht op goedkeuring + + + Verwerking verzoek... + + + Aanvraag afgewezen + + + TV-show-status + + + Een achtergrond taak is momenteel actief, dus er misschien een onverwachte gedrag. Dit moet niet te lang duren. + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.pt.resx b/PlexRequests.UI/Resources/UI.pt.resx index 712b779c4..efedc0856 100644 --- a/PlexRequests.UI/Resources/UI.pt.resx +++ b/PlexRequests.UI/Resources/UI.pt.resx @@ -459,4 +459,31 @@ Não há informações disponíveis para a data de lançamento + + Ver Em Plex + + + Doações para Biblioteca Mantenedor + + + Disponível em Plex + + + status de filme + + + Não solicitada ainda + + + B – P/ Aprovação + + + Processando solicitação... + + + Solicitação negada. + + + Programa de TV status + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx index ced499673..ca792000b 100644 --- a/PlexRequests.UI/Resources/UI.resx +++ b/PlexRequests.UI/Resources/UI.resx @@ -467,4 +467,7 @@ TV show status + + A background process is currently running, so there might be some unexpected behavior. This shouldn't take too long. + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.sv.resx b/PlexRequests.UI/Resources/UI.sv.resx index 8b3708153..924f2cc70 100644 --- a/PlexRequests.UI/Resources/UI.sv.resx +++ b/PlexRequests.UI/Resources/UI.sv.resx @@ -459,4 +459,34 @@ Det finns ingen information tillgänglig för release datum + + Vy I Plex + + + Donera till bibliotek Ansvarig + + + Tillgänglig den + + + Film status + + + Inte Begärd ännu + + + Väntar på godkännande + + + Bearbetning förfrågan + + + Förfrågan nekad + + + Visa status + + + En bakgrundsprocess är igång, så det kan finnas några oväntade beteende. Detta bör inte ta alltför lång tid. + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI1.Designer.cs b/PlexRequests.UI/Resources/UI1.Designer.cs index 548dafff4..0a57c2412 100644 --- a/PlexRequests.UI/Resources/UI1.Designer.cs +++ b/PlexRequests.UI/Resources/UI1.Designer.cs @@ -222,6 +222,15 @@ namespace PlexRequests.UI.Resources { } } + /// + /// Looks up a localized string similar to A background process is currently running, so there might be some unexpected behavior. This shouldn't take too long.. + /// + public static string Layout_CacherRunning { + get { + return ResourceManager.GetString("Layout_CacherRunning", resourceCulture); + } + } + /// /// Looks up a localized string similar to Change Password. /// diff --git a/PlexRequests.UI/Validators/PlexRequestsValidator.cs b/PlexRequests.UI/Validators/PlexRequestsValidator.cs index 7d61eba2c..481364fd9 100644 --- a/PlexRequests.UI/Validators/PlexRequestsValidator.cs +++ b/PlexRequests.UI/Validators/PlexRequestsValidator.cs @@ -44,7 +44,7 @@ namespace PlexRequests.UI.Validators RuleFor(x => x.BaseUrl).NotEqual("login", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'login' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("test", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'test' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("approval", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'approval' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("updatechecker", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'updatechecker' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("layout", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'layout' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("usermanagement", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'usermanagement' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("api", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'api' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("landing", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'landing' as this is reserved by the application."); diff --git a/PlexRequests.UI/Views/Shared/Partial/_LayoutScripts.cshtml b/PlexRequests.UI/Views/Shared/Partial/_LayoutScripts.cshtml index 808fc7a91..62e946479 100644 --- a/PlexRequests.UI/Views/Shared/Partial/_LayoutScripts.cshtml +++ b/PlexRequests.UI/Views/Shared/Partial/_LayoutScripts.cshtml @@ -10,7 +10,7 @@ $(function () { // Check for update - var url = createBaseUrl(urlBase, '/updatechecker'); + var url = createBaseUrl(urlBase, '/layout'); $.ajax({ type: "GET", url: url, @@ -20,7 +20,7 @@ var status = createBaseUrl(urlBase, '/admin/status'); $('#updateAvailable').html(" @UI.Layout_UpdateAvailablePart1 @UI.Layout_UpdateAvailablePart2"); $('#updateAvailable').removeAttr("hidden"); - $('body').addClass('update-available'); + //$('body').addClass('update-available'); } }, error: function (e) { @@ -29,6 +29,7 @@ }); // End Check for update + checkCacheInProgress(); // Scroller $(document).on('scroll', function () { @@ -43,9 +44,6 @@ $('.scroll-top-wrapper').on('click', scrollToTop); // End Scroller - - - // Get Issue count var issueUrl = createBaseUrl(urlBase, '/issues/issuecount'); $.ajax({ @@ -66,6 +64,8 @@ // End issue count + + $('#donate').click(function () { ga('send', 'event', 'Navbar', 'Donate', 'Donate Clicked'); }); @@ -80,4 +80,23 @@ offsetTop = offset.top; $('html, body').animate({ scrollTop: offsetTop }, 500, 'linear'); } + + function checkCacheInProgress() { + + var url = createBaseUrl(urlBase, '/layout/cacher'); + $.ajax({ + type: "GET", + url: url, + dataType: "json", + success: function (response) { + if (response.currentlyRunning) { + $('#cacherRunning').html("@UI.Layout_CacherRunning"); + $('#cacherRunning').removeAttr("hidden"); + } + }, + error: function (e) { + console.log(e); + } + }); + } \ No newline at end of file diff --git a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml index 2dba39e5f..e4d75cc75 100644 --- a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml +++ b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml @@ -35,21 +35,21 @@ @Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search") @Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle") @Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "") - @if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin + @*@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin*@ + @if (Html.IsAdmin()) {
  • } } - @if (Context.Request.Session[SessionKeys.UsernameKey] != null && !Context.CurrentUser.IsAuthenticated()) + @*@if (Context.Request.Session[SessionKeys.UsernameKey] != null && !Context.CurrentUser.IsAuthenticated())*@ + else if (Html.IsLoggedIn(Context)) // Logged in but not admin {
  • + + +
  • @@ -104,16 +105,17 @@ var donateLink = $("#customDonateHref"); var donationText = $("#donationText"); donateLink.attr("href", result.url); - if(result.message) { + if (result.message) { donationText.text(result.message); } } }, - error: function(xhr, status, error) { + error: function (xhr, status, error) { console.log("error " + error); $("#customDonate").hide(); } }); + \ No newline at end of file diff --git a/PlexRequests.UI/Views/Shared/_Layout.cshtml b/PlexRequests.UI/Views/Shared/_Layout.cshtml index 86eae181c..fcd6f8d37 100644 --- a/PlexRequests.UI/Views/Shared/_Layout.cshtml +++ b/PlexRequests.UI/Views/Shared/_Layout.cshtml @@ -16,8 +16,6 @@ - - @*@MiniProfiler.RenderIncludes()*@ @Html.GetInformationalVersion() From c064bc6d44f3a57101a06b5648efdf1657e4d4b4 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 15 Nov 2016 15:01:38 +0000 Subject: [PATCH 23/68] Finishing off the user management page #218 #359 #195 --- .../Migrations/Version1100.cs | 3 + .../Permissions/Permissions.cs | 10 + .../Jobs/PlexAvailabilityChecker.cs | 4 +- PlexRequests.Store/Models/PlexUsers.cs | 8 +- PlexRequests.Store/SqlTables.sql | 6 +- .../userManagementController.js | 38 ++-- .../userManagement/userManagementService.js | 4 +- .../UserManagementUsersViewModel.cs | 2 + .../Modules/UserManagementModule.cs | 174 ++++++++++++++---- PlexRequests.UI/Resources/UI.resx | 3 + PlexRequests.UI/Resources/UI1.Designer.cs | 9 + .../Views/Shared/Partial/_Navbar.cshtml | 4 + .../Views/UserManagement/Index.cshtml | 21 ++- 13 files changed, 220 insertions(+), 66 deletions(-) diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index d4365f357..525114ebb 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -76,6 +76,9 @@ namespace PlexRequests.Core.Migration.Migrations con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER"); con.AlterTable("Users", "ADD", "Features", true, "INTEGER"); + con.AlterTable("PlexUsers", "ADD", "Permissions", true, "INTEGER"); + con.AlterTable("PlexUsers", "ADD", "Features", true, "INTEGER"); + //https://image.tmdb.org/t/p/w150/https://image.tmdb.org/t/p/w150//aqhAqttDq7zgsTaBHtCD8wmTk6k.jpg // UI = https://image.tmdb.org/t/p/w150/{{posterPath}} diff --git a/PlexRequests.Helpers/Permissions/Permissions.cs b/PlexRequests.Helpers/Permissions/Permissions.cs index b0c28db36..8b1d22954 100644 --- a/PlexRequests.Helpers/Permissions/Permissions.cs +++ b/PlexRequests.Helpers/Permissions/Permissions.cs @@ -50,5 +50,15 @@ namespace PlexRequests.Helpers.Permissions [Display(Name = "Read Only User")] ReadOnlyUser = 32, + + [Display(Name = "Auto Approve Movie Requests")] + AutoApproveMovie = 64, + + [Display(Name = "Auto Approve TV Show Requests")] + AutoApproveTv = 128, + + [Display(Name = "Auto Approve Album Requests")] + AutoApproveAlbum = 256 + } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 8ecb00bb5..365a7efa0 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -471,10 +471,10 @@ namespace PlexRequests.Services.Jobs { var sections = PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); - List libs = new List(); + var libs = new List(); if (sections != null) { - foreach (var dir in sections.Directories) + foreach (var dir in sections.Directories ?? new List()) { var lib = PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.Key); if (lib != null) diff --git a/PlexRequests.Store/Models/PlexUsers.cs b/PlexRequests.Store/Models/PlexUsers.cs index 884fe1d4d..c860b9551 100644 --- a/PlexRequests.Store/Models/PlexUsers.cs +++ b/PlexRequests.Store/Models/PlexUsers.cs @@ -24,11 +24,17 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using Dapper.Contrib.Extensions; + namespace PlexRequests.Store.Models { + [Table(nameof(PlexUsers))] public class PlexUsers : Entity { - public int PlexUserId { get; set; } + public string PlexUserId { get; set; } public string UserAlias { get; set; } + public int Permissions { get; set; } + public int Features { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 4d180b1d0..f4a512346 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -114,8 +114,10 @@ CREATE UNIQUE INDEX IF NOT EXISTS RequestLimit_Id ON RequestLimit (Id); CREATE TABLE IF NOT EXISTS PlexUsers ( Id INTEGER PRIMARY KEY AUTOINCREMENT, - PlexUserId INTEGER NOT NULL, - UserAlias varchar(100) NOT NULL + PlexUserId varchar(100) NOT NULL, + UserAlias varchar(100) NOT NULL, + Permissions INTEGER, + Features INTEGER ); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementController.js b/PlexRequests.UI/Content/app/userManagement/userManagementController.js index 3b67832aa..814df40d8 100644 --- a/PlexRequests.UI/Content/app/userManagement/userManagementController.js +++ b/PlexRequests.UI/Content/app/userManagement/userManagementController.js @@ -120,29 +120,30 @@ $scope.updateUser = function () { var u = $scope.selectedUser; - userManagementService.updateUser(u.id, u.permissions, u.alias, u.emailAddress) - .then(function (data) { - if (data) { - $scope.selectedUser = data; - - if (open) { - open = false; - $("#wrapper").toggleClass("toggled"); + userManagementService.updateUser(u.id, u.permissions, u.features, u.alias, u.emailAddress) + .then(function success(data) { + if (data.data) { + $scope.selectedUser = data.data; + + closeSidebar(); + return successCallback("Updated User", "success"); } - return successCallback("Updated User", "success"); - } - }); + }, function errorCallback(response) { + successCallback(response, "danger"); + }); } $scope.deleteUser = function () { var u = $scope.selectedUser; - var result = userManagementService.deleteUser(u.id); - - result.success(function (data) { - if (data.result) { + userManagementService.deleteUser(u.id) + .then(function sucess(data) { + if (data.data.result) { removeUser(u.id, true); + closeSidebar(); return successCallback("Deleted User", "success"); } + }, function errorCallback(response) { + successCallback(response, "danger"); }); } @@ -170,6 +171,13 @@ $scope.selectedUser = null; } } + + function closeSidebar() { + if (open) { + open = false; + $("#wrapper").toggleClass("toggled"); + } + } } function successCallback(message, type) { diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementService.js b/PlexRequests.UI/Content/app/userManagement/userManagementService.js index 72cd7f375..59dfe3571 100644 --- a/PlexRequests.UI/Content/app/userManagement/userManagementService.js +++ b/PlexRequests.UI/Content/app/userManagement/userManagementService.js @@ -28,11 +28,11 @@ return $http.get('/usermanagement/permissions'); } - var updateUser = function (id, permissions, alias, email) { + var updateUser = function (id, permissions, features, alias, email) { return $http({ url: '/usermanagement/updateUser', method: "POST", - data: { id: id, permissions: permissions, alias: alias, emailAddress: email } + data: { id: id, permissions: permissions, features: features, alias: alias, emailAddress: email }, }); } diff --git a/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs b/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs index aad003c05..cdc78a07a 100644 --- a/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs +++ b/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs @@ -75,6 +75,8 @@ namespace PlexRequests.UI.Models public string Id { get; set; } [JsonProperty("permissions")] public List Permissions { get; set; } + [JsonProperty("features")] + public List Features { get; set; } public string Alias { get; set; } public string EmailAddress { get; set; } } diff --git a/PlexRequests.UI/Modules/UserManagementModule.cs b/PlexRequests.UI/Modules/UserManagementModule.cs index ee9558d1f..9870d3533 100644 --- a/PlexRequests.UI/Modules/UserManagementModule.cs +++ b/PlexRequests.UI/Modules/UserManagementModule.cs @@ -6,15 +6,16 @@ using System.Threading.Tasks; using Nancy; using Nancy.Extensions; using Nancy.Responses.Negotiation; -using Nancy.Security; using Newtonsoft.Json; using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; using PlexRequests.Core; using PlexRequests.Core.Models; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Helpers.Permissions; using PlexRequests.Store; +using PlexRequests.Store.Models; using PlexRequests.Store.Repository; using PlexRequests.UI.Models; @@ -22,15 +23,16 @@ namespace PlexRequests.UI.Modules { public class UserManagementModule : BaseModule { - public UserManagementModule(ISettingsService pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService plex, IRepository userLogins) : base("usermanagement", pr) + public UserManagementModule(ISettingsService pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService plex, IRepository userLogins, IRepository plexRepo) : base("usermanagement", pr) { #if !DEBUG - this.RequiresAnyClaim(UserClaims.Admin); + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); #endif UserMapper = m; PlexApi = plexApi; PlexSettings = plex; UserLoginsRepo = userLogins; + PlexUsersRepository = plexRepo; Get["/"] = x => Load(); @@ -40,7 +42,7 @@ namespace PlexRequests.UI.Modules Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id); Get["/permissions"] = x => GetEnum(); Get["/features"] = x => GetEnum(); - Post["/updateuser"] = x => UpdateUser(); + Post["/updateuser", true] = async (x, ct) => await UpdateUser(); Post["/deleteuser"] = x => DeleteUser(); } @@ -48,6 +50,7 @@ namespace PlexRequests.UI.Modules private IPlexApi PlexApi { get; } private ISettingsService PlexSettings { get; } private IRepository UserLoginsRepo { get; } + private IRepository PlexUsersRepository { get; } private Negotiator Load() { @@ -57,13 +60,14 @@ namespace PlexRequests.UI.Modules private async Task LoadUsers() { var localUsers = await UserMapper.GetUsersAsync(); + var plexDbUsers = await PlexUsersRepository.GetAllAsync(); var model = new List(); - var usersDb = UserLoginsRepo.GetAll().ToList(); + var userLogins = UserLoginsRepo.GetAll().ToList(); foreach (var user in localUsers) { - var userDb = usersDb.FirstOrDefault(x => x.UserId == user.UserGuid); + var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid); model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue)); } @@ -75,20 +79,18 @@ namespace PlexRequests.UI.Modules foreach (var u in plexUsers.User) { - var userDb = usersDb.FirstOrDefault(x => x.UserId == u.Id); - model.Add(new UserManagementUsersViewModel + var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == u.Id); + var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id); + // We don't have the user in the database yet + if (dbUser == null) { - Username = u.Username, - Type = UserType.PlexUser, - Id = u.Id, - FeaturesFormattedString = "Requestor", - EmailAddress = u.Email, - PlexInfo = new UserManagementPlexInformation - { - Thumb = u.Thumb - }, - LastLoggedIn = userDb?.LastLoggedIn ?? DateTime.MinValue, - }); + model.Add(MapPlexUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + else + { + // The Plex User is in the database + model.Add(MapPlexUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } } } return Response.AsJson(model); @@ -128,7 +130,7 @@ namespace PlexRequests.UI.Modules permissionsVal += f; } - var user = UserMapper.CreateUser(model.Username, model.Password, featuresVal, permissionsVal, new UserProperties { EmailAddress = model.EmailAddress }); + var user = UserMapper.CreateUser(model.Username, model.Password, permissionsVal, featuresVal, new UserProperties { EmailAddress = model.EmailAddress }); if (user.HasValue) { return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue)); @@ -137,7 +139,7 @@ namespace PlexRequests.UI.Modules return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" }); } - private Response UpdateUser() + private async Task UpdateUser() { var body = Request.Body.AsString(); if (string.IsNullOrEmpty(body)) @@ -156,22 +158,68 @@ namespace PlexRequests.UI.Modules }); } - var val = model.Permissions.Where(c => c.Selected).Sum(c => c.Value); + var permissionsValue = model.Permissions.Where(c => c.Selected).Sum(c => c.Value); + var featuresValue = model.Features.Where(c => c.Selected).Sum(c => c.Value); + + Guid outId; + Guid.TryParse(model.Id, out outId); + var localUser = UserMapper.GetUser(outId); + + // Update Local User + if (localUser != null) + { + localUser.Permissions = permissionsValue; + localUser.Features = featuresValue; + + var currentProps = ByteConverterHelper.ReturnObject(localUser.UserProperties); + currentProps.UserAlias = model.Alias; + currentProps.EmailAddress = model.EmailAddress; + + localUser.UserProperties = ByteConverterHelper.ReturnBytes(currentProps); + + var user = UserMapper.EditUser(localUser); + var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid); + var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } + + var plexSettings = await PlexSettings.GetSettingsAsync(); + var plexDbUsers = await PlexUsersRepository.GetAllAsync(); + var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken); + var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id); + var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id); + var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id); + if (plexDbUser != null && plexUser != null) + { + // We have a user in the DB for this Plex Account + plexDbUser.Permissions = permissionsValue; + plexDbUser.Features = featuresValue; - var userFound = UserMapper.GetUser(new Guid(model.Id)); + plexDbUser.UserAlias = model.Alias; - userFound.Permissions = val; + await PlexUsersRepository.UpdateAsync(plexDbUser); + + var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } - var currentProps = ByteConverterHelper.ReturnObject(userFound.UserProperties); - currentProps.UserAlias = model.Alias; - currentProps.EmailAddress = model.EmailAddress; + // We have a Plex Account but he's not in the DB + if (plexUser != null) + { + var user = new PlexUsers + { + Permissions = permissionsValue, + Features = featuresValue, + UserAlias = model.Alias, + PlexUserId = plexUser.Id + }; - userFound.UserProperties = ByteConverterHelper.ReturnBytes(currentProps); + await PlexUsersRepository.InsertAsync(user); - var user = UserMapper.EditUser(userFound); - var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid); - var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue); - return Response.AsJson(retUser); + var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } + return null; // We should never end up here. } private Response DeleteUser() @@ -248,8 +296,8 @@ namespace PlexRequests.UI.Modules private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn) { - var features = (Features) user.Features; - var permissions = (Permissions) user.Permissions; + var features = (Features)user.Features; + var permissions = (Permissions)user.Permissions; var userProps = ByteConverterHelper.ReturnObject(user.UserProperties); @@ -297,6 +345,64 @@ namespace PlexRequests.UI.Modules return m; } + + private UserManagementUsersViewModel MapPlexUser(UserFriends plexInfo, PlexUsers dbUser, DateTime lastLoggedIn) + { + if (dbUser == null) + { + dbUser = new PlexUsers(); + } + var features = (Features)dbUser?.Features; + var permissions = (Permissions)dbUser?.Permissions; + + var m = new UserManagementUsersViewModel + { + Id = plexInfo.Id, + PermissionsFormattedString = permissions == 0 ? "None" : permissions.ToString(), + FeaturesFormattedString = features.ToString(), + Username = plexInfo.Username, + Type = UserType.PlexUser, + EmailAddress = plexInfo.Email, + Alias = dbUser?.UserAlias ?? string.Empty, + LastLoggedIn = lastLoggedIn, + PlexInfo = new UserManagementPlexInformation + { + Thumb = plexInfo.Thumb + }, + }; + + // Add permissions + foreach (var p in Enum.GetValues(typeof(Permissions))) + { + var perm = (Permissions)p; + var displayValue = EnumHelper.GetDisplayValue(perm); + var pm = new CheckBox + { + Name = displayValue, + Selected = permissions.HasFlag(perm), + Value = (int)perm + }; + + m.Permissions.Add(pm); + } + + // Add features + foreach (var p in Enum.GetValues(typeof(Features))) + { + var perm = (Features)p; + var displayValue = EnumHelper.GetDisplayValue(perm); + var pm = new CheckBox + { + Name = displayValue, + Selected = features.HasFlag(perm), + Value = (int)perm + }; + + m.Features.Add(pm); + } + + return m; + } } } diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx index ca792000b..f82c34fce 100644 --- a/PlexRequests.UI/Resources/UI.resx +++ b/PlexRequests.UI/Resources/UI.resx @@ -470,4 +470,7 @@ A background process is currently running, so there might be some unexpected behavior. This shouldn't take too long. + + User Management + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI1.Designer.cs b/PlexRequests.UI/Resources/UI1.Designer.cs index 0a57c2412..a5cf28b1f 100644 --- a/PlexRequests.UI/Resources/UI1.Designer.cs +++ b/PlexRequests.UI/Resources/UI1.Designer.cs @@ -402,6 +402,15 @@ namespace PlexRequests.UI.Resources { } } + /// + /// Looks up a localized string similar to User Management. + /// + public static string Layout_Usermanagement { + get { + return ResourceManager.GetString("Layout_Usermanagement", resourceCulture); + } + } + /// /// Looks up a localized string similar to Welcome. /// diff --git a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml index e4d75cc75..1f2f1b232 100644 --- a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml +++ b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml @@ -35,6 +35,10 @@ @Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search") @Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle") @Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "") + @if (Html.IsAdmin()) + { + @Html.GetNavbarUrl(Context, "/usermanagement", UI.Layout_Usermanagement, "users") + } @*@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin*@ @if (Html.IsAdmin()) { diff --git a/PlexRequests.UI/Views/UserManagement/Index.cshtml b/PlexRequests.UI/Views/UserManagement/Index.cshtml index 7002cb143..d0cd11dc1 100644 --- a/PlexRequests.UI/Views/UserManagement/Index.cshtml +++ b/PlexRequests.UI/Views/UserManagement/Index.cshtml @@ -15,30 +15,31 @@
    Email Address:
    -
    - Permissions: -
    -
    - Features: -
    User Type:


    -
    +
    - Modify Roles: - + Modify Permissions: +
    + Modify Features: + +
    + + +
    + Email Address
    - +
    Alias From fdba68bb3d989b3da53ccbc0d5a9a0c3431a9ece Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Tue, 15 Nov 2016 16:11:07 +0000 Subject: [PATCH 24/68] More user management --- .../Migrations/Version1100.cs | 86 ++++++++++++++++++- .../PlexRequests.Core.Migration.csproj | 8 ++ PlexRequests.Core/PlexRequests.Core.csproj | 1 + .../SettingModels/UserManagementSettings.cs | 38 ++++++++ .../Modules/UserManagementModule.cs | 2 + 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 PlexRequests.Core/SettingModels/UserManagementSettings.cs diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index 525114ebb..dbbfab158 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -26,13 +26,16 @@ #endregion using System; +using System.Collections.Generic; using System.Data; using NLog; using System.Linq; +using PlexRequests.Api.Interfaces; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Helpers.Permissions; using PlexRequests.Store; +using PlexRequests.Store.Models; using PlexRequests.Store.Repository; namespace PlexRequests.Core.Migration.Migrations @@ -40,16 +43,27 @@ namespace PlexRequests.Core.Migration.Migrations [Migration(11000, "v1.10.0.0")] public class Version1100 : BaseMigration, IMigration { - public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService log) + public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService log, IPlexApi plexApi, ISettingsService plexService, IRepository plexusers, + ISettingsService prSettings, ISettingsService umSettings) { UserRepo = userRepo; RequestService = requestService; Log = log; + PlexApi = plexApi; + PlexSettings = plexService; + PlexUsers = plexusers; + PlexRequestSettings = prSettings; + UserManagementSettings = umSettings; } public int Version => 11000; private IUserRepository UserRepo { get; } private IRequestService RequestService { get; } private ISettingsService Log { get; } + private IPlexApi PlexApi { get; } + private ISettingsService PlexSettings { get; } + private IRepository PlexUsers { get; } + private ISettingsService PlexRequestSettings { get; } + private ISettingsService UserManagementSettings { get; } public void Start(IDbConnection con) { @@ -58,9 +72,79 @@ namespace PlexRequests.Core.Migration.Migrations // Update the current admin permissions set UpdateAdmin(); ResetLogLevel(); + UpdatePlexUsers(); + PopulateDefaultUserManagementSettings(); + UpdateSchema(con, Version); } + private void PopulateDefaultUserManagementSettings() + { + var plexRequestSettings = PlexRequestSettings.GetSettings(); + + UserManagementSettings.SaveSettings(new UserManagementSettings + { + AutoApproveMovies = !plexRequestSettings.RequireMovieApproval, + SearchForTvShows = plexRequestSettings.SearchForTvShows, + SearchForMusic = plexRequestSettings.SearchForMusic, + SearchForMovies = plexRequestSettings.SearchForMovies, + AutoApproveMusic = !plexRequestSettings.RequireMusicApproval, + AutoApproveTvShows = !plexRequestSettings.RequireTvShowApproval + }); + } + + private void UpdatePlexUsers() + { + var settings = PlexSettings.GetSettings(); + var plexUsers = PlexApi.GetUsers(settings.PlexAuthToken); + var prSettings = PlexRequestSettings.GetSettings(); + + var dbUsers = PlexUsers.GetAll().ToList(); + foreach (var user in plexUsers.User) + { + if (dbUsers.FirstOrDefault(x => x.PlexUserId == user.Id) != null) + { + continue; + } + + int permissions = 0; + if (prSettings.SearchForMovies) + { + permissions = (int) Permissions.RequestMovie; + } + if (prSettings.SearchForTvShows) + { + permissions += (int) Permissions.RequestTvShow; + } + if (prSettings.SearchForMusic) + { + permissions += (int) Permissions.RequestMusic; + } + if (!prSettings.RequireMovieApproval) + { + permissions += (int)Permissions.AutoApproveMovie; + } + if (!prSettings.RequireTvShowApproval) + { + permissions += (int)Permissions.AutoApproveTv; + } + if (!prSettings.RequireMusicApproval) + { + permissions += (int)Permissions.AutoApproveAlbum; + } + + var m = new PlexUsers + { + PlexUserId = user.Id, + Permissions = permissions, + Features = 0, + }; + + PlexUsers.Insert(m); + } + + } + private void ResetLogLevel() { var logSettings = Log.GetSettings(); diff --git a/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj b/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj index fa934dfc0..0b582b68b 100644 --- a/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj +++ b/PlexRequests.Core.Migration/PlexRequests.Core.Migration.csproj @@ -74,6 +74,14 @@ + + {95834072-A675-415D-AA8F-877C91623810} + PlexRequests.Api.Interfaces + + + {CB37A5F8-6DFC-4554-99D3-A42B502E4591} + PlexRequests.Api.Models + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} PlexRequests.Core diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 5f093a928..cf47610b6 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -134,6 +134,7 @@ + diff --git a/PlexRequests.Core/SettingModels/UserManagementSettings.cs b/PlexRequests.Core/SettingModels/UserManagementSettings.cs new file mode 100644 index 000000000..2e6d7bc0c --- /dev/null +++ b/PlexRequests.Core/SettingModels/UserManagementSettings.cs @@ -0,0 +1,38 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserManagementSettings.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.Core.SettingModels +{ + public class UserManagementSettings + { + public bool SearchForMovies { get; set; } + public bool SearchForTvShows { get; set; } + public bool SearchForMusic { get; set; } + public bool AutoApproveMovies { get; set; } + public bool AutoApproveTvShows { get; set; } + public bool AutoApproveMusic { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/UserManagementModule.cs b/PlexRequests.UI/Modules/UserManagementModule.cs index 9870d3533..0bfd73d65 100644 --- a/PlexRequests.UI/Modules/UserManagementModule.cs +++ b/PlexRequests.UI/Modules/UserManagementModule.cs @@ -33,6 +33,7 @@ namespace PlexRequests.UI.Modules PlexSettings = plex; UserLoginsRepo = userLogins; PlexUsersRepository = plexRepo; + PlexRequestSettings = pr; Get["/"] = x => Load(); @@ -51,6 +52,7 @@ namespace PlexRequests.UI.Modules private ISettingsService PlexSettings { get; } private IRepository UserLoginsRepo { get; } private IRepository PlexUsersRepository { get; } + private ISettingsService PlexRequestSettings { get; } private Negotiator Load() { From 2f34aaec4bef71956b2e6f1342d80f7352d66d35 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 16 Nov 2016 08:52:38 +0000 Subject: [PATCH 25/68] Fixed #681 --- PlexRequests.Core/SettingModels/UserManagementSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexRequests.Core/SettingModels/UserManagementSettings.cs b/PlexRequests.Core/SettingModels/UserManagementSettings.cs index 2e6d7bc0c..975eebcc3 100644 --- a/PlexRequests.Core/SettingModels/UserManagementSettings.cs +++ b/PlexRequests.Core/SettingModels/UserManagementSettings.cs @@ -26,7 +26,7 @@ #endregion namespace PlexRequests.Core.SettingModels { - public class UserManagementSettings + public class UserManagementSettings : Settings { public bool SearchForMovies { get; set; } public bool SearchForTvShows { get; set; } From cb3c3fe10e93614317ed19d6f1e5e89e701aafec Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 16 Nov 2016 09:55:05 +0000 Subject: [PATCH 26/68] Fixed potential crash #683 --- PlexRequests.Core.Migration/Migrations/Version1100.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index dbbfab158..616f0c3d9 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -96,6 +96,10 @@ namespace PlexRequests.Core.Migration.Migrations private void UpdatePlexUsers() { var settings = PlexSettings.GetSettings(); + if (string.IsNullOrEmpty(settings.PlexAuthToken)) + { + return; + } var plexUsers = PlexApi.GetUsers(settings.PlexAuthToken); var prSettings = PlexRequestSettings.GetSettings(); @@ -138,6 +142,7 @@ namespace PlexRequests.Core.Migration.Migrations PlexUserId = user.Id, Permissions = permissions, Features = 0, + UserAlias = string.Empty, }; PlexUsers.Insert(m); From 7412655c5ab31deead4e47434c2d02fd28925863 Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Wed, 16 Nov 2016 14:20:13 +0000 Subject: [PATCH 27/68] Lots of fixed and stuff. --- PlexRequests.Api/ApiRequest.cs | 3 +- PlexRequests.Core/UserMapper.cs | 26 - PlexRequests.Helpers/EnumHelper.cs | 5 + .../Permissions/Permissions.cs | 1 - .../PlexAvailabilityCheckerTests.cs | 566 +++++++++--------- PlexRequests.Services/Jobs/JobNames.cs | 1 + .../Jobs/PlexAvailabilityChecker.cs | 6 +- .../Jobs/PlexContentCacher.cs | 345 +++++++++++ PlexRequests.Services/Models/PlexAlbum.cs | 2 + PlexRequests.Services/Models/PlexMovie.cs | 1 + PlexRequests.Services/Models/PlexTvShow.cs | 1 + .../PlexRequests.Services.csproj | 1 + PlexRequests.Store/Models/Plex/PlexContent.cs | 53 ++ .../Models/Plex/PlexMediaType .cs | 35 ++ PlexRequests.Store/PlexRequests.Store.csproj | 2 + PlexRequests.Store/SqlTables.sql | 14 + PlexRequests.UI/Helpers/SecurityExtensions.cs | 7 +- PlexRequests.UI/Modules/LoginModule.cs | 3 +- PlexRequests.UI/Modules/UserWizardModule.cs | 3 +- 19 files changed, 758 insertions(+), 317 deletions(-) create mode 100644 PlexRequests.Services/Jobs/PlexContentCacher.cs create mode 100644 PlexRequests.Store/Models/Plex/PlexContent.cs create mode 100644 PlexRequests.Store/Models/Plex/PlexMediaType .cs diff --git a/PlexRequests.Api/ApiRequest.cs b/PlexRequests.Api/ApiRequest.cs index 8cb023ea6..8bf1a2721 100644 --- a/PlexRequests.Api/ApiRequest.cs +++ b/PlexRequests.Api/ApiRequest.cs @@ -104,8 +104,7 @@ namespace PlexRequests.Api } var result = DeserializeXml(response.Content); - return result; - } + return result;} public T ExecuteJson(IRestRequest request, Uri baseUri) where T : new() { diff --git a/PlexRequests.Core/UserMapper.cs b/PlexRequests.Core/UserMapper.cs index 9a0afc8de..50215093d 100644 --- a/PlexRequests.Core/UserMapper.cs +++ b/PlexRequests.Core/UserMapper.cs @@ -145,26 +145,6 @@ namespace PlexRequests.Core Repo.Delete(user); } - public Guid? CreateAdmin(string username, string password, UserProperties properties = null) - { - return CreateUser(username, password, properties); - } - - public Guid? CreatePowerUser(string username, string password, UserProperties properties = null) - { - return CreateUser(username, password, properties); - } - - public Guid? CreateRegularUser(string username, string password, UserProperties properties = null) - { - return CreateUser(username, password, properties); - } - - public IEnumerable GetAllClaims() - { - var properties = typeof(UserClaims).GetConstantsValues(); - return properties; - } public bool UpdatePassword(string username, string oldPassword, string newPassword) { @@ -207,11 +187,8 @@ namespace PlexRequests.Core public interface ICustomUserMapper { - Guid? CreateUser(string username, string password, UserProperties props); - Guid? CreateUser(string username, string password, int permissions, int features, UserProperties properties = null); - IEnumerable GetAllClaims(); IEnumerable GetUsers(); Task> GetUsersAsync(); UsersModel GetUser(Guid userId); @@ -219,9 +196,6 @@ namespace PlexRequests.Core bool DoUsersExist(); Guid? ValidateUser(string username, string password); bool UpdatePassword(string username, string oldPassword, string newPassword); - Guid? CreateAdmin(string username, string password, UserProperties properties = null); - Guid? CreatePowerUser(string username, string password, UserProperties properties = null); - Guid? CreateRegularUser(string username, string password, UserProperties properties = null); void DeleteUser(string userId); } } diff --git a/PlexRequests.Helpers/EnumHelper.cs b/PlexRequests.Helpers/EnumHelper.cs index 051c78508..387f867e9 100644 --- a/PlexRequests.Helpers/EnumHelper.cs +++ b/PlexRequests.Helpers/EnumHelper.cs @@ -108,5 +108,10 @@ namespace PlexRequests.Helpers throw new ArgumentOutOfRangeException(nameof(name)); } + + public static int All() + { + return Enum.GetValues(typeof(T)).Cast().Sum(); + } } } \ No newline at end of file diff --git a/PlexRequests.Helpers/Permissions/Permissions.cs b/PlexRequests.Helpers/Permissions/Permissions.cs index 8b1d22954..1afd34edb 100644 --- a/PlexRequests.Helpers/Permissions/Permissions.cs +++ b/PlexRequests.Helpers/Permissions/Permissions.cs @@ -59,6 +59,5 @@ namespace PlexRequests.Helpers.Permissions [Display(Name = "Auto Approve Album Requests")] AutoApproveAlbum = 256 - } } \ No newline at end of file diff --git a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs index 801af7509..1097bfb67 100644 --- a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs +++ b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs @@ -1,284 +1,284 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexAvailabilityCheckerTests.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Threading.Tasks; - -using Moq; - -using NUnit.Framework; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Plex; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; -using PlexRequests.Helpers; -using PlexRequests.Services.Jobs; -using PlexRequests.Services.Models; -using PlexRequests.Services.Notification; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; - -using Ploeh.AutoFixture; - -namespace PlexRequests.Services.Tests -{ - [TestFixture] - public class PlexAvailabilityCheckerTests - { - public IAvailabilityChecker Checker { get; set; } - private Fixture F { get; set; } = new Fixture(); - private Mock> SettingsMock { get; set; } - private Mock> AuthMock { get; set; } - private Mock RequestMock { get; set; } - private Mock PlexMock { get; set; } - private Mock CacheMock { get; set; } - private Mock NotificationMock { get; set; } - private Mock JobRec { get; set; } - private Mock> NotifyUsers { get; set; } - private Mock> PlexEpisodes { get; set; } - private Mock Engine - { - get; - set; - } - - [SetUp] - public void Setup() - { - SettingsMock = new Mock>(); - AuthMock = new Mock>(); - RequestMock = new Mock(); - PlexMock = new Mock(); - NotificationMock = new Mock(); - CacheMock = new Mock(); - NotifyUsers = new Mock>(); - PlexEpisodes = new Mock>(); - JobRec = new Mock(); - Engine = new Mock(); - Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object, Engine.Object); - - } - - [Test] - public void InvalidSettings() - { - Checker.CheckAndUpdateAll(); - PlexMock.Verify(x => x.GetLibrary(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetAccount(It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetLibrarySections(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetStatus(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [TestCaseSource(nameof(IsMovieAvailableTestData))] - public bool IsMovieAvailableTest(string title, string year) - { - var movies = new List - { - new PlexMovie {Title = title, ProviderId = null, ReleaseYear = year} - }; - var result = Checker.IsMovieAvailable(movies.ToArray(), "title", "2011"); - - return result; - } - - private static IEnumerable IsMovieAvailableTestData - { - get - { - yield return new TestCaseData("title", "2011").Returns(true).SetName("IsMovieAvailable True"); - yield return new TestCaseData("title2", "2011").Returns(false).SetName("IsMovieAvailable False different title"); - yield return new TestCaseData("title", "2001").Returns(false).SetName("IsMovieAvailable False different year"); - } - } - - - [TestCaseSource(nameof(IsMovieAvailableAdvancedTestData))] - public bool IsMovieAvailableAdvancedTest(string title, string year, string providerId) - { - var movies = new List - { - new PlexMovie {Title = title, ProviderId = providerId, ReleaseYear = year } - }; - var result = Checker.IsMovieAvailable(movies.ToArray(), "title", "2011", 9999.ToString()); - - return result; - } - - private static IEnumerable IsMovieAvailableAdvancedTestData - { - get - { - yield return new TestCaseData("title", "2011", "9999").Returns(true).SetName("Advanced IsMovieAvailable True"); - yield return new TestCaseData("title2", "2011", "99929").Returns(false).SetName("Advanced IsMovieAvailable False different title"); - yield return new TestCaseData("title", "2001", "99939").Returns(false).SetName("Advanced IsMovieAvailable False different year"); - yield return new TestCaseData("title", "2001", "44445").Returns(false).SetName("Advanced IsMovieAvailable False different providerID"); - } - } - - [TestCaseSource(nameof(IsTvAvailableTestData))] - public bool IsTvAvailableTest(string title, string year) - { - var tv = new List - { - new PlexTvShow {Title = title, ProviderId = null, ReleaseYear = year} - }; - var result = Checker.IsTvShowAvailable(tv.ToArray(), "title", "2011"); - - return result; - } - - private static IEnumerable IsTvAvailableTestData - { - get - { - yield return new TestCaseData("title", "2011").Returns(true).SetName("IsTvAvailable True"); - yield return new TestCaseData("title2", "2011").Returns(false).SetName("IsTvAvailable False different title"); - yield return new TestCaseData("title", "2001").Returns(false).SetName("IsTvAvailable False different year"); - } - } - - [TestCaseSource(nameof(IsTvAvailableAdvancedTestData))] - public bool IsTvAvailableAdvancedTest(string title, string year, string providerId) - { - var movies = new List - { - new PlexTvShow {Title = title, ProviderId = providerId, ReleaseYear = year } - }; - var result = Checker.IsTvShowAvailable(movies.ToArray(), "title", "2011", 9999.ToString()); - - return result; - } - - private static IEnumerable IsTvAvailableAdvancedTestData - { - get - { - yield return new TestCaseData("title", "2011", "9999").Returns(true).SetName("Advanced IsTvAvailable True"); - yield return new TestCaseData("title2", "2011", "99929").Returns(false).SetName("Advanced IsTvAvailable False different title"); - yield return new TestCaseData("title", "2001", "99939").Returns(false).SetName("Advanced IsTvAvailable False different year"); - yield return new TestCaseData("title", "2001", "44445").Returns(false).SetName("Advanced IsTvAvailable False different providerID"); - } - } - - [TestCaseSource(nameof(IsTvAvailableAdvancedSeasonsTestData))] - public bool IsTvAvailableAdvancedSeasonsTest(string title, string year, string providerId, int[] seasons) - { - var movies = new List - { - new PlexTvShow {Title = title, ProviderId = providerId, ReleaseYear = year , Seasons = seasons} - }; - var result = Checker.IsTvShowAvailable(movies.ToArray(), "title", "2011", 9999.ToString(), new[] { 1, 2, 3 }); - - return result; - } - - private static IEnumerable IsTvAvailableAdvancedSeasonsTestData - { - get - { - yield return new TestCaseData("title", "2011", "9999", new[] { 1, 2, 3 }).Returns(true).SetName("Advanced IsTvSeasonsAvailable True"); - yield return new TestCaseData("title2", "2011", "99929", new[] { 5, 6 }).Returns(false).SetName("Advanced IsTvSeasonsAvailable False no seasons"); - yield return new TestCaseData("title2", "2011", "9999", new[] { 1, 6 }).Returns(true).SetName("Advanced IsTvSeasonsAvailable true one season"); - } - } - - [TestCaseSource(nameof(IsEpisodeAvailableTestData))] - public bool IsEpisodeAvailableTest(string providerId, int season, int episode) - { - var expected = new List - { - new PlexEpisodes {EpisodeNumber = 1, ShowTitle = "The Flash",ProviderId = 23.ToString(), SeasonNumber = 1, EpisodeTitle = "Pilot"} - }; - PlexEpisodes.Setup(x => x.Custom(It.IsAny>>())).Returns(expected); +//#region Copyright +//// /************************************************************************ +//// Copyright (c) 2016 Jamie Rees +//// File: PlexAvailabilityCheckerTests.cs +//// Created By: Jamie Rees +//// +//// Permission is hereby granted, free of charge, to any person obtaining +//// a copy of this software and associated documentation files (the +//// "Software"), to deal in the Software without restriction, including +//// without limitation the rights to use, copy, modify, merge, publish, +//// distribute, sublicense, and/or sell copies of the Software, and to +//// permit persons to whom the Software is furnished to do so, subject to +//// the following conditions: +//// +//// The above copyright notice and this permission notice shall be +//// included in all copies or substantial portions of the Software. +//// +//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +//// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +//// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +//// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +//// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +//// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +//// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//// ************************************************************************/ +//#endregion +//using System; +//using System.Collections.Generic; +//using System.Data; +//using System.Linq; +//using System.Threading.Tasks; + +//using Moq; + +//using NUnit.Framework; + +//using PlexRequests.Api.Interfaces; +//using PlexRequests.Api.Models.Plex; +//using PlexRequests.Core; +//using PlexRequests.Core.SettingModels; +//using PlexRequests.Services.Interfaces; +//using PlexRequests.Helpers; +//using PlexRequests.Services.Jobs; +//using PlexRequests.Services.Models; +//using PlexRequests.Services.Notification; +//using PlexRequests.Store.Models; +//using PlexRequests.Store.Repository; + +//using Ploeh.AutoFixture; + +//namespace PlexRequests.Services.Tests +//{ +// [TestFixture] +// public class PlexAvailabilityCheckerTests +// { +// public IAvailabilityChecker Checker { get; set; } +// private Fixture F { get; set; } = new Fixture(); +// private Mock> SettingsMock { get; set; } +// private Mock> AuthMock { get; set; } +// private Mock RequestMock { get; set; } +// private Mock PlexMock { get; set; } +// private Mock CacheMock { get; set; } +// private Mock NotificationMock { get; set; } +// private Mock JobRec { get; set; } +// private Mock> NotifyUsers { get; set; } +// private Mock> PlexEpisodes { get; set; } +// private Mock Engine +// { +// get; +// set; +// } + +// [SetUp] +// public void Setup() +// { +// SettingsMock = new Mock>(); +// AuthMock = new Mock>(); +// RequestMock = new Mock(); +// PlexMock = new Mock(); +// NotificationMock = new Mock(); +// CacheMock = new Mock(); +// NotifyUsers = new Mock>(); +// PlexEpisodes = new Mock>(); +// JobRec = new Mock(); +// Engine = new Mock(); +// Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object, Engine.Object); + +// } + +// [Test] +// public void InvalidSettings() +// { +// Checker.CheckAndUpdateAll(); +// PlexMock.Verify(x => x.GetLibrary(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); +// PlexMock.Verify(x => x.GetAccount(It.IsAny()), Times.Never); +// PlexMock.Verify(x => x.GetLibrarySections(It.IsAny(), It.IsAny()), Times.Never); +// PlexMock.Verify(x => x.GetStatus(It.IsAny(), It.IsAny()), Times.Never); +// PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); +// } + +// [TestCaseSource(nameof(IsMovieAvailableTestData))] +// public bool IsMovieAvailableTest(string title, string year) +// { +// var movies = new List +// { +// new PlexMovie {Title = title, ProviderId = null, ReleaseYear = year} +// }; +// var result = Checker.IsMovieAvailable(movies.ToArray(), "title", "2011"); + +// return result; +// } + +// private static IEnumerable IsMovieAvailableTestData +// { +// get +// { +// yield return new TestCaseData("title", "2011").Returns(true).SetName("IsMovieAvailable True"); +// yield return new TestCaseData("title2", "2011").Returns(false).SetName("IsMovieAvailable False different title"); +// yield return new TestCaseData("title", "2001").Returns(false).SetName("IsMovieAvailable False different year"); +// } +// } + + +// [TestCaseSource(nameof(IsMovieAvailableAdvancedTestData))] +// public bool IsMovieAvailableAdvancedTest(string title, string year, string providerId) +// { +// var movies = new List +// { +// new PlexMovie {Title = title, ProviderId = providerId, ReleaseYear = year } +// }; +// var result = Checker.IsMovieAvailable(movies.ToArray(), "title", "2011", 9999.ToString()); + +// return result; +// } + +// private static IEnumerable IsMovieAvailableAdvancedTestData +// { +// get +// { +// yield return new TestCaseData("title", "2011", "9999").Returns(true).SetName("Advanced IsMovieAvailable True"); +// yield return new TestCaseData("title2", "2011", "99929").Returns(false).SetName("Advanced IsMovieAvailable False different title"); +// yield return new TestCaseData("title", "2001", "99939").Returns(false).SetName("Advanced IsMovieAvailable False different year"); +// yield return new TestCaseData("title", "2001", "44445").Returns(false).SetName("Advanced IsMovieAvailable False different providerID"); +// } +// } + +// [TestCaseSource(nameof(IsTvAvailableTestData))] +// public bool IsTvAvailableTest(string title, string year) +// { +// var tv = new List +// { +// new PlexTvShow {Title = title, ProviderId = null, ReleaseYear = year} +// }; +// var result = Checker.IsTvShowAvailable(tv.ToArray(), "title", "2011"); + +// return result; +// } + +// private static IEnumerable IsTvAvailableTestData +// { +// get +// { +// yield return new TestCaseData("title", "2011").Returns(true).SetName("IsTvAvailable True"); +// yield return new TestCaseData("title2", "2011").Returns(false).SetName("IsTvAvailable False different title"); +// yield return new TestCaseData("title", "2001").Returns(false).SetName("IsTvAvailable False different year"); +// } +// } + +// [TestCaseSource(nameof(IsTvAvailableAdvancedTestData))] +// public bool IsTvAvailableAdvancedTest(string title, string year, string providerId) +// { +// var movies = new List +// { +// new PlexTvShow {Title = title, ProviderId = providerId, ReleaseYear = year } +// }; +// var result = Checker.IsTvShowAvailable(movies.ToArray(), "title", "2011", 9999.ToString()); + +// return result; +// } + +// private static IEnumerable IsTvAvailableAdvancedTestData +// { +// get +// { +// yield return new TestCaseData("title", "2011", "9999").Returns(true).SetName("Advanced IsTvAvailable True"); +// yield return new TestCaseData("title2", "2011", "99929").Returns(false).SetName("Advanced IsTvAvailable False different title"); +// yield return new TestCaseData("title", "2001", "99939").Returns(false).SetName("Advanced IsTvAvailable False different year"); +// yield return new TestCaseData("title", "2001", "44445").Returns(false).SetName("Advanced IsTvAvailable False different providerID"); +// } +// } + +// [TestCaseSource(nameof(IsTvAvailableAdvancedSeasonsTestData))] +// public bool IsTvAvailableAdvancedSeasonsTest(string title, string year, string providerId, int[] seasons) +// { +// var movies = new List +// { +// new PlexTvShow {Title = title, ProviderId = providerId, ReleaseYear = year , Seasons = seasons} +// }; +// var result = Checker.IsTvShowAvailable(movies.ToArray(), "title", "2011", 9999.ToString(), new[] { 1, 2, 3 }); + +// return result; +// } + +// private static IEnumerable IsTvAvailableAdvancedSeasonsTestData +// { +// get +// { +// yield return new TestCaseData("title", "2011", "9999", new[] { 1, 2, 3 }).Returns(true).SetName("Advanced IsTvSeasonsAvailable True"); +// yield return new TestCaseData("title2", "2011", "99929", new[] { 5, 6 }).Returns(false).SetName("Advanced IsTvSeasonsAvailable False no seasons"); +// yield return new TestCaseData("title2", "2011", "9999", new[] { 1, 6 }).Returns(true).SetName("Advanced IsTvSeasonsAvailable true one season"); +// } +// } + +// [TestCaseSource(nameof(IsEpisodeAvailableTestData))] +// public bool IsEpisodeAvailableTest(string providerId, int season, int episode) +// { +// var expected = new List +// { +// new PlexEpisodes {EpisodeNumber = 1, ShowTitle = "The Flash",ProviderId = 23.ToString(), SeasonNumber = 1, EpisodeTitle = "Pilot"} +// }; +// PlexEpisodes.Setup(x => x.Custom(It.IsAny>>())).Returns(expected); - var result = Checker.IsEpisodeAvailable(providerId, season, episode); - - return result; - } - - private static IEnumerable IsEpisodeAvailableTestData - { - get - { - yield return new TestCaseData("23", 1, 1).Returns(true).SetName("IsEpisodeAvailable True S01E01"); - yield return new TestCaseData("23", 1, 2).Returns(false).SetName("IsEpisodeAvailable False S01E02"); - yield return new TestCaseData("23", 99, 99).Returns(false).SetName("IsEpisodeAvailable False S99E99"); - yield return new TestCaseData("230", 99, 99).Returns(false).SetName("IsEpisodeAvailable False Incorrect ProviderId"); - } - } - - [Test] - public void GetPlexMoviesTests() - { - var cachedMovies = F.Build().Without(x => x.Directory).CreateMany().ToList(); - cachedMovies.Add(new PlexSearch - { - Video = new List
    From 63c27443363ca2b158c29a86427b76391de9943b Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 18 Nov 2016 09:46:04 +0000 Subject: [PATCH 35/68] Added loading spinner --- .../Angular/angular-loading-spinner.js | 25 +++ .../Content/Angular/angular-spinner.min.js | 2 + PlexRequests.UI/Content/app/app.js | 2 +- PlexRequests.UI/Content/spin.min.js | 2 + PlexRequests.UI/Helpers/BaseUrlHelper.cs | 6 +- .../Views/UserManagement/Index.cshtml | 163 +++++++++--------- 6 files changed, 117 insertions(+), 83 deletions(-) create mode 100644 PlexRequests.UI/Content/Angular/angular-loading-spinner.js create mode 100644 PlexRequests.UI/Content/Angular/angular-spinner.min.js create mode 100644 PlexRequests.UI/Content/spin.min.js diff --git a/PlexRequests.UI/Content/Angular/angular-loading-spinner.js b/PlexRequests.UI/Content/Angular/angular-loading-spinner.js new file mode 100644 index 000000000..c25717c81 --- /dev/null +++ b/PlexRequests.UI/Content/Angular/angular-loading-spinner.js @@ -0,0 +1,25 @@ +(function(){ + angular.module('ngLoadingSpinner', ['angularSpinner']) + .directive('usSpinner', ['$http', '$rootScope' ,function ($http, $rootScope){ + return { + link: function (scope, elm, attrs) + { + $rootScope.spinnerActive = false; + scope.isLoading = function () { + return $http.pendingRequests.length > 0; + }; + + scope.$watch(scope.isLoading, function (loading) + { + $rootScope.spinnerActive = loading; + if(loading){ + elm.removeClass('ng-hide'); + }else{ + elm.addClass('ng-hide'); + } + }); + } + }; + + }]); +}).call(this); \ No newline at end of file diff --git a/PlexRequests.UI/Content/Angular/angular-spinner.min.js b/PlexRequests.UI/Content/Angular/angular-spinner.min.js new file mode 100644 index 000000000..ef727fe75 --- /dev/null +++ b/PlexRequests.UI/Content/Angular/angular-spinner.min.js @@ -0,0 +1,2 @@ +!function(a){"use strict";function b(a,b){a.module("angularSpinner",[]).factory("usSpinnerService",["$rootScope",function(a){var b={};return b.spin=function(b){a.$broadcast("us-spinner:spin",b)},b.stop=function(b){a.$broadcast("us-spinner:stop",b)},b}]).directive("usSpinner",["$window",function(c){return{scope:!0,link:function(d,e,f){function g(){d.spinner&&d.spinner.stop()}var h=b||c.Spinner;d.spinner=null,d.key=a.isDefined(f.spinnerKey)?f.spinnerKey:!1,d.startActive=a.isDefined(f.spinnerStartActive)?f.spinnerStartActive:d.key?!1:!0,d.spin=function(){d.spinner&&d.spinner.spin(e[0])},d.stop=function(){d.startActive=!1,g()},d.$watch(f.usSpinner,function(a){g(),d.spinner=new h(a),(!d.key||d.startActive)&&d.spinner.spin(e[0])},!0),d.$on("us-spinner:spin",function(a,b){b===d.key&&d.spin()}),d.$on("us-spinner:stop",function(a,b){b===d.key&&d.stop()}),d.$on("$destroy",function(){d.stop(),d.spinner=null})}}}])}"function"==typeof define&&define.amd?define(["angular","spin"],b):b(a.angular)}(window); +//# sourceMappingURL=angular-spinner.min.js.map \ No newline at end of file diff --git a/PlexRequests.UI/Content/app/app.js b/PlexRequests.UI/Content/app/app.js index af97fb710..197a8a7f6 100644 --- a/PlexRequests.UI/Content/app/app.js +++ b/PlexRequests.UI/Content/app/app.js @@ -1,4 +1,4 @@ (function() { - module = angular.module('PlexRequests', []); + module = angular.module('PlexRequests', ['ngLoadingSpinner']); module.constant("moment", moment); }()); \ No newline at end of file diff --git a/PlexRequests.UI/Content/spin.min.js b/PlexRequests.UI/Content/spin.min.js new file mode 100644 index 000000000..aa2b9c3df --- /dev/null +++ b/PlexRequests.UI/Content/spin.min.js @@ -0,0 +1,2 @@ +// http://spin.js.org/#v2.3.2 +!function(a,b){"object"==typeof module&&module.exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return m[e]||(k.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",k.cssRules.length),m[e]=1),e}function d(a,b){var c,d,e=a.style;if(b=b.charAt(0).toUpperCase()+b.slice(1),void 0!==e[b])return b;for(d=0;d',c)}k.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.scale*d.width,left:d.scale*d.radius,top:-d.scale*d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.scale*(d.length+d.width),k=2*d.scale*j,l=-(d.width+d.length)*d.scale*2+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k", $"", - $"", + //$"", $"", $"", $"", @@ -224,7 +224,9 @@ namespace PlexRequests.UI.Helpers sb.Append($""); sb.Append($""); sb.Append($""); - + sb.Append($""); + sb.Append($""); + sb.Append($""); return helper.Raw(sb.ToString()); } diff --git a/PlexRequests.UI/Views/UserManagement/Index.cshtml b/PlexRequests.UI/Views/UserManagement/Index.cshtml index eb01aa8c9..cfd0f12ec 100644 --- a/PlexRequests.UI/Views/UserManagement/Index.cshtml +++ b/PlexRequests.UI/Views/UserManagement/Index.cshtml @@ -3,11 +3,14 @@ @Html.LoadUserManagementAssets()
    + + Spinner Active: + {{spinnerActive}}