Fixed: Delete orphaned extra and subtitle files during housekeeping

Closes #7785
This commit is contained in:
Mark McDowall 2025-03-31 19:24:55 -07:00
parent b103005aa2
commit 23ee08a573
No known key found for this signature in database
4 changed files with 355 additions and 0 deletions

View file

@ -0,0 +1,121 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupOrphanedExtraFilesFixture : DbTest<CleanupOrphanedExtraFiles, OtherExtraFile>
{
[Test]
public void should_delete_extra_files_that_dont_have_a_coresponding_series()
{
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.With(h => h.Languages = new List<Language> { Language.English })
.BuildNew();
Db.Insert(episodeFile);
var extraFile = Builder<OtherExtraFile>.CreateNew()
.With(m => m.EpisodeFileId = episodeFile.Id)
.BuildNew();
Db.Insert(extraFile);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_extra_files_that_have_a_coresponding_series()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.With(h => h.Languages = new List<Language> { Language.English })
.BuildNew();
Db.Insert(series);
Db.Insert(episodeFile);
var extraFile = Builder<OtherExtraFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = episodeFile.Id)
.BuildNew();
Db.Insert(extraFile);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
[Test]
public void should_delete_extra_files_that_dont_have_a_coresponding_episode_file()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
Db.Insert(series);
var extraFile = Builder<OtherExtraFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = 10)
.BuildNew();
Db.Insert(extraFile);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_extra_files_that_have_a_coresponding_episode_file()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.With(h => h.Languages = new List<Language> { Language.English })
.BuildNew();
Db.Insert(series);
Db.Insert(episodeFile);
var extraFile = Builder<OtherExtraFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = episodeFile.Id)
.BuildNew();
Db.Insert(extraFile);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
[Test]
public void should_delete_extra_files_that_have_episodefileid_of_zero()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
Db.Insert(series);
var extraFile = Builder<OtherExtraFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = 0)
.BuildNew();
Db.Insert(extraFile);
Subject.Clean();
AllStoredModels.Should().HaveCount(0);
}
}
}

View file

@ -0,0 +1,126 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Housekeeping.Housekeepers;
using NzbDrone.Core.Languages;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Housekeeping.Housekeepers
{
[TestFixture]
public class CleanupOrphanedSubtitleFilesFixture : DbTest<CleanupOrphanedSubtitleFiles, SubtitleFile>
{
[Test]
public void should_delete_subtitle_files_that_dont_have_a_coresponding_series()
{
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.With(h => h.Languages = new List<Language> { Language.English })
.BuildNew();
Db.Insert(episodeFile);
var subtitleFile = Builder<SubtitleFile>.CreateNew()
.With(m => m.EpisodeFileId = episodeFile.Id)
.With(m => m.Language = Language.English)
.BuildNew();
Db.Insert(subtitleFile);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_subtitle_files_that_have_a_coresponding_series()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.With(h => h.Languages = new List<Language> { Language.English })
.BuildNew();
Db.Insert(series);
Db.Insert(episodeFile);
var subtitleFile = Builder<SubtitleFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = episodeFile.Id)
.With(m => m.Language = Language.English)
.BuildNew();
Db.Insert(subtitleFile);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
[Test]
public void should_delete_subtitle_files_that_dont_have_a_coresponding_episode_file()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
Db.Insert(series);
var subtitleFile = Builder<SubtitleFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = 10)
.With(m => m.Language = Language.English)
.BuildNew();
Db.Insert(subtitleFile);
Subject.Clean();
AllStoredModels.Should().BeEmpty();
}
[Test]
public void should_not_delete_subtitle_files_that_have_a_coresponding_episode_file()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
var episodeFile = Builder<EpisodeFile>.CreateNew()
.With(h => h.Quality = new QualityModel())
.With(h => h.Languages = new List<Language> { Language.English })
.BuildNew();
Db.Insert(series);
Db.Insert(episodeFile);
var subtitleFile = Builder<SubtitleFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = episodeFile.Id)
.With(m => m.Language = Language.English)
.BuildNew();
Db.Insert(subtitleFile);
Subject.Clean();
AllStoredModels.Should().HaveCount(1);
}
[Test]
public void should_delete_subtitle_files_that_have_episodefileid_of_zero()
{
var series = Builder<Series>.CreateNew()
.BuildNew();
Db.Insert(series);
var subtitleFile = Builder<SubtitleFile>.CreateNew()
.With(m => m.SeriesId = series.Id)
.With(m => m.EpisodeFileId = 0)
.With(m => m.Language = Language.English)
.BuildNew();
Db.Insert(subtitleFile);
Subject.Clean();
AllStoredModels.Should().HaveCount(0);
}
}
}

