Explorar o código

update recording layouts

Luke Pulverenti %!s(int64=8) %!d(string=hai) anos
pai
achega
adb39f4090

+ 3 - 0
MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using MediaBrowser.Model.LiveTv;
 
 
 namespace MediaBrowser.Controller.LiveTv
 namespace MediaBrowser.Controller.LiveTv
 {
 {
@@ -54,6 +55,7 @@ namespace MediaBrowser.Controller.LiveTv
         public bool RecordAnyChannel { get; set; }
         public bool RecordAnyChannel { get; set; }
 
 
         public int KeepUpTo { get; set; }
         public int KeepUpTo { get; set; }
+        public KeepUntil KeepUntil { get; set; }
 
 
         public bool SkipEpisodesInLibrary { get; set; }
         public bool SkipEpisodesInLibrary { get; set; }
 
 
@@ -109,6 +111,7 @@ namespace MediaBrowser.Controller.LiveTv
         {
         {
             Days = new List<DayOfWeek>();
             Days = new List<DayOfWeek>();
             SkipEpisodesInLibrary = true;
             SkipEpisodesInLibrary = true;
+            KeepUntil = KeepUntil.UntilDeleted;
         }
         }
     }
     }
 }
 }

+ 9 - 0
MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs

@@ -27,6 +27,7 @@ namespace MediaBrowser.Model.LiveTv
         public bool RecordAnyChannel { get; set; }
         public bool RecordAnyChannel { get; set; }
 
 
         public int KeepUpTo { get; set; }
         public int KeepUpTo { get; set; }
+        public KeepUntil KeepUntil { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether [record new only].
         /// Gets or sets a value indicating whether [record new only].
@@ -68,4 +69,12 @@ namespace MediaBrowser.Model.LiveTv
             Days = new List<DayOfWeek>();
             Days = new List<DayOfWeek>();
         }
         }
     }
     }
+
+    public enum KeepUntil
+    {
+        UntilDeleted,
+        UntilSpaceNeeded,
+        UntilWatched,
+        UntilDate
+    }
 }
 }

+ 1 - 1
MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs

@@ -117,7 +117,7 @@ namespace MediaBrowser.Providers.MediaInfo
         {
         {
             get
             get
             {
             {
-                return new[] { ".srt", ".ssa", ".ass", ".sub" };
+                return new[] { ".srt", ".ssa", ".ass", ".sub", ".smi", ".sami" };
             }
             }
         }
         }
 
 

+ 162 - 39
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -330,11 +330,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             {
             {
                 if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id))
                 if (DateTime.UtcNow > timer.EndDate && !_activeRecordings.ContainsKey(timer.Id))
                 {
                 {
-                    _timerProvider.Delete(timer);
+                    OnTimerOutOfDate(timer);
                 }
                 }
             }
             }
         }
         }
 
 
+        private void OnTimerOutOfDate(TimerInfo timer)
+        {
+            _timerProvider.Delete(timer);
+        }
+
         private List<ChannelInfo> _channelCache = null;
         private List<ChannelInfo> _channelCache = null;
         private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken)
         private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken)
         {
         {
@@ -424,7 +429,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
             foreach (var timer in timers)
             foreach (var timer in timers)
             {
             {
-                CancelTimerInternal(timer.Id);
+                CancelTimerInternal(timer.Id, true);
             }
             }
 
 
             var remove = _seriesTimerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
             var remove = _seriesTimerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
@@ -435,12 +440,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return Task.FromResult(true);
             return Task.FromResult(true);
         }
         }
 
 
