From 60e01e1f22fa6fc3505469abd96d85d64b05fac1 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 11 Apr 2026 18:00:41 +0200 Subject: Apply review suggestions --- .../LiveTv/ISchedulesDirectService.cs | 7 ++++++ src/Jellyfin.LiveTv/Guide/GuideManager.cs | 8 +++++++ src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs | 26 +++++++++++++++++----- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Controller/LiveTv/ISchedulesDirectService.cs b/MediaBrowser.Controller/LiveTv/ISchedulesDirectService.cs index a33b4422b2..6953650952 100644 --- a/MediaBrowser.Controller/LiveTv/ISchedulesDirectService.cs +++ b/MediaBrowser.Controller/LiveTv/ISchedulesDirectService.cs @@ -21,4 +21,11 @@ public interface ISchedulesDirectService /// /// true if the image limit has been hit and has not yet reset; otherwise false. bool IsImageDailyLimitActive(); + + /// + /// Gets a value indicating whether the Schedules Direct service is available. + /// Returns false if a permanent account error has occurred or a transient backoff is active. + /// + /// true if the service can accept requests; otherwise false. + bool IsServiceAvailable(); } diff --git a/src/Jellyfin.LiveTv/Guide/GuideManager.cs b/src/Jellyfin.LiveTv/Guide/GuideManager.cs index a659cc020b..556516674b 100644 --- a/src/Jellyfin.LiveTv/Guide/GuideManager.cs +++ b/src/Jellyfin.LiveTv/Guide/GuideManager.cs @@ -738,6 +738,14 @@ public class GuideManager : IGuideManager _cacheParallelOptions, async (program, cancellationToken) => { + // Re-check: limit may have been set by a parallel task since the LINQ filter ran. + if (_schedulesDirectService.IsImageDailyLimitActive() + && program.ImageInfos.All( + img => img.IsLocalFile || img.Path.Contains("schedulesdirect", StringComparison.OrdinalIgnoreCase))) + { + return; + } + for (var i = 0; i < program.ImageInfos.Length; i++) { if (cancellationToken.IsCancellationRequested) diff --git a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs index 7b97dcc8db..3aa0f0408b 100644 --- a/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs +++ b/src/Jellyfin.LiveTv/Listings/SchedulesDirect.cs @@ -45,8 +45,8 @@ namespace Jellyfin.LiveTv.Listings private readonly ConcurrentDictionary _tokens = new(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; - private DateTime _lastErrorResponse; - private bool _accountError; + private long _lastErrorResponseTicks; + private volatile bool _accountError; private bool _disposed = false; private byte[] _countriesCache; @@ -594,7 +594,7 @@ namespace Jellyfin.LiveTv.Listings } // Avoid hammering SD after transient login failures (e.g. max attempts / temporary lockout) - if ((DateTime.UtcNow - _lastErrorResponse).TotalMinutes < 30) + if ((DateTime.UtcNow - new DateTime(Interlocked.Read(ref _lastErrorResponseTicks), DateTimeKind.Utc)).TotalMinutes < 30) { return null; } @@ -635,7 +635,7 @@ namespace Jellyfin.LiveTv.Listings && (int)ex.StatusCode.Value < 500) { _tokens.Clear(); - _lastErrorResponse = DateTime.UtcNow; + Interlocked.Exchange(ref _lastErrorResponseTicks, DateTime.UtcNow.Ticks); } throw; @@ -695,7 +695,7 @@ namespace Jellyfin.LiveTv.Listings { // Transient login errors — back off for 30 minutes, then allow retry. _tokens.Clear(); - _lastErrorResponse = DateTime.UtcNow; + Interlocked.Exchange(ref _lastErrorResponseTicks, DateTime.UtcNow.Ticks); } else if (sdCode is SdErrorCode.MaxImageDownloads) { @@ -876,6 +876,22 @@ namespace Jellyfin.LiveTv.Listings return null; } + /// + public bool IsServiceAvailable() + { + if (_accountError) + { + return false; + } + + if ((DateTime.UtcNow - new DateTime(Interlocked.Read(ref _lastErrorResponseTicks), DateTimeKind.Utc)).TotalMinutes < 30) + { + return false; + } + + return true; + } + /// public bool IsImageDailyLimitActive() { -- cgit v1.2.3