diff --git a/src/Ombi.Schedule.Tests/AvailabilityCheckerTests.cs b/src/Ombi.Schedule.Tests/AvailabilityCheckerTests.cs index 1e015b632..0f902a34c 100644 --- a/src/Ombi.Schedule.Tests/AvailabilityCheckerTests.cs +++ b/src/Ombi.Schedule.Tests/AvailabilityCheckerTests.cs @@ -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(x => x.Notify(It.Is(x => x.NotificationType == Helpers.NotificationType.PartiallyAvailable && x.RequestId == 1)), Times.Once); }); } + + [Test] + public async Task ShouldDeferToRadarr_WhenRadarrDisabled_ReturnsFalse() + { + var subject = CreateSubjectWithArrDependencies(); + _mocker.Setup, Task>(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, Task>(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, Task>(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, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List().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, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List { 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, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List { 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, Task>(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, Task>(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, Task>(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, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List().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, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List { new SonarrEpisodeCache { TvDbId = 456, HasFile = true } }.AsQueryable().BuildMock()); + + var result = await subject.TestShouldDeferToSonarr(456); + + Assert.That(result, Is.True); + } + + private TestAvailabilityCheckerWithArrSupport CreateSubjectWithArrDependencies() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new RadarrSettings()); + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new SonarrSettings()); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List().AsQueryable().BuildMock()); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List().AsQueryable().BuildMock()); + + return _mocker.CreateInstance(); + } } 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 seriesEpisodes, ChildRequests child) => base.ProcessTvShow(seriesEpisodes, child); } + + public class TestAvailabilityCheckerWithArrSupport : AvailabilityChecker + { + public TestAvailabilityCheckerWithArrSupport( + ITvRequestRepository tvRequest, + INotificationHelper notification, + ILogger log, + INotificationHubService notificationHubService, + ISettingsService radarrSettings, + ISettingsService sonarrSettings, + IExternalRepository radarrCache, + IExternalRepository sonarrEpisodeCache) + : base(tvRequest, notification, log, notificationHubService, radarrSettings, sonarrSettings, radarrCache, sonarrEpisodeCache) + { + } + + public Task TestShouldDeferToRadarr(int theMovieDbId, bool is4K) => base.ShouldDeferToRadarr(theMovieDbId, is4K); + public Task TestShouldDeferToSonarr(int tvDbId) => base.ShouldDeferToSonarr(tvDbId); + } } diff --git a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs index babd85bc5..48840f4c7 100644 --- a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs +++ b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs @@ -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, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List { new RadarrCache { TheMovieDbId = 123, HasFile = true, HasRegular = true } }.AsQueryable().BuildMock()); + + var request = new MovieRequests + { + ImdbId = "test", + TheMovieDbId = 123 + }; + _mocker.Setup>(x => x.GetAll()).Returns(new List { request }.AsQueryable()); + _mocker.Setup>(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(x => x.SaveChangesAsync(), Times.Never); + _mocker.Verify(x => x.Notify(It.IsAny()), Times.Never); + } + + [Test] + public async Task ProcessMovies_ShouldMarkAvailable_WhenRadarrPriorityDisabled_AndInPlex() + { + // Setup Radarr priority disabled + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = false }); + + var request = new MovieRequests + { + ImdbId = "test" + }; + _mocker.Setup>(x => x.GetAll()).Returns(new List { request }.AsQueryable()); + _mocker.Setup>(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(x => x.SaveChangesAsync(), Times.Once); + _mocker.Verify(x => x.Notify(It.IsAny()), Times.Once); + } + + [Test] + public async Task ProcessMovies_ShouldMarkAvailable_WhenRadarrPriorityEnabled_ButNotInRadarrCache() + { + // Setup Radarr priority enabled but content not in cache + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new RadarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List().AsQueryable().BuildMock()); + + var request = new MovieRequests + { + ImdbId = "test", + TheMovieDbId = 123 + }; + _mocker.Setup>(x => x.GetAll()).Returns(new List { request }.AsQueryable()); + _mocker.Setup>(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(x => x.SaveChangesAsync(), Times.Once); + _mocker.Verify(x => x.Notify(It.IsAny()), Times.Once); + } + + [Test] + public async Task ProcessTv_ShouldNotMarkAvailable_WhenSonarrPriorityEnabled_AndInSonarrCache() + { + // Setup Sonarr priority + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new SonarrSettings { Enabled = true, ScanForAvailability = true, PrioritizeArrAvailability = true }); + _mocker.Setup, IQueryable>(x => x.GetAll()) + .Returns(new List { new SonarrEpisodeCache { TvDbId = 99, HasFile = true } }.AsQueryable().BuildMock()); + + var request = CreateChildRequest(null, 33, 99); + _mocker.Setup>(x => x.GetChild()).Returns(new List { request }.AsQueryable().BuildMock()); + _mocker.Setup>(x => x.GetAllEpisodes()).Returns(new List + { + 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(x => x.Save(), Times.Once); + } + private ChildRequests CreateChildRequest(string imdbId, int theMovieDbId, int tvdbId) { return new ChildRequests diff --git a/src/Ombi.Schedule/Jobs/AvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/AvailabilityChecker.cs index db42778a8..78234ee72 100644 --- a/src/Ombi.Schedule/Jobs/AvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/AvailabilityChecker.cs @@ -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; + protected readonly ISettingsService _sonarrSettings; + protected readonly IExternalRepository _radarrCache; + protected readonly IExternalRepository _sonarrEpisodeCache; public AvailabilityChecker(ITvRequestRepository tvRequest, INotificationHelper notification, - ILogger log, INotificationHubService notificationHubService) + ILogger log, INotificationHubService notificationHubService, + ISettingsService radarrSettings = null, + ISettingsService sonarrSettings = null, + IExternalRepository radarrCache = null, + IExternalRepository sonarrEpisodeCache = null) { _tvRepo = tvRequest; _notificationService = notification; _log = log; NotificationHubService = notificationHubService; + _radarrSettings = radarrSettings; + _sonarrSettings = sonarrSettings; + _radarrCache = radarrCache; + _sonarrEpisodeCache = sonarrEpisodeCache; } protected async Task ProcessTvShow(IQueryable seriesEpisodes, ChildRequests child) @@ -100,5 +115,67 @@ namespace Ombi.Schedule.Jobs await _notificationService.Notify(notification); } } + + protected async Task 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 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; + } } } diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs index 5394c3c20..1d806fad4 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs @@ -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 log, INotificationHubService notification, IFeatureService featureService) - : base(t, n, log, notification) + INotificationHelper n, ILogger log, INotificationHubService notification, IFeatureService featureService, + ISettingsService radarrSettings, ISettingsService sonarrSettings, + IExternalRepository radarrCache, IExternalRepository 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); } diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs index 4a12f9383..8942e30a0 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs @@ -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 log, INotificationHubService notification, IFeatureService featureService) - : base(t, n, log, notification) + INotificationHelper n, ILogger log, INotificationHubService notification, IFeatureService featureService, + ISettingsService radarrSettings, ISettingsService sonarrSettings, + IExternalRepository radarrCache, IExternalRepository 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); } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs index 112998a82..58688c596 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -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 log, INotificationHubService notificationHubService, IFeatureService featureService) - : base(tvRequest, notification, log, notificationHubService) + INotificationHelper notification, ILogger log, INotificationHubService notificationHubService, IFeatureService featureService, + ISettingsService radarrSettings, ISettingsService sonarrSettings, + IExternalRepository radarrCache, IExternalRepository 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; diff --git a/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs index 1791761c5..225ad0bc8 100644 --- a/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/RadarrSettings.cs @@ -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; } } diff --git a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs index 332d97357..25a04cd09 100644 --- a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs @@ -24,6 +24,7 @@ public int LanguageProfile { get; set; } public int LanguageProfileAnime { get; set; } public bool ScanForAvailability { get; set; } + public bool PrioritizeArrAvailability { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index 008628c16..62d8f5894 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -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; } diff --git a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts index f22c1b6a8..3632dc7fd 100644 --- a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts +++ b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts @@ -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 { diff --git a/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html index 8a73368c0..cf2b617c1 100644 --- a/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html +++ b/src/Ombi/ClientApp/src/app/settings/radarr/components/radarr-form.component.html @@ -8,6 +8,10 @@
Scan for Availability
+
+ Prioritize Radarr Availability +
When enabled, Radarr availability takes priority over media server availability
+
Add the user as a tag
This will add the username of the requesting user as a tag in Radarr. If the tag doesn't exist, Ombi will create it.
diff --git a/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts b/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts index fae439446..7e6f25416 100644 --- a/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/radarr/radarr.component.ts @@ -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] }), })) ) diff --git a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html index ab48e5d58..e220919f4 100644 --- a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html +++ b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html @@ -15,6 +15,10 @@
Scan for Availability
+
+ Prioritize Sonarr Availability +
When enabled, Sonarr availability takes priority over media server availability
+
Add the user as a tag
This will add the username of the requesting user as a tag in Sonarr. If the tag doesn't exist, Ombi will create it.
diff --git a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts index 07bd8749c..576ec47a2 100644 --- a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts @@ -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]