-        private void CancelTimerInternal(string timerId)
+        private void CancelTimerInternal(string timerId, bool isSeriesCancelled)
         {
         {
-            var remove = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
-            if (remove != null)
+            var timer = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
+            if (timer != null)
             {
             {
-                _timerProvider.Delete(remove);
+                if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled)
+                {
+                    _timerProvider.Delete(timer);
+                }
+                else
+                {
+                    timer.Status = RecordingStatus.Cancelled;
+                    _timerProvider.AddOrUpdate(timer, false);
+                }
             }
             }
             ActiveRecordingInfo activeRecordingInfo;
             ActiveRecordingInfo activeRecordingInfo;
 
 
@@ -452,7 +465,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
         public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
         public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
         {
         {
-            CancelTimerInternal(timerId);
+            CancelTimerInternal(timerId, false);
             return Task.FromResult(true);
             return Task.FromResult(true);
         }
         }
 
 
@@ -463,6 +476,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
         public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
         public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
         {
         {
+            var existingTimer = _timerProvider.GetAll()
+                .FirstOrDefault(i => string.Equals(info.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase));
+
+            if (existingTimer != null)
+            {
+                if (existingTimer.Status == RecordingStatus.Cancelled)
+                {
+                    existingTimer.Status = RecordingStatus.New;
+                    _timerProvider.Update(existingTimer);
+                }
+                else
+                {
+                    throw new ArgumentException("A scheduled recording already exists for this program.");
+                }
+            }
+
             return CreateTimer(info, cancellationToken);
             return CreateTimer(info, cancellationToken);
         }
         }
 
 
@@ -549,6 +578,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 instance.RecordNewOnly = info.RecordNewOnly;
                 instance.RecordNewOnly = info.RecordNewOnly;
                 instance.SkipEpisodesInLibrary = info.SkipEpisodesInLibrary;
                 instance.SkipEpisodesInLibrary = info.SkipEpisodesInLibrary;
                 instance.KeepUpTo = info.KeepUpTo;
                 instance.KeepUpTo = info.KeepUpTo;
+                instance.KeepUntil = info.KeepUntil;
                 instance.StartDate = info.StartDate;
                 instance.StartDate = info.StartDate;
 
 
                 _seriesTimerProvider.Update(instance);
                 _seriesTimerProvider.Update(instance);
@@ -569,12 +599,54 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             }
             }
         }
         }
 
 
-        public Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
+        public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken)
         {
         {
-            _timerProvider.Update(info);
+            var existingTimer = _timerProvider
+                .GetAll()
+                .FirstOrDefault(i => string.Equals(i.Id, updatedTimer.Id, StringComparison.OrdinalIgnoreCase));
+
+            if (existingTimer == null)
+            {
+                throw new ResourceNotFoundException();
+            }
+
+            // Only update if not currently active
+            ActiveRecordingInfo activeRecordingInfo;
+            if (!_activeRecordings.TryGetValue(updatedTimer.Id, out activeRecordingInfo))
+            {
+                UpdateExistingTimerWithNewData(existingTimer, updatedTimer);
+
+                _timerProvider.Update(existingTimer);
+            }
+
             return Task.FromResult(true);
             return Task.FromResult(true);
         }
         }
 
 
+        private void UpdateExistingTimerWithNewData(TimerInfo existingTimer, TimerInfo updatedTimer)
+        {
+            // Update the program info but retain the status
+            existingTimer.ChannelId = updatedTimer.ChannelId;
+            existingTimer.CommunityRating = updatedTimer.CommunityRating;
+            existingTimer.EndDate = updatedTimer.EndDate;
+            existingTimer.EpisodeNumber = updatedTimer.EpisodeNumber;
+            existingTimer.EpisodeTitle = updatedTimer.EpisodeTitle;
+            existingTimer.Genres = updatedTimer.Genres;
+            existingTimer.HomePageUrl = updatedTimer.HomePageUrl;
+            existingTimer.IsKids = updatedTimer.IsKids;
+            existingTimer.IsMovie = updatedTimer.IsMovie;
+            existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries;
+            existingTimer.IsSports = updatedTimer.IsSports;
+            existingTimer.Name = updatedTimer.Name;
+            existingTimer.OfficialRating = updatedTimer.OfficialRating;
+            existingTimer.OriginalAirDate = updatedTimer.OriginalAirDate;
+            existingTimer.Overview = updatedTimer.Overview;
+            existingTimer.ProductionYear = updatedTimer.ProductionYear;
+            existingTimer.ProgramId = updatedTimer.ProgramId;
+            existingTimer.SeasonNumber = updatedTimer.SeasonNumber;
+            existingTimer.ShortOverview = updatedTimer.ShortOverview;
+            existingTimer.StartDate = updatedTimer.StartDate;
+        }
+
         public Task<ImageStream> GetChannelImageAsync(string channelId, CancellationToken cancellationToken)
         public Task<ImageStream> GetChannelImageAsync(string channelId, CancellationToken cancellationToken)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
@@ -597,7 +669,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
         public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
         public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
         {
         {
-            return Task.FromResult((IEnumerable<TimerInfo>)_timerProvider.GetAll());
+            var excludeStatues = new List<RecordingStatus>
+            {
+                RecordingStatus.Completed,
+                RecordingStatus.Cancelled
+            };
+
+            var timers = _timerProvider.GetAll()
+                .Where(i => !excludeStatues.Contains(i.Status));
+
+            return Task.FromResult(timers);
         }
         }
 
 
         public Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
         public Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
