< Summary - Jellyfin

Information
Class: Jellyfin.LiveTv.Timers.TimerManager
Assembly: Jellyfin.LiveTv
File(s): /srv/git/jellyfin/src/Jellyfin.LiveTv/Timers/TimerManager.cs
Line coverage
16%
Covered lines: 12
Uncovered lines: 63
Coverable lines: 75
Total lines: 172
Line coverage: 16%
Branch coverage
7%
Covered branches: 2
Total branches: 26
Branch coverage: 7.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
RestartTimers()50%2.06275%
StopTimers()50%2.06275%
Delete(...)100%210%
Update(...)100%210%
AddOrUpdate(...)0%620%
AddOrUpdate(...)100%210%
Add(...)100%210%
AddOrUpdateSystemTimer(...)0%7280%
StartTimer(...)0%2040%
StopTimer(...)0%620%
TimerCallback(...)0%4260%
GetTimer(...)100%210%
GetTimerByProgramId(...)100%210%

File(s)

/srv/git/jellyfin/src/Jellyfin.LiveTv/Timers/TimerManager.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Concurrent;
 5using System.Globalization;
 6using System.IO;
 7using System.Linq;
 8using System.Threading;
 9using Jellyfin.Data.Events;
 10using Jellyfin.LiveTv.Recordings;
 11using MediaBrowser.Common.Configuration;
 12using MediaBrowser.Controller.LiveTv;
 13using MediaBrowser.Model.LiveTv;
 14using Microsoft.Extensions.Logging;
 15
 16namespace Jellyfin.LiveTv.Timers
 17{
 18    public class TimerManager : ItemDataProvider<TimerInfo>
 19    {
 2220        private readonly ConcurrentDictionary<string, Timer> _timers = new(StringComparer.OrdinalIgnoreCase);
 21
 22        public TimerManager(ILogger<TimerManager> logger, IConfigurationManager config)
 2223            : base(
 2224                logger,
 2225                Path.Combine(config.CommonApplicationPaths.DataPath, "livetv/timers.json"),
 2226                (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
 27        {
 2228        }
 29
 30        public event EventHandler<GenericEventArgs<TimerInfo>>? TimerFired;
 31
 32        public void RestartTimers()
 33        {
 2234            StopTimers();
 35
 4436            foreach (var item in GetAll())
 37            {
 038                AddOrUpdateSystemTimer(item);
 39            }
 2240        }
 41
 42        public void StopTimers()
 43        {
 4444            foreach (var pair in _timers.ToList())
 45            {
 046                pair.Value.Dispose();
 47            }
 48
 2249            _timers.Clear();
 2250        }
 51
 52        public override void Delete(TimerInfo item)
 53        {
 054            base.Delete(item);
 055            StopTimer(item);
 056        }
 57
 58        public override void Update(TimerInfo item)
 59        {
 060            base.Update(item);
 061            AddOrUpdateSystemTimer(item);
 062        }
 63
 64        public void AddOrUpdate(TimerInfo item, bool resetTimer)
 65        {
 066            if (resetTimer)
 67            {
 068                AddOrUpdate(item);
 069                return;
 70            }
 71
 072            base.AddOrUpdate(item);
 073        }
 74
 75        public override void AddOrUpdate(TimerInfo item)
 76        {
 077            base.AddOrUpdate(item);
 078            AddOrUpdateSystemTimer(item);
 079        }
 80
 81        public override void Add(TimerInfo item)
 82        {
 083            ArgumentException.ThrowIfNullOrEmpty(item.Id);
 84
 085            base.Add(item);
 086            AddOrUpdateSystemTimer(item);
 087        }
 88
 89        private void AddOrUpdateSystemTimer(TimerInfo item)
 90        {
 091            StopTimer(item);
 92
 093            if (item.Status is RecordingStatus.Completed or RecordingStatus.Cancelled)
 94            {
 095                return;
 96            }
 97
 098            var startDate = item.StartDate.AddSeconds(-item.PrePaddingSeconds);
 099            var now = DateTime.UtcNow;
 100
 0101            if (startDate < now)
 102            {
 0103                TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(item));
 0104                return;
 105            }
 106
 0107            var dueTime = startDate - now;
 0108            StartTimer(item, dueTime);
 0109        }
 110
 111        private void StartTimer(TimerInfo item, TimeSpan dueTime)
 112        {
 0113            var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
 114
 0115            if (_timers.TryAdd(item.Id, timer))
 116            {
 0117                if (item.IsSeries)
 118                {
 0119                    Logger.LogInformation(
 0120                    "Creating recording timer for {Id}, {Name} {SeasonNumber}x{EpisodeNumber:D2} on channel {ChannelId}.
 0121                    item.Id,
 0122                    item.Name,
 0123                    item.SeasonNumber,
 0124                    item.EpisodeNumber,
 0125                    item.ChannelId,
 0126                    dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture),
 0127                    item.StartDate);
 128                }
 129                else
 130                {
 0131                    Logger.LogInformation(
 0132                    "Creating recording timer for {Id}, {Name} on channel {ChannelId}. Timer will fire in {Minutes} minu
 0133                    item.Id,
 0134                    item.Name,
 0135                    item.ChannelId,
 0136                    dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture),
 0137                    item.StartDate);
 138                }
 139            }
 140            else
 141            {
 0142                timer.Dispose();
 0143                Logger.LogWarning("Timer already exists for item {Id}", item.Id);
 144            }
 0145        }
 146
 147        private void StopTimer(TimerInfo item)
 148        {
 0149            if (_timers.TryRemove(item.Id, out var timer))
 150            {
 0151                timer.Dispose();
 152            }
 0153        }
 154
 155        private void TimerCallback(object? state)
 156        {
 0157            var timerId = (string?)state ?? throw new ArgumentNullException(nameof(state));
 158
 0159            var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase));
 0160            if (timer is not null)
 161            {
 0162                TimerFired?.Invoke(this, new GenericEventArgs<TimerInfo>(timer));
 163            }
 0164        }
 165
 166        public TimerInfo? GetTimer(string id)
 0167            => GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase));
 168
 169        public TimerInfo? GetTimerByProgramId(string programId)
 0170            => GetAll().FirstOrDefault(r => string.Equals(r.ProgramId, programId, StringComparison.OrdinalIgnoreCase));
 171    }
 172}