feat(availability): Add the option for the *arr to take media availability priority

feat(availability):  Add the option for the *arr to take media availability priority
This commit is contained in:
Jamie Rees 2025-11-06 22:41:27 +00:00 committed by GitHub
commit c6b7512245
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 478 additions and 18 deletions

View File

@ -4,11 +4,14 @@ using Moq;
using Moq.AutoMock;
using NUnit.Framework;
using Ombi.Core;
using Ombi.Core.Settings;
using Ombi.Hubs;
using Ombi.Notifications.Models;
using Ombi.Schedule.Jobs;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Ombi.Tests;
using System.Collections.Generic;
@ -177,15 +180,190 @@ namespace Ombi.Schedule.Tests
_mocker.Verify<INotificationHelper>(x => x.Notify(It.Is<NotificationOptions>(x => x.NotificationType == Helpers.NotificationType.PartiallyAvailable && x.RequestId == 1)), Times.Once);
});
}
[Test]
public async Task ShouldDeferToRadarr_WhenRadarrDisabled_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = false, ScanForAvailability = true, PrioritizeArrAvailability = true });
var result = await subject.TestShouldDeferToRadarr(123, false);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToRadarr_WhenScanForAvailabilityDisabled_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = false, PrioritizeArrAvailability = true });
var result = await subject.TestShouldDeferToRadarr(123, false);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToRadarr_WhenPrioritizeArrAvailabilityDisabled_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = false });
var result = await subject.TestShouldDeferToRadarr(123, false);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToRadarr_WhenNotInCache_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<RadarrCache>, IQueryable<RadarrCache>>(x => x.GetAll())
.Returns(new List<RadarrCache>().AsQueryable().BuildMock());
var result = await subject.TestShouldDeferToRadarr(123, false);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToRadarr_WhenInCacheWithFile_ReturnsTrue()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<RadarrCache>, IQueryable<RadarrCache>>(x => x.GetAll())
.Returns(new List<RadarrCache> { new RadarrCache { TheMovieDbId = 123, HasFile = true, HasRegular = true } }.AsQueryable().BuildMock());
var result = await subject.TestShouldDeferToRadarr(123, false);
Assert.That(result, Is.True);
}
[Test]
public async Task ShouldDeferToRadarr_4K_WhenInCacheWith4K_ReturnsTrue()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<RadarrCache>, IQueryable<RadarrCache>>(x => x.GetAll())
.Returns(new List<RadarrCache> { new RadarrCache { TheMovieDbId = 123, HasFile = true, Has4K = true } }.AsQueryable().BuildMock());
var result = await subject.TestShouldDeferToRadarr(123, true);
Assert.That(result, Is.True);
}
[Test]
public async Task ShouldDeferToSonarr_WhenSonarrDisabled_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<SonarrSettings>, Task<SonarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new SonarrSettings { Enabled = false, ScanForAvailability = true, PrioritizeArrAvailability = true });
var result = await subject.TestShouldDeferToSonarr(456);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToSonarr_WhenScanForAvailabilityDisabled_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<SonarrSettings>, Task<SonarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = false, PrioritizeArrAvailability = true });
var result = await subject.TestShouldDeferToSonarr(456);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToSonarr_WhenPrioritizeArrAvailabilityDisabled_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<SonarrSettings>, Task<SonarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = false });
var result = await subject.TestShouldDeferToSonarr(456);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToSonarr_WhenNoEpisodesInCache_ReturnsFalse()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<SonarrSettings>, Task<SonarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<SonarrEpisodeCache>, IQueryable<SonarrEpisodeCache>>(x => x.GetAll())
.Returns(new List<SonarrEpisodeCache>().AsQueryable().BuildMock());
var result = await subject.TestShouldDeferToSonarr(456);
Assert.That(result, Is.False);
}
[Test]
public async Task ShouldDeferToSonarr_WhenEpisodesInCacheWithFile_ReturnsTrue()
{
var subject = CreateSubjectWithArrDependencies();
_mocker.Setup<ISettingsService<SonarrSettings>, Task<SonarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<SonarrEpisodeCache>, IQueryable<SonarrEpisodeCache>>(x => x.GetAll())
.Returns(new List<SonarrEpisodeCache> { new SonarrEpisodeCache { TvDbId = 456, HasFile = true } }.AsQueryable().BuildMock());
var result = await subject.TestShouldDeferToSonarr(456);
Assert.That(result, Is.True);
}
private TestAvailabilityCheckerWithArrSupport CreateSubjectWithArrDependencies()
{
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings());
_mocker.Setup<ISettingsService<SonarrSettings>, Task<SonarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new SonarrSettings());
_mocker.Setup<IExternalRepository<RadarrCache>, IQueryable<RadarrCache>>(x => x.GetAll())
.Returns(new List<RadarrCache>().AsQueryable().BuildMock());
_mocker.Setup<IExternalRepository<SonarrEpisodeCache>, IQueryable<SonarrEpisodeCache>>(x => x.GetAll())
.Returns(new List<SonarrEpisodeCache>().AsQueryable().BuildMock());
return _mocker.CreateInstance<TestAvailabilityCheckerWithArrSupport>();
}
}
public class TestAvailabilityChecker : AvailabilityChecker
{
public TestAvailabilityChecker(ITvRequestRepository tvRequest, INotificationHelper notification, ILogger log, INotificationHubService notificationHubService) : base(tvRequest, notification, log, notificationHubService)
public TestAvailabilityChecker(ITvRequestRepository tvRequest, INotificationHelper notification, ILogger log, INotificationHubService notificationHubService) : base(tvRequest, notification, log, notificationHubService, null, null, null, null)
{
}
public new Task ProcessTvShow(IQueryable<IBaseMediaServerEpisode> seriesEpisodes, ChildRequests child) => base.ProcessTvShow(seriesEpisodes, child);
}
public class TestAvailabilityCheckerWithArrSupport : AvailabilityChecker
{
public TestAvailabilityCheckerWithArrSupport(
ITvRequestRepository tvRequest,
INotificationHelper notification,
ILogger log,
INotificationHubService notificationHubService,
ISettingsService<RadarrSettings> radarrSettings,
ISettingsService<SonarrSettings> sonarrSettings,
IExternalRepository<RadarrCache> radarrCache,
IExternalRepository<SonarrEpisodeCache> sonarrEpisodeCache)
: base(tvRequest, notification, log, notificationHubService, radarrSettings, sonarrSettings, radarrCache, sonarrEpisodeCache)
{
}
public Task<bool> TestShouldDeferToRadarr(int theMovieDbId, bool is4K) => base.ShouldDeferToRadarr(theMovieDbId, is4K);
public Task<bool> TestShouldDeferToSonarr(int tvDbId) => base.ShouldDeferToSonarr(tvDbId);
}
}