@@ -630,6 +711,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 defaults.ProgramId = program.Id;
                 defaults.ProgramId = program.Id;
             }
             }
 
 
+            defaults.SkipEpisodesInLibrary = true;
+            defaults.KeepUntil = KeepUntil.UntilDeleted;
+
             return Task.FromResult(defaults);
             return Task.FromResult(defaults);
         }
         }
 
 
@@ -860,8 +944,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
                 if (recordingEndDate <= DateTime.UtcNow)
                 if (recordingEndDate <= DateTime.UtcNow)
                 {
                 {
-                    _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id);
-                    _timerProvider.Delete(timer);
+                    _logger.Warn("Recording timer fired for updatedTimer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id);
+                    OnTimerOutOfDate(timer);
                     return;
                     return;
                 }
                 }
 
 
@@ -982,7 +1066,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return Path.Combine(recordPath, recordingFileName);
             return Path.Combine(recordPath, recordingFileName);
         }
         }
 
 
-        private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
+        private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate,
+            ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
         {
         {
             if (timer == null)
             if (timer == null)
             {
             {
@@ -1014,9 +1099,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
             try
             try
             {
             {
-                var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false);
+                var allMediaSources =
+                    await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false);
 
 
-                var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None).ConfigureAwait(false);
+                var liveStreamInfo =
+                    await
+                        GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None)
+                            .ConfigureAwait(false);
                 liveStream = liveStreamInfo.Item1;
                 liveStream = liveStreamInfo.Item1;
                 var mediaStreamInfo = liveStreamInfo.Item1.PublicMediaSource;
                 var mediaStreamInfo = liveStreamInfo.Item1.PublicMediaSource;
                 var tunerHost = liveStreamInfo.Item2;
                 var tunerHost = liveStreamInfo.Item2;
@@ -1036,7 +1125,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
                 var duration = recordingEndDate - DateTime.UtcNow;
                 var duration = recordingEndDate - DateTime.UtcNow;
 
 
-                _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+                _logger.Info("Beginning recording. Will record for {0} minutes.",
+                    duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
 
 
                 _logger.Info("Writing file to path: " + recordPath);
                 _logger.Info("Writing file to path: " + recordPath);
                 _logger.Info("Opening recording stream from tuner provider");
                 _logger.Info("Opening recording stream from tuner provider");
@@ -1058,7 +1148,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                     mediaStreamInfo.RunTimeTicks = duration.Ticks;
                     mediaStreamInfo.RunTimeTicks = duration.Ticks;
                 }
                 }
 
 
-                await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
+                await
+                    recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken)
+                        .ConfigureAwait(false);
 
 
                 recordingStatus = RecordingStatus.Completed;
                 recordingStatus = RecordingStatus.Completed;
                 _logger.Info("Recording completed: {0}", recordPath);
                 _logger.Info("Recording completed: {0}", recordPath);
@@ -1092,14 +1184,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             ActiveRecordingInfo removed;
             ActiveRecordingInfo removed;
             _activeRecordings.TryRemove(timer.Id, out removed);
             _activeRecordings.TryRemove(timer.Id, out removed);
 
 
-            if (recordingStatus == RecordingStatus.Completed)
-            {
-                timer.Status = RecordingStatus.Completed;
-                _timerProvider.Delete(timer);
-
-                OnSuccessfulRecording(timer, recordPath);
-            }
-            else if (DateTime.UtcNow < timer.EndDate)
+            if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate)
             {
             {
                 const int retryIntervalSeconds = 60;
                 const int retryIntervalSeconds = 60;
                 _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds);
                 _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds);
@@ -1108,6 +1193,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds);
                 timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds);
                 _timerProvider.AddOrUpdate(timer);
                 _timerProvider.AddOrUpdate(timer);
             }
             }
+            else if (File.Exists(recordPath))
+            {
+                timer.Status = RecordingStatus.Completed;
+                _timerProvider.AddOrUpdate(timer, false);
+                OnSuccessfulRecording(timer, recordPath);
+            }
             else
             else
             {
             {
                 _timerProvider.Delete(timer);
                 _timerProvider.Delete(timer);
@@ -1353,41 +1444,78 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             return _config.GetConfiguration<LiveTvOptions>("livetv");
             return _config.GetConfiguration<LiveTvOptions>("livetv");
         }
         }
 
 
