From 53f806fc710f18a8fa8d45335ed94fd1967d8731 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 4 Aug 2016 11:48:05 +0100 Subject: [PATCH] Some performance improvements around the new TV stuff --- PlexRequests.Helpers/LoggingHelper.cs | 354 +++++++++--------- .../Interfaces/IAvailabilityChecker.cs | 11 + .../Jobs/PlexAvailabilityChecker.cs | 32 ++ PlexRequests.UI/Modules/SearchModule.cs | 7 +- 4 files changed, 225 insertions(+), 179 deletions(-) diff --git a/PlexRequests.Helpers/LoggingHelper.cs b/PlexRequests.Helpers/LoggingHelper.cs index 73562eebe..350040cc0 100644 --- a/PlexRequests.Helpers/LoggingHelper.cs +++ b/PlexRequests.Helpers/LoggingHelper.cs @@ -1,177 +1,177 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: LoggingHelper.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.Data; - -using Newtonsoft.Json; - -using NLog; -using NLog.Config; -using NLog.Targets; - -namespace PlexRequests.Helpers -{ - public static class LoggingHelper - { - public static string DumpJson(this object value) - { - var dumpTarget = value; - //if this is a string that contains a JSON object, do a round-trip serialization to format it: - var stringValue = value as string; - if (stringValue != null) - { - if (stringValue.Trim().StartsWith("{", StringComparison.Ordinal)) - { - var obj = JsonConvert.DeserializeObject(stringValue); - dumpTarget = JsonConvert.SerializeObject(obj, Formatting.Indented); - } - else - { - dumpTarget = stringValue; - } - } - else - { - dumpTarget = JsonConvert.SerializeObject(value, Formatting.Indented); - } - return dumpTarget.ToString(); - } - - public static void ConfigureLogging(string connectionString) - { - LogManager.ThrowExceptions = true; - // Step 1. Create configuration object - var config = new LoggingConfiguration(); - - // Step 2. Create targets and add them to the configuration - var databaseTarget = new DatabaseTarget - { - CommandType = CommandType.Text, - ConnectionString = connectionString, - DBProvider = "Mono.Data.Sqlite.SqliteConnection, Mono.Data.Sqlite, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756", - Name = "database" - }; - - var messageParam = new DatabaseParameterInfo { Name = "@Message", Layout = "${message}" }; - var callsiteParam = new DatabaseParameterInfo { Name = "@Callsite", Layout = "${callsite}" }; - var levelParam = new DatabaseParameterInfo { Name = "@Level", Layout = "${level}" }; - var dateParam = new DatabaseParameterInfo { Name = "@Date", Layout = "${date}" }; - var loggerParam = new DatabaseParameterInfo { Name = "@Logger", Layout = "${logger}" }; - var exceptionParam = new DatabaseParameterInfo { Name = "@Exception", Layout = "${exception:tostring}" }; - - databaseTarget.Parameters.Add(messageParam); - databaseTarget.Parameters.Add(callsiteParam); - databaseTarget.Parameters.Add(levelParam); - databaseTarget.Parameters.Add(dateParam); - databaseTarget.Parameters.Add(loggerParam); - databaseTarget.Parameters.Add(exceptionParam); - - databaseTarget.CommandText = "INSERT INTO Logs (Date,Level,Logger, Message, Callsite, Exception) VALUES(@Date,@Level,@Logger, @Message, @Callsite, @Exception);"; - config.AddTarget("database", databaseTarget); - - // Step 4. Define rules - var rule1 = new LoggingRule("*", LogLevel.Info, databaseTarget); - config.LoggingRules.Add(rule1); - - - var fileTarget = new FileTarget - { - Name = "file", - FileName = "logs/${shortdate}.log", - Layout = "${date} ${logger} ${level}: ${message} ${exception:tostring}", - CreateDirs = true - }; - config.AddTarget(fileTarget); - var rule2 = new LoggingRule("*", LogLevel.Info, fileTarget); - config.LoggingRules.Add(rule2); - - // Step 5. Activate the configuration - LogManager.Configuration = config; - } - - public static void ReconfigureLogLevel(LogLevel level) - { - - foreach (var rule in LogManager.Configuration.LoggingRules) - { - // Remove all levels - rule.DisableLoggingForLevel(LogLevel.Trace); - rule.DisableLoggingForLevel(LogLevel.Info); - rule.DisableLoggingForLevel(LogLevel.Debug); - rule.DisableLoggingForLevel(LogLevel.Warn); - rule.DisableLoggingForLevel(LogLevel.Error); - rule.DisableLoggingForLevel(LogLevel.Fatal); - - - if (level == LogLevel.Trace) - { - rule.EnableLoggingForLevel(LogLevel.Trace); - rule.EnableLoggingForLevel(LogLevel.Info); - rule.EnableLoggingForLevel(LogLevel.Debug); - rule.EnableLoggingForLevel(LogLevel.Warn); - rule.EnableLoggingForLevel(LogLevel.Error); - rule.EnableLoggingForLevel(LogLevel.Fatal); - } - if (level == LogLevel.Info) - { - rule.EnableLoggingForLevel(LogLevel.Info); - rule.EnableLoggingForLevel(LogLevel.Warn); - rule.EnableLoggingForLevel(LogLevel.Error); - rule.EnableLoggingForLevel(LogLevel.Fatal); - } - if (level == LogLevel.Debug) - { - rule.EnableLoggingForLevel(LogLevel.Debug); - rule.EnableLoggingForLevel(LogLevel.Warn); - rule.EnableLoggingForLevel(LogLevel.Error); - rule.EnableLoggingForLevel(LogLevel.Fatal); - } - if (level == LogLevel.Warn) - { - rule.EnableLoggingForLevel(LogLevel.Warn); - rule.EnableLoggingForLevel(LogLevel.Error); - rule.EnableLoggingForLevel(LogLevel.Fatal); - } - if (level == LogLevel.Error) - { - rule.EnableLoggingForLevel(LogLevel.Error); - rule.EnableLoggingForLevel(LogLevel.Fatal); - } - if (level == LogLevel.Fatal) - { - rule.EnableLoggingForLevel(LogLevel.Fatal); - } - } - - - //Call to update existing Loggers created with GetLogger() or - //GetCurrentClassLogger() - LogManager.ReconfigExistingLoggers(); - } - } -} - +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: LoggingHelper.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.Data; + +using Newtonsoft.Json; + +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace PlexRequests.Helpers +{ + public static class LoggingHelper + { + /// + /// WARNING, This method uses up a LOT of memory and can lead to leaks. + /// + /// The value. + /// + public static string DumpJson(this object value) + { + object dumpTarget; + //if this is a string that contains a JSON object, do a round-trip serialization to format it: + var stringValue = value as string; + if (stringValue != null) + { + dumpTarget = stringValue.Trim().StartsWith("{", StringComparison.Ordinal) + ? JsonConvert.SerializeObject(JsonConvert.DeserializeObject(stringValue), Formatting.Indented) + : stringValue; + } + else + { + dumpTarget = JsonConvert.SerializeObject(value, Formatting.Indented); + } + return dumpTarget.ToString(); + } + + public static void ConfigureLogging(string connectionString) + { + LogManager.ThrowExceptions = true; + // Step 1. Create configuration object + var config = new LoggingConfiguration(); + + // Step 2. Create targets and add them to the configuration + var databaseTarget = new DatabaseTarget + { + CommandType = CommandType.Text, + ConnectionString = connectionString, + DBProvider = "Mono.Data.Sqlite.SqliteConnection, Mono.Data.Sqlite, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756", + Name = "database", + + }; + + var messageParam = new DatabaseParameterInfo { Name = "@Message", Layout = "${message}" }; + var callsiteParam = new DatabaseParameterInfo { Name = "@Callsite", Layout = "${callsite}" }; + var levelParam = new DatabaseParameterInfo { Name = "@Level", Layout = "${level}" }; + var dateParam = new DatabaseParameterInfo { Name = "@Date", Layout = "${date}" }; + var loggerParam = new DatabaseParameterInfo { Name = "@Logger", Layout = "${logger}" }; + var exceptionParam = new DatabaseParameterInfo { Name = "@Exception", Layout = "${exception:tostring}" }; + + databaseTarget.Parameters.Add(messageParam); + databaseTarget.Parameters.Add(callsiteParam); + databaseTarget.Parameters.Add(levelParam); + databaseTarget.Parameters.Add(dateParam); + databaseTarget.Parameters.Add(loggerParam); + databaseTarget.Parameters.Add(exceptionParam); + + databaseTarget.CommandText = "INSERT INTO Logs (Date,Level,Logger, Message, Callsite, Exception) VALUES(@Date,@Level,@Logger, @Message, @Callsite, @Exception);"; + config.AddTarget("database", databaseTarget); + + // Step 4. Define rules + var rule1 = new LoggingRule("*", LogLevel.Debug, databaseTarget); + config.LoggingRules.Add(rule1); + + + var fileTarget = new FileTarget + { + Name = "file", + FileName = "logs/${shortdate}.log", + Layout = "${date} ${logger} ${level}: ${message} ${exception:tostring}", + CreateDirs = true + }; + config.AddTarget(fileTarget); + var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget); + config.LoggingRules.Add(rule2); + + // Step 5. Activate the configuration + LogManager.Configuration = config; + } + + public static void ReconfigureLogLevel(LogLevel level) + { + + foreach (var rule in LogManager.Configuration.LoggingRules) + { + // Remove all levels + rule.DisableLoggingForLevel(LogLevel.Trace); + rule.DisableLoggingForLevel(LogLevel.Info); + rule.DisableLoggingForLevel(LogLevel.Debug); + rule.DisableLoggingForLevel(LogLevel.Warn); + rule.DisableLoggingForLevel(LogLevel.Error); + rule.DisableLoggingForLevel(LogLevel.Fatal); + + + if (level == LogLevel.Trace) + { + rule.EnableLoggingForLevel(LogLevel.Trace); + rule.EnableLoggingForLevel(LogLevel.Info); + rule.EnableLoggingForLevel(LogLevel.Debug); + rule.EnableLoggingForLevel(LogLevel.Warn); + rule.EnableLoggingForLevel(LogLevel.Error); + rule.EnableLoggingForLevel(LogLevel.Fatal); + } + if (level == LogLevel.Info) + { + rule.EnableLoggingForLevel(LogLevel.Info); + rule.EnableLoggingForLevel(LogLevel.Warn); + rule.EnableLoggingForLevel(LogLevel.Error); + rule.EnableLoggingForLevel(LogLevel.Fatal); + } + if (level == LogLevel.Debug) + { + rule.EnableLoggingForLevel(LogLevel.Debug); + rule.EnableLoggingForLevel(LogLevel.Warn); + rule.EnableLoggingForLevel(LogLevel.Error); + rule.EnableLoggingForLevel(LogLevel.Fatal); + } + if (level == LogLevel.Warn) + { + rule.EnableLoggingForLevel(LogLevel.Warn); + rule.EnableLoggingForLevel(LogLevel.Error); + rule.EnableLoggingForLevel(LogLevel.Fatal); + } + if (level == LogLevel.Error) + { + rule.EnableLoggingForLevel(LogLevel.Error); + rule.EnableLoggingForLevel(LogLevel.Fatal); + } + if (level == LogLevel.Fatal) + { + rule.EnableLoggingForLevel(LogLevel.Fatal); + } + } + + + //Call to update existing Loggers created with GetLogger() or + //GetCurrentClassLogger() + LogManager.ReconfigExistingLoggers(); + } + } +} + diff --git a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs index 137b98a5c..96411cb15 100644 --- a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs +++ b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs @@ -39,5 +39,16 @@ namespace PlexRequests.Services.Interfaces List GetPlexAlbums(); bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, string artist); bool IsEpisodeAvailable(string theTvDbId, int season, int episode); + /// + /// Gets the episode's stored in the cache. + /// + /// + IEnumerable GetEpisodeCache(); + /// + /// Gets the episode's stored in the cache and then filters on the TheTvDBId. + /// + /// The tv database identifier. + /// + IEnumerable GetEpisodeCache(int theTvDbId); } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 5f7afd151..a0df532c7 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -257,6 +257,38 @@ namespace PlexRequests.Services.Jobs return false; } + /// + /// Gets the episode's stored in the cache. + /// + /// + public IEnumerable GetEpisodeCache() + { + var episodes = Cache.Get>(CacheKeys.PlexEpisodes); + if (episodes == null) + { + Log.Info("Episode cache info is not available."); + return new List(); + } + return episodes; + } + + /// + /// Gets the episode's stored in the cache and then filters on the TheTvDBId. + /// + /// The tv database identifier. + /// + public IEnumerable GetEpisodeCache(int theTvDbId) + { + var episodes = Cache.Get>(CacheKeys.PlexEpisodes); + if (episodes == null) + { + Log.Info("Episode cache info is not available."); + return new List(); + } + + return episodes.Where(x => x.Episodes.ProviderId == theTvDbId.ToString()); + } + public List GetPlexAlbums() { var albums = new List(); diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 06bab8857..4c0634c61 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -608,9 +608,10 @@ namespace PlexRequests.UI.Modules } if (episodeRequest) { + var cachedEpisodes = Checker.GetEpisodeCache().ToList(); foreach (var d in difference) { - if (Checker.IsEpisodeAvailable(providerId, d.SeasonNumber, d.EpisodeNumber)) + if (cachedEpisodes.Any(x => x.Episodes.SeasonNumber == d.SeasonNumber && x.Episodes.EpisodeNumber == d.EpisodeNumber && x.Episodes.ProviderId == providerId)) { return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {d.SeasonNumber} - {d.EpisodeNumber} {Resources.UI.Search_AlreadyInPlex}" }); } @@ -982,13 +983,15 @@ namespace PlexRequests.UI.Modules sonarrEpisodes = sonarrEp?.ToList() ?? new List(); } + var plexCache = Checker.GetEpisodeCache(seriesId).ToList(); + foreach (var ep in seasons) { var requested = dbDbShow?.Episodes .Any(episodesModel => ep.number == episodesModel.EpisodeNumber && ep.season == episodesModel.SeasonNumber) ?? false; - var alreadyInPlex = Checker.IsEpisodeAvailable(seriesId.ToString(), ep.season, ep.number); + var alreadyInPlex = plexCache.Any(x => x.Episodes.EpisodeNumber == ep.number && x.Episodes.SeasonNumber == ep.season); var inSonarr = sonarrEpisodes.Any(x => x.seasonNumber == ep.season && x.episodeNumber == ep.number && x.monitored); model.Add(new EpisodeListViewModel