View File

@ -17,6 +17,8 @@ using Ombi.Core.Services;
using Ombi.Tests;
using Moq.AutoMock;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Core.Settings;
using Ombi.Notifications.Models;
namespace Ombi.Schedule.Tests
@ -241,6 +243,126 @@ namespace Ombi.Schedule.Tests
Assert.True(request.SeasonRequests[0].Episodes[0].Available);
}
[Test]
public async Task ProcessMovies_ShouldNotMarkAvailable_WhenRadarrPriorityEnabled_AndInRadarrCache()
{
// Setup Radarr priority
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<RadarrCache>, IQueryable<RadarrCache>>(x => x.GetAll())
.Returns(new List<RadarrCache> { new RadarrCache { TheMovieDbId = 123, HasFile = true, HasRegular = true } }.AsQueryable().BuildMock());
var request = new MovieRequests
{
ImdbId = "test",
TheMovieDbId = 123
};
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent());
await _subject.Execute(null);
// Should NOT mark as available because Radarr has priority
Assert.Multiple(() =>
{
Assert.That(request.Available, Is.False);
Assert.That(request.MarkedAsAvailable, Is.Null);
});
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Never);
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Never);
}
[Test]
public async Task ProcessMovies_ShouldMarkAvailable_WhenRadarrPriorityDisabled_AndInPlex()
{
// Setup Radarr priority disabled
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = false });
var request = new MovieRequests
{
ImdbId = "test"
};
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent());
await _subject.Execute(null);
// Should mark as available because priority is disabled
Assert.Multiple(() =>
{
Assert.That(request.Available, Is.True);
Assert.That(request.MarkedAsAvailable, Is.Not.Null);
});
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Once);
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Once);
}
[Test]
public async Task ProcessMovies_ShouldMarkAvailable_WhenRadarrPriorityEnabled_ButNotInRadarrCache()
{
// Setup Radarr priority enabled but content not in cache
_mocker.Setup<ISettingsService<RadarrSettings>, Task<RadarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<RadarrCache>, IQueryable<RadarrCache>>(x => x.GetAll())
.Returns(new List<RadarrCache>().AsQueryable().BuildMock());
var request = new MovieRequests
{
ImdbId = "test",
TheMovieDbId = 123
};
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(new List<MovieRequests> { request }.AsQueryable());
_mocker.Setup<IPlexContentRepository, Task<PlexServerContent>>(x => x.Get("test", ProviderType.ImdbId)).ReturnsAsync(new PlexServerContent());
await _subject.Execute(null);
// Should mark as available because content is not in Radarr cache
Assert.Multiple(() =>
{
Assert.That(request.Available, Is.True);
Assert.That(request.MarkedAsAvailable, Is.Not.Null);
});
_mocker.Verify<IMovieRequestRepository>(x => x.SaveChangesAsync(), Times.Once);
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Once);
}
[Test]
public async Task ProcessTv_ShouldNotMarkAvailable_WhenSonarrPriorityEnabled_AndInSonarrCache()
{
// Setup Sonarr priority
_mocker.Setup<ISettingsService<SonarrSettings>, Task<SonarrSettings>>(x => x.GetSettingsAsync())
.ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true });
_mocker.Setup<IExternalRepository<SonarrEpisodeCache>, IQueryable<SonarrEpisodeCache>>(x => x.GetAll())
.Returns(new List<SonarrEpisodeCache> { new SonarrEpisodeCache { TvDbId = 99, HasFile = true } }.AsQueryable().BuildMock());
var request = CreateChildRequest(null, 33, 99);
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock());
_mocker.Setup<IPlexContentRepository, IQueryable<IMediaServerEpisode>>(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
{
new PlexEpisode
{
Series = new PlexServerContent
{
TheMovieDbId = 33.ToString(),
Title = "abc"
},
EpisodeNumber = 1,
SeasonNumber = 2,
}
}.AsQueryable().BuildMock());
await _subject.Execute(null);
// Should NOT mark as available because Sonarr has priority
Assert.That(request.SeasonRequests[0].Episodes[0].Available, Is.False);
// Save is called at the end of ProcessTv, but episodes should not be marked available
_mocker.Verify<ITvRequestRepository>(x => x.Save(), Times.Once);
}
private ChildRequests CreateChildRequest(string imdbId, int theMovieDbId, int tvdbId)
{
return new ChildRequests

View File

@ -5,11 +5,14 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Schedule.Jobs
@ -20,14 +23,26 @@ namespace Ombi.Schedule.Jobs
protected readonly INotificationHelper _notificationService;
protected readonly ILogger _log;
protected readonly INotificationHubService NotificationHubService;
protected readonly ISettingsService<RadarrSettings> _radarrSettings;
protected readonly ISettingsService<SonarrSettings> _sonarrSettings;
protected readonly IExternalRepository<RadarrCache> _radarrCache;
protected readonly IExternalRepository<SonarrEpisodeCache> _sonarrEpisodeCache;
public AvailabilityChecker(ITvRequestRepository tvRequest, INotificationHelper notification,
ILogger log, INotificationHubService notificationHubService)
ILogger log, INotificationHubService notificationHubService,
ISettingsService<RadarrSettings> radarrSettings = null,
ISettingsService<SonarrSettings> sonarrSettings = null,
IExternalRepository<RadarrCache> radarrCache = null,
IExternalRepository<SonarrEpisodeCache> sonarrEpisodeCache = null)
{
_tvRepo = tvRequest;
_notificationService = notification;
_log = log;
NotificationHubService = notificationHubService;
_radarrSettings = radarrSettings;
_sonarrSettings = sonarrSettings;
_radarrCache = radarrCache;
_sonarrEpisodeCache = sonarrEpisodeCache;
}
protected async Task ProcessTvShow(IQueryable<IBaseMediaServerEpisode> seriesEpisodes, ChildRequests child)
@ -100,5 +115,67 @@ namespace Ombi.Schedule.Jobs
await _notificationService.Notify(notification);
}
}
protected async Task<bool> ShouldDeferToRadarr(int theMovieDbId, bool is4K)
{
// If no Radarr settings service injected, don't defer
if (_radarrSettings == null || _radarrCache == null)
{
return false;
}
var radarrSettings = await _radarrSettings.GetSettingsAsync();
// Check if Radarr is enabled, scanning for availability, and prioritizing *Arr availability
if (radarrSettings == null || !radarrSettings.Enabled || !radarrSettings.ScanForAvailability || !radarrSettings.PrioritizeArrAvailability)
{
return false;
}
// Check if content exists in Radarr cache with HasFile = true
var cachedMovie = await _radarrCache.GetAll()
.Where(x => x.TheMovieDbId == theMovieDbId)
.FirstOrDefaultAsync();
if (cachedMovie == null)
{
// Content not in Radarr cache at all, allow media server to mark as available
return false;
}
// If is4K, check Has4K property; otherwise check HasRegular or HasFile
if (is4K)
{
return cachedMovie.Has4K;
}
else
{
return cachedMovie.HasRegular || cachedMovie.HasFile;
}
}
protected async Task<bool> ShouldDeferToSonarr(int tvDbId)
{
// If no Sonarr settings service injected, don't defer
if (_sonarrSettings == null || _sonarrEpisodeCache == null)
{
return false;
}
var sonarrSettings = await _sonarrSettings.GetSettingsAsync();
// Check if Sonarr is enabled, scanning for availability, and prioritizing *Arr availability
if (sonarrSettings == null || !sonarrSettings.Enabled || !sonarrSettings.ScanForAvailability || !sonarrSettings.PrioritizeArrAvailability)
{
return false;
}
// Check if any episodes exist in Sonarr cache with HasFile = true
var hasAnyEpisodes = await _sonarrEpisodeCache.GetAll()
.Where(x => x.TvDbId == tvDbId && x.HasFile)
.AnyAsync();
return hasAnyEpisodes;
}
}
}

View File

@ -5,10 +5,12 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core;
using Ombi.Core.Services;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
@ -19,8 +21,10 @@ namespace Ombi.Schedule.Jobs.Emby
public class EmbyAvaliabilityChecker : AvailabilityChecker, IEmbyAvaliabilityChecker
{
public EmbyAvaliabilityChecker(IEmbyContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
INotificationHelper n, ILogger<EmbyAvaliabilityChecker> log, INotificationHubService notification, IFeatureService featureService)
: base(t, n, log, notification)
INotificationHelper n, ILogger<EmbyAvaliabilityChecker> log, INotificationHubService notification, IFeatureService featureService,
ISettingsService<RadarrSettings> radarrSettings, ISettingsService<SonarrSettings> sonarrSettings,
IExternalRepository<RadarrCache> radarrCache, IExternalRepository<SonarrEpisodeCache> sonarrEpisodeCache)
: base(t, n, log, notification, radarrSettings, sonarrSettings, radarrCache, sonarrEpisodeCache)
{
_repo = repo;
_movieRepo = m;
@ -66,11 +70,22 @@ namespace Ombi.Schedule.Jobs.Emby
continue;
}
// Check if we should defer to Radarr for 4K availability
var shouldDeferRadarr4K = has4kRequest && await ShouldDeferToRadarr(movie.TheMovieDbId, true);
// Check if we should defer to Radarr for regular availability
var shouldDeferRadarrRegular = await ShouldDeferToRadarr(movie.TheMovieDbId, false);
if (shouldDeferRadarr4K && shouldDeferRadarrRegular)
{
_log.LogInformation("Movie request {0} - {1} found in Emby but deferring to Radarr availability", movie?.Title ?? string.Empty, movie.Id);
continue;
}
_log.LogInformation("We have found the request {0} on Emby, sending the notification", movie?.Title ?? string.Empty);
var notify = false;
if (has4kRequest && embyContent.Has4K && !movie.Available4K)
if (has4kRequest && embyContent.Has4K && !movie.Available4K && !shouldDeferRadarr4K)
{
movie.Available4K = true;
movie.MarkedAsAvailable4K = DateTime.Now;
@ -78,7 +93,7 @@ namespace Ombi.Schedule.Jobs.Emby
}
// If we have a non-4k version or we don't care about versions, then mark as available
if (!movie.Available && ( !feature4kEnabled || embyContent.Quality != null ))
if (!movie.Available && ( !feature4kEnabled || embyContent.Quality != null ) && !shouldDeferRadarrRegular)
{
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
@ -154,6 +169,13 @@ namespace Ombi.Schedule.Jobs.Emby
x.Series.Title == child.Title);
}
// Check if we should defer to Sonarr for this TV show
if (await ShouldDeferToSonarr(tvDbId))
{
_log.LogInformation("TV request {0} - {1} found in Emby but deferring to Sonarr availability", child.Title, child.Id);
continue;
}
await ProcessTvShow(seriesEpisodes, child);
}

View File

@ -32,10 +32,12 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core;
using Ombi.Core.Services;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
@ -46,8 +48,10 @@ namespace Ombi.Schedule.Jobs.Jellyfin
public class JellyfinAvaliabilityChecker : AvailabilityChecker, IJellyfinAvaliabilityChecker
{
public JellyfinAvaliabilityChecker(IJellyfinContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m,
INotificationHelper n, ILogger<JellyfinAvaliabilityChecker> log, INotificationHubService notification, IFeatureService featureService)
: base(t, n, log, notification)
INotificationHelper n, ILogger<JellyfinAvaliabilityChecker> log, INotificationHubService notification, IFeatureService featureService,
ISettingsService<RadarrSettings> radarrSettings, ISettingsService<SonarrSettings> sonarrSettings,
IExternalRepository<RadarrCache> radarrCache, IExternalRepository<SonarrEpisodeCache> sonarrEpisodeCache)
: base(t, n, log, notification, radarrSettings, sonarrSettings, radarrCache, sonarrEpisodeCache)
{
_repo = repo;
_movieRepo = m;
@ -93,11 +97,22 @@ namespace Ombi.Schedule.Jobs.Jellyfin
continue;
}
// Check if we should defer to Radarr for 4K availability
var shouldDeferRadarr4K = has4kRequest && await ShouldDeferToRadarr(movie.TheMovieDbId, true);
// Check if we should defer to Radarr for regular availability
var shouldDeferRadarrRegular = await ShouldDeferToRadarr(movie.TheMovieDbId, false);
if (shouldDeferRadarr4K && shouldDeferRadarrRegular)
{
_log.LogInformation("Movie request {0} - {1} found in Jellyfin but deferring to Radarr availability", movie?.Title ?? string.Empty, movie.Id);
continue;
}
_log.LogInformation("We have found the request {0} on Jellyfin, sending the notification", movie?.Title ?? string.Empty);
var notify = false;
if (has4kRequest && jellyfinContent.Has4K && !movie.Available4K)
if (has4kRequest && jellyfinContent.Has4K && !movie.Available4K && !shouldDeferRadarr4K)
{
movie.Available4K = true;
movie.MarkedAsAvailable4K = DateTime.Now;
@ -105,7 +120,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
}
// If we have a non-4k version or we don't care about versions, then mark as available
if (!movie.Available && ( !feature4kEnabled || jellyfinContent.Quality != null ))
if (!movie.Available && ( !feature4kEnabled || jellyfinContent.Quality != null ) && !shouldDeferRadarrRegular)
{
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
@ -182,6 +197,13 @@ namespace Ombi.Schedule.Jobs.Jellyfin
x.Series.Title == child.Title);
}
// Check if we should defer to Sonarr for this TV show
if (await ShouldDeferToSonarr(tvDbId))
{
_log.LogInformation("TV request {0} - {1} found in Jellyfin but deferring to Sonarr availability", child.Title, child.Id);
continue;
}
await ProcessTvShow(seriesEpisodes, child);
}

View File

@ -6,10 +6,12 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core;
using Ombi.Core.Services;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
@ -21,8 +23,10 @@ namespace Ombi.Schedule.Jobs.Plex
public class PlexAvailabilityChecker : AvailabilityChecker, IPlexAvailabilityChecker
{
public PlexAvailabilityChecker(IPlexContentRepository repo, ITvRequestRepository tvRequest, IMovieRequestRepository movies,
INotificationHelper notification, ILogger<PlexAvailabilityChecker> log, INotificationHubService notificationHubService, IFeatureService featureService)
: base(tvRequest, notification, log, notificationHubService)
INotificationHelper notification, ILogger<PlexAvailabilityChecker> log, INotificationHubService notificationHubService, IFeatureService featureService,
ISettingsService<RadarrSettings> radarrSettings, ISettingsService<SonarrSettings> sonarrSettings,
IExternalRepository<RadarrCache> radarrCache, IExternalRepository<SonarrEpisodeCache> sonarrEpisodeCache)
: base(tvRequest, notification, log, notificationHubService, radarrSettings, sonarrSettings, radarrCache, sonarrEpisodeCache)
{
_repo = repo;
_movieRepo = movies;
@ -104,6 +108,13 @@ namespace Ombi.Schedule.Jobs.Plex
x.Series.Title == child.Title);
}
// Check if we should defer to Sonarr for this TV show
if (await ShouldDeferToSonarr(tvDbId))
{
_log.LogInformation($"[PAC] - TV request {child.Title} - {child.Id} found in Plex but deferring to Sonarr availability");
continue;
}
await ProcessTvShow(seriesEpisodes, child);
}
@ -138,11 +149,22 @@ namespace Ombi.Schedule.Jobs.Plex
continue;
}
// Check if we should defer to Radarr for 4K availability
var shouldDeferRadarr4K = has4kRequest && await ShouldDeferToRadarr(movie.TheMovieDbId, true);
// Check if we should defer to Radarr for regular availability
var shouldDeferRadarrRegular = await ShouldDeferToRadarr(movie.TheMovieDbId, false);
if (shouldDeferRadarr4K && shouldDeferRadarrRegular)
{
_log.LogInformation($"[PAC] - Movie request {movie.Title} - {movie.Id} found in Plex but deferring to Radarr availability");
continue;
}
_log.LogInformation($"[PAC] - Movie request {movie.Title} - {movie.Id} is now available, sending notification");
var notify = false;
if (has4kRequest && item.Has4K && !movie.Available4K && feature4kEnabled)
if (has4kRequest && item.Has4K && !movie.Available4K && feature4kEnabled && !shouldDeferRadarr4K)
{
movie.Available4K = true;
movie.Approved4K = true;
@ -151,7 +173,7 @@ namespace Ombi.Schedule.Jobs.Plex
notify = true;
}
if (!feature4kEnabled && !movie.Available)
if (!feature4kEnabled && !movie.Available && !shouldDeferRadarrRegular)
{
movie.Available = true;
movie.MarkedAsAvailable = DateTime.Now;
@ -160,7 +182,7 @@ namespace Ombi.Schedule.Jobs.Plex
}
// If we have a non-4k versison then mark as available
if (item.Quality != null && !movie.Available)
if (item.Quality != null && !movie.Available && !shouldDeferRadarrRegular)
{
movie.Available = true;
movie.Approved = true;

View File

@ -9,6 +9,7 @@
public bool AddOnly { get; set; }
public string MinimumAvailability { get; set; }
public bool ScanForAvailability { get; set; }
public bool PrioritizeArrAvailability { get; set; }
public int? Tag { get; set; }
public bool SendUserTags { get; set; }
}