+        private bool ShouldCancelTimerForSeriesTimer(SeriesTimerInfo seriesTimer, TimerInfo timer)
+        {
+            return seriesTimer.SkipEpisodesInLibrary && IsProgramAlreadyInLibrary(timer);
+        }
+
         private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
         private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
         {
         {
-            var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList();
+            var allTimers = GetTimersForSeries(seriesTimer, epgData)
+                .ToList();
 
 
             var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
             var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
 
 
             if (registration.IsValid)
             if (registration.IsValid)
             {
             {
-                foreach (var timer in newTimers)
+                foreach (var timer in allTimers)
                 {
                 {
-                    _timerProvider.AddOrUpdate(timer);
+                    var existingTimer = _timerProvider
+                        .GetAll()
+                        .FirstOrDefault(i => string.Equals(i.Id, timer.Id, StringComparison.OrdinalIgnoreCase));
+
+                    if (existingTimer == null)
+                    {
+                        if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
+                        {
+                            timer.Status = RecordingStatus.Cancelled;
+                        }
+                        _timerProvider.Add(timer);
+                    }
+                    else
+                    {
+                        // Only update if not currently active
+                        ActiveRecordingInfo activeRecordingInfo;
+                        if (!_activeRecordings.TryGetValue(timer.Id, out activeRecordingInfo))
+                        {
+                            UpdateExistingTimerWithNewData(existingTimer, timer);
+
+                            if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer))
+                            {
+                                existingTimer.Status = RecordingStatus.Cancelled;
+                            }
+                            _timerProvider.Update(existingTimer);
+                        }
+                    }
                 }
                 }
             }
             }
 
 
             if (deleteInvalidTimers)
             if (deleteInvalidTimers)
             {
             {
-                var allTimers = GetTimersForSeries(seriesTimer, epgData, false)
+                var allTimerIds = allTimers
                     .Select(i => i.Id)
                     .Select(i => i.Id)
                     .ToList();
                     .ToList();
 
 
+                var deleteStatuses = new List<RecordingStatus>
+                {
+                    RecordingStatus.New
+                };
+
                 var deletes = _timerProvider.GetAll()
                 var deletes = _timerProvider.GetAll()
                     .Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
                     .Where(i => string.Equals(i.SeriesTimerId, seriesTimer.Id, StringComparison.OrdinalIgnoreCase))
-                    .Where(i => !allTimers.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
+                    .Where(i => !allTimerIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase) && i.StartDate > DateTime.UtcNow)
+                    .Where(i => deleteStatuses.Contains(i.Status))
                     .ToList();
                     .ToList();
 
 
                 foreach (var timer in deletes)
                 foreach (var timer in deletes)
                 {
                 {
-                    await CancelTimerAsync(timer.Id, CancellationToken.None).ConfigureAwait(false);
+                    CancelTimerInternal(timer.Id, false);
                 }
                 }
             }
             }
         }
         }
 
 
         private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer,
         private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer,
-            IEnumerable<ProgramInfo> allPrograms,
-            bool filterByCurrentRecordings)
+            IEnumerable<ProgramInfo> allPrograms)
         {
         {
             if (seriesTimer == null)
             if (seriesTimer == null)
             {
             {
@@ -1403,15 +1531,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
             allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
             allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
 
 
-            if (filterByCurrentRecordings && seriesTimer.SkipEpisodesInLibrary)
-            {
-                allPrograms = allPrograms.Where(i => !IsProgramAlreadyInLibrary(i));
-            }
-
             return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
             return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
         }
         }
 
 
-        private bool IsProgramAlreadyInLibrary(ProgramInfo program)
+        private bool IsProgramAlreadyInLibrary(TimerInfo program)
         {
         {
             if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle))
             if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle))
             {
             {

+ 21 - 20
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs

@@ -32,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
 
 
             foreach (var item in GetAll().ToList())
             foreach (var item in GetAll().ToList())
             {
             {
-                AddTimer(item);
+                AddOrUpdateSystemTimer(item);
             }
             }
         }
         }
 
 
@@ -55,17 +55,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
         public override void Update(TimerInfo item)
         public override void Update(TimerInfo item)
         {
         {
             base.Update(item);
             base.Update(item);
-
-            Timer timer;
-            if (_timers.TryGetValue(item.Id, out timer))
-            {
-                var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
-                timer.Change(timespan, TimeSpan.Zero);
-            }
-            else
-            {
-                AddTimer(item);
-            }
+            AddOrUpdateSystemTimer(item);
         }
         }
 
 
         public void AddOrUpdate(TimerInfo item, bool resetTimer)
         public void AddOrUpdate(TimerInfo item, bool resetTimer)
