From 0dccc7e91ee6d090439ca714ed988b1c96e65fa7 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Fri, 10 May 2019 23:11:03 +0200 Subject: [PATCH] Fixed: Various performance improvements for large collections --- .../UpdateCleanTitleForSeriesFixture.cs | 51 +++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../Housekeepers/UpdateCleanTitleForSeries.cs | 8 ++- .../SeriesStats/SeriesStatisticsRepository.cs | 10 ++-- .../NzbDronePersistentConnection.cs | 35 ++++++++++++- .../SonarrRestModuleWithSignalR.cs | 8 +++ 6 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 src/NzbDrone.Core.Test/Housekeeping/Housekeepers/UpdateCleanTitleForSeriesFixture.cs diff --git a/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/UpdateCleanTitleForSeriesFixture.cs b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/UpdateCleanTitleForSeriesFixture.cs new file mode 100644 index 000000000..99fdb9d70 --- /dev/null +++ b/src/NzbDrone.Core.Test/Housekeeping/Housekeepers/UpdateCleanTitleForSeriesFixture.cs @@ -0,0 +1,51 @@ +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Housekeeping.Housekeepers; +using NzbDrone.Core.Profiles.Releases; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.Housekeeping.Housekeepers +{ + [TestFixture] + public class UpdateCleanTitleForSeriesFixture : CoreTest + { + [Test] + public void should_update_clean_title() + { + var series = Builder.CreateNew() + .With(s => s.Title = "Full Title") + .With(s => s.CleanTitle = "unclean") + .Build(); + + Mocker.GetMock() + .Setup(s => s.All()) + .Returns(new[] { series }); + + Subject.Clean(); + + Mocker.GetMock() + .Verify(v => v.Update(It.Is(s => s.CleanTitle == "fulltitle")), Times.Once()); + } + + [Test] + public void should_not_update_unchanged_title() + { + var series = Builder.CreateNew() + .With(s => s.Title = "Full Title") + .With(s => s.CleanTitle = "fulltitle") + .Build(); + + Mocker.GetMock() + .Setup(s => s.All()) + .Returns(new[] { series }); + + Subject.Clean(); + + Mocker.GetMock() + .Verify(v => v.Update(It.Is(s => s.CleanTitle == "fulltitle")), Times.Never()); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 46893e154..c8164a548 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -263,6 +263,7 @@ + diff --git a/src/NzbDrone.Core/Housekeeping/Housekeepers/UpdateCleanTitleForSeries.cs b/src/NzbDrone.Core/Housekeeping/Housekeepers/UpdateCleanTitleForSeries.cs index 16b19c505..91ab10fb3 100644 --- a/src/NzbDrone.Core/Housekeeping/Housekeepers/UpdateCleanTitleForSeries.cs +++ b/src/NzbDrone.Core/Housekeeping/Housekeepers/UpdateCleanTitleForSeries.cs @@ -19,8 +19,12 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers series.ForEach(s => { - s.CleanTitle = s.CleanTitle.CleanSeriesTitle(); - _seriesRepository.Update(s); + var cleanTitle = s.Title.CleanSeriesTitle(); + if (s.CleanTitle != cleanTitle) + { + s.CleanTitle = cleanTitle; + _seriesRepository.Update(s); + } }); } } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index e5d50a6bb..699531198 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Core.SeriesStats mapper.AddParameter("currentDate", DateTime.UtcNow); var sb = new StringBuilder(); - sb.AppendLine(GetSelectClause()); + sb.AppendLine(GetSelectClause(false)); sb.AppendLine(GetEpisodeFilesJoin()); sb.AppendLine(GetGroupByClause()); var queryText = sb.ToString(); @@ -43,16 +43,15 @@ namespace NzbDrone.Core.SeriesStats mapper.AddParameter("seriesId", seriesId); var sb = new StringBuilder(); - sb.AppendLine(GetSelectClause()); + sb.AppendLine(GetSelectClause(true)); sb.AppendLine(GetEpisodeFilesJoin()); - sb.AppendLine("WHERE Episodes.SeriesId = @seriesId"); sb.AppendLine(GetGroupByClause()); var queryText = sb.ToString(); return mapper.Query(queryText); } - private string GetSelectClause() + private string GetSelectClause(bool series) { return @"SELECT Episodes.*, SUM(EpisodeFiles.Size) as SizeOnDisk FROM (SELECT @@ -64,7 +63,8 @@ namespace NzbDrone.Core.SeriesStats SUM(CASE WHEN EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeFileCount, MIN(CASE WHEN AirDateUtc < @currentDate OR EpisodeFileId > 0 OR Monitored = 0 THEN NULL ELSE AirDateUtc END) AS NextAiringString, MAX(CASE WHEN AirDateUtc >= @currentDate OR EpisodeFileId = 0 AND Monitored = 0 THEN NULL ELSE AirDateUtc END) AS PreviousAiringString - FROM Episodes + FROM Episodes" + + (series ? " WHERE Episodes.SeriesId = @seriesId" : "") + @" GROUP BY Episodes.SeriesId, Episodes.SeasonNumber) as Episodes"; } diff --git a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs index ff1a2dca8..d0a57bb79 100644 --- a/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs +++ b/src/NzbDrone.SignalR/NzbDronePersistentConnection.cs @@ -12,6 +12,7 @@ namespace NzbDrone.SignalR { public interface IBroadcastSignalRMessage { + bool IsConnected { get; } void BroadcastMessage(SignalRMessage message); } @@ -20,7 +21,8 @@ namespace NzbDrone.SignalR private IPersistentConnectionContext Context => ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); private static string API_KEY; - private readonly Dictionary _messageHistory; + private readonly Dictionary _messageHistory; + private HashSet _connections = new HashSet(); public NzbDronePersistentConnection(IConfigFileProvider configFileProvider) { @@ -28,6 +30,17 @@ namespace NzbDrone.SignalR _messageHistory = new Dictionary(); } + public bool IsConnected + { + get + { + lock (_connections) + { + return _connections.Count != 0; + } + } + } + public void BroadcastMessage(SignalRMessage message) { @@ -59,14 +72,34 @@ namespace NzbDrone.SignalR protected override Task OnConnected(IRequest request, string connectionId) { + lock (_connections) + { + _connections.Add(connectionId); + } + return SendVersion(connectionId); } protected override Task OnReconnected(IRequest request, string connectionId) { + lock (_connections) + { + _connections.Add(connectionId); + } + return SendVersion(connectionId); } + protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) + { + lock (_connections) + { + _connections.Remove(connectionId); + } + + return base.OnDisconnected(request, connectionId, stopCalled); + } + private Task SendVersion(string connectionId) { return Context.Connection.Send(connectionId, new SignalRMessage diff --git a/src/Sonarr.Http/SonarrRestModuleWithSignalR.cs b/src/Sonarr.Http/SonarrRestModuleWithSignalR.cs index 11fc74f2c..7774dc395 100644 --- a/src/Sonarr.Http/SonarrRestModuleWithSignalR.cs +++ b/src/Sonarr.Http/SonarrRestModuleWithSignalR.cs @@ -25,6 +25,8 @@ namespace Sonarr.Http public void Handle(ModelEvent message) { + if (!_signalRBroadcaster.IsConnected) return; + if (message.Action == ModelAction.Deleted || message.Action == ModelAction.Sync) { BroadcastResourceChange(message.Action); @@ -35,6 +37,8 @@ namespace Sonarr.Http protected void BroadcastResourceChange(ModelAction action, int id) { + if (!_signalRBroadcaster.IsConnected) return; + if (action == ModelAction.Deleted) { BroadcastResourceChange(action, new TResource {Id = id}); @@ -48,6 +52,8 @@ namespace Sonarr.Http protected void BroadcastResourceChange(ModelAction action, TResource resource) { + if (!_signalRBroadcaster.IsConnected) return; + if (GetType().Namespace.Contains("V3")) { var signalRMessage = new SignalRMessage @@ -63,6 +69,8 @@ namespace Sonarr.Http protected void BroadcastResourceChange(ModelAction action) { + if (!_signalRBroadcaster.IsConnected) return; + if (GetType().Namespace.Contains("V3")) { var signalRMessage = new SignalRMessage