diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index ea21ba46e5..42c3480ce7 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -9,6 +9,7 @@ namespace MediaBrowser.Controller.LiveTv public TimerInfo() { Genres = new List(); + KeepUntil = KeepUntil.UntilDeleted; } /// @@ -109,5 +110,7 @@ namespace MediaBrowser.Controller.LiveTv public string ShortOverview { get; set; } public string OfficialRating { get; set; } public List Genres { get; set; } + public string RecordingPath { get; set; } + public KeepUntil KeepUntil { get; set; } } } diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs index 4d863c6eb2..0ceed70c05 100644 --- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs @@ -105,5 +105,6 @@ namespace MediaBrowser.Model.LiveTv /// /// true if this instance is post padding required; otherwise, false. public bool IsPostPaddingRequired { get; set; } + public KeepUntil KeepUntil { get; set; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs index 77b915a752..ce65cc7bfc 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs @@ -27,7 +27,6 @@ namespace MediaBrowser.Model.LiveTv public bool RecordAnyChannel { get; set; } public int KeepUpTo { get; set; } - public KeepUntil KeepUntil { get; set; } /// /// Gets or sets a value indicating whether [record new only]. diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7cdb623f30..6ea3fbc5e2 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -442,7 +442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private void CancelTimerInternal(string timerId, bool isSeriesCancelled) { - var timer = _timerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase)); + var timer = _timerProvider.GetTimer(timerId); if (timer != null) { if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled) @@ -474,10 +474,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return Task.FromResult(true); } + public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken) { var existingTimer = _timerProvider.GetAll() - .FirstOrDefault(i => string.Equals(info.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); + .FirstOrDefault(i => string.Equals(timer.ProgramId, i.ProgramId, StringComparison.OrdinalIgnoreCase)); if (existingTimer != null) { @@ -485,6 +495,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { existingTimer.Status = RecordingStatus.New; _timerProvider.Update(existingTimer); + return Task.FromResult(existingTimer.Id); } else { @@ -492,16 +503,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - return CreateTimer(info, cancellationToken); - } - - public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) - { - return CreateSeriesTimer(info, cancellationToken); - } - - public Task CreateTimer(TimerInfo timer, CancellationToken cancellationToken) - { timer.Id = Guid.NewGuid().ToString("N"); ProgramInfo programInfo = null; @@ -601,9 +602,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken) { - var existingTimer = _timerProvider - .GetAll() - .FirstOrDefault(i => string.Equals(i.Id, updatedTimer.Id, StringComparison.OrdinalIgnoreCase)); + var existingTimer = _timerProvider.GetTimer(updatedTimer.Id); if (existingTimer == null) { @@ -1137,6 +1136,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _timerProvider.AddOrUpdate(timer, false); SaveNfo(timer, recordPath, seriesPath); + EnforceKeepUpTo(timer); }; var pathWithDuration = tunerHost.ApplyDuration(mediaStreamInfo.Path, duration); @@ -1148,8 +1148,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV mediaStreamInfo.RunTimeTicks = duration.Ticks; } - await - recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) + await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) .ConfigureAwait(false); recordingStatus = RecordingStatus.Completed; @@ -1195,6 +1194,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } else if (File.Exists(recordPath)) { + timer.RecordingPath = recordPath; timer.Status = RecordingStatus.Completed; _timerProvider.AddOrUpdate(timer, false); OnSuccessfulRecording(timer, recordPath); @@ -1205,6 +1205,102 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } + private async void EnforceKeepUpTo(TimerInfo timer) + { + if (string.IsNullOrWhiteSpace(timer.SeriesTimerId)) + { + return; + } + + var seriesTimerId = timer.SeriesTimerId; + var seriesTimer = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, seriesTimerId, StringComparison.OrdinalIgnoreCase)); + + if (seriesTimer == null || seriesTimer.KeepUpTo <= 1) + { + return; + } + + if (_disposed) + { + return; + } + + await _recordingDeleteSemaphore.WaitAsync().ConfigureAwait(false); + + try + { + if (_disposed) + { + return; + } + + var timersToDelete = _timerProvider.GetAll() + .Where(i => i.Status == RecordingStatus.Completed && !string.IsNullOrWhiteSpace(i.RecordingPath)) + .Where(i => string.Equals(i.SeriesTimerId, seriesTimerId, StringComparison.OrdinalIgnoreCase)) + .OrderByDescending(i => i.EndDate) + .Where(i => File.Exists(i.RecordingPath)) + .Skip(seriesTimer.KeepUpTo - 1) + .ToList(); + + await DeleteLibraryItemsForTimers(timersToDelete).ConfigureAwait(false); + } + finally + { + _recordingDeleteSemaphore.Release(); + } + } + + private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1,1); + private async Task DeleteLibraryItemsForTimers(List timers) + { + foreach (var timer in timers) + { + if (_disposed) + { + return; + } + + try + { + await DeleteLibraryItemForTimer(timer).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error deleting recording", ex); + } + } + } + + private async Task DeleteLibraryItemForTimer(TimerInfo timer) + { + var libraryItem = _libraryManager.FindByPath(timer.RecordingPath, false); + + if (libraryItem != null) + { + await _libraryManager.DeleteItem(libraryItem, new DeleteOptions + { + DeleteFileLocation = true + }); + } + else + { + try + { + File.Delete(timer.RecordingPath); + } + catch (DirectoryNotFoundException) + { + + } + catch (FileNotFoundException) + { + + } + } + + _timerProvider.Delete(timer); + } + private string EnsureFileUnique(string path, string timerId) { var originalPath = path; @@ -1460,9 +1556,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { foreach (var timer in allTimers) { - var existingTimer = _timerProvider - .GetAll() - .FirstOrDefault(i => string.Equals(i.Id, timer.Id, StringComparison.OrdinalIgnoreCase)); + var existingTimer = _timerProvider.GetTimer(timer.Id); if (existingTimer == null) { @@ -1484,6 +1578,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { existingTimer.Status = RecordingStatus.Cancelled; } + + existingTimer.SeriesTimerId = seriesTimer.Id; _timerProvider.Update(existingTimer); } } @@ -1649,8 +1745,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return channelIds.SelectMany(GetEpgDataForChannel).ToList(); } + private bool _disposed; public void Dispose() { + _disposed = true; foreach (var pair in _activeRecordings.ToList()) { pair.Value.CancellationTokenSource.Cancel(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 67356da2fe..f9c04abc5f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV timer.PostPaddingSeconds = series.PostPaddingSeconds; timer.IsPostPaddingRequired = series.IsPostPaddingRequired; timer.IsPrePaddingRequired = series.IsPrePaddingRequired; + timer.KeepUntil = series.KeepUntil; timer.Priority = series.Priority; timer.Name = parent.Name; timer.Overview = parent.Overview; diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 28cec34f4a..bddce04201 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -156,5 +156,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs { Argument = timer }, Logger); } } + + public TimerInfo GetTimer(string id) + { + return GetAll().FirstOrDefault(r => string.Equals(r.Id, id, StringComparison.OrdinalIgnoreCase)); + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 462bb27210..348bcd97b7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -52,6 +52,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv PostPaddingSeconds = info.PostPaddingSeconds, IsPostPaddingRequired = info.IsPostPaddingRequired, IsPrePaddingRequired = info.IsPrePaddingRequired, + KeepUntil = info.KeepUntil, ExternalChannelId = info.ChannelId, ExternalSeriesTimerId = info.SeriesTimerId, ServiceName = service.Name, @@ -247,6 +248,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv PostPaddingSeconds = dto.PostPaddingSeconds, IsPostPaddingRequired = dto.IsPostPaddingRequired, IsPrePaddingRequired = dto.IsPrePaddingRequired, + KeepUntil = dto.KeepUntil, Priority = dto.Priority, SeriesTimerId = dto.ExternalSeriesTimerId, ProgramId = dto.ExternalProgramId,