View file

@ -0,0 +1,54 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class CleanupOrphanedExtraFiles : IHousekeepingTask
{
private readonly IMainDatabase _database;
public CleanupOrphanedExtraFiles(IMainDatabase database)
{
_database = database;
}
public void Clean()
{
DeleteOrphanedBySeries();
DeleteOrphanedByEpisodeFile();
DeleteWhereEpisodeFileIsZero();
}
private void DeleteOrphanedBySeries()
{
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""ExtraFiles""
WHERE ""Id"" IN (
SELECT ""ExtraFiles"".""Id"" FROM ""ExtraFiles""
LEFT OUTER JOIN ""Series""
ON ""ExtraFiles"".""SeriesId"" = ""Series"".""Id""
WHERE ""Series"".""Id"" IS NULL)");
}
private void DeleteOrphanedByEpisodeFile()
{
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""ExtraFiles""
WHERE ""Id"" IN (
SELECT ""ExtraFiles"".""Id"" FROM ""ExtraFiles""
LEFT OUTER JOIN ""EpisodeFiles""
ON ""ExtraFiles"".""EpisodeFileId"" = ""EpisodeFiles"".""Id""
WHERE ""ExtraFiles"".""EpisodeFileId"" > 0
AND ""EpisodeFiles"".""Id"" IS NULL)");
}
private void DeleteWhereEpisodeFileIsZero()
{
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""ExtraFiles""
WHERE ""Id"" IN (
SELECT ""Id"" FROM ""ExtraFiles""
WHERE ""EpisodeFileId"" = 0)");
}
}
}

View file

@ -0,0 +1,54 @@
using Dapper;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Housekeeping.Housekeepers
{
public class CleanupOrphanedSubtitleFiles : IHousekeepingTask
{
private readonly IMainDatabase _database;
public CleanupOrphanedSubtitleFiles(IMainDatabase database)
{
_database = database;
}
public void Clean()
{
DeleteOrphanedBySeries();
DeleteOrphanedByEpisodeFile();
DeleteWhereEpisodeFileIsZero();
}
private void DeleteOrphanedBySeries()
{
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""SubtitleFiles""
WHERE ""Id"" IN (
SELECT ""SubtitleFiles"".""Id"" FROM ""SubtitleFiles""
LEFT OUTER JOIN ""Series""
ON ""SubtitleFiles"".""SeriesId"" = ""Series"".""Id""
WHERE ""Series"".""Id"" IS NULL)");
}
private void DeleteOrphanedByEpisodeFile()
{
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""SubtitleFiles""
WHERE ""Id"" IN (
SELECT ""SubtitleFiles"".""Id"" FROM ""SubtitleFiles""
LEFT OUTER JOIN ""EpisodeFiles""
ON ""SubtitleFiles"".""EpisodeFileId"" = ""EpisodeFiles"".""Id""
WHERE ""SubtitleFiles"".""EpisodeFileId"" > 0
AND ""EpisodeFiles"".""Id"" IS NULL)");
}
private void DeleteWhereEpisodeFileIsZero()
{
using var mapper = _database.OpenConnection();
mapper.Execute(@"DELETE FROM ""SubtitleFiles""
WHERE ""Id"" IN (
SELECT ""Id"" FROM ""SubtitleFiles""
WHERE ""EpisodeFileId"" = 0)");
}
}
}