From 8e004cea2e62f9e244f14eec4769d934fd76e968 Mon Sep 17 00:00:00 2001 From: Drewster727 Date: Tue, 21 Jun 2016 10:00:22 -0500 Subject: [PATCH] workaround for #334 --- .../Movie/CouchPotatoAdd.cs | 200 +- PlexRequests.Api/CouchPotatoApi.cs | 330 +-- PlexRequests.Core/Setup.cs | 490 ++--- PlexRequests.UI/Modules/AdminModule.cs | 1764 ++++++++--------- README.md | 116 +- 5 files changed, 1452 insertions(+), 1448 deletions(-) diff --git a/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs b/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs index adad93c74..21c222974 100644 --- a/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs +++ b/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs @@ -1,100 +1,100 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PlexRequests.Api.Models.Movie -{ - - public class CouchPotatoAdd - { - public Movie movie { get; set; } - public bool success { get; set; } - } - public class Rating - { - public List imdb { get; set; } - } - - - - public class Images - { - public List disc_art { get; set; } - public List poster { get; set; } - public List backdrop { get; set; } - public List extra_thumbs { get; set; } - public List poster_original { get; set; } - public List actors { get; set; } - public List backdrop_original { get; set; } - public List clear_art { get; set; } - public List logo { get; set; } - public List banner { get; set; } - public List landscape { get; set; } - public List extra_fanart { get; set; } - } - - public class Info - { - public Rating rating { get; set; } - public List genres { get; set; } - public int tmdb_id { get; set; } - public string plot { get; set; } - public string tagline { get; set; } - public Release_Date release_date { get; set; } - public int year { get; set; } - public string original_title { get; set; } - public List actor_roles { get; set; } - public bool via_imdb { get; set; } - public Images images { get; set; } - public List directors { get; set; } - public List titles { get; set; } - public string imdb { get; set; } - public string mpaa { get; set; } - public bool via_tmdb { get; set; } - public List actors { get; set; } - public List writers { get; set; } - public int runtime { get; set; } - public string type { get; set; } - public string released { get; set; } - } - - public class Release_Date - { - public int dvd { get; set; } - public int expires { get; set; } - public int theater { get; set; } - public bool bluray { get; set; } - } - - public class Files - { - public List image_poster { get; set; } - } - - public class Identifiers - { - public string imdb { get; set; } - } - - public class Movie - { - public string status { get; set; } - public Info info { get; set; } - public string _t { get; set; } - public List releases { get; set; } - public string title { get; set; } - public string _rev { get; set; } - public string profile_id { get; set; } - public string _id { get; set; } - public List tags { get; set; } - public int last_edit { get; set; } - public object category_id { get; set; } - public string type { get; set; } - public Files files { get; set; } - public Identifiers identifiers { 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 CouchPotatoAdd + { + public Movie movie { get; set; } + public bool success { get; set; } + } + public class Rating + { + public List imdb { get; set; } + } + + + + public class Images + { + public List disc_art { get; set; } + public List poster { get; set; } + public List backdrop { get; set; } + public List extra_thumbs { get; set; } + public List poster_original { get; set; } + public List actors { get; set; } + public List backdrop_original { get; set; } + public List clear_art { get; set; } + public List logo { get; set; } + public List banner { get; set; } + public List landscape { get; set; } + public List extra_fanart { get; set; } + } + + public class Info + { + public Rating rating { get; set; } + public List genres { get; set; } + public int tmdb_id { get; set; } + public string plot { get; set; } + public string tagline { get; set; } + public Release_Date release_date { get; set; } + public int year { get; set; } + public string original_title { get; set; } + public List actor_roles { get; set; } + public bool via_imdb { get; set; } + public Images images { get; set; } + public List directors { get; set; } + public List titles { get; set; } + public string imdb { get; set; } + public string mpaa { get; set; } + public bool via_tmdb { get; set; } + public List actors { get; set; } + public List writers { get; set; } + public int runtime { get; set; } + public string type { get; set; } + public string released { get; set; } + } + + public class Release_Date + { + public int dvd { get; set; } + public int expires { get; set; } + public int theater { get; set; } + public bool bluray { get; set; } + } + + public class Files + { + public List image_poster { get; set; } + } + + public class Identifiers + { + public string imdb { get; set; } + } + + public class Movie + { + public string status { get; set; } + public Info info { get; set; } + public string _t { get; set; } + public List releases { get; set; } + public string title { get; set; } + public string _rev { get; set; } + public string profile_id { get; set; } + public string _id { get; set; } + public List tags { get; set; } + public int last_edit { get; set; } + public object category_id { get; set; } + public string type { get; set; } + public Files files { get; set; } + public Identifiers identifiers { get; set; } + } + + +} diff --git a/PlexRequests.Api/CouchPotatoApi.cs b/PlexRequests.Api/CouchPotatoApi.cs index 4ec0356c5..b7118185a 100644 --- a/PlexRequests.Api/CouchPotatoApi.cs +++ b/PlexRequests.Api/CouchPotatoApi.cs @@ -1,164 +1,168 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: CouchPotatoApi.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.Linq; - -using NLog; -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Movie; -using PlexRequests.Helpers.Exceptions; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class CouchPotatoApi : ICouchPotatoApi - { - public CouchPotatoApi() - { - Api = new ApiRequest(); - } - private ApiRequest Api { get; set; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileId = default(string)) - { - RestRequest request; - request = string.IsNullOrEmpty(profileId) - ? new RestRequest {Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}"} - : new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}&profile_id={profileId}" }; - - if (!string.IsNullOrEmpty(profileId)) - { - request.AddUrlSegment("profileId", profileId); - } - - request.AddUrlSegment("apikey", apiKey); - request.AddUrlSegment("imdbid", imdbid); - request.AddUrlSegment("title", title); - - var obj = RetryHandler.Execute(() => Api.ExecuteJson (request, baseUrl),new[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10)}, - (exception, timespan) => Log.Error (exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan)); - - Log.Trace("CP movie Add result count {0}", obj.Count); - - if (obj.Count > 0) - { - try - { - Log.Trace("CP movie obj[\"success\"] = {0}", obj["success"]); - var result = (bool)obj["success"]; - Log.Trace("CP movie Add result {0}", result); - return result; - } - catch (Exception e) - { - Log.Fatal(e); - return false; - } - } - return false; - } - - /// - /// Gets the status. - /// - /// The URL. - /// The API key. - /// - public CouchPotatoStatus GetStatus(Uri url, string apiKey) - { - Log.Trace("Getting CP Status, ApiKey = {0}", apiKey); - var request = new RestRequest - { - Resource = "api/{apikey}/app.available/", - Method = Method.GET - }; - - request.AddUrlSegment("apikey", apiKey); - - - var obj = RetryHandler.Execute(() => Api.Execute (request, url),new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10)}, - (exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan)); - - return obj; - } - - public CouchPotatoProfiles GetProfiles(Uri url, string apiKey) - { - Log.Trace("Getting CP Profiles, ApiKey = {0}", apiKey); - var request = new RestRequest - { - Resource = "api/{apikey}/profile.list/", - Method = Method.GET - }; - - request.AddUrlSegment("apikey", apiKey); - - var obj = RetryHandler.Execute(() => Api.Execute (request, url),null, - (exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan)); - - return obj; - } - - public CouchPotatoMovies GetMovies(Uri baseUrl, string apiKey, string[] status) - { - var request = new RestRequest - { - Resource = "/api/{apikey}/movie.list?status={status}" - }; - - request.AddUrlSegment("apikey", apiKey); - request.AddUrlSegment("status", string.Join(",", status)); - try - { - var obj = RetryHandler.Execute(() => Api.Execute (request, baseUrl), - new[] { - TimeSpan.FromSeconds (5), - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30) - }, - (exception, timespan) => Log.Error (exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan)); - - return obj; - } - catch (Exception e) // Request error is already logged in the ApiRequest class - { - Log.Error("Error when attempting to GetMovies."); - Log.Error (e); - return new CouchPotatoMovies(); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CouchPotatoApi.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.Linq; + +using NLog; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Movie; +using PlexRequests.Helpers.Exceptions; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class CouchPotatoApi : ICouchPotatoApi + { + public CouchPotatoApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; set; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileId = default(string)) + { + RestRequest request; + request = string.IsNullOrEmpty(profileId) + ? new RestRequest {Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}"} + : new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}&profile_id={profileId}" }; + + if (!string.IsNullOrEmpty(profileId)) + { + request.AddUrlSegment("profileId", profileId); + } + + request.AddUrlSegment("apikey", apiKey); + request.AddUrlSegment("imdbid", imdbid); + request.AddUrlSegment("title", title); + + var obj = RetryHandler.Execute(() => Api.ExecuteJson (request, baseUrl),new[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10)}, + (exception, timespan) => Log.Error (exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan)); + + Log.Trace("CP movie Add result count {0}", obj.Count); + + if (obj.Count > 0) + { + try + { + Log.Trace("CP movie obj[\"success\"] = {0}", obj["success"]); + var result = (bool)obj["success"]; + Log.Trace("CP movie Add result {0}", result); + return result; + } + catch (Exception e) + { + Log.Fatal(e); + return false; + } + } + return false; + } + + /// + /// Gets the status. + /// + /// The URL. + /// The API key. + /// + public CouchPotatoStatus GetStatus(Uri url, string apiKey) + { + Log.Trace("Getting CP Status, ApiKey = {0}", apiKey); + var request = new RestRequest + { + Resource = "api/{apikey}/app.available/", + Method = Method.GET + }; + + request.AddUrlSegment("apikey", apiKey); + + + var obj = RetryHandler.Execute(() => Api.Execute (request, url),new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10)}, + (exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan)); + + return obj; + } + + public CouchPotatoProfiles GetProfiles(Uri url, string apiKey) + { + Log.Trace("Getting CP Profiles, ApiKey = {0}", apiKey); + var request = new RestRequest + { + Resource = "api/{apikey}/profile.list/", + Method = Method.GET + }; + + request.AddUrlSegment("apikey", apiKey); + + var obj = RetryHandler.Execute(() => Api.Execute (request, url),null, + (exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan)); + + return obj; + } + + public CouchPotatoMovies GetMovies(Uri baseUrl, string apiKey, string[] status) + { + var request = new RestRequest + { + Resource = "/api/{apikey}/movie.list?status={status}", + OnBeforeDeserialization = (x => + { + x.Content = x.Content.Replace("[]", "{}"); + }) + }; + + request.AddUrlSegment("apikey", apiKey); + request.AddUrlSegment("status", string.Join(",", status)); + try + { + var obj = RetryHandler.Execute(() => Api.Execute (request, baseUrl), + new[] { + TimeSpan.FromSeconds (5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30) + }, + (exception, timespan) => Log.Error (exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan)); + + return obj; + } + catch (Exception e) // Request error is already logged in the ApiRequest class + { + Log.Error("Error when attempting to GetMovies."); + Log.Error (e); + return new CouchPotatoMovies(); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index 33682d7b3..85a72d7f9 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -1,245 +1,245 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: Setup.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.Text.RegularExpressions; - -using Mono.Data.Sqlite; -using NLog; -using PlexRequests.Api; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Store; -using PlexRequests.Store.Repository; -using System.Threading.Tasks; - -namespace PlexRequests.Core -{ - public class Setup - { - private static Logger Log = LogManager.GetCurrentClassLogger(); - private static DbConfiguration Db { get; set; } - public string SetupDb(string urlBase) - { - Db = new DbConfiguration(new SqliteFactory()); - var created = Db.CheckDb(); - TableCreation.CreateTables(Db.DbConnection()); - - if (created) - { - CreateDefaultSettingsPage(urlBase); - } - - var version = CheckSchema(); - if (version > 0) - { - if (version > 1700 && version <= 1799) - { - MigrateToVersion1700(); - } - if (version > 1799 && version <= 1800) - { - MigrateToVersion1800(); - } - } - - 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 - { - RequireTvShowApproval = true, - RequireMovieApproval = true, - SearchForMovies = true, - SearchForTvShows = true, - WeeklyRequestLimit = 0, - BaseUrl = baseUrl ?? string.Empty, - CollectAnalyticData = true, - }; - var s = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); - s.SaveSettings(defaultSettings); - } - - public void CacheQualityProfiles() - { - var mc = new MemoryCacheProvider(); - - try - { - 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) - { - Log.Error("Failed to cache quality profiles on startup!"); - } - } - - private void CacheSonarrQualityProfiles(MemoryCacheProvider 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 sonarrSettings = sonarrSettingsService.GetSettings(); - if (sonarrSettings.Enabled) - { - Log.Info("Begin executing GetProfiles call to Sonarr for quality profiles"); - SonarrApi sonarrApi = new SonarrApi(); - var profiles = sonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri); - cacheProvider.Set(CacheKeys.SonarrQualityProfiles, profiles); - Log.Info("Finished executing GetProfiles call to Sonarr for quality profiles"); - } - } - catch (Exception ex) - { - Log.Error(ex, "Failed to cache Sonarr quality profiles!"); - } - } - - private void CacheCouchPotatoQualityProfiles(MemoryCacheProvider 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 cpSettings = cpSettingsService.GetSettings(); - if (cpSettings.Enabled) - { - Log.Info("Begin executing GetProfiles call to CouchPotato for quality profiles"); - CouchPotatoApi cpApi = new CouchPotatoApi(); - var profiles = cpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey); - cacheProvider.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); - Log.Info("Finished executing GetProfiles call to CouchPotato for quality profiles"); - } - } - catch (Exception ex) - { - Log.Error(ex, "Failed to cache CouchPotato quality profiles!"); - } - } - public void MigrateToVersion1700() - { - // Drop old tables - TableCreation.DropTable(Db.DbConnection(), "User"); - TableCreation.DropTable(Db.DbConnection(), "Log"); - } - - /// - /// Migrates to version 1.8. - /// This includes updating the admin account to have all roles. - /// Set the log level to Error - /// Enable Analytics by default - /// - private void MigrateToVersion1800() - { - - // Give admin all roles/claims - try - { - var userMapper = new UserMapper(new UserRepository(Db, new MemoryCacheProvider())); - var users = userMapper.GetUsers(); - - foreach (var u in users) - { - var claims = new[] { UserClaims.User, UserClaims.Admin, UserClaims.PowerUser }; - u.Claims = ByteConverterHelper.ReturnBytes(claims); - - userMapper.EditUser(u); - } - } - catch (Exception e) - { - Log.Error(e); - } - - - // Set 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); - } - - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Setup.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.Text.RegularExpressions; + +using Mono.Data.Sqlite; +using NLog; +using PlexRequests.Api; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Store; +using PlexRequests.Store.Repository; +using System.Threading.Tasks; + +namespace PlexRequests.Core +{ + public class Setup + { + private static Logger Log = LogManager.GetCurrentClassLogger(); + private static DbConfiguration Db { get; set; } + public string SetupDb(string urlBase) + { + Db = new DbConfiguration(new SqliteFactory()); + var created = Db.CheckDb(); + TableCreation.CreateTables(Db.DbConnection()); + + if (created) + { + CreateDefaultSettingsPage(urlBase); + } + + var version = CheckSchema(); + if (version > 0) + { + if (version > 1700 && version <= 1799) + { + MigrateToVersion1700(); + } + if (version > 1799 && version <= 1800) + { + MigrateToVersion1800(); + } + } + + 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 + { + RequireTvShowApproval = true, + RequireMovieApproval = true, + SearchForMovies = true, + SearchForTvShows = true, + WeeklyRequestLimit = 0, + BaseUrl = baseUrl ?? string.Empty, + CollectAnalyticData = true, + }; + var s = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + s.SaveSettings(defaultSettings); + } + + public void CacheQualityProfiles() + { + var mc = new MemoryCacheProvider(); + + try + { + 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) + { + Log.Error("Failed to cache quality profiles on startup!"); + } + } + + private void CacheSonarrQualityProfiles(MemoryCacheProvider 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 sonarrSettings = sonarrSettingsService.GetSettings(); + if (sonarrSettings.Enabled) + { + Log.Info("Begin executing GetProfiles call to Sonarr for quality profiles"); + SonarrApi sonarrApi = new SonarrApi(); + var profiles = sonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri); + cacheProvider.Set(CacheKeys.SonarrQualityProfiles, profiles); + Log.Info("Finished executing GetProfiles call to Sonarr for quality profiles"); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to cache Sonarr quality profiles!"); + } + } + + private void CacheCouchPotatoQualityProfiles(MemoryCacheProvider 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 cpSettings = cpSettingsService.GetSettings(); + if (cpSettings.Enabled) + { + Log.Info("Begin executing GetProfiles call to CouchPotato for quality profiles"); + CouchPotatoApi cpApi = new CouchPotatoApi(); + var profiles = cpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey); + cacheProvider.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); + Log.Info("Finished executing GetProfiles call to CouchPotato for quality profiles"); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to cache CouchPotato quality profiles!"); + } + } + public void MigrateToVersion1700() + { + // Drop old tables + TableCreation.DropTable(Db.DbConnection(), "User"); + TableCreation.DropTable(Db.DbConnection(), "Log"); + } + + /// + /// Migrates to version 1.8. + /// This includes updating the admin account to have all roles. + /// Set the log level to Error + /// Enable Analytics by default + /// + private void MigrateToVersion1800() + { + + // Give admin all roles/claims + try + { + var userMapper = new UserMapper(new UserRepository(Db, new MemoryCacheProvider())); + var users = userMapper.GetUsers(); + + foreach (var u in users) + { + var claims = new[] { UserClaims.User, UserClaims.Admin, UserClaims.PowerUser }; + u.Claims = ByteConverterHelper.ReturnBytes(claims); + + userMapper.EditUser(u); + } + } + catch (Exception e) + { + Log.Error(e); + } + + + // Set 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); + } + + } + } +} diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 0cdebf427..c22734b9f 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -1,883 +1,883 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AdminModule.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 System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Net; - -using Nancy; -using Nancy.Extensions; -using Nancy.ModelBinding; -using Nancy.Responses.Negotiation; -using Nancy.Validation; -using Nancy.Json; -using Nancy.Security; -using NLog; - -using MarkdownSharp; - -using PlexRequests.Api; -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Helpers.Exceptions; -using PlexRequests.Services.Interfaces; -using PlexRequests.Services.Notification; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; -using PlexRequests.UI.Helpers; -using PlexRequests.UI.Models; - - -namespace PlexRequests.UI.Modules -{ - public class AdminModule : BaseModule - { - private ISettingsService PrService { get; } - private ISettingsService CpService { get; } - private ISettingsService AuthService { get; } - private ISettingsService PlexService { get; } - private ISettingsService SonarrService { get; } - private ISettingsService SickRageService { get; } - private ISettingsService EmailService { get; } - private ISettingsService PushbulletService { get; } - private ISettingsService PushoverService { get; } - private ISettingsService HeadphonesService { get; } - private ISettingsService LogService { get; } - private IPlexApi PlexApi { get; } - private ISonarrApi SonarrApi { get; } - private IPushbulletApi PushbulletApi { get; } - private IPushoverApi PushoverApi { get; } - private ICouchPotatoApi CpApi { get; } - private IRepository LogsRepo { get; } - private INotificationService NotificationService { get; } - private ICacheProvider Cache { get; } - private ISettingsService SlackSettings { get; } - private ISettingsService LandingSettings { get; } - private ISettingsService ScheduledJobSettings { get; } - private ISlackApi SlackApi { get; } - private IJobRecord JobRecorder { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - public AdminModule(ISettingsService prService, - ISettingsService cpService, - ISettingsService auth, - ISettingsService plex, - ISettingsService sonarr, - ISettingsService sickrage, - ISonarrApi sonarrApi, - ISettingsService email, - IPlexApi plexApi, - ISettingsService pbSettings, - PushbulletApi pbApi, - ICouchPotatoApi cpApi, - ISettingsService pushoverSettings, - IPushoverApi pushoverApi, - IRepository logsRepo, - INotificationService notify, - ISettingsService headphones, - ISettingsService logs, - ICacheProvider cache, ISettingsService slackSettings, - ISlackApi slackApi, ISettingsService lp, - ISettingsService scheduler, IJobRecord rec) : base("admin", prService) - { - PrService = prService; - CpService = cpService; - AuthService = auth; - PlexService = plex; - SonarrService = sonarr; - SonarrApi = sonarrApi; - EmailService = email; - PlexApi = plexApi; - PushbulletService = pbSettings; - PushbulletApi = pbApi; - CpApi = cpApi; - SickRageService = sickrage; - LogsRepo = logsRepo; - PushoverService = pushoverSettings; - PushoverApi = pushoverApi; - NotificationService = notify; - HeadphonesService = headphones; - LogService = logs; - Cache = cache; - SlackSettings = slackSettings; - SlackApi = slackApi; - LandingSettings = lp; - ScheduledJobSettings = scheduler; - JobRecorder = rec; - - this.RequiresClaims(UserClaims.Admin); - - Get["/"] = _ => Admin(); - - Get["/authentication", true] = async (x, ct) => await Authentication(); - Post["/authentication", true] = async (x, ct) => await SaveAuthentication(); - - Post["/"] = _ => SaveAdmin(); - - Post["/requestauth"] = _ => RequestAuthToken(); - - Get["/getusers"] = _ => GetUsers(); - - Get["/couchpotato"] = _ => CouchPotato(); - Post["/couchpotato"] = _ => SaveCouchPotato(); - - Get["/plex"] = _ => Plex(); - Post["/plex"] = _ => SavePlex(); - - Get["/sonarr"] = _ => Sonarr(); - Post["/sonarr"] = _ => SaveSonarr(); - - Get["/sickrage"] = _ => Sickrage(); - Post["/sickrage"] = _ => SaveSickrage(); - - Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); - Post["/cpprofiles"] = _ => GetCpProfiles(); - - Get["/emailnotification"] = _ => EmailNotifications(); - Post["/emailnotification"] = _ => SaveEmailNotifications(); - Post["/testemailnotification"] = _ => TestEmailNotifications(); - Get["/status", true] = async (x,ct) => await Status(); - - Get["/pushbulletnotification"] = _ => PushbulletNotifications(); - Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); - Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications(); - - Get["/pushovernotification"] = _ => PushoverNotifications(); - Post["/pushovernotification"] = _ => SavePushoverNotifications(); - Post["/testpushovernotification"] = _ => TestPushoverNotifications(); - - Get["/logs"] = _ => Logs(); - Get["/loglevel"] = _ => GetLogLevels(); - Post["/loglevel"] = _ => UpdateLogLevels(Request.Form.level); - Get["/loadlogs"] = _ => LoadLogs(); - - Get["/headphones"] = _ => Headphones(); - Post["/headphones"] = _ => SaveHeadphones(); - - Post["/createapikey"] = x => CreateApiKey(); - - Post["/autoupdate"] = x => AutoUpdate(); - - Post["/testslacknotification"] = _ => TestSlackNotification(); - - Get["/slacknotification"] = _ => SlackNotifications(); - Post["/slacknotification"] = _ => SaveSlackNotifications(); - - Get["/landingpage", true] = async (x, ct) => await LandingPage(); - Post["/landingpage", true] = async (x, ct) => await SaveLandingPage(); - - Get["/scheduledjobs", true] = async (x, ct) => await GetScheduledJobs(); - Post["/scheduledjobs", true] = async (x, ct) => await SaveScheduledJobs(); - } - - private async Task Authentication() - { - var settings = await AuthService.GetSettingsAsync(); - - return View["/Authentication", settings]; - } - - private async Task SaveAuthentication() - { - var model = this.Bind(); - - var result = await AuthService.SaveSettingsAsync(model); - if (result) - { - if (!string.IsNullOrEmpty(BaseUrl)) - { - return Context.GetRedirect($"~/{BaseUrl}/admin/authentication"); - } - return Context.GetRedirect("~/admin/authentication"); - } - if (!string.IsNullOrEmpty(BaseUrl)) - { - return Context.GetRedirect($"~/{BaseUrl}/error"); //TODO create error page - } - return Context.GetRedirect("~/error"); //TODO create error page - } - - private Negotiator Admin() - { - var settings = PrService.GetSettings(); - - return View["Settings", settings]; - } - - private Response SaveAdmin() - { - var model = this.Bind(); - var valid = this.Validate(model); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - if (!string.IsNullOrWhiteSpace(model.BaseUrl)) - { - if (model.BaseUrl.StartsWith("/", StringComparison.CurrentCultureIgnoreCase) || model.BaseUrl.StartsWith("\\", StringComparison.CurrentCultureIgnoreCase)) - { - model.BaseUrl = model.BaseUrl.Remove(0, 1); - } - } - var result = PrService.SaveSettings(model); - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); - } - - private Response RequestAuthToken() - { - var user = this.Bind(); - - if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password)) - { - return Response.AsJson(new { Result = false, Message = "Please provide a valid username and password" }); - } - - var model = PlexApi.SignIn(user.username, user.password); - - if (model?.user == null) - { - return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" }); - } - - var oldSettings = AuthService.GetSettings(); - if (oldSettings != null) - { - oldSettings.PlexAuthToken = model.user.authentication_token; - AuthService.SaveSettings(oldSettings); - } - else - { - var newModel = new AuthenticationSettings - { - PlexAuthToken = model.user.authentication_token - }; - AuthService.SaveSettings(newModel); - } - - return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token }); - } - - - private Response GetUsers() - { - var settings = AuthService.GetSettings(); - - var token = settings?.PlexAuthToken; - if (token == null) - { - return Response.AsJson(new { Result = true, Users = string.Empty }); - } - - try - { - var users = PlexApi.GetUsers(token); - if (users == null) - { - return Response.AsJson(string.Empty); - } - if (users.User == null || users.User?.Length == 0) - { - return Response.AsJson(string.Empty); - } - - var usernames = users.User.Select(x => x.Title); - return Response.AsJson(new { Result = true, Users = usernames }); - } - catch (Exception ex) - { - Log.Error(ex); - if (ex is WebException || ex is ApiRequestException) - { - return Response.AsJson(new { Result = false, Message = "Could not load the user list! We have connectivity problems connecting to Plex, Please ensure we can access Plex.Tv, The error has been logged." }); - } - - return Response.AsJson(new { Result = false, Message = ex.Message }); - } - } - - private Negotiator CouchPotato() - { - var settings = CpService.GetSettings(); - - return View["CouchPotato", settings]; - } - - private Response SaveCouchPotato() - { - var couchPotatoSettings = this.Bind(); - var valid = this.Validate(couchPotatoSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = CpService.SaveSettings(couchPotatoSettings); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Negotiator Plex() - { - var settings = PlexService.GetSettings(); - - return View["Plex", settings]; - } - - private Response SavePlex() - { - var plexSettings = this.Bind(); - var valid = this.Validate(plexSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - - var result = PlexService.SaveSettings(plexSettings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Plex!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Negotiator Sonarr() - { - var settings = SonarrService.GetSettings(); - - return View["Sonarr", settings]; - } - - private Response SaveSonarr() - { - var sonarrSettings = this.Bind(); - - var valid = this.Validate(sonarrSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var sickRageEnabled = SickRageService.GetSettings().Enabled; - if (sickRageEnabled) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" }); - } - var result = SonarrService.SaveSettings(sonarrSettings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Sonarr!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Negotiator Sickrage() - { - var settings = SickRageService.GetSettings(); - - return View["Sickrage", settings]; - } - - private Response SaveSickrage() - { - var sickRageSettings = this.Bind(); - - var valid = this.Validate(sickRageSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var sonarrEnabled = SonarrService.GetSettings().Enabled; - if (sonarrEnabled) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sonarr is enabled, we cannot enable Sonarr and SickRage" }); - } - var result = SickRageService.SaveSettings(sickRageSettings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for SickRage!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response GetSonarrQualityProfiles() - { - var settings = this.Bind(); - var profiles = SonarrApi.GetProfiles(settings.ApiKey, settings.FullUri); - - // set the cache - if (profiles != null) - { - Cache.Set(CacheKeys.SonarrQualityProfiles, profiles); - } - - return Response.AsJson(profiles); - } - - - private Negotiator EmailNotifications() - { - var settings = EmailService.GetSettings(); - return View["EmailNotifications", settings]; - } - - private Response TestEmailNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new EmailMessageNotification(EmailService)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent email notification test"); - } - catch (Exception) - { - Log.Error("Failed to subscribe and publish test Email Notification"); - } - finally - { - NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" }); - } - - private Response SaveEmailNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = EmailService.SaveSettings(settings); - - if (settings.Enabled) - { - NotificationService.Subscribe(new EmailMessageNotification(EmailService)); - } - else - { - NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); - } - - Log.Info("Saved email settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Email Notifications!" } - : 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() - { - var settings = PushbulletService.GetSettings(); - return View["PushbulletNotifications", settings]; - } - - private Response SavePushbulletNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = PushbulletService.SaveSettings(settings); - if (settings.Enabled) - { - NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - } - else - { - NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - } - - Log.Info("Saved email settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushbullet Notifications!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response TestPushbulletNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent pushbullet notification test"); - } - catch (Exception) - { - Log.Error("Failed to subscribe and publish test Pushbullet Notification"); - } - finally - { - NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" }); - } - - private Negotiator PushoverNotifications() - { - var settings = PushoverService.GetSettings(); - return View["PushoverNotifications", settings]; - } - - private Response SavePushoverNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = PushoverService.SaveSettings(settings); - if (settings.Enabled) - { - NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); - } - else - { - NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); - } - - Log.Info("Saved email settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushover Notifications!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response TestPushoverNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent pushover notification test"); - } - catch (Exception) - { - Log.Error("Failed to subscribe and publish test Pushover Notification"); - } - finally - { - NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" }); - } - - private Response GetCpProfiles() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey); - - // set the cache - if (profiles != null) - { - Cache.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); - } - - return Response.AsJson(profiles); - } - - private Negotiator Logs() - { - return View["Logs"]; - } - - private Response LoadLogs() - { - JsonSettings.MaxJsonLength = int.MaxValue; - var allLogs = LogsRepo.GetAll().OrderByDescending(x => x.Id).Take(200); - var model = new DatatablesModel { Data = new List() }; - foreach (var l in allLogs) - { - l.DateString = l.Date.ToString("G"); - model.Data.Add(l); - } - return Response.AsJson(model); - } - - private Response GetLogLevels() - { - var levels = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database")); - return Response.AsJson(levels.Levels); - } - - private Response UpdateLogLevels(int level) - { - var settings = LogService.GetSettings(); - - // apply the level - var newLevel = LogLevel.FromOrdinal(level); - LoggingHelper.ReconfigureLogLevel(newLevel); - - //save the log settings - settings.Level = level; - LogService.SaveSettings(settings); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}" }); - } - - private Negotiator Headphones() - { - var settings = HeadphonesService.GetSettings(); - return View["Headphones", settings]; - } - - private Response SaveHeadphones() - { - var settings = this.Bind(); - - var valid = this.Validate(settings); - if (!valid.IsValid) - { - var error = valid.SendJsonError(); - Log.Info("Error validating Headphones settings, message: {0}", error.Message); - return Response.AsJson(error); - } - - var result = HeadphonesService.SaveSettings(settings); - - Log.Info("Saved headphones settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Headphones!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response CreateApiKey() - { - this.RequiresClaims(UserClaims.Admin); - var apiKey = Guid.NewGuid().ToString("N"); - var settings = PrService.GetSettings(); - - settings.ApiKey = apiKey; - PrService.SaveSettings(settings); - - return Response.AsJson(apiKey); - } - - private Response TestSlackNotification() - { - var settings = this.BindAndValidate(); - if (!ModelValidationResult.IsValid) - { - return Response.AsJson(ModelValidationResult.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent slack notification test"); - } - catch (Exception e) - { - Log.Error(e, "Failed to subscribe and publish test Slack Notification"); - } - finally - { - NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Slack Notification! If you do not receive it please check the logs." }); - } - - private Negotiator SlackNotifications() - { - var settings = SlackSettings.GetSettings(); - return View["SlackNotifications", settings]; - } - - private Response SaveSlackNotifications() - { - var settings = this.BindAndValidate(); - if (!ModelValidationResult.IsValid) - { - return Response.AsJson(ModelValidationResult.SendJsonError()); - } - - var result = SlackSettings.SaveSettings(settings); - if (settings.Enabled) - { - NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); - } - else - { - NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); - } - - Log.Info("Saved slack settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Slack Notifications!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private async Task LandingPage() - { - var settings = await LandingSettings.GetSettingsAsync(); - if (settings.NoticeEnd == DateTime.MinValue) - { - settings.NoticeEnd = DateTime.Now; - } - if (settings.NoticeStart == DateTime.MinValue) - { - settings.NoticeStart = DateTime.Now; - } - return View["LandingPage", settings]; - } - - private async Task SaveLandingPage() - { - var settings = this.Bind(); - - var plexSettings = await PlexService.GetSettingsAsync(); - if (string.IsNullOrEmpty(plexSettings.Ip)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We cannot enable the landing page if Plex is not setup!" }); - } - - if (settings.Enabled && settings.EnabledNoticeTime && string.IsNullOrEmpty(settings.NoticeMessage)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "If you are going to enabled the notice, then we need a message!" }); - } - - var result = await LandingSettings.SaveSettingsAsync(settings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); - } - - private async Task GetScheduledJobs() - { - var s = await ScheduledJobSettings.GetSettingsAsync(); - var allJobs = await JobRecorder.GetJobsAsync(); - var jobsDict = allJobs.ToDictionary(k => k.Name, v => v.LastRun); - var model = new ScheduledJobsViewModel - { - CouchPotatoCacher = s.CouchPotatoCacher, - PlexAvailabilityChecker = s.PlexAvailabilityChecker, - SickRageCacher = s.SickRageCacher, - SonarrCacher = s.SonarrCacher, - StoreBackup = s.StoreBackup, - StoreCleanup = s.StoreCleanup, - JobRecorder = jobsDict - }; - return View["SchedulerSettings", model]; - } - - private async Task SaveScheduledJobs() - { - var settings = this.Bind(); - - var result = await ScheduledJobSettings.SaveSettingsAsync(settings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AdminModule.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 System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Net; + +using Nancy; +using Nancy.Extensions; +using Nancy.ModelBinding; +using Nancy.Responses.Negotiation; +using Nancy.Validation; +using Nancy.Json; +using Nancy.Security; +using NLog; + +using MarkdownSharp; + +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Exceptions; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Models; + + +namespace PlexRequests.UI.Modules +{ + public class AdminModule : BaseModule + { + private ISettingsService PrService { get; } + private ISettingsService CpService { get; } + private ISettingsService AuthService { get; } + private ISettingsService PlexService { get; } + private ISettingsService SonarrService { get; } + private ISettingsService SickRageService { get; } + private ISettingsService EmailService { get; } + private ISettingsService PushbulletService { get; } + private ISettingsService PushoverService { get; } + private ISettingsService HeadphonesService { get; } + private ISettingsService LogService { get; } + private IPlexApi PlexApi { get; } + private ISonarrApi SonarrApi { get; } + private IPushbulletApi PushbulletApi { get; } + private IPushoverApi PushoverApi { get; } + private ICouchPotatoApi CpApi { get; } + private IRepository LogsRepo { get; } + private INotificationService NotificationService { get; } + private ICacheProvider Cache { get; } + private ISettingsService SlackSettings { get; } + private ISettingsService LandingSettings { get; } + private ISettingsService ScheduledJobSettings { get; } + private ISlackApi SlackApi { get; } + private IJobRecord JobRecorder { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + public AdminModule(ISettingsService prService, + ISettingsService cpService, + ISettingsService auth, + ISettingsService plex, + ISettingsService sonarr, + ISettingsService sickrage, + ISonarrApi sonarrApi, + ISettingsService email, + IPlexApi plexApi, + ISettingsService pbSettings, + PushbulletApi pbApi, + ICouchPotatoApi cpApi, + ISettingsService pushoverSettings, + IPushoverApi pushoverApi, + IRepository logsRepo, + INotificationService notify, + ISettingsService headphones, + ISettingsService logs, + ICacheProvider cache, ISettingsService slackSettings, + ISlackApi slackApi, ISettingsService lp, + ISettingsService scheduler, IJobRecord rec) : base("admin", prService) + { + PrService = prService; + CpService = cpService; + AuthService = auth; + PlexService = plex; + SonarrService = sonarr; + SonarrApi = sonarrApi; + EmailService = email; + PlexApi = plexApi; + PushbulletService = pbSettings; + PushbulletApi = pbApi; + CpApi = cpApi; + SickRageService = sickrage; + LogsRepo = logsRepo; + PushoverService = pushoverSettings; + PushoverApi = pushoverApi; + NotificationService = notify; + HeadphonesService = headphones; + LogService = logs; + Cache = cache; + SlackSettings = slackSettings; + SlackApi = slackApi; + LandingSettings = lp; + ScheduledJobSettings = scheduler; + JobRecorder = rec; + + this.RequiresClaims(UserClaims.Admin); + + Get["/"] = _ => Admin(); + + Get["/authentication", true] = async (x, ct) => await Authentication(); + Post["/authentication", true] = async (x, ct) => await SaveAuthentication(); + + Post["/"] = _ => SaveAdmin(); + + Post["/requestauth"] = _ => RequestAuthToken(); + + Get["/getusers"] = _ => GetUsers(); + + Get["/couchpotato"] = _ => CouchPotato(); + Post["/couchpotato"] = _ => SaveCouchPotato(); + + Get["/plex"] = _ => Plex(); + Post["/plex"] = _ => SavePlex(); + + Get["/sonarr"] = _ => Sonarr(); + Post["/sonarr"] = _ => SaveSonarr(); + + Get["/sickrage"] = _ => Sickrage(); + Post["/sickrage"] = _ => SaveSickrage(); + + Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); + Post["/cpprofiles"] = _ => GetCpProfiles(); + + Get["/emailnotification"] = _ => EmailNotifications(); + Post["/emailnotification"] = _ => SaveEmailNotifications(); + Post["/testemailnotification"] = _ => TestEmailNotifications(); + Get["/status", true] = async (x,ct) => await Status(); + + Get["/pushbulletnotification"] = _ => PushbulletNotifications(); + Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); + Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications(); + + Get["/pushovernotification"] = _ => PushoverNotifications(); + Post["/pushovernotification"] = _ => SavePushoverNotifications(); + Post["/testpushovernotification"] = _ => TestPushoverNotifications(); + + Get["/logs"] = _ => Logs(); + Get["/loglevel"] = _ => GetLogLevels(); + Post["/loglevel"] = _ => UpdateLogLevels(Request.Form.level); + Get["/loadlogs"] = _ => LoadLogs(); + + Get["/headphones"] = _ => Headphones(); + Post["/headphones"] = _ => SaveHeadphones(); + + Post["/createapikey"] = x => CreateApiKey(); + + Post["/autoupdate"] = x => AutoUpdate(); + + Post["/testslacknotification"] = _ => TestSlackNotification(); + + Get["/slacknotification"] = _ => SlackNotifications(); + Post["/slacknotification"] = _ => SaveSlackNotifications(); + + Get["/landingpage", true] = async (x, ct) => await LandingPage(); + Post["/landingpage", true] = async (x, ct) => await SaveLandingPage(); + + Get["/scheduledjobs", true] = async (x, ct) => await GetScheduledJobs(); + Post["/scheduledjobs", true] = async (x, ct) => await SaveScheduledJobs(); + } + + private async Task Authentication() + { + var settings = await AuthService.GetSettingsAsync(); + + return View["/Authentication", settings]; + } + + private async Task SaveAuthentication() + { + var model = this.Bind(); + + var result = await AuthService.SaveSettingsAsync(model); + if (result) + { + if (!string.IsNullOrEmpty(BaseUrl)) + { + return Context.GetRedirect($"~/{BaseUrl}/admin/authentication"); + } + return Context.GetRedirect("~/admin/authentication"); + } + if (!string.IsNullOrEmpty(BaseUrl)) + { + return Context.GetRedirect($"~/{BaseUrl}/error"); //TODO create error page + } + return Context.GetRedirect("~/error"); //TODO create error page + } + + private Negotiator Admin() + { + var settings = PrService.GetSettings(); + + return View["Settings", settings]; + } + + private Response SaveAdmin() + { + var model = this.Bind(); + var valid = this.Validate(model); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + if (!string.IsNullOrWhiteSpace(model.BaseUrl)) + { + if (model.BaseUrl.StartsWith("/", StringComparison.CurrentCultureIgnoreCase) || model.BaseUrl.StartsWith("\\", StringComparison.CurrentCultureIgnoreCase)) + { + model.BaseUrl = model.BaseUrl.Remove(0, 1); + } + } + var result = PrService.SaveSettings(model); + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); + } + + private Response RequestAuthToken() + { + var user = this.Bind(); + + if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password)) + { + return Response.AsJson(new { Result = false, Message = "Please provide a valid username and password" }); + } + + var model = PlexApi.SignIn(user.username, user.password); + + if (model?.user == null) + { + return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" }); + } + + var oldSettings = AuthService.GetSettings(); + if (oldSettings != null) + { + oldSettings.PlexAuthToken = model.user.authentication_token; + AuthService.SaveSettings(oldSettings); + } + else + { + var newModel = new AuthenticationSettings + { + PlexAuthToken = model.user.authentication_token + }; + AuthService.SaveSettings(newModel); + } + + return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token }); + } + + + private Response GetUsers() + { + var settings = AuthService.GetSettings(); + + var token = settings?.PlexAuthToken; + if (token == null) + { + return Response.AsJson(new { Result = true, Users = string.Empty }); + } + + try + { + var users = PlexApi.GetUsers(token); + if (users == null) + { + return Response.AsJson(string.Empty); + } + if (users.User == null || users.User?.Length == 0) + { + return Response.AsJson(string.Empty); + } + + var usernames = users.User.Select(x => x.Title); + return Response.AsJson(new { Result = true, Users = usernames }); + } + catch (Exception ex) + { + Log.Error(ex); + if (ex is WebException || ex is ApiRequestException) + { + return Response.AsJson(new { Result = false, Message = "Could not load the user list! We have connectivity problems connecting to Plex, Please ensure we can access Plex.Tv, The error has been logged." }); + } + + return Response.AsJson(new { Result = false, Message = ex.Message }); + } + } + + private Negotiator CouchPotato() + { + var settings = CpService.GetSettings(); + + return View["CouchPotato", settings]; + } + + private Response SaveCouchPotato() + { + var couchPotatoSettings = this.Bind(); + var valid = this.Validate(couchPotatoSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = CpService.SaveSettings(couchPotatoSettings); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Negotiator Plex() + { + var settings = PlexService.GetSettings(); + + return View["Plex", settings]; + } + + private Response SavePlex() + { + var plexSettings = this.Bind(); + var valid = this.Validate(plexSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + + var result = PlexService.SaveSettings(plexSettings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Plex!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Negotiator Sonarr() + { + var settings = SonarrService.GetSettings(); + + return View["Sonarr", settings]; + } + + private Response SaveSonarr() + { + var sonarrSettings = this.Bind(); + + var valid = this.Validate(sonarrSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var sickRageEnabled = SickRageService.GetSettings().Enabled; + if (sickRageEnabled) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" }); + } + var result = SonarrService.SaveSettings(sonarrSettings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Sonarr!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Negotiator Sickrage() + { + var settings = SickRageService.GetSettings(); + + return View["Sickrage", settings]; + } + + private Response SaveSickrage() + { + var sickRageSettings = this.Bind(); + + var valid = this.Validate(sickRageSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var sonarrEnabled = SonarrService.GetSettings().Enabled; + if (sonarrEnabled) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sonarr is enabled, we cannot enable Sonarr and SickRage" }); + } + var result = SickRageService.SaveSettings(sickRageSettings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for SickRage!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response GetSonarrQualityProfiles() + { + var settings = this.Bind(); + var profiles = SonarrApi.GetProfiles(settings.ApiKey, settings.FullUri); + + // set the cache + if (profiles != null) + { + Cache.Set(CacheKeys.SonarrQualityProfiles, profiles); + } + + return Response.AsJson(profiles); + } + + + private Negotiator EmailNotifications() + { + var settings = EmailService.GetSettings(); + return View["EmailNotifications", settings]; + } + + private Response TestEmailNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new EmailMessageNotification(EmailService)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent email notification test"); + } + catch (Exception) + { + Log.Error("Failed to subscribe and publish test Email Notification"); + } + finally + { + NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" }); + } + + private Response SaveEmailNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = EmailService.SaveSettings(settings); + + if (settings.Enabled) + { + NotificationService.Subscribe(new EmailMessageNotification(EmailService)); + } + else + { + NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); + } + + Log.Info("Saved email settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Email Notifications!" } + : 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() + { + var settings = PushbulletService.GetSettings(); + return View["PushbulletNotifications", settings]; + } + + private Response SavePushbulletNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = PushbulletService.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } + else + { + NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } + + Log.Info("Saved email settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushbullet Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response TestPushbulletNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent pushbullet notification test"); + } + catch (Exception) + { + Log.Error("Failed to subscribe and publish test Pushbullet Notification"); + } + finally + { + NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" }); + } + + private Negotiator PushoverNotifications() + { + var settings = PushoverService.GetSettings(); + return View["PushoverNotifications", settings]; + } + + private Response SavePushoverNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = PushoverService.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + else + { + NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + + Log.Info("Saved email settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushover Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response TestPushoverNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent pushover notification test"); + } + catch (Exception) + { + Log.Error("Failed to subscribe and publish test Pushover Notification"); + } + finally + { + NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" }); + } + + private Response GetCpProfiles() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey); + + // set the cache + if (profiles != null) + { + Cache.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); + } + + return Response.AsJson(profiles); + } + + private Negotiator Logs() + { + return View["Logs"]; + } + + private Response LoadLogs() + { + JsonSettings.MaxJsonLength = int.MaxValue; + var allLogs = LogsRepo.GetAll().OrderByDescending(x => x.Id).Take(200); + var model = new DatatablesModel { Data = new List() }; + foreach (var l in allLogs) + { + l.DateString = l.Date.ToString("G"); + model.Data.Add(l); + } + return Response.AsJson(model); + } + + private Response GetLogLevels() + { + var levels = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database")); + return Response.AsJson(levels.Levels); + } + + private Response UpdateLogLevels(int level) + { + var settings = LogService.GetSettings(); + + // apply the level + var newLevel = LogLevel.FromOrdinal(level); + LoggingHelper.ReconfigureLogLevel(newLevel); + + //save the log settings + settings.Level = level; + LogService.SaveSettings(settings); + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}" }); + } + + private Negotiator Headphones() + { + var settings = HeadphonesService.GetSettings(); + return View["Headphones", settings]; + } + + private Response SaveHeadphones() + { + var settings = this.Bind(); + + var valid = this.Validate(settings); + if (!valid.IsValid) + { + var error = valid.SendJsonError(); + Log.Info("Error validating Headphones settings, message: {0}", error.Message); + return Response.AsJson(error); + } + + var result = HeadphonesService.SaveSettings(settings); + + Log.Info("Saved headphones settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Headphones!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response CreateApiKey() + { + this.RequiresClaims(UserClaims.Admin); + var apiKey = Guid.NewGuid().ToString("N"); + var settings = PrService.GetSettings(); + + settings.ApiKey = apiKey; + PrService.SaveSettings(settings); + + return Response.AsJson(apiKey); + } + + private Response TestSlackNotification() + { + var settings = this.BindAndValidate(); + if (!ModelValidationResult.IsValid) + { + return Response.AsJson(ModelValidationResult.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent slack notification test"); + } + catch (Exception e) + { + Log.Error(e, "Failed to subscribe and publish test Slack Notification"); + } + finally + { + NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Slack Notification! If you do not receive it please check the logs." }); + } + + private Negotiator SlackNotifications() + { + var settings = SlackSettings.GetSettings(); + return View["SlackNotifications", settings]; + } + + private Response SaveSlackNotifications() + { + var settings = this.BindAndValidate(); + if (!ModelValidationResult.IsValid) + { + return Response.AsJson(ModelValidationResult.SendJsonError()); + } + + var result = SlackSettings.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); + } + else + { + NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); + } + + Log.Info("Saved slack settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Slack Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private async Task LandingPage() + { + var settings = await LandingSettings.GetSettingsAsync(); + if (settings.NoticeEnd == DateTime.MinValue) + { + settings.NoticeEnd = DateTime.Now; + } + if (settings.NoticeStart == DateTime.MinValue) + { + settings.NoticeStart = DateTime.Now; + } + return View["LandingPage", settings]; + } + + private async Task SaveLandingPage() + { + var settings = this.Bind(); + + var plexSettings = await PlexService.GetSettingsAsync(); + if (string.IsNullOrEmpty(plexSettings.Ip)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We cannot enable the landing page if Plex is not setup!" }); + } + + if (settings.Enabled && settings.EnabledNoticeTime && string.IsNullOrEmpty(settings.NoticeMessage)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "If you are going to enabled the notice, then we need a message!" }); + } + + var result = await LandingSettings.SaveSettingsAsync(settings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); + } + + private async Task GetScheduledJobs() + { + var s = await ScheduledJobSettings.GetSettingsAsync(); + var allJobs = await JobRecorder.GetJobsAsync(); + var jobsDict = allJobs.ToDictionary(k => k.Name, v => v.LastRun); + var model = new ScheduledJobsViewModel + { + CouchPotatoCacher = s.CouchPotatoCacher, + PlexAvailabilityChecker = s.PlexAvailabilityChecker, + SickRageCacher = s.SickRageCacher, + SonarrCacher = s.SonarrCacher, + StoreBackup = s.StoreBackup, + StoreCleanup = s.StoreCleanup, + JobRecorder = jobsDict + }; + return View["SchedulerSettings", model]; + } + + private async Task SaveScheduledJobs() + { + var settings = this.Bind(); + + var result = await ScheduledJobSettings.SaveSettingsAsync(settings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); + } + } } \ No newline at end of file diff --git a/README.md b/README.md index 08f5873e4..0153cfb7a 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,58 @@ - -![](http://i.imgur.com/s4nswSA.png?1) -____ -[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex) -[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net) -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue") -[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open") -[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)](https://github.com/tidusjar/PlexRequests.Net) -[![Stories in Progress](https://badge.waffle.io/tidusjar/PlexRequests.Net.svg?label=in progress&title=In Progress)](http://waffle.io/tidusjar/PlexRequests.Net) - -This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy! -I wanted to write a similar application in .Net! - -# Features - -* Movie and TV Show searching, can't find something on Plex? Just request it! -* Notifications! Get notified via Email, Pushbullet and Pushover for new requests and issue reports! -* Send your TV Shows to either [Sonarr](https://sonarr.tv/) or [SickRage](http://www.sickrage.ca/)! -* Secure authentication so you don't have to worry about those script kiddies -* We check to see if the request is already in Plex, if it's already in Plex then why you requesting it?! -* We have allowed the ability for a user to add a custom note on a request -* It automatically update the status of requests when they are available on Plex -* Slick, responsive and mobile friendly UI -* Headphones integration! -* Ability to run with a reverse proxy! - -# Preview () - -![Preview](http://i.imgur.com/DgwkIsW.gif) - -#Installation - -[Windows Guide!](http://www.htpcguides.com/install-plex-requests-net-windows-system-service/) -[Ubuntu Guide!](http://www.htpcguides.com/install-plex-requests-net-ubuntu-14-x/) - -# FAQ -Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ) - -# Docker - -Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile: - -# Contributors - -We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it! - -Please feed free to submit a pull request! - -# Donation -If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet) - -## A massive thanks to everyone below for all their help! - -[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake), [Drewster727](https://github.com/Drewster727), Majawat, [EddiYo](https://github.com/EddiYo), [SaskiFX](https://github.com/SaskiFX), [zenjabba](https://github.com/zenjabba) - -## Stats -[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput) + +![](http://i.imgur.com/s4nswSA.png?1) +____ +[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex) +[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open") +[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)](https://github.com/tidusjar/PlexRequests.Net) +[![Stories in Progress](https://badge.waffle.io/tidusjar/PlexRequests.Net.svg?label=in progress&title=In Progress)](http://waffle.io/tidusjar/PlexRequests.Net) + +This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy! +I wanted to write a similar application in .Net! + +# Features + +* Movie and TV Show searching, can't find something on Plex? Just request it! +* Notifications! Get notified via Email, Pushbullet and Pushover for new requests and issue reports! +* Send your TV Shows to either [Sonarr](https://sonarr.tv/) or [SickRage](http://www.sickrage.ca/)! +* Secure authentication so you don't have to worry about those script kiddies +* We check to see if the request is already in Plex, if it's already in Plex then why you requesting it?! +* We have allowed the ability for a user to add a custom note on a request +* It automatically update the status of requests when they are available on Plex +* Slick, responsive and mobile friendly UI +* Headphones integration! +* Ability to run with a reverse proxy! + +# Preview () + +![Preview](http://i.imgur.com/DgwkIsW.gif) + +#Installation + +[Windows Guide!](http://www.htpcguides.com/install-plex-requests-net-windows-system-service/) +[Ubuntu Guide!](http://www.htpcguides.com/install-plex-requests-net-ubuntu-14-x/) + +# FAQ +Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ) + +# Docker + +Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile: + +# Contributors + +We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it! + +Please feed free to submit a pull request! + +# Donation +If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet) + +## A massive thanks to everyone below for all their help! + +[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake), [Drewster727](https://github.com/Drewster727), Majawat, [EddiYo](https://github.com/EddiYo), [SaskiFX](https://github.com/SaskiFX), [zenjabba](https://github.com/zenjabba) + +## Stats +[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput)