mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 02:38:55 -04:00
fixed cf upgrade searches skipping grace period
This commit is contained in:
1 parent
8f2454dc8a
commit
a9ff7283bd
11 files changed
+210
-16
No files matched your search
+10
-5
@@ -452,10 +452,11 @@ public class CustomFormatScoreSyncerTests : IDisposable
|
||||
new SearchableSeries { Id = 10, Title = "Test Series", QualityProfileId = 1, Monitored = true }
|
||||
]));
|
||||
|
||||
DateTimeOffset airDateUtc = new(2026, 1, 15, 0, 0, 0, TimeSpan.Zero);
|
||||
_sonarrClient
|
||||
.GetEpisodesAsync(sonarrInstance, 10)
|
||||
.Returns([
|
||||
new SearchableEpisode { Id = 100, SeasonNumber = 3, EpisodeNumber = 1, EpisodeFileId = 500, HasFile = true, Monitored = true }
|
||||
new SearchableEpisode { Id = 100, SeasonNumber = 3, EpisodeNumber = 1, EpisodeFileId = 500, HasFile = true, Monitored = true, AirDateUtc = airDateUtc }
|
||||
]);
|
||||
|
||||
_sonarrClient
|
||||
@@ -469,10 +470,11 @@ public class CustomFormatScoreSyncerTests : IDisposable
|
||||
// Act
|
||||
await sut.ExecuteAsync();
|
||||
|
||||
// Assert — the synced entry carries the episode's season number
|
||||
// Assert: the synced entry carries the episode's season number and air date
|
||||
List<CustomFormatScoreEntry> entries = await _fixture.EventsContext.CustomFormatScoreEntries.ToListAsync();
|
||||
entries.ShouldHaveSingleItem();
|
||||
entries[0].SeasonNumber.ShouldBe(3);
|
||||
entries[0].AirDateUtc.ShouldBe(airDateUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -489,7 +491,7 @@ public class CustomFormatScoreSyncerTests : IDisposable
|
||||
UseCustomFormatScore = true
|
||||
});
|
||||
|
||||
// Pre-existing entry from before SeasonNumber was tracked
|
||||
// Pre-existing entry from before SeasonNumber/AirDateUtc were tracked
|
||||
_fixture.EventsContext.CustomFormatScoreEntries.Add(new CustomFormatScoreEntry
|
||||
{
|
||||
ArrInstanceId = sonarrInstance.Id,
|
||||
@@ -502,6 +504,7 @@ public class CustomFormatScoreSyncerTests : IDisposable
|
||||
CutoffScore = 500,
|
||||
QualityProfileName = "HD",
|
||||
SeasonNumber = null,
|
||||
AirDateUtc = null,
|
||||
LastSyncedAt = DateTime.UtcNow.AddHours(-1)
|
||||
});
|
||||
await _fixture.DataContext.SaveChangesAsync();
|
||||
@@ -517,10 +520,11 @@ public class CustomFormatScoreSyncerTests : IDisposable
|
||||
new SearchableSeries { Id = 10, Title = "Test Series", QualityProfileId = 1, Monitored = true }
|
||||
]));
|
||||
|
||||
DateTimeOffset airDateUtc = new(2026, 2, 20, 0, 0, 0, TimeSpan.Zero);
|
||||
_sonarrClient
|
||||
.GetEpisodesAsync(sonarrInstance, 10)
|
||||
.Returns([
|
||||
new SearchableEpisode { Id = 100, SeasonNumber = 2, EpisodeNumber = 1, EpisodeFileId = 500, HasFile = true, Monitored = true }
|
||||
new SearchableEpisode { Id = 100, SeasonNumber = 2, EpisodeNumber = 1, EpisodeFileId = 500, HasFile = true, Monitored = true, AirDateUtc = airDateUtc }
|
||||
]);
|
||||
|
||||
_sonarrClient
|
||||
@@ -534,10 +538,11 @@ public class CustomFormatScoreSyncerTests : IDisposable
|
||||
// Act
|
||||
await sut.ExecuteAsync();
|
||||
|
||||
// Assert — the next sync backfills the season number on the existing row
|
||||
// Assert: the next sync backfills the season number and air date on the existing row
|
||||
List<CustomFormatScoreEntry> entries = await _fixture.EventsContext.CustomFormatScoreEntries.ToListAsync();
|
||||
entries.ShouldHaveSingleItem();
|
||||
entries[0].SeasonNumber.ShouldBe(2);
|
||||
entries[0].AirDateUtc.ShouldBe(airDateUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -3532,6 +3532,7 @@ public class SeekerTests : IDisposable
|
||||
EpisodeId = 100,
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 1,
|
||||
AirDateUtc = _fixture.TimeProvider.GetUtcNow().AddDays(-30),
|
||||
CurrentScore = 10,
|
||||
CutoffScore = 50,
|
||||
Title = "Below CF Cutoff"
|
||||
@@ -3543,6 +3544,7 @@ public class SeekerTests : IDisposable
|
||||
EpisodeId = 101,
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 2,
|
||||
AirDateUtc = _fixture.TimeProvider.GetUtcNow().AddDays(-30),
|
||||
CurrentScore = 60,
|
||||
CutoffScore = 50,
|
||||
Title = "Above CF Cutoff"
|
||||
@@ -3619,6 +3621,7 @@ public class SeekerTests : IDisposable
|
||||
EpisodeId = 100,
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 1,
|
||||
AirDateUtc = _fixture.TimeProvider.GetUtcNow().AddDays(-30),
|
||||
CurrentScore = 10,
|
||||
CutoffScore = 50,
|
||||
Title = "Below CF Cutoff"
|
||||
@@ -4208,6 +4211,141 @@ public class SeekerTests : IDisposable
|
||||
.SearchItemAsync(Arg.Any<ArrInstance>(), Arg.Any<SearchItem>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_Sonarr_CustomFormatScoreEntryWithNullAirDate_IsNotACandidate()
|
||||
{
|
||||
// Arrange: a CF score row synced before the air date column existed carries no air date
|
||||
var config = await _fixture.DataContext.SeekerConfigs.FirstAsync();
|
||||
config.SearchEnabled = true;
|
||||
config.ProactiveSearchEnabled = true;
|
||||
await _fixture.DataContext.SaveChangesAsync();
|
||||
await _fixture.EventsContext.SaveChangesAsync();
|
||||
|
||||
var sonarrInstance = TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
||||
|
||||
_fixture.DataContext.SeekerInstanceConfigs.Add(new SeekerInstanceConfig
|
||||
{
|
||||
ArrInstanceId = sonarrInstance.Id,
|
||||
ArrInstance = sonarrInstance,
|
||||
Enabled = true,
|
||||
MonitoredOnly = false,
|
||||
UseCutoff = false,
|
||||
UseCustomFormatScore = true
|
||||
});
|
||||
|
||||
_fixture.EventsContext.CustomFormatScoreEntries.Add(new CustomFormatScoreEntry
|
||||
{
|
||||
ArrInstanceId = sonarrInstance.Id,
|
||||
ExternalItemId = 10,
|
||||
EpisodeId = 100,
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 1,
|
||||
AirDateUtc = null,
|
||||
CurrentScore = 10,
|
||||
CutoffScore = 50,
|
||||
Title = "No Air Date Yet"
|
||||
});
|
||||
await _fixture.DataContext.SaveChangesAsync();
|
||||
await _fixture.EventsContext.SaveChangesAsync();
|
||||
|
||||
var mockArrClient = Substitute.For<IArrClient>();
|
||||
|
||||
_fixture.ArrQueueReader
|
||||
.ReadAllAsync(mockArrClient, Arg.Any<ArrInstance>())
|
||||
.Returns([]);
|
||||
|
||||
_sonarrClient
|
||||
.StreamAllSeriesAsync(sonarrInstance, Arg.Any<CancellationToken>())
|
||||
.Returns(ToAsyncEnumerable<SearchableSeries>([
|
||||
new SearchableSeries { Id = 10, Title = "Test Series", Status = "continuing", Monitored = true, Tags = [], Statistics = new SeriesStatistics { EpisodeCount = 10, EpisodeFileCount = 10 } }
|
||||
]));
|
||||
|
||||
_sonarrClient
|
||||
.GetQualityProfilesAsync(sonarrInstance)
|
||||
.Returns([new ArrQualityProfile { Id = 0, UpgradeAllowed = true }]);
|
||||
|
||||
_fixture.ArrClientFactory
|
||||
.GetClient(InstanceType.Sonarr, Arg.Any<float>())
|
||||
.Returns(mockArrClient);
|
||||
|
||||
var sut = CreateSut();
|
||||
|
||||
// Act
|
||||
await sut.ExecuteAsync();
|
||||
|
||||
// Assert: no air date to check against the grace period, so nothing was searched
|
||||
await mockArrClient.DidNotReceive()
|
||||
.SearchItemAsync(Arg.Any<ArrInstance>(), Arg.Any<SearchItem>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_Sonarr_CustomFormatScoreEntryWithinGracePeriod_IsNotACandidate()
|
||||
{
|
||||
// Arrange: grace period of 24 hours, entry's episode aired 1 hour ago
|
||||
var config = await _fixture.DataContext.SeekerConfigs.FirstAsync();
|
||||
config.SearchEnabled = true;
|
||||
config.ProactiveSearchEnabled = true;
|
||||
config.PostReleaseGraceHours = 24;
|
||||
await _fixture.DataContext.SaveChangesAsync();
|
||||
await _fixture.EventsContext.SaveChangesAsync();
|
||||
|
||||
var sonarrInstance = TestDataContextFactory.AddSonarrInstance(_fixture.DataContext);
|
||||
|
||||
_fixture.DataContext.SeekerInstanceConfigs.Add(new SeekerInstanceConfig
|
||||
{
|
||||
ArrInstanceId = sonarrInstance.Id,
|
||||
ArrInstance = sonarrInstance,
|
||||
Enabled = true,
|
||||
MonitoredOnly = false,
|
||||
UseCutoff = false,
|
||||
UseCustomFormatScore = true
|
||||
});
|
||||
|
||||
_fixture.EventsContext.CustomFormatScoreEntries.Add(new CustomFormatScoreEntry
|
||||
{
|
||||
ArrInstanceId = sonarrInstance.Id,
|
||||
ExternalItemId = 10,
|
||||
EpisodeId = 100,
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 1,
|
||||
AirDateUtc = _fixture.TimeProvider.GetUtcNow().AddHours(-1),
|
||||
CurrentScore = 10,
|
||||
CutoffScore = 50,
|
||||
Title = "Recently Aired"
|
||||
});
|
||||
await _fixture.DataContext.SaveChangesAsync();
|
||||
await _fixture.EventsContext.SaveChangesAsync();
|
||||
|
||||
var mockArrClient = Substitute.For<IArrClient>();
|
||||
|
||||
_fixture.ArrQueueReader
|
||||
.ReadAllAsync(mockArrClient, Arg.Any<ArrInstance>())
|
||||
.Returns([]);
|
||||
|
||||
_sonarrClient
|
||||
.StreamAllSeriesAsync(sonarrInstance, Arg.Any<CancellationToken>())
|
||||
.Returns(ToAsyncEnumerable<SearchableSeries>([
|
||||
new SearchableSeries { Id = 10, Title = "Test Series", Status = "continuing", Monitored = true, Tags = [], Statistics = new SeriesStatistics { EpisodeCount = 10, EpisodeFileCount = 10 } }
|
||||
]));
|
||||
|
||||
_sonarrClient
|
||||
.GetQualityProfilesAsync(sonarrInstance)
|
||||
.Returns([new ArrQualityProfile { Id = 0, UpgradeAllowed = true }]);
|
||||
|
||||
_fixture.ArrClientFactory
|
||||
.GetClient(InstanceType.Sonarr, Arg.Any<float>())
|
||||
.Returns(mockArrClient);
|
||||
|
||||
var sut = CreateSut();
|
||||
|
||||
// Act
|
||||
await sut.ExecuteAsync();
|
||||
|
||||
// Assert: episode is within the grace period, so nothing was searched
|
||||
await mockArrClient.DidNotReceive()
|
||||
.SearchItemAsync(Arg.Any<ArrInstance>(), Arg.Any<SearchItem>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_Sonarr_CustomFormatScoreEntryWithSeasonNumber_SearchesThatSeasonWithCustomFormatReason()
|
||||
{
|
||||
@@ -4237,6 +4375,7 @@ public class SeekerTests : IDisposable
|
||||
EpisodeId = 100,
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 3,
|
||||
AirDateUtc = _fixture.TimeProvider.GetUtcNow().AddDays(-30),
|
||||
CurrentScore = 10,
|
||||
CutoffScore = 50,
|
||||
Title = "Below CF Cutoff"
|
||||
@@ -4321,6 +4460,7 @@ public class SeekerTests : IDisposable
|
||||
EpisodeId = 100,
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 1,
|
||||
AirDateUtc = _fixture.TimeProvider.GetUtcNow().AddDays(-30),
|
||||
CurrentScore = 10,
|
||||
CutoffScore = 50,
|
||||
Title = "Below CF Cutoff"
|
||||
|
||||
@@ -264,7 +264,7 @@ public sealed class CustomFormatScoreSyncer : IHandler
|
||||
string profileName = profile?.Name ?? "Unknown";
|
||||
|
||||
existingEntries.TryGetValue(movie.Id, out CustomFormatScoreEntry? existing);
|
||||
UpsertCustomFormatScore(existing, arrInstance.Id, movie.Id, 0, InstanceType.Radarr, movie.Title, fileId, cfScore, cutoffScore, profileName, movie.Monitored, now, seasonNumber: null);
|
||||
UpsertCustomFormatScore(existing, arrInstance.Id, movie.Id, 0, InstanceType.Radarr, movie.Title, fileId, cfScore, cutoffScore, profileName, movie.Monitored, now, seasonNumber: null, airDateUtc: null);
|
||||
|
||||
synced++;
|
||||
}
|
||||
@@ -417,7 +417,7 @@ public sealed class CustomFormatScoreSyncer : IHandler
|
||||
string title = $"{series.Title} S{episode.SeasonNumber:D2}E{episode.EpisodeNumber:D2}";
|
||||
|
||||
existingEntries.TryGetValue((series.Id, episode.Id), out CustomFormatScoreEntry? existing);
|
||||
UpsertCustomFormatScore(existing, arrInstance.Id, series.Id, episode.Id, InstanceType.Sonarr, title, fileId, cfScore, cutoffScore, profileName, isMonitored, now, episode.SeasonNumber);
|
||||
UpsertCustomFormatScore(existing, arrInstance.Id, series.Id, episode.Id, InstanceType.Sonarr, title, fileId, cfScore, cutoffScore, profileName, isMonitored, now, episode.SeasonNumber, episode.AirDateUtc);
|
||||
|
||||
synced++;
|
||||
}
|
||||
@@ -513,7 +513,8 @@ public sealed class CustomFormatScoreSyncer : IHandler
|
||||
string profileName,
|
||||
bool isMonitored,
|
||||
DateTimeOffset now,
|
||||
int? seasonNumber)
|
||||
int? seasonNumber,
|
||||
DateTimeOffset? airDateUtc)
|
||||
{
|
||||
if (existing is not null)
|
||||
{
|
||||
@@ -545,6 +546,7 @@ public sealed class CustomFormatScoreSyncer : IHandler
|
||||
existing.IsMonitored = isMonitored;
|
||||
existing.LastSyncedAt = now;
|
||||
existing.SeasonNumber = seasonNumber;
|
||||
existing.AirDateUtc = airDateUtc;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -562,6 +564,7 @@ public sealed class CustomFormatScoreSyncer : IHandler
|
||||
IsMonitored = isMonitored,
|
||||
LastSyncedAt = now,
|
||||
SeasonNumber = seasonNumber,
|
||||
AirDateUtc = airDateUtc,
|
||||
});
|
||||
|
||||
// Record initial score in history
|
||||
|
||||
@@ -838,12 +838,14 @@ public sealed class Seeker : IHandler
|
||||
|
||||
if (instanceConfig.UseCustomFormatScore)
|
||||
{
|
||||
// Rows synced before the season column existed carry no season until the next sync.
|
||||
// Rows synced before the season/air date columns existed carry neither until the next sync.
|
||||
List<CustomFormatScoreEntry> belowCutoff = await _eventsContext.CustomFormatScoreEntries
|
||||
.AsNoTracking()
|
||||
.Where(e => e.ArrInstanceId == arrInstance.Id
|
||||
&& e.ItemType == InstanceType.Sonarr
|
||||
&& e.SeasonNumber != null
|
||||
&& e.AirDateUtc != null
|
||||
&& e.AirDateUtc <= graceCutoff
|
||||
&& e.CurrentScore < e.CutoffScore
|
||||
&& (!instanceConfig.MonitoredOnly || e.IsMonitored))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
+6
-2
@@ -13,8 +13,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
namespace Cleanuparr.Persistence.Postgres.Migrations.Events
|
||||
{
|
||||
[DbContext(typeof(EventsContext))]
|
||||
[Migration("20261017120323_AddCustomFormatScoreSeasonNumber")]
|
||||
partial class AddCustomFormatScoreSeasonNumber
|
||||
[Migration("20261018093113_AddCustomFormatScoreSeasonAndAirDate")]
|
||||
partial class AddCustomFormatScoreSeasonAndAirDate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -304,6 +304,10 @@ namespace Cleanuparr.Persistence.Postgres.Migrations.Events
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTimeOffset?>("AirDateUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("air_date_utc");
|
||||
|
||||
b.Property<Guid>("ArrInstanceId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("arr_instance_id");
|
||||
+15
-2
@@ -1,15 +1,23 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Cleanuparr.Persistence.Postgres.Migrations.Events
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCustomFormatScoreSeasonNumber : Migration
|
||||
public partial class AddCustomFormatScoreSeasonAndAirDate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "air_date_utc",
|
||||
schema: "events",
|
||||
table: "custom_format_score_entries",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "season_number",
|
||||
schema: "events",
|
||||
@@ -21,6 +29,11 @@ namespace Cleanuparr.Persistence.Postgres.Migrations.Events
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "air_date_utc",
|
||||
schema: "events",
|
||||
table: "custom_format_score_entries");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "season_number",
|
||||
schema: "events",
|
||||
+4
@@ -301,6 +301,10 @@ namespace Cleanuparr.Persistence.Postgres.Migrations.Events
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTimeOffset?>("AirDateUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("air_date_utc");
|
||||
|
||||
b.Property<Guid>("ArrInstanceId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("arr_instance_id");
|
||||
|
||||
+6
-2
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
namespace Cleanuparr.Persistence.Migrations.Events
|
||||
{
|
||||
[DbContext(typeof(EventsContext))]
|
||||
[Migration("20261017120316_AddCustomFormatScoreSeasonNumber")]
|
||||
partial class AddCustomFormatScoreSeasonNumber
|
||||
[Migration("20261018093103_AddCustomFormatScoreSeasonAndAirDate")]
|
||||
partial class AddCustomFormatScoreSeasonAndAirDate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@@ -299,6 +299,10 @@ namespace Cleanuparr.Persistence.Migrations.Events
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AirDateUtc")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("air_date_utc");
|
||||
|
||||
b.Property<Guid>("ArrInstanceId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("arr_instance_id");
|
||||
+11
-1
@@ -5,11 +5,17 @@
|
||||
namespace Cleanuparr.Persistence.Migrations.Events
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCustomFormatScoreSeasonNumber : Migration
|
||||
public partial class AddCustomFormatScoreSeasonAndAirDate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "air_date_utc",
|
||||
table: "custom_format_score_entries",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "season_number",
|
||||
table: "custom_format_score_entries",
|
||||
@@ -20,6 +26,10 @@ namespace Cleanuparr.Persistence.Migrations.Events
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "air_date_utc",
|
||||
table: "custom_format_score_entries");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "season_number",
|
||||
table: "custom_format_score_entries");
|
||||
+4
@@ -296,6 +296,10 @@ namespace Cleanuparr.Persistence.Migrations.Events
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AirDateUtc")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("air_date_utc");
|
||||
|
||||
b.Property<Guid>("ArrInstanceId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("arr_instance_id");
|
||||
|
||||
@@ -80,4 +80,9 @@ public sealed record CustomFormatScoreEntry
|
||||
/// Sonarr season of the episode; null for Radarr and for rows not yet re-synced.
|
||||
/// </summary>
|
||||
public int? SeasonNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sonarr episode air date; null for Radarr and for rows not yet re-synced.
|
||||
/// </summary>
|
||||
public DateTimeOffset? AirDateUtc { get; set; }
|
||||
}
|
||||
Reference in new issue
Block a user