@@ -96,12 +86,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
             }
             }
 
 
             base.Add(item);
             base.Add(item);
-            AddTimer(item);
+            AddOrUpdateSystemTimer(item);
         }
         }
 
 
-        private void AddTimer(TimerInfo item)
+        private bool ShouldStartTimer(TimerInfo item)
         {
         {
-            if (item.Status == RecordingStatus.Completed)
+            if (item.Status == RecordingStatus.Completed ||
+                item.Status == RecordingStatus.Cancelled)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        private void AddOrUpdateSystemTimer(TimerInfo item)
+        {
+            StopTimer(item);
+
+            if (!ShouldStartTimer(item))
             {
             {
                 return;
                 return;
             }
             }
@@ -115,14 +118,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 return;
                 return;
             }
             }
 
 
-            var timerLength = startDate - now;
-            StartTimer(item, timerLength);
+            var dueTime = startDate - now;
+            StartTimer(item, dueTime);
         }
         }
 
 
-        public void StartTimer(TimerInfo item, TimeSpan dueTime)
+        private void StartTimer(TimerInfo item, TimeSpan dueTime)
         {
         {
-            StopTimer(item);
-
             var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
             var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
 
 
             if (_timers.TryAdd(item.Id, timer))
             if (_timers.TryAdd(item.Id, timer))

+ 2 - 0
MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs

@@ -102,6 +102,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 RecordAnyTime = info.RecordAnyTime,
                 RecordAnyTime = info.RecordAnyTime,
                 SkipEpisodesInLibrary = info.SkipEpisodesInLibrary,
                 SkipEpisodesInLibrary = info.SkipEpisodesInLibrary,
                 KeepUpTo = info.KeepUpTo,
                 KeepUpTo = info.KeepUpTo,
+                KeepUntil = info.KeepUntil,
                 RecordNewOnly = info.RecordNewOnly,
                 RecordNewOnly = info.RecordNewOnly,
                 ExternalChannelId = info.ChannelId,
                 ExternalChannelId = info.ChannelId,
                 ExternalProgramId = info.ProgramId,
                 ExternalProgramId = info.ProgramId,
@@ -312,6 +313,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 RecordAnyTime = dto.RecordAnyTime,
                 RecordAnyTime = dto.RecordAnyTime,
                 SkipEpisodesInLibrary = dto.SkipEpisodesInLibrary,
                 SkipEpisodesInLibrary = dto.SkipEpisodesInLibrary,
                 KeepUpTo = dto.KeepUpTo,
                 KeepUpTo = dto.KeepUpTo,
+                KeepUntil = dto.KeepUntil,
                 RecordNewOnly = dto.RecordNewOnly,
                 RecordNewOnly = dto.RecordNewOnly,
                 ProgramId = dto.ExternalProgramId,
                 ProgramId = dto.ExternalProgramId,
                 ChannelId = dto.ExternalChannelId,
                 ChannelId = dto.ExternalChannelId,

+ 1 - 1
MediaBrowser.Server.Mono/app.config

@@ -8,7 +8,7 @@
   </nlog>
   </nlog>
   <appSettings>
   <appSettings>
     <add key="DebugProgramDataPath" value="ProgramData-Server"/>
     <add key="DebugProgramDataPath" value="ProgramData-Server"/>
-    <add key="ReleaseProgramDataPath" value="%ApplicationData%/emby"/>
+    <add key="ReleaseProgramDataPath" value="ProgramData-Server"/>
   </appSettings>
   </appSettings>
   <runtime>
   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

+ 1 - 1
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -1219,7 +1219,7 @@ namespace MediaBrowser.Server.Startup.Common
             var apiUrl = GetLocalApiUrl(address);
             var apiUrl = GetLocalApiUrl(address);
             apiUrl += "/system/ping";
             apiUrl += "/system/ping";
 
 
-            if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 5)
+            if ((DateTime.UtcNow - _lastAddressCacheClear).TotalMinutes >= 10)
             {
             {
                 _lastAddressCacheClear = DateTime.UtcNow;
                 _lastAddressCacheClear = DateTime.UtcNow;
                 _validAddressResults.Clear();
                 _validAddressResults.Clear();