View File

@ -24,6 +24,7 @@
public int LanguageProfile { get; set; }
public int LanguageProfileAnime { get; set; }
public bool ScanForAvailability { get; set; }
public bool PrioritizeArrAvailability { get; set; }
}
}

View File

@ -147,6 +147,7 @@ export interface ISonarrSettings extends IExternalSettings {
languageProfile: number;
languageProfileAnime: number;
scanForAvailability: boolean;
prioritizeArrAvailability: boolean;
sendUserTags: boolean;
tag: number | null;
animeTag: number | null;
@ -161,6 +162,7 @@ export interface IRadarrSettings extends IExternalSettings {
addOnly: boolean;
minimumAvailability: string;
scanForAvailability: boolean;
prioritizeArrAvailability: boolean;
tag: number | null;
sendUserTags: boolean;
}

View File

@ -16,6 +16,7 @@ import { MatIconModule } from "@angular/material/icon";
import { MatInputModule } from "@angular/material/input";
import { MatSnackBarModule } from "@angular/material/snack-bar";
import { ImageBackgroundComponent } from "app/components";
import { TranslateModule } from "@ngx-translate/core";
@Component({
standalone: true,
@ -30,7 +31,8 @@ import { ImageBackgroundComponent } from "app/components";
MatFormFieldModule,
MatIconModule,
MatSnackBarModule,
ImageBackgroundComponent
ImageBackgroundComponent,
TranslateModule
]
})
export class TokenResetPasswordComponent implements OnInit {

View File

@ -8,6 +8,10 @@
<div class="md-form-field">
<mat-slide-toggle formControlName="scanForAvailability">Scan for Availability</mat-slide-toggle>
</div>
<div class="md-form-field">
<mat-slide-toggle formControlName="prioritizeArrAvailability">Prioritize Radarr Availability</mat-slide-toggle>
<small><br>When enabled, Radarr availability takes priority over media server availability</small>
</div>
<div class="md-form-field">
<mat-slide-toggle formControlName="sendUserTags" id="sendUserTags">Add the user as a tag</mat-slide-toggle>
<small><br>This will add the username of the requesting user as a tag in Radarr. If the tag doesn't exist, Ombi will create it.</small>

View File

@ -78,7 +78,8 @@ export class RadarrComponent implements OnInit, OnDestroy {
port: [x.settings.radarr.port],
addOnly: [x.settings.radarr.addOnly],
minimumAvailability: [x.settings.radarr.minimumAvailability],
scanForAvailability: [x.settings.radarr.scanForAvailability]
scanForAvailability: [x.settings.radarr.scanForAvailability],
prioritizeArrAvailability: [x.settings.radarr.prioritizeArrAvailability]
}),
radarr4K: fb.group({
enabled: [x.settings.radarr4K.enabled],
@ -93,7 +94,8 @@ export class RadarrComponent implements OnInit, OnDestroy {
port: [x.settings.radarr4K.port],
addOnly: [x.settings.radarr4K.addOnly],
minimumAvailability: [x.settings.radarr4K.minimumAvailability],
scanForAvailability: [x.settings.radarr4K.scanForAvailability]
scanForAvailability: [x.settings.radarr4K.scanForAvailability],
prioritizeArrAvailability: [x.settings.radarr4K.prioritizeArrAvailability]
}),
}))
)

View File

@ -15,6 +15,10 @@
<div class="md-form-field">
<mat-slide-toggle formControlName="scanForAvailability">Scan for Availability</mat-slide-toggle>
</div>
<div class="md-form-field">
<mat-slide-toggle formControlName="prioritizeArrAvailability">Prioritize Sonarr Availability</mat-slide-toggle>
<small><br>When enabled, Sonarr availability takes priority over media server availability</small>
</div>
<div class="md-form-field">
<mat-slide-toggle formControlName="sendUserTags" id="sendUserTags">Add the user as a tag</mat-slide-toggle>
<small><br>This will add the username of the requesting user as a tag in Sonarr. If the tag doesn't exist, Ombi will create it.</small>

View File

@ -107,6 +107,7 @@ export class SonarrComponent implements OnInit {
languageProfile: [settings.languageProfile],
languageProfileAnime: [settings.languageProfileAnime],
scanForAvailability: [settings.scanForAvailability],
prioritizeArrAvailability: [settings.prioritizeArrAvailability],
sendUserTags: [settings.sendUserTags],
tag: [settings.tag],
animeTag: [settings.animeTag]