From 6c53bf30d52b9d10aa0f65c05cb6561cfbf3f8bd Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 14 Dec 2022 17:45:43 -0800 Subject: [PATCH] Fixed: Size on disk calculation including multi-episode files multiple times Closes #5296 --- .../SeriesStatisticsFixture.cs | 21 +++++++++ .../SeriesStats/SeriesStatisticsRepository.cs | 45 ++++++++++++++----- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs index 980107237..254b4e987 100644 --- a/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs +++ b/src/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs @@ -4,6 +4,7 @@ using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Languages; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; @@ -180,5 +181,25 @@ namespace NzbDrone.Core.Test.SeriesStatsTests stats.Should().HaveCount(1); stats.First().SizeOnDisk.Should().Be(_episodeFile.Size); } + + [Test] + public void should_not_duplicate_size_for_multi_episode_files() + { + GivenEpisodeWithFile(); + GivenEpisode(); + GivenEpisodeFile(); + + var episode2 = _episode.JsonClone(); + + episode2.Id = 0; + episode2.EpisodeNumber += 1; + + Db.Insert(episode2); + + var stats = Subject.SeriesStatistics(); + + stats.Should().HaveCount(1); + stats.First().SizeOnDisk.Should().Be(_episodeFile.Size); + } } } diff --git a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index f267f369f..fd854d75a 100644 --- a/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/src/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; @@ -17,7 +16,8 @@ namespace NzbDrone.Core.SeriesStats public class SeriesStatisticsRepository : ISeriesStatisticsRepository { - private const string _selectTemplate = "SELECT /**select**/ FROM Episodes /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; + private const string _selectEpisodesTemplate = "SELECT /**select**/ FROM Episodes /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; + private const string _selectEpisodeFilesTemplate = "SELECT /**select**/ FROM EpisodeFiles /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/"; private readonly IMainDatabase _database; @@ -29,18 +29,34 @@ namespace NzbDrone.Core.SeriesStats public List SeriesStatistics() { var time = DateTime.UtcNow; - return Query(Builder(time)); + return MapResults(Query(EpisodesBuilder(time), _selectEpisodesTemplate), + Query(EpisodeFilesBuilder(), _selectEpisodeFilesTemplate)); } public List SeriesStatistics(int seriesId) { var time = DateTime.UtcNow; - return Query(Builder(time).Where(x => x.SeriesId == seriesId)); + + return MapResults(Query(EpisodesBuilder(time).Where(x => x.SeriesId == seriesId), _selectEpisodesTemplate), + Query(EpisodeFilesBuilder().Where(x => x.SeriesId == seriesId), _selectEpisodeFilesTemplate)); } - private List Query(SqlBuilder builder) + private List MapResults(List episodesResult, List filesResult) { - var sql = builder.AddTemplate(_selectTemplate).LogQuery(); + episodesResult.ForEach(e => + { + var file = filesResult.SingleOrDefault(f => f.SeriesId == e.SeriesId & f.SeasonNumber == e.SeasonNumber); + + e.SizeOnDisk = file?.SizeOnDisk ?? 0; + e.ReleaseGroupsString = file?.ReleaseGroupsString; + }); + + return episodesResult; + } + + private List Query(SqlBuilder builder, string template) + { + var sql = builder.AddTemplate(template).LogQuery(); using (var conn = _database.OpenConnection()) { @@ -48,24 +64,33 @@ namespace NzbDrone.Core.SeriesStats } } - private SqlBuilder Builder(DateTime currentDate) + private SqlBuilder EpisodesBuilder(DateTime currentDate) { var parameters = new DynamicParameters(); parameters.Add("currentDate", currentDate, null); + return new SqlBuilder() .Select(@"Episodes.SeriesId AS SeriesId, Episodes.SeasonNumber, - SUM(COALESCE(EpisodeFiles.Size, 0)) AS SizeOnDisk, - GROUP_CONCAT(EpisodeFiles.ReleaseGroup, '|') AS ReleaseGroupsString, COUNT(*) AS TotalEpisodeCount, SUM(CASE WHEN AirdateUtc <= @currentDate OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS AvailableEpisodeCount, SUM(CASE WHEN (Monitored = 1 AND AirdateUtc <= @currentDate) OR EpisodeFileId > 0 THEN 1 ELSE 0 END) AS EpisodeCount, 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", parameters) - .LeftJoin((t, f) => t.EpisodeFileId == f.Id) .GroupBy(x => x.SeriesId) .GroupBy(x => x.SeasonNumber); } + + private SqlBuilder EpisodeFilesBuilder() + { + return new SqlBuilder() + .Select(@"SeriesId, + SeasonNumber, + SUM(COALESCE(Size, 0)) AS SizeOnDisk, + GROUP_CONCAT(ReleaseGroup, '|') AS ReleaseGroupsString") + .GroupBy(x => x.SeriesId) + .GroupBy(x => x.SeasonNumber); + } } }