mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 02:38:55 -04:00
changed seeker order to arr last search
This commit is contained in:
1 parent
0c9736493f
commit
41d8c28e93
5 files changed
+139
-4
No files matched your search
@@ -25,4 +25,9 @@ public sealed record SearchableMovie
|
||||
public DateTimeOffset? PhysicalRelease { get; init; }
|
||||
|
||||
public DateTimeOffset? InCinemas { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Set when a search reached an indexer; RSS grabs leave it alone.
|
||||
/// </summary>
|
||||
public DateTimeOffset? LastSearchTime { get; init; }
|
||||
}
|
||||
+112
-2
@@ -80,7 +80,8 @@ public class SeekerUnitEvaluatorTests : IDisposable
|
||||
bool hasFile = false,
|
||||
int qualityProfileId = 1,
|
||||
DateTimeOffset? digitalRelease = null,
|
||||
DateTimeOffset? added = null)
|
||||
DateTimeOffset? added = null,
|
||||
DateTimeOffset? lastSearchTime = null)
|
||||
{
|
||||
return new SearchableMovie
|
||||
{
|
||||
@@ -93,6 +94,7 @@ public class SeekerUnitEvaluatorTests : IDisposable
|
||||
QualityProfileId = qualityProfileId,
|
||||
DigitalRelease = digitalRelease,
|
||||
Added = added,
|
||||
LastSearchTime = lastSearchTime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ public class SeekerUnitEvaluatorTests : IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
private SearchableEpisode Episode(long id, long seriesId, int season = 1, int number = 1, DateTimeOffset? airDate = null, bool noAirDate = false)
|
||||
private SearchableEpisode Episode(long id, long seriesId, int season = 1, int number = 1, DateTimeOffset? airDate = null, bool noAirDate = false, DateTimeOffset? lastSearchTime = null)
|
||||
{
|
||||
return new SearchableEpisode
|
||||
{
|
||||
@@ -143,6 +145,7 @@ public class SeekerUnitEvaluatorTests : IDisposable
|
||||
EpisodeNumber = number,
|
||||
Monitored = true,
|
||||
AirDateUtc = noAirDate ? null : airDate ?? _timeProvider.GetUtcNow().AddDays(-30),
|
||||
LastSearchTime = lastSearchTime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1042,6 +1045,97 @@ public class SeekerUnitEvaluatorTests : IDisposable
|
||||
candidates[1].SeasonNumber.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectNext_OldestSearchFirst_OrdersMoviesByTheArrLastSearchTime()
|
||||
{
|
||||
// Arrange
|
||||
DateTimeOffset now = _timeProvider.GetUtcNow();
|
||||
SetupRadarr(
|
||||
[
|
||||
Movie(1, lastSearchTime: now.AddDays(-1)),
|
||||
Movie(2),
|
||||
Movie(3, lastSearchTime: now.AddDays(-5)),
|
||||
]);
|
||||
SeekerEvaluation evaluation = await EvaluateRadarrAsync();
|
||||
|
||||
// Act
|
||||
List<SeekerSearchCandidate> candidates = SeekerUnitEvaluator.SelectNext(evaluation, SelectionStrategy.OldestSearchFirst, 3);
|
||||
|
||||
// Assert
|
||||
candidates.Select(c => c.ItemId).ShouldBe([2L, 3L, 1L]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectNext_OldestSearchFirst_RanksSeriesByTheirLatestEpisodeSearch()
|
||||
{
|
||||
// Arrange
|
||||
DateTimeOffset now = _timeProvider.GetUtcNow();
|
||||
SetupSonarr([Series(1), Series(2)],
|
||||
[
|
||||
Episode(10, 1, season: 1, lastSearchTime: now.AddDays(-10)),
|
||||
Episode(11, 1, season: 2, lastSearchTime: now.AddDays(-1)),
|
||||
Episode(20, 2, season: 1, lastSearchTime: now.AddDays(-5)),
|
||||
]);
|
||||
SeekerEvaluation evaluation = await EvaluateSonarrAsync();
|
||||
|
||||
// Act
|
||||
List<SeekerSearchCandidate> candidates = SeekerUnitEvaluator.SelectNext(evaluation, SelectionStrategy.OldestSearchFirst, 1);
|
||||
|
||||
// Assert
|
||||
candidates.ShouldHaveSingleItem().ItemId.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SelectNext_OldestSearchFirst_CycleSearchNewerThanTheArrTimeWins()
|
||||
{
|
||||
// Arrange
|
||||
DateTimeOffset now = _timeProvider.GetUtcNow();
|
||||
SetupSonarr([Series(1), Series(2)],
|
||||
[
|
||||
Episode(10, 1, season: 1, lastSearchTime: now.AddDays(-10)),
|
||||
Episode(11, 1, season: 2, lastSearchTime: now.AddDays(-10)),
|
||||
Episode(20, 2, season: 1, lastSearchTime: now.AddDays(-5)),
|
||||
]);
|
||||
SeekerEvaluation evaluation = await EvaluateSonarrAsync(
|
||||
history: [History(_sonarr, 1, now.AddDays(-1), seasonNumber: 1)]);
|
||||
|
||||
// Act
|
||||
List<SeekerSearchCandidate> candidates = SeekerUnitEvaluator.SelectNext(evaluation, SelectionStrategy.OldestSearchFirst, 1);
|
||||
|
||||
// Assert
|
||||
candidates.ShouldHaveSingleItem().ItemId.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EvaluateAsync_Sonarr_CustomFormatOnlyEpisode_TakesTheCachedLastSearchTime()
|
||||
{
|
||||
// Arrange
|
||||
DateTimeOffset lastSearchTime = _timeProvider.GetUtcNow().AddDays(-3);
|
||||
SetupSonarr([Series(1)], []);
|
||||
_sonarrClient.GetQualityProfilesAsync(Arg.Any<ArrInstance>()).Returns([]);
|
||||
_eventsContext.CustomFormatScoreEntries.Add(new CustomFormatScoreEntry
|
||||
{
|
||||
ArrInstanceId = _sonarr.Id,
|
||||
ExternalItemId = 1,
|
||||
EpisodeId = 20,
|
||||
Title = "Series 1 S01E01",
|
||||
ItemType = InstanceType.Sonarr,
|
||||
SeasonNumber = 1,
|
||||
AirDateUtc = _timeProvider.GetUtcNow().AddDays(-30),
|
||||
LastSearchTime = lastSearchTime,
|
||||
IsMonitored = true,
|
||||
CurrentScore = 5,
|
||||
CutoffScore = 10,
|
||||
});
|
||||
await _eventsContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
SeekerEvaluation evaluation = await EvaluateSonarrAsync(InstanceConfig(_sonarr, c => c.UseCustomFormatScore = true));
|
||||
|
||||
// Assert
|
||||
evaluation.Units.ShouldHaveSingleItem().LastSearchTime.ShouldBe(lastSearchTime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region WithoutCycleHistory
|
||||
@@ -1087,6 +1181,22 @@ public class SeekerUnitEvaluatorTests : IDisposable
|
||||
fresh.LastSearchedByItem.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WithoutCycleHistory_KeepsTheArrLastSearchTime()
|
||||
{
|
||||
// Arrange
|
||||
DateTimeOffset lastSearchTime = _timeProvider.GetUtcNow().AddDays(-4);
|
||||
SetupRadarr([Movie(1, lastSearchTime: lastSearchTime)]);
|
||||
SeekerEvaluation evaluation = await EvaluateRadarrAsync(
|
||||
history: [History(_radarr, 1, _timeProvider.GetUtcNow().AddDays(-1))]);
|
||||
|
||||
// Act
|
||||
SeekerEvaluation fresh = evaluation.WithoutCycleHistory();
|
||||
|
||||
// Assert
|
||||
fresh.Units.ShouldHaveSingleItem().LastSearchTime.ShouldBe(lastSearchTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WithoutCycleHistory_KeepsQueueBlocks()
|
||||
{
|
||||
|
||||
@@ -41,6 +41,11 @@ public sealed record SeekerSearchUnit
|
||||
/// </summary>
|
||||
public DateTimeOffset? Added { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Latest search the arr recorded for the movie or any episode the unit covers.
|
||||
/// </summary>
|
||||
public DateTimeOffset? LastSearchTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the current cycle already searched this unit.
|
||||
/// </summary>
|
||||
|
||||
@@ -118,9 +118,17 @@ public sealed class SeekerUnitEvaluator
|
||||
.GroupBy(u => u.ItemId)
|
||||
.ToDictionary(g => g.Key, g => g.ToList());
|
||||
|
||||
Dictionary<long, DateTimeOffset?> arrSearchedByItem = evaluation.Units
|
||||
.GroupBy(u => u.ItemId)
|
||||
.ToDictionary(g => g.Key, g => g.Max(u => u.LastSearchTime));
|
||||
|
||||
List<(long Id, DateTimeOffset? Added, DateTimeOffset? LastSearched)> selectionCandidates = remainingByItem
|
||||
.Select(p => (p.Key, p.Value[0].Added,
|
||||
LastSearched: evaluation.LastSearchedByItem.TryGetValue(p.Key, out DateTimeOffset at) ? (DateTimeOffset?)at : null))
|
||||
.Select(p =>
|
||||
{
|
||||
DateTimeOffset? cycleSearched = evaluation.LastSearchedByItem.TryGetValue(p.Key, out DateTimeOffset at) ? at : null;
|
||||
DateTimeOffset? lastSearched = new[] { arrSearchedByItem[p.Key], cycleSearched }.Max();
|
||||
return (p.Key, p.Value[0].Added, lastSearched);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return ItemSelectorFactory.Create(strategy)
|
||||
@@ -294,6 +302,7 @@ public sealed class SeekerUnitEvaluator
|
||||
Name = movie.Title,
|
||||
Reasons = reasons,
|
||||
Added = movie.Added,
|
||||
LastSearchTime = movie.LastSearchTime,
|
||||
Searched = false,
|
||||
BlockedBy = blockedBy,
|
||||
});
|
||||
@@ -413,6 +422,7 @@ public sealed class SeekerUnitEvaluator
|
||||
EpisodeId = e.Id,
|
||||
EpisodeNumber = e.EpisodeNumber,
|
||||
AirDateUtc = e.AirDateUtc,
|
||||
LastSearchTime = e.LastSearchTime,
|
||||
Monitored = e.Monitored,
|
||||
Reason = reason,
|
||||
};
|
||||
@@ -475,6 +485,7 @@ public sealed class SeekerUnitEvaluator
|
||||
EpisodeId = entry.EpisodeId,
|
||||
Title = entry.Title,
|
||||
AirDateUtc = entry.AirDateUtc,
|
||||
LastSearchTime = entry.LastSearchTime,
|
||||
Monitored = entry.IsMonitored,
|
||||
Reason = SeekerSearchReason.CustomFormatScoreBelowCutoff,
|
||||
});
|
||||
@@ -593,6 +604,7 @@ public sealed class SeekerUnitEvaluator
|
||||
ContentKind = kind,
|
||||
Reasons = season.Select(w => w.Reason).ToList(),
|
||||
Added = series.Added,
|
||||
LastSearchTime = season.Max(w => w.LastSearchTime),
|
||||
Searched = seasonSearched,
|
||||
});
|
||||
continue;
|
||||
@@ -615,6 +627,7 @@ public sealed class SeekerUnitEvaluator
|
||||
Reasons = episode.Select(w => w.Reason).ToList(),
|
||||
AirDateUtc = first.AirDateUtc,
|
||||
Added = series.Added,
|
||||
LastSearchTime = episode.Max(w => w.LastSearchTime),
|
||||
Searched = seasonSearched || searchedEpisodeIds.Contains(episode.Key),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ internal sealed record SonarrWantedEpisode
|
||||
|
||||
public DateTimeOffset? AirDateUtc { get; init; }
|
||||
|
||||
public DateTimeOffset? LastSearchTime { get; init; }
|
||||
|
||||
public required bool Monitored { get; init; }
|
||||
|
||||
public required SeekerSearchReason Reason { get; init; }
|
||||
|
||||
Reference in new issue
Block a user